From 842a95615a2ffa0061daf083caebebcefe86530f Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Mon, 24 Feb 2025 16:17:57 -0800 Subject: [PATCH 01/63] refactor, use new CameraDataObject for ui --- agave_app/CameraDockWidget.cpp | 2 +- agave_app/CameraDockWidget.h | 5 +- agave_app/CameraWidget.cpp | 382 +++++++++++++++++++++++++++++++-- agave_app/CameraWidget.h | 11 +- agave_app/GLView3D.cpp | 8 +- agave_app/GLView3D.h | 11 +- agave_app/agaveGui.cpp | 23 +- agave_app/agaveGui.h | 9 +- renderlib/CMakeLists.txt | 2 - renderlib/CameraDataObject.cpp | 67 ++++++ renderlib/CameraDataObject.hpp | 25 ++- 11 files changed, 484 insertions(+), 61 deletions(-) diff --git a/agave_app/CameraDockWidget.cpp b/agave_app/CameraDockWidget.cpp index 28a7eb772..5e213175f 100644 --- a/agave_app/CameraDockWidget.cpp +++ b/agave_app/CameraDockWidget.cpp @@ -1,6 +1,6 @@ #include "CameraDockWidget.h" -QCameraDockWidget::QCameraDockWidget(QWidget* pParent, RenderSettings* rs, CameraObject* cdo) +QCameraDockWidget::QCameraDockWidget(QWidget* pParent, QCamera* cam, RenderSettings* rs, CameraDataObject* cdo) : QDockWidget(pParent) , m_CameraWidget(nullptr, cam, rs, cdo) { diff --git a/agave_app/CameraDockWidget.h b/agave_app/CameraDockWidget.h index debb462f6..e2ac83fe2 100644 --- a/agave_app/CameraDockWidget.h +++ b/agave_app/CameraDockWidget.h @@ -9,7 +9,10 @@ class QCameraDockWidget : public QDockWidget Q_OBJECT public: - QCameraDockWidget(QWidget* pParent = NULL, RenderSettings* rs = NULL, CameraObject* cdo = NULL); + QCameraDockWidget(QWidget* pParent = NULL, + QCamera* cam = NULL, + RenderSettings* rs = NULL, + CameraDataObject* cdo = NULL); private: QCameraWidget m_CameraWidget; diff --git a/agave_app/CameraWidget.cpp b/agave_app/CameraWidget.cpp index 6078af462..e7528cad8 100644 --- a/agave_app/CameraWidget.cpp +++ b/agave_app/CameraWidget.cpp @@ -11,29 +11,379 @@ #include #include -QCameraWidget::QCameraWidget(QWidget* pParent, RenderSettings* rs, CameraObject* cameraObject) +struct GenericUIInfo +{ + std::string type; + std::string formLabel; + std::string statusTip; + std::string toolTip; + + GenericUIInfo() = default; + GenericUIInfo(std::string type, std::string formLabel, std::string statusTip, std::string toolTip) + : type(type) + , formLabel(formLabel) + , statusTip(statusTip) + , toolTip(toolTip) + { + } +}; +struct CheckBoxUiInfo : public GenericUIInfo +{ + static constexpr const char* TYPE = "CheckBox"; + + CheckBoxUiInfo() { type = CheckBoxUiInfo::TYPE; } + CheckBoxUiInfo(std::string formLabel, std::string statusTip, std::string toolTip) + : GenericUIInfo(CheckBoxUiInfo::TYPE, formLabel, statusTip, toolTip) + { + } +}; +struct ComboBoxUiInfo : public GenericUIInfo +{ + static constexpr const char* TYPE = "ComboBox"; + std::vector items; + + ComboBoxUiInfo() { type = ComboBoxUiInfo::TYPE; } + ComboBoxUiInfo(std::string formLabel, std::string statusTip, std::string toolTip, std::vector items) + : GenericUIInfo(ComboBoxUiInfo::TYPE, formLabel, statusTip, toolTip) + , items(items) + { + } +}; +struct FloatSliderSpinnerUiInfo : public GenericUIInfo +{ + static constexpr const char* TYPE = "FloatSliderSpinner"; + float min = 0.0f; + float max = 0.0f; + int decimals = 0; + float singleStep = 0.0f; + int numTickMarks = 0; + std::string suffix; + + FloatSliderSpinnerUiInfo() { type = FloatSliderSpinnerUiInfo::TYPE; } + FloatSliderSpinnerUiInfo(std::string formLabel, + std::string statusTip, + std::string toolTip, + float min, + float max, + int decimals, + float singleStep, + int numTickMarks = 0, + std::string suffix = "") + : GenericUIInfo(FloatSliderSpinnerUiInfo::TYPE, formLabel, statusTip, toolTip) + , min(min) + , max(max) + , decimals(decimals) + , singleStep(singleStep) + , numTickMarks(numTickMarks) + , suffix(suffix) + { + } +}; +struct IntSliderSpinnerUiInfo : public GenericUIInfo +{ + static constexpr const char* TYPE = "IntSliderSpinner"; + int min; + int max; + int singleStep; + int numTickMarks; + std::string suffix; + + IntSliderSpinnerUiInfo() { type = IntSliderSpinnerUiInfo::TYPE; } +}; + +QNumericSlider* +create(const FloatSliderSpinnerUiInfo* info, std::shared_ptr> prop) +{ + QNumericSlider* slider = new QNumericSlider(); + slider->setStatusTip(QString::fromStdString(info->statusTip)); + slider->setToolTip(QString::fromStdString(info->toolTip)); + slider->setRange(info->min, info->max); + slider->setDecimals(info->decimals); + slider->setSingleStep(info->singleStep); + slider->setNumTickMarks(info->numTickMarks); + slider->setSuffix(QString::fromStdString(info->suffix)); + + slider->setValue(prop->get(), true); + QObject::connect(slider, &QNumericSlider::valueChanged, [slider, prop](double value) { prop->set(value, true); }); + // TODO how would this capture the "previous" value, for undo? + QObject::connect(slider, &QNumericSlider::valueChangeCommit, [slider, prop]() { prop->notifyAll(true); }); + + return slider; +} +QNumericSlider* +create(const IntSliderSpinnerUiInfo* info, std::shared_ptr> prop) +{ + QNumericSlider* slider = new QNumericSlider(); + slider->setStatusTip(QString::fromStdString(info->statusTip)); + slider->setToolTip(QString::fromStdString(info->toolTip)); + slider->setRange(info->min, info->max); + slider->setSingleStep(info->singleStep); + slider->setNumTickMarks(info->numTickMarks); + slider->setSuffix(QString::fromStdString(info->suffix)); + + slider->setValue(prop->get(), true); + QObject::connect(slider, &QNumericSlider::valueChanged, [slider, prop](double value) { prop->set(value, true); }); + // TODO how would this capture the "previous" value, for undo? + QObject::connect(slider, &QNumericSlider::valueChangeCommit, [slider, prop]() { prop->notifyAll(true); }); + + return slider; +} +QCheckBox* +create(const CheckBoxUiInfo* info, std::shared_ptr> prop) +{ + QCheckBox* checkBox = new QCheckBox(); + checkBox->setStatusTip(QString::fromStdString(info->statusTip)); + checkBox->setToolTip(QString::fromStdString(info->toolTip)); + // checkBox->setText(QString::fromStdString(info->formLabel)); + checkBox->setCheckState(prop->get() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + QObject::connect(checkBox, &QCheckBox::stateChanged, [checkBox, prop](int state) { + prop->set(state == Qt::CheckState::Checked, true); + }); + return checkBox; +} +QComboBox* +create(const ComboBoxUiInfo* info, std::shared_ptr> prop) +{ + QComboBox* comboBox = new QComboBox(); + comboBox->setStatusTip(QString::fromStdString(info->statusTip)); + comboBox->setToolTip(QString::fromStdString(info->toolTip)); + for (const auto& item : info->items) { + comboBox->addItem(QString::fromStdString(item)); + } + comboBox->setCurrentIndex(prop->get()); + QObject::connect(comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->set(index, true); }); + return comboBox; +} + +void +createSection(QFormLayout* layout, std::vector controlDescs) +{ + // for (const auto& desc : controlDescs) { + // QLabel* label = new QLabel(QString::fromStdString(desc->formLabel)); + // if (desc->type == CheckBoxUiInfo::TYPE) { + // layout->addRow(label, create(static_cast(desc), )); + // } else if (desc->type == ComboBoxUiInfo::TYPE) { + // layout->addRow(label, create(static_cast(desc))); + // } else if (desc->type == FloatSliderSpinnerUiInfo::TYPE) { + // layout->addRow(label, create(static_cast(desc))); + // } else if (desc->type == IntSliderSpinnerUiInfo::TYPE) { + // layout->addRow(label, create(static_cast(desc))); + // } + // } +} + +QNumericSlider* +addRow(const FloatSliderSpinnerUiInfo& info, prtyProperty* prop) +{ + QNumericSlider* slider = new QNumericSlider(); + slider->setStatusTip(QString::fromStdString(info.statusTip)); + slider->setToolTip(QString::fromStdString(info.toolTip)); + slider->setRange(info.min, info.max); + slider->setDecimals(info.decimals); + slider->setSingleStep(info.singleStep); + slider->setNumTickMarks(info.numTickMarks); + slider->setSuffix(QString::fromStdString(info.suffix)); + + slider->setValue(prop->get(), true); + QObject::connect(slider, &QNumericSlider::valueChanged, [slider, prop](double value) { prop->set(value, true); }); + // TODO how would this capture the "previous" value, for undo? + QObject::connect(slider, &QNumericSlider::valueChangeCommit, [slider, prop]() { prop->notifyAll(true); }); + + return slider; +} +QComboBox* +addRow(const ComboBoxUiInfo& info, prtyProperty* prop) +{ + QComboBox* comboBox = new QComboBox(); + comboBox->setStatusTip(QString::fromStdString(info.statusTip)); + comboBox->setToolTip(QString::fromStdString(info.toolTip)); + for (const auto& item : info.items) { + comboBox->addItem(QString::fromStdString(item)); + } + comboBox->setCurrentIndex(prop->get()); + QObject::connect(comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->set(index, true); }); + return comboBox; +} +QCheckBox* +addRow(const CheckBoxUiInfo& info, prtyProperty* prop) +{ + QCheckBox* checkBox = new QCheckBox(); + checkBox->setStatusTip(QString::fromStdString(info.statusTip)); + checkBox->setToolTip(QString::fromStdString(info.toolTip)); + // checkBox->setText(QString::fromStdString(info.formLabel)); + checkBox->setCheckState(prop->get() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + QObject::connect(checkBox, &QCheckBox::stateChanged, [checkBox, prop](int state) { + prop->set(state == Qt::CheckState::Checked, true); + }); + return checkBox; +} + +QCameraWidget::QCameraWidget(QWidget* pParent, QCamera* cam, RenderSettings* rs, CameraDataObject* cdo) : QWidget(pParent) , m_MainLayout() , m_renderSettings(rs) - , m_cameraObject(cameraObject) + , m_cameraDataObject(cdo) { Controls::initFormLayout(m_MainLayout); setLayout(&m_MainLayout); - if (m_cameraObject) { - createFlatList(&m_MainLayout, m_cameraObject); - } - // // loop over all properties in cameraobject. for each property, add a callback that updates the rendersetttings - // // cameradirty flags - // for (const auto& prop : m_cameraObject->GetList()) { - // if (prop) { - // prop->GetProperty(0)->AddCallback(new prtyCallbackLambda([this](prtyProperty* i_Property, bool i_bDirty) { - // if (i_bDirty) { - // m_renderSettings->m_DirtyFlags.SetFlag(CameraDirty); - // } - // })); - // } - // } + QNumericSlider* slider = addRow(FloatSliderSpinnerUiInfo("Exposure", + "Set Exposure", + "Set camera exposure", + 0.0f, + 1.0f, + 2, // decimals + 0.01, // singleStep + 0 // numTickMarks + ), + &m_cameraDataObject->Exposure); + m_MainLayout.addRow("Exposure", slider); + QComboBox* comboBox = addRow(ComboBoxUiInfo("Exposure Time", + "Set Exposure Time", + "Set number of samples to accumulate per viewport update", + { "1", "2", "4", "8" }), + &m_cameraDataObject->ExposureIterations); + m_MainLayout.addRow("Exposure Time", comboBox); + QCheckBox* checkBox = addRow(CheckBoxUiInfo("Noise Reduction", "Enable denoising pass", "Enable denoising pass"), + &m_cameraDataObject->NoiseReduction); + m_MainLayout.addRow("Noise Reduction", checkBox); + QNumericSlider* slider2 = addRow(FloatSliderSpinnerUiInfo("Aperture Size", + "Set camera aperture size", + "Set camera aperture size", + 0.0f, + 0.1f, + 2, // decimals + 0.01, // singleStep + 0, // numTickMarks + " mm"), + &m_cameraDataObject->ApertureSize); + m_MainLayout.addRow("Aperture Size", slider2); + QNumericSlider* slider3 = addRow(FloatSliderSpinnerUiInfo("Field of view", + "Set camera field of view angle", + "Set camera field of view angle", + 10.0f, + 150.0f, + 2, // decimals + 0.01, // singleStep + 0, // numTickMarks + " deg."), + &m_cameraDataObject->FieldOfView); + m_MainLayout.addRow("Field of view", slider3); + QNumericSlider* slider4 = addRow(FloatSliderSpinnerUiInfo("Focal distance", + "Set focal distance", + "Set focal distance", + 0.0f, + 15.0f, + 2, // decimals + 0.01, // singleStep + 0, // numTickMarks + " m"), + &m_cameraDataObject->FocalDistance); + m_MainLayout.addRow("Focal distance", slider4); + +#if 0 + // Exposure, controls how bright or dim overall scene is + m_ExposureSlider.setStatusTip(tr("Set Exposure")); + m_ExposureSlider.setToolTip(tr("Set camera exposure")); + m_ExposureSlider.setRange(0.0f, 1.0f); + m_ExposureSlider.setValue(cam->GetFilm().GetExposure()); + m_ExposureSlider.setDecimals(2); + m_ExposureSlider.setSingleStep(0.01); + + m_MainLayout.addRow("Exposure", &m_ExposureSlider); + + connect(&m_ExposureSlider, &QNumericSlider::valueChanged, this, &QCameraWidget::SetExposure); + + // Number of render iterations per viewport update + m_ExposureIterationsSpinner.setStatusTip(tr("Set Exposure Time")); + m_ExposureIterationsSpinner.setToolTip(tr("Set number of samples to accumulate per viewport update")); + m_ExposureIterationsSpinner.addItem("1", 1); + m_ExposureIterationsSpinner.addItem("2", 2); + m_ExposureIterationsSpinner.addItem("4", 4); + m_ExposureIterationsSpinner.addItem("8", 8); + m_ExposureIterationsSpinner.setCurrentIndex( + m_ExposureIterationsSpinner.findData(cam->GetFilm().GetExposureIterations())); + m_MainLayout.addRow("Exposure Time", &m_ExposureIterationsSpinner); + connect(&m_ExposureIterationsSpinner, &QComboBox::currentIndexChanged, this, &QCameraWidget::SetExposureIterations); + + m_NoiseReduction.setStatusTip(tr("Enable denoising pass")); + m_NoiseReduction.setToolTip(tr("Enable denoising pass")); + m_NoiseReduction.setCheckState(rs->m_DenoiseParams.m_Enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + m_MainLayout.addRow("Noise Reduction", &m_NoiseReduction); + + connect(&m_NoiseReduction, &QCheckBox::stateChanged, this, &QCameraWidget::OnNoiseReduction); + + m_ApertureSizeSlider.setStatusTip(tr("Set camera aperture size")); + m_ApertureSizeSlider.setToolTip(tr("Set camera aperture size")); + m_ApertureSizeSlider.setRange(0.0, 0.1); + m_ApertureSizeSlider.setSuffix(" mm"); + m_ApertureSizeSlider.setDecimals(2); + m_ApertureSizeSlider.setValue(0.0); + m_ApertureSizeSlider.setSingleStep(0.01); + m_MainLayout.addRow("Aperture Size", &m_ApertureSizeSlider); + + connect(&m_ApertureSizeSlider, &QNumericSlider::valueChanged, this, &QCameraWidget::SetAperture); + + m_FieldOfViewSlider.setStatusTip(tr("Set camera field of view angle")); + m_FieldOfViewSlider.setToolTip(tr("Set camera field of view angle")); + m_FieldOfViewSlider.setRange(10.0, 150.0); + m_FieldOfViewSlider.setDecimals(2); + m_FieldOfViewSlider.setValue(cam->GetProjection().GetFieldOfView()); + m_FieldOfViewSlider.setSuffix(" deg."); + m_MainLayout.addRow("Field of view", &m_FieldOfViewSlider); + + connect(&m_FieldOfViewSlider, &QNumericSlider::valueChanged, this, &QCameraWidget::SetFieldOfView); + + // Focal distance + m_FocalDistanceSlider.setStatusTip(tr("Set focal distance")); + m_FocalDistanceSlider.setToolTip(tr("Set focal distance")); + m_FocalDistanceSlider.setRange(0.0, 15.0); + m_FocalDistanceSlider.setDecimals(2); + m_FocalDistanceSlider.setValue(0.0); + m_FocalDistanceSlider.setSuffix(" m"); + + m_MainLayout.addRow("Focal distance", &m_FocalDistanceSlider); + + connect(&m_FocalDistanceSlider, &QNumericSlider::valueChanged, this, &QCameraWidget::SetFocalDistance); + + QObject::connect(&cam->GetFilm(), SIGNAL(Changed(const QFilm&)), this, SLOT(OnFilmChanged())); + QObject::connect(&cam->GetAperture(), SIGNAL(Changed(const QAperture&)), this, SLOT(OnApertureChanged())); + QObject::connect(&cam->GetProjection(), SIGNAL(Changed(const QProjection&)), this, SLOT(OnProjectionChanged())); + QObject::connect(&cam->GetFocus(), SIGNAL(Changed(const QFocus&)), this, SLOT(OnFocusChanged())); +#endif +} + +void +QCameraWidget::OnFilmChanged() +{ + m_ExposureSlider.setValue(m_qcamera->GetFilm().GetExposure(), true); + m_ExposureIterationsSpinner.blockSignals(true); + m_ExposureIterationsSpinner.setCurrentIndex( + m_ExposureIterationsSpinner.findData(m_qcamera->GetFilm().GetExposureIterations())); + m_ExposureIterationsSpinner.blockSignals(false); + m_NoiseReduction.blockSignals(true); + m_NoiseReduction.setCheckState(m_renderSettings->m_DenoiseParams.m_Enabled ? Qt::CheckState::Checked + : Qt::CheckState::Unchecked); + m_NoiseReduction.blockSignals(false); + emit m_qcamera->Changed(); +} +void +QCameraWidget::OnApertureChanged() +{ + m_ApertureSizeSlider.setValue(m_qcamera->GetAperture().GetSize(), true); + emit m_qcamera->Changed(); +} +void +QCameraWidget::OnProjectionChanged() +{ + m_FieldOfViewSlider.setValue(m_qcamera->GetProjection().GetFieldOfView(), true); + emit m_qcamera->Changed(); +} +void +QCameraWidget::OnFocusChanged() +{ + m_FocalDistanceSlider.setValue(m_qcamera->GetFocus().GetFocalDistance(), true); + emit m_qcamera->Changed(); } QSize diff --git a/agave_app/CameraWidget.h b/agave_app/CameraWidget.h index 35ed6fdba..085a12477 100644 --- a/agave_app/CameraWidget.h +++ b/agave_app/CameraWidget.h @@ -3,8 +3,8 @@ #include "Camera.h" #include "qtControls/Controls.h" -// #include "renderlib/core/prty/prtyProperty.h" -#include "renderlib/CameraObject.hpp" +#include "renderlib/core/prty/prtyProperty.h" +#include "renderlib/CameraDataObject.hpp" #include "renderlib/Logging.h" #include @@ -19,7 +19,10 @@ class QCameraWidget : public QWidget Q_OBJECT public: - QCameraWidget(QWidget* pParent = NULL, RenderSettings* rs = nullptr, CameraObject* cameraObject = nullptr); + QCameraWidget(QWidget* pParent = NULL, + QCamera* cam = nullptr, + RenderSettings* rs = nullptr, + CameraDataObject* cdo = nullptr); virtual QSize sizeHint() const; @@ -29,5 +32,5 @@ class QCameraWidget : public QWidget RenderSettings* m_renderSettings; private: - CameraObject* m_cameraObject; + CameraDataObject* m_cameraDataObject; }; diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 8cf085baa..74de97759 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -3,8 +3,7 @@ #include "QRenderSettings.h" #include "ViewerState.h" -#include "renderlib/AppearanceObject.hpp" -#include "renderlib/CameraObject.hpp" +#include "renderlib/CameraDataObject.hpp" #include "renderlib/ImageXYZC.h" #include "renderlib/Logging.h" #include "renderlib/MoveTool.h" @@ -44,6 +43,9 @@ GLView3D::GLView3D(QRenderSettings* qrs, RenderSettings* rs, Scene* scene, QWidg m_viewerWindow = new ViewerWindow(rs); m_viewerWindow->gesture.input.setDoubleClickTime((double)QApplication::doubleClickInterval() / 1000.0); + // camera is created deep down inside m_viewerWindow. + m_cameraDataObject = new CameraDataObject(&m_viewerWindow->m_CCamera); + setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); @@ -140,7 +142,7 @@ GLView3D::onNewImage(Scene* scene) GLView3D::~GLView3D() { - delete m_appearanceDataObject; + delete m_cameraDataObject; makeCurrent(); check_gl("view dtor makecurrent"); diff --git a/agave_app/GLView3D.h b/agave_app/GLView3D.h index c8018c335..b3beae733 100644 --- a/agave_app/GLView3D.h +++ b/agave_app/GLView3D.h @@ -12,7 +12,6 @@ #include #include -class AppearanceDataObject; class CameraDataObject; class CStatus; class ImageXYZC; @@ -66,11 +65,9 @@ class GLView3D : public QOpenGLWidget void onNewImage(Scene* scene); - const CCamera& getCamera() { return *m_viewerWindow->m_CCamera; } + const CCamera& getCamera() { return m_viewerWindow->m_CCamera; } // tied to the above camera. CCamera must outlive this: - // CameraObject* getCameraDataObject() { return m_cameraObject; } - void setCameraObject(CameraObject* cameraObject); - AppearanceObject* getAppearanceDataObject() { return m_appearanceDataObject; } + CameraDataObject* getCameraDataObject() { return m_cameraDataObject; } void fromViewerState(const Serialize::ViewerState& s); @@ -113,8 +110,8 @@ public slots: void wheelEvent(QWheelEvent* event); private: - CameraObject* m_cameraObject; - AppearanceObject* m_appearanceDataObject; + CameraDataObject* m_cameraDataObject; + QCamera* m_qcamera; QRenderSettings* m_qrendersettings; /// Rendering timer. diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 3ee18c4ea..b79370a35 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -136,17 +136,8 @@ agaveGui::agaveGui(QWidget* parent) // We need a minimum size or else the size defaults to zero. m_glView->setMinimumSize(256, 512); m_glView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_glView->setCameraObject(m_cameraObject.get()); - // create camera ui window now that there is an actual camera. - setupCameraDock(m_cameraObject.get()); - // setupAreaLightDock(m_areaLightObject.get()); - // setupSkyLightDock(m_skyLightObject.get()); - setupTimelineDock(); - setupStatisticsDock(); - setupAppearanceDock(m_glView->getAppearanceDataObject()); - - addDockItemsToViewMenu(); + setupCameraDock(m_glView->getCameraDataObject()); // TODO can make this a custom widget that exposes the toolbar and the view m_viewWithToolbar = new QWidget(this); @@ -420,6 +411,18 @@ agaveGui::setupAppearanceDock(AppearanceObject* ado) addDockWidget(Qt::LeftDockWidgetArea, m_appearanceDockWidget); } +void +agaveGui::setupCameraDock(CameraDataObject* cdo) +{ + // TODO enable changing/resetting the camera data object shown in this dock? + m_cameradock = new QCameraDockWidget(this, &m_qcamera, &m_renderSettings, cdo); + m_cameradock->setAllowedAreas(Qt::AllDockWidgetAreas); + addDockWidget(Qt::RightDockWidgetArea, m_cameradock); + + m_viewMenu->addSeparator(); + m_viewMenu->addAction(m_cameradock->toggleViewAction()); +} + void agaveGui::createDockWindows() { diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index 10e4b67fa..9c1da3134 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -102,13 +102,8 @@ private slots: void createActions(); void createMenus(); void createToolbars(); - void addDockItemsToViewMenu(); - void setupAreaLightDock(AreaLightObject* light); - void setupSkyLightDock(SkyLightObject* light); - void setupCameraDock(CameraObject* cdo); - void setupTimelineDock(); - void setupAppearanceDock(AppearanceObject* ado); - void setupStatisticsDock(); + void createDockWindows(); + void setupCameraDock(CameraDataObject* cdo); void showOpenFailedMessageBox(QString path); diff --git a/renderlib/CMakeLists.txt b/renderlib/CMakeLists.txt index 668adafba..d5451b52f 100644 --- a/renderlib/CMakeLists.txt +++ b/renderlib/CMakeLists.txt @@ -34,8 +34,6 @@ target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/CCamera.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraDataObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraDataObject.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.h" "${CMAKE_CURRENT_SOURCE_DIR}/Colormap.cpp" diff --git a/renderlib/CameraDataObject.cpp b/renderlib/CameraDataObject.cpp index 461b8a678..269db43bd 100644 --- a/renderlib/CameraDataObject.cpp +++ b/renderlib/CameraDataObject.cpp @@ -1,3 +1,70 @@ #include "CameraDataObject.hpp" #include "Logging.h" + +CameraDataObject::CameraDataObject(CCamera* camera) + : m_camera(camera) +{ + updatePropsFromCamera(); + // hook up properties to update the underlying camera + Exposure.addCallback([this](prtyProperty* p, bool) { + // LOG_DEBUG << "Setting exposure to " << p->get(); + update(); + }); + ExposureIterations.addCallback([this](prtyProperty* p, bool) { + // LOG_DEBUG << "Setting exposure iterations to " << p->get(); + update(); + }); + NoiseReduction.addCallback([this](prtyProperty* p, bool) { + // LOG_DEBUG << "Setting noise reduction to " << p->get(); + update(); + }); + ApertureSize.addCallback([this](prtyProperty* p, bool) { + // LOG_DEBUG << "Setting aperture size to " << p->get(); + update(); + }); + FieldOfView.addCallback([this](prtyProperty* p, bool) { + // LOG_DEBUG << "Setting field of view to " << p->get(); + update(); + }); + FocalDistance.addCallback([this](prtyProperty* p, bool) { + // LOG_DEBUG << "Setting focal distance to " << p->get(); + update(); + }); +} + +void +CameraDataObject::updatePropsFromCamera() +{ + if (m_camera) { + Exposure.set(1.0f - m_camera->m_Film.m_Exposure); + ExposureIterations.set(m_camera->m_Film.m_ExposureIterations); + // NoiseReduction.set(m_camera->m_Film.m_NoiseReduction); + ApertureSize.set(m_camera->m_Aperture.m_Size); + FieldOfView.set(m_camera->m_FovV); + FocalDistance.set(m_camera->m_Focus.m_FocalDistance); + } +} +void +CameraDataObject::update() +{ + // update low-level camera object from properties + if (m_camera) { + m_camera->m_Film.m_Exposure = 1.0f - Exposure.get(); + m_camera->m_Film.m_ExposureIterations = ExposureIterations.get(); + + // Aperture + m_camera->m_Aperture.m_Size = ApertureSize.get(); + + // Projection + m_camera->m_FovV = FieldOfView.get(); + + // Focus + m_camera->m_Focus.m_FocalDistance = FocalDistance.get(); + + m_camera->Update(); + + // renderer should pick this up and do the right thing (TM) + m_camera->m_Dirty = true; + } +} \ No newline at end of file diff --git a/renderlib/CameraDataObject.hpp b/renderlib/CameraDataObject.hpp index a26c862c6..6456956b1 100644 --- a/renderlib/CameraDataObject.hpp +++ b/renderlib/CameraDataObject.hpp @@ -1,18 +1,23 @@ #pragma once -#include "core/prty/prtyFloat.hpp" -#include "core/prty/prtyInt8.hpp" -#include "core/prty/prtyBoolean.hpp" +#include "core/prty/prtyProperty.h" +#include "CCamera.h" class CameraDataObject { public: - CameraDataObject() {} + CameraDataObject(CCamera* camera); - prtyFloat Exposure{ "Exposure", 0.75f }; - prtyInt8 ExposureIterations{ "ExposureIterations", 1 }; - prtyBoolean NoiseReduction{ "NoiseReduction", false }; - prtyFloat ApertureSize{ "ApertureSize", 0.0f }; - prtyFloat FieldOfView{ "FieldOfView", 30.0f }; - prtyFloat FocalDistance{ "FocalDistance", 0.0f }; + prtyProperty Exposure{ "Exposure", 0.75f }; + prtyProperty ExposureIterations{ "ExposureIterations", 1 }; + prtyProperty NoiseReduction{ "NoiseReduction", false }; + prtyProperty ApertureSize{ "ApertureSize", 0.0f }; + prtyProperty FieldOfView{ "FieldOfView", 30.0f }; + prtyProperty FocalDistance{ "FocalDistance", 0.0f }; + + CCamera* m_camera; + +private: + void update(); + void updatePropsFromCamera(); }; From fb90beb855f5c4317ab0723e0733291cf604657c Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Mon, 24 Feb 2025 17:13:45 -0800 Subject: [PATCH 02/63] move controls into subdir --- agave_app/AppearanceSettingsWidget.cpp | 1 - agave_app/CameraWidget.cpp | 101 -- agave_app/qtControls/Controls.cpp | 49 - agave_app/qtControls/Controls.h | 26 - agave_app/tfeditor/gradients.cpp | 1397 ++++++++++++------------ renderlib/uiInfo.hpp | 42 +- 6 files changed, 699 insertions(+), 917 deletions(-) diff --git a/agave_app/AppearanceSettingsWidget.cpp b/agave_app/AppearanceSettingsWidget.cpp index b8f8b423b..66f72f03f 100644 --- a/agave_app/AppearanceSettingsWidget.cpp +++ b/agave_app/AppearanceSettingsWidget.cpp @@ -3,7 +3,6 @@ #include "QRenderSettings.h" #include "RangeWidget.h" #include "qtControls/Section.h" -#include "qtControls/controlFactory.h" #include "ImageXYZC.h" #include "renderlib/AppScene.h" diff --git a/agave_app/CameraWidget.cpp b/agave_app/CameraWidget.cpp index e7528cad8..c18d48cfa 100644 --- a/agave_app/CameraWidget.cpp +++ b/agave_app/CameraWidget.cpp @@ -1,96 +1,12 @@ #include "CameraWidget.h" #include "RenderSettings.h" -#include "qtControls/controlFactory.h" -#include "qtControls/Section.h" - #include "renderlib/uiInfo.hpp" -#include "renderlib/CameraObject.hpp" #include #include #include -struct GenericUIInfo -{ - std::string type; - std::string formLabel; - std::string statusTip; - std::string toolTip; - - GenericUIInfo() = default; - GenericUIInfo(std::string type, std::string formLabel, std::string statusTip, std::string toolTip) - : type(type) - , formLabel(formLabel) - , statusTip(statusTip) - , toolTip(toolTip) - { - } -}; -struct CheckBoxUiInfo : public GenericUIInfo -{ - static constexpr const char* TYPE = "CheckBox"; - - CheckBoxUiInfo() { type = CheckBoxUiInfo::TYPE; } - CheckBoxUiInfo(std::string formLabel, std::string statusTip, std::string toolTip) - : GenericUIInfo(CheckBoxUiInfo::TYPE, formLabel, statusTip, toolTip) - { - } -}; -struct ComboBoxUiInfo : public GenericUIInfo -{ - static constexpr const char* TYPE = "ComboBox"; - std::vector items; - - ComboBoxUiInfo() { type = ComboBoxUiInfo::TYPE; } - ComboBoxUiInfo(std::string formLabel, std::string statusTip, std::string toolTip, std::vector items) - : GenericUIInfo(ComboBoxUiInfo::TYPE, formLabel, statusTip, toolTip) - , items(items) - { - } -}; -struct FloatSliderSpinnerUiInfo : public GenericUIInfo -{ - static constexpr const char* TYPE = "FloatSliderSpinner"; - float min = 0.0f; - float max = 0.0f; - int decimals = 0; - float singleStep = 0.0f; - int numTickMarks = 0; - std::string suffix; - - FloatSliderSpinnerUiInfo() { type = FloatSliderSpinnerUiInfo::TYPE; } - FloatSliderSpinnerUiInfo(std::string formLabel, - std::string statusTip, - std::string toolTip, - float min, - float max, - int decimals, - float singleStep, - int numTickMarks = 0, - std::string suffix = "") - : GenericUIInfo(FloatSliderSpinnerUiInfo::TYPE, formLabel, statusTip, toolTip) - , min(min) - , max(max) - , decimals(decimals) - , singleStep(singleStep) - , numTickMarks(numTickMarks) - , suffix(suffix) - { - } -}; -struct IntSliderSpinnerUiInfo : public GenericUIInfo -{ - static constexpr const char* TYPE = "IntSliderSpinner"; - int min; - int max; - int singleStep; - int numTickMarks; - std::string suffix; - - IntSliderSpinnerUiInfo() { type = IntSliderSpinnerUiInfo::TYPE; } -}; - QNumericSlider* create(const FloatSliderSpinnerUiInfo* info, std::shared_ptr> prop) { @@ -155,23 +71,6 @@ create(const ComboBoxUiInfo* info, std::shared_ptr> prop) return comboBox; } -void -createSection(QFormLayout* layout, std::vector controlDescs) -{ - // for (const auto& desc : controlDescs) { - // QLabel* label = new QLabel(QString::fromStdString(desc->formLabel)); - // if (desc->type == CheckBoxUiInfo::TYPE) { - // layout->addRow(label, create(static_cast(desc), )); - // } else if (desc->type == ComboBoxUiInfo::TYPE) { - // layout->addRow(label, create(static_cast(desc))); - // } else if (desc->type == FloatSliderSpinnerUiInfo::TYPE) { - // layout->addRow(label, create(static_cast(desc))); - // } else if (desc->type == IntSliderSpinnerUiInfo::TYPE) { - // layout->addRow(label, create(static_cast(desc))); - // } - // } -} - QNumericSlider* addRow(const FloatSliderSpinnerUiInfo& info, prtyProperty* prop) { diff --git a/agave_app/qtControls/Controls.cpp b/agave_app/qtControls/Controls.cpp index 6c272e291..700003dee 100644 --- a/agave_app/qtControls/Controls.cpp +++ b/agave_app/qtControls/Controls.cpp @@ -586,52 +586,3 @@ Controls::createAgaveFormLayout(QWidget* parent) layout->setColumnStretch(1, 100); return layout; } - -QColorWithIntensity::QColorWithIntensity(QColor color, float intensity, QWidget* parent) - : QWidget(parent) -{ - auto* layout = new QHBoxLayout(); - m_intensitySlider = new QNumericSlider(); - m_intensitySlider->setStatusTip(tr("Set intensity")); - m_intensitySlider->setToolTip(tr("Set intensity")); - m_intensitySlider->setRange(0.0, 1000.0); - m_intensitySlider->setSingleStep(10.0); - m_intensitySlider->setValue(intensity); - m_intensitySlider->setDecimals(1); - layout->addWidget(m_intensitySlider, 1); - m_colorButton = new QColorPushButton(); - m_colorButton->setStatusTip(tr("Set color")); - m_colorButton->setToolTip(tr("Set color")); - m_colorButton->SetColor(color); - layout->addWidget(m_colorButton, 0); - layout->setContentsMargins(0, 0, 0, 0); - setLayout(layout); - - // connect intensity slider to emit intensityChanged - connect(m_intensitySlider, &QNumericSlider::valueChanged, this, &QColorWithIntensity::intensityChanged); - connect(m_colorButton, &QColorPushButton::currentColorChanged, this, &QColorWithIntensity::colorChanged); -} - -void -QColorWithIntensity::setIntensity(float intensity) -{ - m_intensitySlider->setValue(intensity); -} - -void -QColorWithIntensity::setColor(const QColor& color) -{ - m_colorButton->SetColor(color); -} - -QColor -QColorWithIntensity::getColor() -{ - return m_colorButton->GetColor(); -} - -float -QColorWithIntensity::getIntensity() -{ - return m_intensitySlider->value(); -} diff --git a/agave_app/qtControls/Controls.h b/agave_app/qtControls/Controls.h index aa280bc44..4bce3125e 100644 --- a/agave_app/qtControls/Controls.h +++ b/agave_app/qtControls/Controls.h @@ -245,32 +245,6 @@ private slots: QSlider m_slider; }; -class QColorWithIntensity : public QWidget -{ - Q_OBJECT -public: - QColorWithIntensity(QColor color, float intensity = 1.0f, QWidget* parent = nullptr); - - void setIntensity(float intensity); - void setColor(const QColor& color); - QColor getColor(); - float getIntensity(); - - void setRange(double min, double max) { m_intensitySlider->setRange(min, max); } - void setDecimals(int decimals) { m_intensitySlider->setDecimals(decimals); } - void setSingleStep(double step) { m_intensitySlider->setSingleStep(step); } - void setNumTickMarks(int num) { m_intensitySlider->setNumTickMarks(num); } - void setSuffix(const QString& suffix) { m_intensitySlider->setSuffix(suffix); } - -signals: - void intensityChanged(float intensity); - void colorChanged(const QColor& color); - -private: - QNumericSlider* m_intensitySlider; - QColorPushButton* m_colorButton; -}; - class AgaveFormLayout : public QGridLayout { Q_OBJECT diff --git a/agave_app/tfeditor/gradients.cpp b/agave_app/tfeditor/gradients.cpp index dd21eff98..13bf7b534 100644 --- a/agave_app/tfeditor/gradients.cpp +++ b/agave_app/tfeditor/gradients.cpp @@ -1,699 +1,698 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the demonstration applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "gradients.h" -#include "hoverpoints.h" - -#include "qtControls/Controls.h" -#include "renderlib/Defines.h" -#include "renderlib/Logging.h" -#include "renderlib/MathUtil.h" - -#include - -std::vector -gradientStopsToVector(QGradientStops& stops) -{ - std::vector v; - for (int i = 0; i < stops.size(); ++i) { - v.push_back(LutControlPoint(stops.at(i).first, stops.at(i).second.alphaF())); - } - return v; -} - -QGradientStops -vectorToGradientStops(std::vector& v) -{ - QGradientStops stops; - for (int i = 0; i < v.size(); ++i) { - stops.push_back( - QPair(v[i].first, QColor::fromRgbF(v[i].second, v[i].second, v[i].second, v[i].second))); - } - return stops; -} - -ShadeWidget::ShadeWidget(const Histogram& histogram, ShadeType type, QWidget* parent) - : QWidget(parent) - , m_shade_type(type) - , m_alpha_gradient(QLinearGradient(0, 0, 0, 0)) - , m_histogram(histogram) -{ - // Checkers background - if (m_shade_type == ARGBShade) { - QPixmap pm(20, 20); - QPainter pmp(&pm); - pmp.fillRect(0, 0, 10, 10, Qt::lightGray); - pmp.fillRect(10, 10, 10, 10, Qt::lightGray); - pmp.fillRect(0, 10, 10, 10, Qt::darkGray); - pmp.fillRect(10, 0, 10, 10, Qt::darkGray); - pmp.end(); - QPalette pal = palette(); - pal.setBrush(backgroundRole(), QBrush(pm)); - setAutoFillBackground(true); - setPalette(pal); - - } else { - setAttribute(Qt::WA_OpaquePaintEvent); - } - - QPolygonF points; - points << QPointF(0.0f, 0.0f) << QPointF(1.0f, 1.0f); - - m_hoverPoints = new HoverPoints(this, HoverPoints::CircleShape); - // m_hoverPoints->setConnectionType(HoverPoints::LineConnection); - m_hoverPoints->setPoints(points); - m_hoverPoints->setPointLock(0, HoverPoints::LockToLeft); - m_hoverPoints->setPointLock(1, HoverPoints::LockToRight); - m_hoverPoints->setSortType(HoverPoints::XSort); - - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - - connect(m_hoverPoints, &HoverPoints::pointsChanged, this, &ShadeWidget::colorsChanged); -} - -QPolygonF -ShadeWidget::points() const -{ - return m_hoverPoints->points(); -} - -void -ShadeWidget::setEditable(bool editable) -{ - m_hoverPoints->setEditable(editable); -} - -uint -ShadeWidget::colorAt(int x) -{ - generateShade(); - if (m_shade.isNull()) { - return 0; - } - - QPolygonF pts = m_hoverPoints->points(); - for (int i = 1; i < pts.size(); ++i) { - if (pts.at(i - 1).x() <= x && pts.at(i).x() >= x) { - QLineF l(pts.at(i - 1), pts.at(i)); - l.setLength(l.length() * ((x - l.x1()) / l.dx())); - return m_shade.pixel(qRound(qMin(l.x2(), (qreal(m_shade.width() - 1)))), - qRound(qMin(l.y2(), qreal(m_shade.height() - 1)))); - } - } - return 0; -} - -void -ShadeWidget::setGradientStops(const QGradientStops& stops) -{ - if (m_shade_type == ARGBShade) { - m_alpha_gradient = QLinearGradient(0, 0, width(), 0); - - for (int i = 0; i < stops.size(); ++i) { - QColor c = stops.at(i).second; - m_alpha_gradient.setColorAt(stops.at(i).first, QColor(c.red(), c.green(), c.blue())); - } - - m_shade = QImage(); - generateShade(); - update(); - } -} - -void -ShadeWidget::paintEvent(QPaintEvent*) -{ - generateShade(); - - QPainter p(this); - p.drawImage(0, 0, m_shade); - /* - qreal barWidth = width() / (qreal)m_histogram.size(); - - for (int i = 0; i < m_histogram.size(); ++i) { - qreal h = m_histogram[i] * height(); - // draw level - painter.fillRect(barWidth * i, height() - h, barWidth * (i + 1), height(), Qt::red); - // clear the rest of the control - painter.fillRect(barWidth * i, 0, barWidth * (i + 1), height() - h, Qt::black); - } - */ - p.setPen(QColor(146, 146, 146)); - p.drawRect(0, 0, width() - 1, height() - 1); -} - -void -ShadeWidget::drawHistogram(QPainter& p, int w, int h) -{ - size_t nbins = m_histogram._bins.size(); - int maxbinsize = m_histogram._bins[m_histogram._maxBin]; - for (size_t i = 0; i < nbins; ++i) { - float binheight = (float)m_histogram._bins[i] * (float)(h - 1) / (float)maxbinsize; - p.fillRect( - QRectF((float)i * (float)(w - 1) / (float)nbins, h - 1 - binheight, (float)(w - 1) / (float)nbins, binheight), - QColor(0, 0, 0, 255)); - } -} - -void -ShadeWidget::generateShade() -{ - if (m_shade.isNull() || m_shade.size() != size()) { - - QRect qrect = rect(); - QSize qsize = size(); - if (m_shade_type == ARGBShade) { - m_shade = QImage(qsize, QImage::Format_ARGB32_Premultiplied); - if (m_shade.isNull()) { - return; - } - m_shade.fill(0); - - QPainter p(&m_shade); - p.fillRect(qrect, m_alpha_gradient); - - p.setCompositionMode(QPainter::CompositionMode_DestinationIn); - QLinearGradient fade(0, 0, 0, height() - 1); - fade.setColorAt(0, QColor(255, 255, 255, 255)); - fade.setColorAt(1, QColor(0, 0, 0, 0)); - p.fillRect(qrect, fade); - - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - - drawHistogram(p, qsize.width(), qsize.height()); - - } else { - m_shade = QImage(qsize, QImage::Format_RGB32); - if (m_shade.isNull()) { - return; - } - QLinearGradient shade(0, 0, 0, height()); - shade.setColorAt(1, Qt::black); - - if (m_shade_type == RedShade) - shade.setColorAt(0, Qt::red); - else if (m_shade_type == GreenShade) - shade.setColorAt(0, Qt::green); - else - shade.setColorAt(0, Qt::blue); - - QPainter p(&m_shade); - p.fillRect(qrect, shade); - - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - - drawHistogram(p, qsize.width(), qsize.height()); - } - } -} - -GradientEditor::GradientEditor(const Histogram& histogram, QWidget* parent) - : QWidget(parent) -{ - QVBoxLayout* vbox = new QVBoxLayout(this); - vbox->setSpacing(1); - // vbox->setMargin(1); - - m_alpha_shade = new ShadeWidget(histogram, ShadeWidget::ARGBShade, this); - - vbox->addWidget(m_alpha_shade); - - connect(m_alpha_shade, &ShadeWidget::colorsChanged, this, &GradientEditor::pointsUpdated); -} - -inline static bool -x_less_than(const QPointF& p1, const QPointF& p2) -{ - return p1.x() < p2.x(); -} - -inline static bool -controlpoint_x_less_than(const LutControlPoint& p1, const LutControlPoint& p2) -{ - return p1.first < p2.first; -} - -QGradientStops -pointsToGradientStops(QPolygonF points) -{ - QGradientStops stops; - std::sort(points.begin(), points.end(), x_less_than); - - for (int i = 0; i < points.size(); ++i) { - qreal x = points.at(i).x(); - if (i + 1 < points.size() && x == points.at(i + 1).x()) - continue; - float pixelvalue = points.at(i).y(); - // TODO future: let each point in m_alpha_shade have a full RGBA color and use a color picker to assign it via dbl - // click or some other means - // unsigned int pixelvalue = m_alpha_shade->colorAt(int(x)); - // unsigned int r = (0x00ff0000 & pixelvalue) >> 16; - // unsigned int g = (0x0000ff00 & pixelvalue) >> 8; - // unsigned int b = (0x000000ff & pixelvalue); - // unsigned int a = (0xff000000 & pixelvalue) >> 24; - // QColor color(r, g, b, a); - - QColor color = QColor::fromRgbF(pixelvalue, pixelvalue, pixelvalue, pixelvalue); - if (x > 1) { - LOG_ERROR << "control point x greater than 1"; - return stops; - } - - stops << QGradientStop(x, color); - } - return stops; -} - -void -GradientEditor::pointsUpdated() -{ - // qreal w = m_alpha_shade->width(); - - QGradientStops stops = pointsToGradientStops(m_alpha_shade->points()); - - m_alpha_shade->setGradientStops(stops); - - emit gradientStopsChanged(stops); -} - -static void -set_shade_points(const QPolygonF& points, ShadeWidget* shade) -{ - if (points.size() < 2) { - return; - } - - QGradientStops stops = pointsToGradientStops(points); - shade->setGradientStops(stops); - - shade->hoverPoints()->setPoints(points); - shade->hoverPoints()->setPointLock(0, HoverPoints::LockToLeft); - shade->hoverPoints()->setPointLock(points.size() - 1, HoverPoints::LockToRight); - shade->update(); -} - -void -GradientEditor::setControlPoints(const std::vector& points) -{ - QPolygonF pts_alpha; - - for (auto p : points) { - pts_alpha << QPointF(p.first, p.second); - } - - set_shade_points(pts_alpha, m_alpha_shade); -} - -void -GradientEditor::wheelEvent(QWheelEvent* event) -{ - // wheel does nothing here! - event->ignore(); -} - -GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObject, QWidget* parent) - : QWidget(parent) - , m_histogram(histogram) - , m_gradientData(dataObject) -{ - QVBoxLayout* mainGroupLayout = new QVBoxLayout(this); - - // setWindowTitle(tr("Gradients")); - - // QGroupBox* editorGroup = new QGroupBox(this); - // editorGroup->setTitle(tr("Color Editor")); - m_editor = new GradientEditor(m_histogram, this); - mainGroupLayout->addWidget(m_editor); - - auto* sectionLayout = Controls::createAgaveFormLayout(); - - QButtonGroup* btnGroup = new QButtonGroup(this); - QPushButton* minMaxButton = new QPushButton("Min/Max"); - minMaxButton->setToolTip(tr("Min/Max")); - minMaxButton->setStatusTip(tr("Choose Min/Max mode")); - QPushButton* windowLevelButton = new QPushButton("Wnd/Lvl"); - windowLevelButton->setToolTip(tr("Window/Level")); - windowLevelButton->setStatusTip(tr("Choose Window/Level mode")); - QPushButton* isoButton = new QPushButton("Iso"); - isoButton->setToolTip(tr("Isovalue")); - isoButton->setStatusTip(tr("Choose Isovalue mode")); - QPushButton* pctButton = new QPushButton("Pct"); - pctButton->setToolTip(tr("Histogram Percentiles")); - pctButton->setStatusTip(tr("Choose Histogram percentiles mode")); - QPushButton* customButton = new QPushButton("Custom"); - customButton->setToolTip(tr("Custom")); - customButton->setStatusTip(tr("Choose Custom editing mode")); - - static const int WINDOW_LEVEL_BTNID = 1; - static const int ISO_BTNID = 2; - static const int PCT_BTNID = 3; - static const int CUSTOM_BTNID = 4; - static const int MINMAX_BTNID = 5; - static std::map btnIdToGradientMode = { { WINDOW_LEVEL_BTNID, GradientEditMode::WINDOW_LEVEL }, - { ISO_BTNID, GradientEditMode::ISOVALUE }, - { PCT_BTNID, GradientEditMode::PERCENTILE }, - { MINMAX_BTNID, GradientEditMode::MINMAX }, - { CUSTOM_BTNID, GradientEditMode::CUSTOM } }; - static std::map gradientModeToBtnId = { { GradientEditMode::WINDOW_LEVEL, WINDOW_LEVEL_BTNID }, - { GradientEditMode::ISOVALUE, ISO_BTNID }, - { GradientEditMode::PERCENTILE, PCT_BTNID }, - { GradientEditMode::MINMAX, MINMAX_BTNID }, - { GradientEditMode::CUSTOM, CUSTOM_BTNID } }; - static std::map btnIdToStackedPage = { - { WINDOW_LEVEL_BTNID, 1 }, { ISO_BTNID, 2 }, { PCT_BTNID, 3 }, { MINMAX_BTNID, 0 }, { CUSTOM_BTNID, 4 } - }; - btnGroup->addButton(minMaxButton, MINMAX_BTNID); - btnGroup->addButton(windowLevelButton, WINDOW_LEVEL_BTNID); - btnGroup->addButton(isoButton, ISO_BTNID); - btnGroup->addButton(pctButton, PCT_BTNID); - btnGroup->addButton(customButton, CUSTOM_BTNID); - QHBoxLayout* hbox = new QHBoxLayout(); - hbox->setSpacing(0); - - int initialButtonId = WINDOW_LEVEL_BTNID; - GradientEditMode m = m_gradientData->m_activeMode; - initialButtonId = gradientModeToBtnId[m]; - - for (auto btn : btnGroup->buttons()) { - btn->setCheckable(true); - // set checked state initially. - int btnid = btnGroup->id(btn); - if (btnid == initialButtonId) { - btn->setChecked(true); - } - hbox->addWidget(btn); - } - mainGroupLayout->addLayout(hbox); - - QWidget* firstPageWidget = new QWidget; - auto* section0Layout = Controls::createAgaveFormLayout(); - firstPageWidget->setLayout(section0Layout); - - QWidget* secondPageWidget = new QWidget; - auto* section1Layout = Controls::createAgaveFormLayout(); - secondPageWidget->setLayout(section1Layout); - - QWidget* thirdPageWidget = new QWidget; - auto* section2Layout = Controls::createAgaveFormLayout(); - thirdPageWidget->setLayout(section2Layout); - - QWidget* fourthPageWidget = new QWidget; - auto* section3Layout = Controls::createAgaveFormLayout(); - fourthPageWidget->setLayout(section3Layout); - - QWidget* fifthPageWidget = new QWidget; - auto* section4Layout = Controls::createAgaveFormLayout(); - fifthPageWidget->setLayout(section4Layout); - - QStackedLayout* stackedLayout = new QStackedLayout(mainGroupLayout); - stackedLayout->addWidget(firstPageWidget); - stackedLayout->addWidget(secondPageWidget); - stackedLayout->addWidget(thirdPageWidget); - stackedLayout->addWidget(fourthPageWidget); - stackedLayout->addWidget(fifthPageWidget); - - int initialStackedPageIndex = btnIdToStackedPage[initialButtonId]; - stackedLayout->setCurrentIndex(initialStackedPageIndex); - // if this is not custom mode, then disable the gradient editor - m_editor->setEditable(m == GradientEditMode::CUSTOM); - - connect(btnGroup, - QOverload::of(&QButtonGroup::buttonClicked), - [this, btnGroup, stackedLayout](QAbstractButton* button) { - int id = btnGroup->id(button); - GradientEditMode modeToSet = btnIdToGradientMode[id]; - // if mode is not changing, we are done. - if (modeToSet == this->m_gradientData->m_activeMode) { - return; - } - this->m_gradientData->m_activeMode = modeToSet; - - stackedLayout->setCurrentIndex(btnIdToStackedPage[id]); - - // if this is not custom mode, then disable the gradient editor - m_editor->setEditable(modeToSet == GradientEditMode::CUSTOM); - - this->forceDataUpdate(); - }); - - QIntSlider* minu16Slider = new QIntSlider(); - minu16Slider->setStatusTip(tr("Minimum u16 value")); - minu16Slider->setToolTip(tr("Set minimum u16 value")); - minu16Slider->setRange(0, 65535); - minu16Slider->setSingleStep(1); - minu16Slider->setValue(m_gradientData->m_minu16); - section0Layout->addRow("Min u16", minu16Slider); - QIntSlider* maxu16Slider = new QIntSlider(); - maxu16Slider->setStatusTip(tr("Maximum u16 value")); - maxu16Slider->setToolTip(tr("Set maximum u16 value")); - maxu16Slider->setRange(0, 65535); - maxu16Slider->setSingleStep(1); - maxu16Slider->setValue(m_gradientData->m_maxu16); - section0Layout->addRow("Max u16", maxu16Slider); - connect(minu16Slider, &QIntSlider::valueChanged, [this, maxu16Slider](int i) { - this->m_gradientData->m_minu16 = i; - this->onSetMinMax(i, this->m_gradientData->m_maxu16); - }); - connect(maxu16Slider, &QIntSlider::valueChanged, [this, minu16Slider](int i) { - this->m_gradientData->m_maxu16 = i; - this->onSetMinMax(this->m_gradientData->m_minu16, i); - }); - - QNumericSlider* windowSlider = new QNumericSlider(); - windowSlider->setStatusTip(tr("Window")); - windowSlider->setToolTip(tr("Set size of range of intensities")); - windowSlider->setRange(0.0, 1.0); - windowSlider->setSingleStep(0.01); - windowSlider->setDecimals(3); - windowSlider->setValue(m_gradientData->m_window); - section1Layout->addRow("Window", windowSlider); - QNumericSlider* levelSlider = new QNumericSlider(); - levelSlider->setStatusTip(tr("Level")); - levelSlider->setToolTip(tr("Set level of mid intensity")); - levelSlider->setRange(0.0, 1.0); - levelSlider->setSingleStep(0.01); - levelSlider->setDecimals(3); - levelSlider->setValue(m_gradientData->m_level); - section1Layout->addRow("Level", levelSlider); - connect(windowSlider, &QNumericSlider::valueChanged, [this, levelSlider](double d) { - this->m_gradientData->m_window = d; - this->onSetWindowLevel(d, levelSlider->value()); - }); - connect(levelSlider, &QNumericSlider::valueChanged, [this, windowSlider](double d) { - this->m_gradientData->m_level = d; - this->onSetWindowLevel(windowSlider->value(), d); - }); - - QNumericSlider* isovalueSlider = new QNumericSlider(); - isovalueSlider->setStatusTip(tr("Isovalue")); - isovalueSlider->setToolTip(tr("Set Isovalue")); - isovalueSlider->setRange(0.0, 1.0); - isovalueSlider->setSingleStep(0.01); - isovalueSlider->setDecimals(3); - isovalueSlider->setValue(m_gradientData->m_isovalue); - section2Layout->addRow("Isovalue", isovalueSlider); - QNumericSlider* isorangeSlider = new QNumericSlider(); - isorangeSlider->setStatusTip(tr("Isovalue range")); - isorangeSlider->setToolTip(tr("Set range above and below isovalue")); - isorangeSlider->setRange(0.0, 1.0); - isorangeSlider->setSingleStep(0.01); - isorangeSlider->setDecimals(3); - isorangeSlider->setValue(m_gradientData->m_isorange); - section2Layout->addRow("Iso-range", isorangeSlider); - connect(isovalueSlider, &QNumericSlider::valueChanged, [this, isorangeSlider](double d) { - this->m_gradientData->m_isovalue = d; - this->onSetIsovalue(d, isorangeSlider->value()); - }); - connect(isorangeSlider, &QNumericSlider::valueChanged, [this, isovalueSlider](double d) { - this->m_gradientData->m_isorange = d; - this->onSetIsovalue(isovalueSlider->value(), d); - }); - - QNumericSlider* pctLowSlider = new QNumericSlider(); - pctLowSlider->setStatusTip(tr("Low percentile")); - pctLowSlider->setToolTip(tr("Set bottom percentile")); - pctLowSlider->setRange(0.0, 1.0); - pctLowSlider->setSingleStep(0.01); - pctLowSlider->setDecimals(3); - pctLowSlider->setValue(m_gradientData->m_pctLow); - section3Layout->addRow("Pct Min", pctLowSlider); - QNumericSlider* pctHighSlider = new QNumericSlider(); - pctHighSlider->setStatusTip(tr("High percentile")); - pctHighSlider->setToolTip(tr("Set top percentile")); - pctHighSlider->setRange(0.0, 1.0); - pctHighSlider->setSingleStep(0.01); - pctHighSlider->setDecimals(3); - pctHighSlider->setValue(m_gradientData->m_pctHigh); - section3Layout->addRow("Pct Max", pctHighSlider); - connect(pctLowSlider, &QNumericSlider::valueChanged, [this, pctHighSlider](double d) { - this->m_gradientData->m_pctLow = d; - this->onSetHistogramPercentiles(d, pctHighSlider->value()); - }); - connect(pctHighSlider, &QNumericSlider::valueChanged, [this, pctLowSlider](double d) { - this->m_gradientData->m_pctHigh = d; - this->onSetHistogramPercentiles(pctLowSlider->value(), d); - }); - - mainGroupLayout->addLayout(sectionLayout); - mainGroupLayout->addStretch(1); - - connect(m_editor, &GradientEditor::gradientStopsChanged, this, &GradientWidget::onGradientStopsChanged); - - forceDataUpdate(); -} - -void -GradientWidget::forceDataUpdate() -{ - GradientEditMode mode = this->m_gradientData->m_activeMode; - - switch (mode) { - case GradientEditMode::WINDOW_LEVEL: - this->onSetWindowLevel(this->m_gradientData->m_window, this->m_gradientData->m_level); - break; - case GradientEditMode::ISOVALUE: - this->onSetIsovalue(this->m_gradientData->m_isovalue, this->m_gradientData->m_isorange); - break; - case GradientEditMode::PERCENTILE: - this->onSetHistogramPercentiles(this->m_gradientData->m_pctLow, this->m_gradientData->m_pctHigh); - break; - case GradientEditMode::MINMAX: - this->onSetMinMax(this->m_gradientData->m_minu16, this->m_gradientData->m_maxu16); - break; - case GradientEditMode::CUSTOM: { - m_editor->setControlPoints(this->m_gradientData->m_customControlPoints); - QGradientStops stops = vectorToGradientStops(this->m_gradientData->m_customControlPoints); - emit gradientStopsChanged(stops); - } break; - default: - LOG_ERROR << "Bad gradient editor mode"; - break; - } -} - -void -GradientWidget::onGradientStopsChanged(const QGradientStops& stops) -{ - // update the data stored in m_gradientData - m_gradientData->m_customControlPoints.clear(); - for (int i = 0; i < stops.size(); ++i) { - m_gradientData->m_customControlPoints.push_back(LutControlPoint(stops.at(i).first, stops.at(i).second.alphaF())); - } - - emit gradientStopsChanged(stops); -} - -void -GradientWidget::onSetHistogramPercentiles(float pctLow, float pctHigh) -{ - float window, level; - m_histogram.computeWindowLevelFromPercentiles(pctLow, pctHigh, window, level); - this->onSetWindowLevel(window, level); -} - -void -GradientWidget::onSetWindowLevel(float window, float level) -{ - std::vector points; - static const float epsilon = 0.000001f; - window = std::max(window, epsilon); - float lowEnd = level - window * 0.5f; - float highEnd = level + window * 0.5f; - if (lowEnd <= 0.0f) { - float val = -lowEnd / (highEnd - lowEnd); - points.push_back({ 0.0f, val }); - } else { - points.push_back({ 0.0f, 0.0f }); - points.push_back({ lowEnd, 0.0f }); - } - if (highEnd >= 1.0f) { - float val = (1.0f - lowEnd) / (highEnd - lowEnd); - points.push_back({ 1.0f, val }); - } else { - points.push_back({ highEnd, 1.0f }); - points.push_back({ 1.0f, 1.0f }); - } - m_editor->setControlPoints(points); - emit gradientStopsChanged(vectorToGradientStops(points)); -} - -void -GradientWidget::onSetMinMax(uint16_t minu16, uint16_t maxu16) -{ - float relativeMin = normalizeInt(minu16); - float relativeMax = normalizeInt(maxu16); - relativeMin = std::max(relativeMin, 0.0f); - relativeMax = std::min(relativeMax, 1.0f); - if (relativeMin >= relativeMax) { - LOG_ERROR << "Min value is greater than or equal to max value: " << minu16 << " >= " << maxu16 - << ", datarange=" << m_histogram.dataRange(); - return; - } - float window = relativeMax - relativeMin; - float level = (relativeMax + relativeMin) / 2.0f; - this->onSetWindowLevel(window, level); -} - -void -GradientWidget::onSetIsovalue(float isovalue, float width) -{ - std::vector points; - float lowEnd = isovalue - width * 0.5f; - float highEnd = isovalue + width * 0.5f; - static const float epsilon = 0.00001f; - points.push_back({ 0.0f, 0.0f }); - points.push_back({ lowEnd - epsilon, 0.0f }); - points.push_back({ lowEnd + epsilon, 1.0f }); - points.push_back({ highEnd - epsilon, 1.0f }); - points.push_back({ highEnd + epsilon, 0.0f }); - points.push_back({ 1.0f, 0.0f }); - m_editor->setControlPoints(points); - emit gradientStopsChanged(vectorToGradientStops(points)); -} +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "gradients.h" +#include "hoverpoints.h" + +#include "qtControls/Controls.h" +#include "Defines.h" +#include "Logging.h" + +#include + +std::vector +gradientStopsToVector(QGradientStops& stops) +{ + std::vector v; + for (int i = 0; i < stops.size(); ++i) { + v.push_back(LutControlPoint(stops.at(i).first, stops.at(i).second.alphaF())); + } + return v; +} + +QGradientStops +vectorToGradientStops(std::vector& v) +{ + QGradientStops stops; + for (int i = 0; i < v.size(); ++i) { + stops.push_back( + QPair(v[i].first, QColor::fromRgbF(v[i].second, v[i].second, v[i].second, v[i].second))); + } + return stops; +} + +ShadeWidget::ShadeWidget(const Histogram& histogram, ShadeType type, QWidget* parent) + : QWidget(parent) + , m_shade_type(type) + , m_alpha_gradient(QLinearGradient(0, 0, 0, 0)) + , m_histogram(histogram) +{ + // Checkers background + if (m_shade_type == ARGBShade) { + QPixmap pm(20, 20); + QPainter pmp(&pm); + pmp.fillRect(0, 0, 10, 10, Qt::lightGray); + pmp.fillRect(10, 10, 10, 10, Qt::lightGray); + pmp.fillRect(0, 10, 10, 10, Qt::darkGray); + pmp.fillRect(10, 0, 10, 10, Qt::darkGray); + pmp.end(); + QPalette pal = palette(); + pal.setBrush(backgroundRole(), QBrush(pm)); + setAutoFillBackground(true); + setPalette(pal); + + } else { + setAttribute(Qt::WA_OpaquePaintEvent); + } + + QPolygonF points; + points << QPointF(0.0f, 0.0f) << QPointF(1.0f, 1.0f); + + m_hoverPoints = new HoverPoints(this, HoverPoints::CircleShape); + // m_hoverPoints->setConnectionType(HoverPoints::LineConnection); + m_hoverPoints->setPoints(points); + m_hoverPoints->setPointLock(0, HoverPoints::LockToLeft); + m_hoverPoints->setPointLock(1, HoverPoints::LockToRight); + m_hoverPoints->setSortType(HoverPoints::XSort); + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + connect(m_hoverPoints, &HoverPoints::pointsChanged, this, &ShadeWidget::colorsChanged); +} + +QPolygonF +ShadeWidget::points() const +{ + return m_hoverPoints->points(); +} + +void +ShadeWidget::setEditable(bool editable) +{ + m_hoverPoints->setEditable(editable); +} + +uint +ShadeWidget::colorAt(int x) +{ + generateShade(); + if (m_shade.isNull()) { + return 0; + } + + QPolygonF pts = m_hoverPoints->points(); + for (int i = 1; i < pts.size(); ++i) { + if (pts.at(i - 1).x() <= x && pts.at(i).x() >= x) { + QLineF l(pts.at(i - 1), pts.at(i)); + l.setLength(l.length() * ((x - l.x1()) / l.dx())); + return m_shade.pixel(qRound(qMin(l.x2(), (qreal(m_shade.width() - 1)))), + qRound(qMin(l.y2(), qreal(m_shade.height() - 1)))); + } + } + return 0; +} + +void +ShadeWidget::setGradientStops(const QGradientStops& stops) +{ + if (m_shade_type == ARGBShade) { + m_alpha_gradient = QLinearGradient(0, 0, width(), 0); + + for (int i = 0; i < stops.size(); ++i) { + QColor c = stops.at(i).second; + m_alpha_gradient.setColorAt(stops.at(i).first, QColor(c.red(), c.green(), c.blue())); + } + + m_shade = QImage(); + generateShade(); + update(); + } +} + +void +ShadeWidget::paintEvent(QPaintEvent*) +{ + generateShade(); + + QPainter p(this); + p.drawImage(0, 0, m_shade); + /* + qreal barWidth = width() / (qreal)m_histogram.size(); + + for (int i = 0; i < m_histogram.size(); ++i) { + qreal h = m_histogram[i] * height(); + // draw level + painter.fillRect(barWidth * i, height() - h, barWidth * (i + 1), height(), Qt::red); + // clear the rest of the control + painter.fillRect(barWidth * i, 0, barWidth * (i + 1), height() - h, Qt::black); + } + */ + p.setPen(QColor(146, 146, 146)); + p.drawRect(0, 0, width() - 1, height() - 1); +} + +void +ShadeWidget::drawHistogram(QPainter& p, int w, int h) +{ + size_t nbins = m_histogram._bins.size(); + int maxbinsize = m_histogram._bins[m_histogram._maxBin]; + for (size_t i = 0; i < nbins; ++i) { + float binheight = (float)m_histogram._bins[i] * (float)(h - 1) / (float)maxbinsize; + p.fillRect( + QRectF((float)i * (float)(w - 1) / (float)nbins, h - 1 - binheight, (float)(w - 1) / (float)nbins, binheight), + QColor(0, 0, 0, 255)); + } +} + +void +ShadeWidget::generateShade() +{ + if (m_shade.isNull() || m_shade.size() != size()) { + + QRect qrect = rect(); + QSize qsize = size(); + if (m_shade_type == ARGBShade) { + m_shade = QImage(qsize, QImage::Format_ARGB32_Premultiplied); + if (m_shade.isNull()) { + return; + } + m_shade.fill(0); + + QPainter p(&m_shade); + p.fillRect(qrect, m_alpha_gradient); + + p.setCompositionMode(QPainter::CompositionMode_DestinationIn); + QLinearGradient fade(0, 0, 0, height() - 1); + fade.setColorAt(0, QColor(255, 255, 255, 255)); + fade.setColorAt(1, QColor(0, 0, 0, 0)); + p.fillRect(qrect, fade); + + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + drawHistogram(p, qsize.width(), qsize.height()); + + } else { + m_shade = QImage(qsize, QImage::Format_RGB32); + if (m_shade.isNull()) { + return; + } + QLinearGradient shade(0, 0, 0, height()); + shade.setColorAt(1, Qt::black); + + if (m_shade_type == RedShade) + shade.setColorAt(0, Qt::red); + else if (m_shade_type == GreenShade) + shade.setColorAt(0, Qt::green); + else + shade.setColorAt(0, Qt::blue); + + QPainter p(&m_shade); + p.fillRect(qrect, shade); + + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + + drawHistogram(p, qsize.width(), qsize.height()); + } + } +} + +GradientEditor::GradientEditor(const Histogram& histogram, QWidget* parent) + : QWidget(parent) +{ + QVBoxLayout* vbox = new QVBoxLayout(this); + vbox->setSpacing(1); + // vbox->setMargin(1); + + m_alpha_shade = new ShadeWidget(histogram, ShadeWidget::ARGBShade, this); + + vbox->addWidget(m_alpha_shade); + + connect(m_alpha_shade, &ShadeWidget::colorsChanged, this, &GradientEditor::pointsUpdated); +} + +inline static bool +x_less_than(const QPointF& p1, const QPointF& p2) +{ + return p1.x() < p2.x(); +} + +inline static bool +controlpoint_x_less_than(const LutControlPoint& p1, const LutControlPoint& p2) +{ + return p1.first < p2.first; +} + +QGradientStops +pointsToGradientStops(QPolygonF points) +{ + QGradientStops stops; + std::sort(points.begin(), points.end(), x_less_than); + + for (int i = 0; i < points.size(); ++i) { + qreal x = points.at(i).x(); + if (i + 1 < points.size() && x == points.at(i + 1).x()) + continue; + float pixelvalue = points.at(i).y(); + // TODO future: let each point in m_alpha_shade have a full RGBA color and use a color picker to assign it via dbl + // click or some other means + // unsigned int pixelvalue = m_alpha_shade->colorAt(int(x)); + // unsigned int r = (0x00ff0000 & pixelvalue) >> 16; + // unsigned int g = (0x0000ff00 & pixelvalue) >> 8; + // unsigned int b = (0x000000ff & pixelvalue); + // unsigned int a = (0xff000000 & pixelvalue) >> 24; + // QColor color(r, g, b, a); + + QColor color = QColor::fromRgbF(pixelvalue, pixelvalue, pixelvalue, pixelvalue); + if (x > 1) { + LOG_ERROR << "control point x greater than 1"; + return stops; + } + + stops << QGradientStop(x, color); + } + return stops; +} + +void +GradientEditor::pointsUpdated() +{ + // qreal w = m_alpha_shade->width(); + + QGradientStops stops = pointsToGradientStops(m_alpha_shade->points()); + + m_alpha_shade->setGradientStops(stops); + + emit gradientStopsChanged(stops); +} + +static void +set_shade_points(const QPolygonF& points, ShadeWidget* shade) +{ + if (points.size() < 2) { + return; + } + + QGradientStops stops = pointsToGradientStops(points); + shade->setGradientStops(stops); + + shade->hoverPoints()->setPoints(points); + shade->hoverPoints()->setPointLock(0, HoverPoints::LockToLeft); + shade->hoverPoints()->setPointLock(points.size() - 1, HoverPoints::LockToRight); + shade->update(); +} + +void +GradientEditor::setControlPoints(const std::vector& points) +{ + QPolygonF pts_alpha; + + for (auto p : points) { + pts_alpha << QPointF(p.first, p.second); + } + + set_shade_points(pts_alpha, m_alpha_shade); +} + +void +GradientEditor::wheelEvent(QWheelEvent* event) +{ + // wheel does nothing here! + event->ignore(); +} + +GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObject, QWidget* parent) + : QWidget(parent) + , m_histogram(histogram) + , m_gradientData(dataObject) +{ + QVBoxLayout* mainGroupLayout = new QVBoxLayout(this); + + // setWindowTitle(tr("Gradients")); + + // QGroupBox* editorGroup = new QGroupBox(this); + // editorGroup->setTitle(tr("Color Editor")); + m_editor = new GradientEditor(m_histogram, this); + mainGroupLayout->addWidget(m_editor); + + auto* sectionLayout = Controls::createAgaveFormLayout(); + + QButtonGroup* btnGroup = new QButtonGroup(this); + QPushButton* minMaxButton = new QPushButton("Min/Max"); + minMaxButton->setToolTip(tr("Min/Max")); + minMaxButton->setStatusTip(tr("Choose Min/Max mode")); + QPushButton* windowLevelButton = new QPushButton("Wnd/Lvl"); + windowLevelButton->setToolTip(tr("Window/Level")); + windowLevelButton->setStatusTip(tr("Choose Window/Level mode")); + QPushButton* isoButton = new QPushButton("Iso"); + isoButton->setToolTip(tr("Isovalue")); + isoButton->setStatusTip(tr("Choose Isovalue mode")); + QPushButton* pctButton = new QPushButton("Pct"); + pctButton->setToolTip(tr("Histogram Percentiles")); + pctButton->setStatusTip(tr("Choose Histogram percentiles mode")); + QPushButton* customButton = new QPushButton("Custom"); + customButton->setToolTip(tr("Custom")); + customButton->setStatusTip(tr("Choose Custom editing mode")); + + static const int WINDOW_LEVEL_BTNID = 1; + static const int ISO_BTNID = 2; + static const int PCT_BTNID = 3; + static const int CUSTOM_BTNID = 4; + static const int MINMAX_BTNID = 5; + static std::map btnIdToGradientMode = { { WINDOW_LEVEL_BTNID, GradientEditMode::WINDOW_LEVEL }, + { ISO_BTNID, GradientEditMode::ISOVALUE }, + { PCT_BTNID, GradientEditMode::PERCENTILE }, + { MINMAX_BTNID, GradientEditMode::MINMAX }, + { CUSTOM_BTNID, GradientEditMode::CUSTOM } }; + static std::map gradientModeToBtnId = { { GradientEditMode::WINDOW_LEVEL, WINDOW_LEVEL_BTNID }, + { GradientEditMode::ISOVALUE, ISO_BTNID }, + { GradientEditMode::PERCENTILE, PCT_BTNID }, + { GradientEditMode::MINMAX, MINMAX_BTNID }, + { GradientEditMode::CUSTOM, CUSTOM_BTNID } }; + static std::map btnIdToStackedPage = { + { WINDOW_LEVEL_BTNID, 1 }, { ISO_BTNID, 2 }, { PCT_BTNID, 3 }, { MINMAX_BTNID, 0 }, { CUSTOM_BTNID, 4 } + }; + btnGroup->addButton(minMaxButton, MINMAX_BTNID); + btnGroup->addButton(windowLevelButton, WINDOW_LEVEL_BTNID); + btnGroup->addButton(isoButton, ISO_BTNID); + btnGroup->addButton(pctButton, PCT_BTNID); + btnGroup->addButton(customButton, CUSTOM_BTNID); + QHBoxLayout* hbox = new QHBoxLayout(); + hbox->setSpacing(0); + + int initialButtonId = WINDOW_LEVEL_BTNID; + GradientEditMode m = m_gradientData->m_activeMode; + initialButtonId = gradientModeToBtnId[m]; + + for (auto btn : btnGroup->buttons()) { + btn->setCheckable(true); + // set checked state initially. + int btnid = btnGroup->id(btn); + if (btnid == initialButtonId) { + btn->setChecked(true); + } + hbox->addWidget(btn); + } + mainGroupLayout->addLayout(hbox); + + QWidget* firstPageWidget = new QWidget; + auto* section0Layout = Controls::createAgaveFormLayout(); + firstPageWidget->setLayout(section0Layout); + + QWidget* secondPageWidget = new QWidget; + auto* section1Layout = Controls::createAgaveFormLayout(); + secondPageWidget->setLayout(section1Layout); + + QWidget* thirdPageWidget = new QWidget; + auto* section2Layout = Controls::createAgaveFormLayout(); + thirdPageWidget->setLayout(section2Layout); + + QWidget* fourthPageWidget = new QWidget; + auto* section3Layout = Controls::createAgaveFormLayout(); + fourthPageWidget->setLayout(section3Layout); + + QWidget* fifthPageWidget = new QWidget; + auto* section4Layout = Controls::createAgaveFormLayout(); + fifthPageWidget->setLayout(section4Layout); + + QStackedLayout* stackedLayout = new QStackedLayout(mainGroupLayout); + stackedLayout->addWidget(firstPageWidget); + stackedLayout->addWidget(secondPageWidget); + stackedLayout->addWidget(thirdPageWidget); + stackedLayout->addWidget(fourthPageWidget); + stackedLayout->addWidget(fifthPageWidget); + + int initialStackedPageIndex = btnIdToStackedPage[initialButtonId]; + stackedLayout->setCurrentIndex(initialStackedPageIndex); + // if this is not custom mode, then disable the gradient editor + m_editor->setEditable(m == GradientEditMode::CUSTOM); + + connect(btnGroup, + QOverload::of(&QButtonGroup::buttonClicked), + [this, btnGroup, stackedLayout](QAbstractButton* button) { + int id = btnGroup->id(button); + GradientEditMode modeToSet = btnIdToGradientMode[id]; + // if mode is not changing, we are done. + if (modeToSet == this->m_gradientData->m_activeMode) { + return; + } + this->m_gradientData->m_activeMode = modeToSet; + + stackedLayout->setCurrentIndex(btnIdToStackedPage[id]); + + // if this is not custom mode, then disable the gradient editor + m_editor->setEditable(modeToSet == GradientEditMode::CUSTOM); + + this->forceDataUpdate(); + }); + + QIntSlider* minu16Slider = new QIntSlider(); + minu16Slider->setStatusTip(tr("Minimum u16 value")); + minu16Slider->setToolTip(tr("Set minimum u16 value")); + minu16Slider->setRange(0, 65535); + minu16Slider->setSingleStep(1); + minu16Slider->setValue(m_gradientData->m_minu16); + section0Layout->addRow("Min u16", minu16Slider); + QIntSlider* maxu16Slider = new QIntSlider(); + maxu16Slider->setStatusTip(tr("Maximum u16 value")); + maxu16Slider->setToolTip(tr("Set maximum u16 value")); + maxu16Slider->setRange(0, 65535); + maxu16Slider->setSingleStep(1); + maxu16Slider->setValue(m_gradientData->m_maxu16); + section0Layout->addRow("Max u16", maxu16Slider); + connect(minu16Slider, &QIntSlider::valueChanged, [this, maxu16Slider](int i) { + this->m_gradientData->m_minu16 = i; + this->onSetMinMax(i, this->m_gradientData->m_maxu16); + }); + connect(maxu16Slider, &QIntSlider::valueChanged, [this, minu16Slider](int i) { + this->m_gradientData->m_maxu16 = i; + this->onSetMinMax(this->m_gradientData->m_minu16, i); + }); + + QNumericSlider* windowSlider = new QNumericSlider(); + windowSlider->setStatusTip(tr("Window")); + windowSlider->setToolTip(tr("Set size of range of intensities")); + windowSlider->setRange(0.0, 1.0); + windowSlider->setSingleStep(0.01); + windowSlider->setDecimals(3); + windowSlider->setValue(m_gradientData->m_window); + section1Layout->addRow("Window", windowSlider); + QNumericSlider* levelSlider = new QNumericSlider(); + levelSlider->setStatusTip(tr("Level")); + levelSlider->setToolTip(tr("Set level of mid intensity")); + levelSlider->setRange(0.0, 1.0); + levelSlider->setSingleStep(0.01); + levelSlider->setDecimals(3); + levelSlider->setValue(m_gradientData->m_level); + section1Layout->addRow("Level", levelSlider); + connect(windowSlider, &QNumericSlider::valueChanged, [this, levelSlider](double d) { + this->m_gradientData->m_window = d; + this->onSetWindowLevel(d, levelSlider->value()); + }); + connect(levelSlider, &QNumericSlider::valueChanged, [this, windowSlider](double d) { + this->m_gradientData->m_level = d; + this->onSetWindowLevel(windowSlider->value(), d); + }); + + QNumericSlider* isovalueSlider = new QNumericSlider(); + isovalueSlider->setStatusTip(tr("Isovalue")); + isovalueSlider->setToolTip(tr("Set Isovalue")); + isovalueSlider->setRange(0.0, 1.0); + isovalueSlider->setSingleStep(0.01); + isovalueSlider->setDecimals(3); + isovalueSlider->setValue(m_gradientData->m_isovalue); + section2Layout->addRow("Isovalue", isovalueSlider); + QNumericSlider* isorangeSlider = new QNumericSlider(); + isorangeSlider->setStatusTip(tr("Isovalue range")); + isorangeSlider->setToolTip(tr("Set range above and below isovalue")); + isorangeSlider->setRange(0.0, 1.0); + isorangeSlider->setSingleStep(0.01); + isorangeSlider->setDecimals(3); + isorangeSlider->setValue(m_gradientData->m_isorange); + section2Layout->addRow("Iso-range", isorangeSlider); + connect(isovalueSlider, &QNumericSlider::valueChanged, [this, isorangeSlider](double d) { + this->m_gradientData->m_isovalue = d; + this->onSetIsovalue(d, isorangeSlider->value()); + }); + connect(isorangeSlider, &QNumericSlider::valueChanged, [this, isovalueSlider](double d) { + this->m_gradientData->m_isorange = d; + this->onSetIsovalue(isovalueSlider->value(), d); + }); + + QNumericSlider* pctLowSlider = new QNumericSlider(); + pctLowSlider->setStatusTip(tr("Low percentile")); + pctLowSlider->setToolTip(tr("Set bottom percentile")); + pctLowSlider->setRange(0.0, 1.0); + pctLowSlider->setSingleStep(0.01); + pctLowSlider->setDecimals(3); + pctLowSlider->setValue(m_gradientData->m_pctLow); + section3Layout->addRow("Pct Min", pctLowSlider); + QNumericSlider* pctHighSlider = new QNumericSlider(); + pctHighSlider->setStatusTip(tr("High percentile")); + pctHighSlider->setToolTip(tr("Set top percentile")); + pctHighSlider->setRange(0.0, 1.0); + pctHighSlider->setSingleStep(0.01); + pctHighSlider->setDecimals(3); + pctHighSlider->setValue(m_gradientData->m_pctHigh); + section3Layout->addRow("Pct Max", pctHighSlider); + connect(pctLowSlider, &QNumericSlider::valueChanged, [this, pctHighSlider](double d) { + this->m_gradientData->m_pctLow = d; + this->onSetHistogramPercentiles(d, pctHighSlider->value()); + }); + connect(pctHighSlider, &QNumericSlider::valueChanged, [this, pctLowSlider](double d) { + this->m_gradientData->m_pctHigh = d; + this->onSetHistogramPercentiles(pctLowSlider->value(), d); + }); + + mainGroupLayout->addLayout(sectionLayout); + mainGroupLayout->addStretch(1); + + connect(m_editor, &GradientEditor::gradientStopsChanged, this, &GradientWidget::onGradientStopsChanged); + + forceDataUpdate(); +} + +void +GradientWidget::forceDataUpdate() +{ + GradientEditMode mode = this->m_gradientData->m_activeMode; + + switch (mode) { + case GradientEditMode::WINDOW_LEVEL: + this->onSetWindowLevel(this->m_gradientData->m_window, this->m_gradientData->m_level); + break; + case GradientEditMode::ISOVALUE: + this->onSetIsovalue(this->m_gradientData->m_isovalue, this->m_gradientData->m_isorange); + break; + case GradientEditMode::PERCENTILE: + this->onSetHistogramPercentiles(this->m_gradientData->m_pctLow, this->m_gradientData->m_pctHigh); + break; + case GradientEditMode::MINMAX: + this->onSetMinMax(this->m_gradientData->m_minu16, this->m_gradientData->m_maxu16); + break; + case GradientEditMode::CUSTOM: { + m_editor->setControlPoints(this->m_gradientData->m_customControlPoints); + QGradientStops stops = vectorToGradientStops(this->m_gradientData->m_customControlPoints); + emit gradientStopsChanged(stops); + } break; + default: + LOG_ERROR << "Bad gradient editor mode"; + break; + } +} + +void +GradientWidget::onGradientStopsChanged(const QGradientStops& stops) +{ + // update the data stored in m_gradientData + m_gradientData->m_customControlPoints.clear(); + for (int i = 0; i < stops.size(); ++i) { + m_gradientData->m_customControlPoints.push_back(LutControlPoint(stops.at(i).first, stops.at(i).second.alphaF())); + } + + emit gradientStopsChanged(stops); +} + +void +GradientWidget::onSetHistogramPercentiles(float pctLow, float pctHigh) +{ + float window, level; + m_histogram.computeWindowLevelFromPercentiles(pctLow, pctHigh, window, level); + this->onSetWindowLevel(window, level); +} + +void +GradientWidget::onSetWindowLevel(float window, float level) +{ + std::vector points; + static const float epsilon = 0.000001f; + window = std::max(window, epsilon); + float lowEnd = level - window * 0.5f; + float highEnd = level + window * 0.5f; + if (lowEnd <= 0.0f) { + float val = -lowEnd / (highEnd - lowEnd); + points.push_back({ 0.0f, val }); + } else { + points.push_back({ 0.0f, 0.0f }); + points.push_back({ lowEnd, 0.0f }); + } + if (highEnd >= 1.0f) { + float val = (1.0f - lowEnd) / (highEnd - lowEnd); + points.push_back({ 1.0f, val }); + } else { + points.push_back({ highEnd, 1.0f }); + points.push_back({ 1.0f, 1.0f }); + } + m_editor->setControlPoints(points); + emit gradientStopsChanged(vectorToGradientStops(points)); +} + +void +GradientWidget::onSetMinMax(uint16_t minu16, uint16_t maxu16) +{ + float relativeMin = normalizeInt(minu16); + float relativeMax = normalizeInt(maxu16); + relativeMin = std::max(relativeMin, 0.0f); + relativeMax = std::min(relativeMax, 1.0f); + if (relativeMin >= relativeMax) { + LOG_ERROR << "Min value is greater than or equal to max value: " << minu16 << " >= " << maxu16 + << ", datarange=" << m_histogram.dataRange(); + return; + } + float window = relativeMax - relativeMin; + float level = (relativeMax + relativeMin) / 2.0f; + this->onSetWindowLevel(window, level); +} + +void +GradientWidget::onSetIsovalue(float isovalue, float width) +{ + std::vector points; + float lowEnd = isovalue - width * 0.5f; + float highEnd = isovalue + width * 0.5f; + static const float epsilon = 0.00001f; + points.push_back({ 0.0f, 0.0f }); + points.push_back({ lowEnd - epsilon, 0.0f }); + points.push_back({ lowEnd + epsilon, 1.0f }); + points.push_back({ highEnd - epsilon, 1.0f }); + points.push_back({ highEnd + epsilon, 0.0f }); + points.push_back({ 1.0f, 0.0f }); + m_editor->setControlPoints(points); + emit gradientStopsChanged(vectorToGradientStops(points)); +} diff --git a/renderlib/uiInfo.hpp b/renderlib/uiInfo.hpp index 5a3ac8c72..fcdb86dab 100644 --- a/renderlib/uiInfo.hpp +++ b/renderlib/uiInfo.hpp @@ -41,36 +41,7 @@ struct ComboBoxUiInfo : public GenericUIInfo { } }; - -class ColorWithIntensityUiInfo : public prtyPropertyUIInfo -{ -public: - static constexpr const char* TYPE = "ColorWithIntensity"; - float min = 0.0f; - float max = 0.0f; - int decimals = 0; - float singleStep = 0.0f; - int numTickMarks = 0; - std::string suffix; - - ColorWithIntensityUiInfo(prtyProperty* i_pColorProperty, prtyProperty* i_pIntensityProperty) - : prtyPropertyUIInfo(i_pColorProperty) - { - AddProperty(i_pIntensityProperty); - SetControlName(TYPE); - } - ColorWithIntensityUiInfo(prtyProperty* i_pColorProperty, - prtyProperty* i_pIntensityProperty, - const std::string& i_Category, - const std::string& i_Description) - : prtyPropertyUIInfo(i_pColorProperty, i_Category, i_Description) - { - AddProperty(i_pIntensityProperty); - SetControlName(TYPE); - } -}; - -class FloatSliderSpinnerUiInfo : public prtyPropertyUIInfo +struct FloatSliderSpinnerUiInfo : public GenericUIInfo { static constexpr const char* TYPE = "FloatSliderSpinner"; float min = 0.0f; @@ -111,14 +82,3 @@ struct IntSliderSpinnerUiInfo : public GenericUIInfo IntSliderSpinnerUiInfo() { type = IntSliderSpinnerUiInfo::TYPE; } }; - -struct ColorPickerUiInfo : public GenericUIInfo -{ - static constexpr const char* TYPE = "ColorPicker"; - - ColorPickerUiInfo() { type = ColorPickerUiInfo::TYPE; } - ColorPickerUiInfo(std::string formLabel, std::string statusTip, std::string toolTip) - : GenericUIInfo(ColorPickerUiInfo::TYPE, formLabel, statusTip, toolTip) - { - } -}; From 532c6435d0425b7b4fe72f5f473ab61df04f9865 Mon Sep 17 00:00:00 2001 From: DMT Date: Mon, 19 May 2025 18:34:55 -0700 Subject: [PATCH 03/63] add properties for appearance settings --- renderlib/AppearanceDataObject.cpp | 66 ++++++++++++++++++++++++++- renderlib/AppearanceDataObject.hpp | 38 ++++++++------- renderlib/AppearanceUiDescription.cpp | 56 +++++++++++++++++++++++ renderlib/AppearanceUiDescription.hpp | 18 ++++++++ renderlib/CMakeLists.txt | 4 +- renderlib/uiInfo.hpp | 11 +++++ 6 files changed, 173 insertions(+), 20 deletions(-) create mode 100644 renderlib/AppearanceUiDescription.cpp create mode 100644 renderlib/AppearanceUiDescription.hpp diff --git a/renderlib/AppearanceDataObject.cpp b/renderlib/AppearanceDataObject.cpp index 82055d9a1..3ef9bf6bc 100644 --- a/renderlib/AppearanceDataObject.cpp +++ b/renderlib/AppearanceDataObject.cpp @@ -3,4 +3,68 @@ #include "Enumerations.h" #include "Logging.h" -AppearanceDataObject::AppearanceDataObject() {} +AppearanceDataOject::AppearanceDataOject(RenderSettings* renderSettings) + : m_renderSettings(renderSettings) +{ + updatePropsFromRenderSettings(); + // hook up properties to update the underlying camera + RendererType.addCallback([this](prtyProperty* p, bool) { update(); }); + ShadingType.addCallback([this](prtyProperty* p, bool) { update(); }); + DensityScale.addCallback([this](prtyProperty* p, bool) { update(); }); + GradientFactor.addCallback([this](prtyProperty* p, bool) { update(); }); + StepSizePrimaryRay.addCallback([this](prtyProperty* p, bool) { update(); }); + StepSizeSecondaryRay.addCallback([this](prtyProperty* p, bool) { update(); }); + Interpolate.addCallback([this](prtyProperty* p, bool) { update(); }); + BackgroundColor.addCallback([this](prtyProperty* p, bool) { update(); }); + ShowBoundingBox.addCallback([this](prtyProperty* p, bool) { update(); }); + BoundingBoxColor.addCallback([this](prtyProperty* p, bool) { update(); }); + ShowScaleBar.addCallback([this](prtyProperty* p, bool) { update(); }); +} + +void +AppearanceDataOject::updatePropsFromRenderSettings() +{ + if (m_renderSettings) { + ShadingType.set(m_renderSettings->m_RenderSettings.m_ShadingType); + // RendererType.set(m_renderSettings->m_RenderSettings.m_RendererType); + DensityScale.set(m_renderSettings->m_RenderSettings.m_DensityScale); + GradientFactor.set(m_renderSettings->m_RenderSettings.m_GradientFactor); + StepSizePrimaryRay.set(m_renderSettings->m_RenderSettings.m_StepSizeFactor); + StepSizeSecondaryRay.set(m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow); + Interpolate.set(m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling); + // BackgroundColor.set(glm::vec3(m_renderSettings->m_RenderSettings.m_BackgroundColor[0], + // m_renderSettings->m_RenderSettings.m_BackgroundColor[1], + // m_renderSettings->m_RenderSettings.m_BackgroundColor[2])); + // ShowBoundingBox.set(m_renderSettings->m_RenderSettings.m_ShowBoundingBox); + // BoundingBoxColor.set(glm::vec3(m_renderSettings->m_RenderSettings.m_BoundingBoxColor[0], + // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[1], + // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[2])); + // ShowScaleBar.set(m_renderSettings->m_RenderSettings.m_ShowScaleBar); + } +} +void +AppearanceDataOject::update() +{ + // update low-level object from properties + if (m_renderSettings) { + m_renderSettings->m_RenderSettings.m_ShadingType = ShadingType.get(); + // m_renderSettings->m_RenderSettings.m_RendererType = RendererType.get(); + m_renderSettings->m_RenderSettings.m_DensityScale = DensityScale.get(); + m_renderSettings->m_RenderSettings.m_GradientFactor = GradientFactor.get(); + m_renderSettings->m_RenderSettings.m_StepSizeFactor = StepSizePrimaryRay.get(); + m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow = StepSizeSecondaryRay.get(); + m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling = Interpolate.get(); + // m_renderSettings->m_RenderSettings.m_BackgroundColor[0] = BackgroundColor.get().x; + // m_renderSettings->m_RenderSettings.m_BackgroundColor[1] = BackgroundColor.get().y; + // m_renderSettings->m_RenderSettings.m_BackgroundColor[2] = BackgroundColor.get().z; + // m_renderSettings->m_RenderSettings.m_ShowBoundingBox = ShowBoundingBox.get(); + // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[0] = BoundingBoxColor.get().x; + // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[1] = BoundingBoxColor.get().y; + // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[2] = BoundingBoxColor.get().z; + // m_renderSettings->m_RenderSettings.m_ShowScaleBar = ShowScaleBar.get(); + + m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); + m_renderSettings->m_DirtyFlags.SetFlag(TransferFunctionDirty); + m_renderSettings->m_DirtyFlags.SetFlag(LightsDirty); + } +} \ No newline at end of file diff --git a/renderlib/AppearanceDataObject.hpp b/renderlib/AppearanceDataObject.hpp index 02df6f82e..a5804064b 100644 --- a/renderlib/AppearanceDataObject.hpp +++ b/renderlib/AppearanceDataObject.hpp @@ -1,26 +1,30 @@ #pragma once -#include "core/prty/prtyInt8.hpp" -#include "core/prty/prtyFloat.hpp" -#include "core/prty/prtyBoolean.hpp" -#include "core/prty/prtyVector3d.hpp" -#include "core/prty/prtyColor.hpp" +#include "core/prty/prtyProperty.h" +#include "RenderSettings.h" #include "glm.h" class AppearanceDataOject { public: - AppearanceDataObject(); + AppearanceDataOject(RenderSettings* camera); - prtyInt8 RendererType{ "RendererType", 0 }; - prtyInt8 ShadingType{ "ShadingType", 0 }; - prtyFloat DensityScale{ "DensityScale", 1.0f }; - prtyFloat GradientFactor{ "GradientFactor", 0.5f }; - prtyFloat StepSizePrimaryRay{ "StepSizePrimaryRay", 1.0f }; - prtyFloat StepSizeSecondaryRay{ "StepSizeSecondaryRay", 1.0f }; - prtyBoolean Interpolate{ "Interpolate", false }; - prtyColor BackgroundColor{ "BackgroundColor", glm::vec4(0.0f, 0.0f, 0.0f, 1.0f) }; - prtyBoolean ShowBoundingBox{ "ShowBoundingBox", false }; - prtyColor BoundingBoxColor{ "BoundingBoxColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; - prtyBoolean ShowScaleBar{ "ShowScaleBar", false }; + prtyProperty RendererType{ "RendererType", 0 }; + prtyProperty ShadingType{ "ShadingType", 0 }; + prtyProperty DensityScale{ "DensityScale", 1.0f }; + prtyProperty GradientFactor{ "GradientFactor", 0.5f }; + prtyProperty StepSizePrimaryRay{ "StepSizePrimaryRay", 1.0f }; + prtyProperty StepSizeSecondaryRay{ "StepSizeSecondaryRay", 1.0f }; + prtyProperty Interpolate{ "Interpolate", false }; + prtyProperty BackgroundColor{ "BackgroundColor", glm::vec3(0.0f, 0.0f, 0.0f) }; + prtyProperty ShowBoundingBox{ "ShowBoundingBox", false }; + prtyProperty BoundingBoxColor{ "BoundingBoxColor", glm::vec3(1.0f, 1.0f, 1.0f) }; + prtyProperty ShowScaleBar{ "ShowScaleBar", false }; + + RenderSettings* m_renderSettings; + + void updatePropsFromRenderSettings(); + +private: + void update(); }; diff --git a/renderlib/AppearanceUiDescription.cpp b/renderlib/AppearanceUiDescription.cpp new file mode 100644 index 000000000..c8925306c --- /dev/null +++ b/renderlib/AppearanceUiDescription.cpp @@ -0,0 +1,56 @@ +#include "AppearanceUiDescription.hpp" + +ComboBoxUiInfo AppearanceUiDescription::m_rendererType("Renderer Type", + "Select volume rendering type", + "Select volume rendering type", + { "Ray march blending", "Path Traced" }); +ComboBoxUiInfo AppearanceUiDescription::m_shadingType("Shading Type", + "Select volume shading style", + "Select volume shading style", + { "BRDF Only", "Phase Function Only", "Mixed" }); +FloatSliderSpinnerUiInfo AppearanceUiDescription::m_densityScale("Scattering Density", + "Set scattering density for volume", + "Set scattering density for volume", + 0.001f, + 100.0f, + 3, + 0.01f, + 10); +FloatSliderSpinnerUiInfo AppearanceUiDescription::m_gradientFactor("Shading Type Mixture", + "Mix between BRDF and Phase shading", + "Mix between BRDF and Phase shading", + 0.0f, + 1.0f, + 3, + 0.01f, + 10); +FloatSliderSpinnerUiInfo AppearanceUiDescription::m_stepSizePrimaryRay("Primary Ray Step Size", + "Set volume ray march step size for camera rays", + "Set volume ray march step size for camera rays", + 1.0f, + 100.0f, + 3, + 0.01f, + 10); +FloatSliderSpinnerUiInfo AppearanceUiDescription::m_stepSizeSecondaryRay( + "Secondary Ray Step Size", + "Set volume ray march step size for scattered rays", + "Set volume ray march step size for scattered rays", + 1.0f, + 100.0f, + 3, + 0.01f, + 10); +CheckBoxUiInfo AppearanceUiDescription::m_interpolate("Interpolate", + "Interpolated volume sampling", + "Interpolated volume sampling"); +ColorPickerUiInfo AppearanceUiDescription::m_backgroundColor("Background Color", + "Set background color", + "Set background color"); +CheckBoxUiInfo AppearanceUiDescription::m_showBoundingBox("Show Bounding Box", + "Show/hide bounding box", + "Show/hide bounding box"); +ColorPickerUiInfo AppearanceUiDescription::m_boundingBoxColor("Bounding Box Color", + "Set bounding box color", + "Set bounding box color"); +CheckBoxUiInfo AppearanceUiDescription::m_showScaleBar("Show Scale Bar", "Show/hide scale bar", "Show/hide scale bar"); diff --git a/renderlib/AppearanceUiDescription.hpp b/renderlib/AppearanceUiDescription.hpp new file mode 100644 index 000000000..6441c56a0 --- /dev/null +++ b/renderlib/AppearanceUiDescription.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "uiInfo.hpp" + +struct AppearanceUiDescription +{ + static ComboBoxUiInfo m_rendererType; + static ComboBoxUiInfo m_shadingType; + static FloatSliderSpinnerUiInfo m_densityScale; + static FloatSliderSpinnerUiInfo m_gradientFactor; + static FloatSliderSpinnerUiInfo m_stepSizePrimaryRay; + static FloatSliderSpinnerUiInfo m_stepSizeSecondaryRay; + static CheckBoxUiInfo m_interpolate; + static ColorPickerUiInfo m_backgroundColor; + static CheckBoxUiInfo m_showBoundingBox; + static ColorPickerUiInfo m_boundingBoxColor; + static CheckBoxUiInfo m_showScaleBar; +}; diff --git a/renderlib/CMakeLists.txt b/renderlib/CMakeLists.txt index d5451b52f..4309efc7b 100644 --- a/renderlib/CMakeLists.txt +++ b/renderlib/CMakeLists.txt @@ -16,8 +16,8 @@ target_include_directories(renderlib PUBLIC target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceDataObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceDataObject.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceObject.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceObject.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceUiDescription.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceUiDescription.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/AppScene.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/AppScene.h" "${CMAKE_CURRENT_SOURCE_DIR}/AreaLightObject.cpp" diff --git a/renderlib/uiInfo.hpp b/renderlib/uiInfo.hpp index fcdb86dab..21acacd59 100644 --- a/renderlib/uiInfo.hpp +++ b/renderlib/uiInfo.hpp @@ -82,3 +82,14 @@ struct IntSliderSpinnerUiInfo : public GenericUIInfo IntSliderSpinnerUiInfo() { type = IntSliderSpinnerUiInfo::TYPE; } }; + +struct ColorPickerUiInfo : public GenericUIInfo +{ + static constexpr const char* TYPE = "ColorPicker"; + + ColorPickerUiInfo() { type = ColorPickerUiInfo::TYPE; } + ColorPickerUiInfo(std::string formLabel, std::string statusTip, std::string toolTip) + : GenericUIInfo(ColorPickerUiInfo::TYPE, formLabel, statusTip, toolTip) + { + } +}; From 8579cb77f179bd3b0bc9e578ff24b46abbcf10a1 Mon Sep 17 00:00:00 2001 From: DMT Date: Sun, 1 Jun 2025 08:42:33 -0700 Subject: [PATCH 04/63] adding the dockwidget --- agave_app/AppearanceDockWidget2.cpp | 5 +---- agave_app/AppearanceDockWidget2.h | 5 +---- agave_app/GLView3D.cpp | 7 +++++-- agave_app/GLView3D.h | 4 +++- agave_app/agaveGui.cpp | 21 +++++++++------------ agave_app/agaveGui.h | 4 +++- renderlib/AppearanceDataObject.hpp | 2 +- 7 files changed, 23 insertions(+), 25 deletions(-) diff --git a/agave_app/AppearanceDockWidget2.cpp b/agave_app/AppearanceDockWidget2.cpp index 7523a3853..fa40c5466 100644 --- a/agave_app/AppearanceDockWidget2.cpp +++ b/agave_app/AppearanceDockWidget2.cpp @@ -1,9 +1,6 @@ #include "AppearanceDockWidget2.h" -QAppearanceDockWidget2::QAppearanceDockWidget2(QWidget* pParent, - RenderSettings* rs, - ViewerWindow* vw, - AppearanceObject* ado) +QAppearanceDockWidget2::QAppearanceDockWidget2(QWidget* pParent, RenderSettings* rs, AppearanceDataObject* ado) : QDockWidget(pParent) , m_AppearanceWidget(nullptr, rs, ado) { diff --git a/agave_app/AppearanceDockWidget2.h b/agave_app/AppearanceDockWidget2.h index babb984ee..32c12f095 100644 --- a/agave_app/AppearanceDockWidget2.h +++ b/agave_app/AppearanceDockWidget2.h @@ -9,10 +9,7 @@ class QAppearanceDockWidget2 : public QDockWidget Q_OBJECT public: - QAppearanceDockWidget2(QWidget* pParent = NULL, - RenderSettings* rs = NULL, - ViewerWindow* vw = NULL, - AppearanceObject* cdo = NULL); + QAppearanceDockWidget2(QWidget* pParent = NULL, RenderSettings* rs = NULL, AppearanceDataObject* cdo = NULL); private: QAppearanceWidget2 m_AppearanceWidget; diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 74de97759..481f8f256 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -3,6 +3,7 @@ #include "QRenderSettings.h" #include "ViewerState.h" +#include "renderlib/AppearanceDataObject.hpp" #include "renderlib/CameraDataObject.hpp" #include "renderlib/ImageXYZC.h" #include "renderlib/Logging.h" @@ -45,6 +46,7 @@ GLView3D::GLView3D(QRenderSettings* qrs, RenderSettings* rs, Scene* scene, QWidg // camera is created deep down inside m_viewerWindow. m_cameraDataObject = new CameraDataObject(&m_viewerWindow->m_CCamera); + m_appearanceDataObject = new AppearanceDataObject(rs); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); @@ -143,6 +145,7 @@ GLView3D::onNewImage(Scene* scene) GLView3D::~GLView3D() { delete m_cameraDataObject; + delete m_appearanceDataObject; makeCurrent(); check_gl("view dtor makecurrent"); @@ -493,9 +496,9 @@ GLView3D::fromViewerState(const Serialize::ViewerState& s) camera->m_Focus.m_FocalDistance = s.camera.focalDistance; // ASSUMES THIS IS ATTACHED TO m_viewerWindow->m_CCamera !!! - m_cameraObject->updatePropsFromObject(); + m_cameraDataObject->updatePropsFromCamera(); - m_appearanceDataObject->updatePropsFromObject(); + m_appearanceDataObject->updatePropsFromRenderSettings(); } QPixmap diff --git a/agave_app/GLView3D.h b/agave_app/GLView3D.h index b3beae733..fd665452a 100644 --- a/agave_app/GLView3D.h +++ b/agave_app/GLView3D.h @@ -12,6 +12,7 @@ #include #include +class AppearanceDataObject; class CameraDataObject; class CStatus; class ImageXYZC; @@ -68,6 +69,7 @@ class GLView3D : public QOpenGLWidget const CCamera& getCamera() { return m_viewerWindow->m_CCamera; } // tied to the above camera. CCamera must outlive this: CameraDataObject* getCameraDataObject() { return m_cameraDataObject; } + AppearanceDataObject* getAppearanceDataObject() { return m_appearanceDataObject; } void fromViewerState(const Serialize::ViewerState& s); @@ -111,7 +113,7 @@ public slots: private: CameraDataObject* m_cameraDataObject; - QCamera* m_qcamera; + AppearanceDataObject* m_appearanceDataObject; QRenderSettings* m_qrendersettings; /// Rendering timer. diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index b79370a35..ac52673a6 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -17,7 +17,6 @@ #include "AppearanceDockWidget.h" #include "AppearanceDockWidget2.h" -#include "ArealightDockWidget.h" #include "CameraDockWidget.h" #include "SkylightDockWidget.h" #include "Serialize.h" @@ -138,6 +137,11 @@ agaveGui::agaveGui(QWidget* parent) m_glView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // create camera ui window now that there is an actual camera. setupCameraDock(m_glView->getCameraDataObject()); + setupTimelineDock(); + setupStatisticsDock(); + setupAppearanceDock(m_glView->getAppearanceDataObject()); + + addDockItemsToViewMenu(); // TODO can make this a custom widget that exposes the toolbar and the view m_viewWithToolbar = new QWidget(this); @@ -390,23 +394,16 @@ agaveGui::setupCameraDock(CameraObject* cdo) } void -agaveGui::setupAppearanceDock(AppearanceObject* ado) +agaveGui::setupAppearanceDock(AppearanceDataObject* ado) { - // DANGER see borrowRenderer call - m_appearanceDockWidget2 = - new QAppearanceDockWidget2(this, m_appearanceObject->getRenderSettings().get(), m_glView->borrowRenderer(), ado); + m_appearanceDockWidget2 = new QAppearanceDockWidget2(this, &m_renderSettings, ado); m_appearanceDockWidget2->setAllowedAreas(Qt::AllDockWidgetAreas); addDockWidget(Qt::LeftDockWidgetArea, m_appearanceDockWidget2); // original appearance dock widget - m_appearanceDockWidget = new QAppearanceDockWidget(this, - &m_qrendersettings, - m_appearanceObject->getRenderSettings().get(), - m_areaLightObject.get(), - m_skyLightObject.get(), - m_toggleRotateControlsAction, - m_toggleTranslateControlsAction); + m_appearanceDockWidget = new QAppearanceDockWidget( + this, &m_qrendersettings, &m_renderSettings, m_toggleRotateControlsAction, m_toggleTranslateControlsAction); m_appearanceDockWidget->setAllowedAreas(Qt::AllDockWidgetAreas); addDockWidget(Qt::LeftDockWidgetArea, m_appearanceDockWidget); } diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index 9c1da3134..a5a6af638 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -15,7 +15,6 @@ class QAppearanceDockWidget; class QAppearanceDockWidget2; -class QAreaLightDockWidget; class QCameraDockWidget; class QSkyLightDockWidget; class QStatisticsDockWidget; @@ -104,6 +103,9 @@ private slots: void createToolbars(); void createDockWindows(); void setupCameraDock(CameraDataObject* cdo); + void setupTimelineDock(); + void setupAppearanceDock(AppearanceDataObject* ado); + void setupStatisticsDock(); void showOpenFailedMessageBox(QString path); diff --git a/renderlib/AppearanceDataObject.hpp b/renderlib/AppearanceDataObject.hpp index a5804064b..4bf2b7497 100644 --- a/renderlib/AppearanceDataObject.hpp +++ b/renderlib/AppearanceDataObject.hpp @@ -7,7 +7,7 @@ class AppearanceDataOject { public: - AppearanceDataOject(RenderSettings* camera); + AppearanceDataObject(RenderSettings* rs); prtyProperty RendererType{ "RendererType", 0 }; prtyProperty ShadingType{ "ShadingType", 0 }; From 732b3a02590cb115e19938b00f4524eee59afad4 Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 1 Jun 2025 21:20:46 -0700 Subject: [PATCH 05/63] update tensorstore --- renderlib/io/CMakeLists.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/renderlib/io/CMakeLists.txt b/renderlib/io/CMakeLists.txt index e9706fe9c..ef2e6d152 100644 --- a/renderlib/io/CMakeLists.txt +++ b/renderlib/io/CMakeLists.txt @@ -36,10 +36,8 @@ if(APPLE) endif(APPLE) FetchContent_Declare( tensorstore - # URL "https://github.com/google/tensorstore/archive/refs/tags/v0.1.75.tar.gz" - # URL_HASH SHA256=82f8a170bd6635147315036dcad4e5f32c4d5374a3faa7cdd4e958a15b9cba2e - URL "https://github.com/google/tensorstore/tarball/9e1feb7215ee876d3b62b8d84c03c9c2a1eec0c2" - URL_HASH SHA256=baeb9e1eafe1d8244d37c614711a8125467404801274e35979ba2896c8abdf29 + URL "https://github.com/google/tensorstore/archive/refs/tags/v0.1.75.tar.gz" + URL_HASH SHA256=82f8a170bd6635147315036dcad4e5f32c4d5374a3faa7cdd4e958a15b9cba2e ) FetchContent_MakeAvailable(tensorstore) From d3b611e3ffb51b5953b74f4eeab45ac0201ba89a Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Wed, 11 Jun 2025 20:58:11 -0700 Subject: [PATCH 06/63] loading from json is broken: the ui does not update to the correct values --- renderlib/AppearanceDataObject.cpp | 55 ++++++++++++++++++++++++------ renderlib/CameraDataObject.cpp | 36 +++++++++---------- 2 files changed, 62 insertions(+), 29 deletions(-) diff --git a/renderlib/AppearanceDataObject.cpp b/renderlib/AppearanceDataObject.cpp index 3ef9bf6bc..4401fd71b 100644 --- a/renderlib/AppearanceDataObject.cpp +++ b/renderlib/AppearanceDataObject.cpp @@ -8,17 +8,50 @@ AppearanceDataOject::AppearanceDataOject(RenderSettings* renderSettings) { updatePropsFromRenderSettings(); // hook up properties to update the underlying camera - RendererType.addCallback([this](prtyProperty* p, bool) { update(); }); - ShadingType.addCallback([this](prtyProperty* p, bool) { update(); }); - DensityScale.addCallback([this](prtyProperty* p, bool) { update(); }); - GradientFactor.addCallback([this](prtyProperty* p, bool) { update(); }); - StepSizePrimaryRay.addCallback([this](prtyProperty* p, bool) { update(); }); - StepSizeSecondaryRay.addCallback([this](prtyProperty* p, bool) { update(); }); - Interpolate.addCallback([this](prtyProperty* p, bool) { update(); }); - BackgroundColor.addCallback([this](prtyProperty* p, bool) { update(); }); - ShowBoundingBox.addCallback([this](prtyProperty* p, bool) { update(); }); - BoundingBoxColor.addCallback([this](prtyProperty* p, bool) { update(); }); - ShowScaleBar.addCallback([this](prtyProperty* p, bool) { update(); }); + RendererType.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + ShadingType.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + DensityScale.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + GradientFactor.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + StepSizePrimaryRay.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + StepSizeSecondaryRay.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + Interpolate.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + BackgroundColor.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + ShowBoundingBox.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + BoundingBoxColor.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); + ShowScaleBar.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); + }); } void diff --git a/renderlib/CameraDataObject.cpp b/renderlib/CameraDataObject.cpp index 269db43bd..55c206f6e 100644 --- a/renderlib/CameraDataObject.cpp +++ b/renderlib/CameraDataObject.cpp @@ -7,29 +7,29 @@ CameraDataObject::CameraDataObject(CCamera* camera) { updatePropsFromCamera(); // hook up properties to update the underlying camera - Exposure.addCallback([this](prtyProperty* p, bool) { - // LOG_DEBUG << "Setting exposure to " << p->get(); - update(); + Exposure.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); }); - ExposureIterations.addCallback([this](prtyProperty* p, bool) { - // LOG_DEBUG << "Setting exposure iterations to " << p->get(); - update(); + ExposureIterations.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); }); - NoiseReduction.addCallback([this](prtyProperty* p, bool) { - // LOG_DEBUG << "Setting noise reduction to " << p->get(); - update(); + NoiseReduction.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); }); - ApertureSize.addCallback([this](prtyProperty* p, bool) { - // LOG_DEBUG << "Setting aperture size to " << p->get(); - update(); + ApertureSize.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); }); - FieldOfView.addCallback([this](prtyProperty* p, bool) { - // LOG_DEBUG << "Setting field of view to " << p->get(); - update(); + FieldOfView.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); }); - FocalDistance.addCallback([this](prtyProperty* p, bool) { - // LOG_DEBUG << "Setting focal distance to " << p->get(); - update(); + FocalDistance.addCallback([this](prtyProperty* p, bool fromUi) { + if (fromUi) + update(); }); } From 413ad7e079b885c294366bda5d668691bbe79e3b Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Wed, 18 Jun 2025 16:32:35 -0700 Subject: [PATCH 07/63] still missing prtyVec3 --- renderlib/AppearanceDataObject.cpp | 150 +++++++++++++++-------------- renderlib/CameraDataObject.cpp | 69 ++++++------- 2 files changed, 111 insertions(+), 108 deletions(-) diff --git a/renderlib/AppearanceDataObject.cpp b/renderlib/AppearanceDataObject.cpp index 4401fd71b..15799e8d2 100644 --- a/renderlib/AppearanceDataObject.cpp +++ b/renderlib/AppearanceDataObject.cpp @@ -8,71 +8,73 @@ AppearanceDataOject::AppearanceDataOject(RenderSettings* renderSettings) { updatePropsFromRenderSettings(); // hook up properties to update the underlying camera - RendererType.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - ShadingType.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - DensityScale.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - GradientFactor.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - StepSizePrimaryRay.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - StepSizeSecondaryRay.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - Interpolate.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - BackgroundColor.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - ShowBoundingBox.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - BoundingBoxColor.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - ShowScaleBar.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); + // RendererType.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // ShadingType.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // DensityScale.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // GradientFactor.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // StepSizePrimaryRay.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // StepSizeSecondaryRay.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // Interpolate.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // BackgroundColor.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // ShowBoundingBox.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // BoundingBoxColor.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // ShowScaleBar.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); } void AppearanceDataOject::updatePropsFromRenderSettings() { if (m_renderSettings) { - ShadingType.set(m_renderSettings->m_RenderSettings.m_ShadingType); - // RendererType.set(m_renderSettings->m_RenderSettings.m_RendererType); - DensityScale.set(m_renderSettings->m_RenderSettings.m_DensityScale); - GradientFactor.set(m_renderSettings->m_RenderSettings.m_GradientFactor); - StepSizePrimaryRay.set(m_renderSettings->m_RenderSettings.m_StepSizeFactor); - StepSizeSecondaryRay.set(m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow); - Interpolate.set(m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling); - // BackgroundColor.set(glm::vec3(m_renderSettings->m_RenderSettings.m_BackgroundColor[0], - // m_renderSettings->m_RenderSettings.m_BackgroundColor[1], - // m_renderSettings->m_RenderSettings.m_BackgroundColor[2])); - // ShowBoundingBox.set(m_renderSettings->m_RenderSettings.m_ShowBoundingBox); - // BoundingBoxColor.set(glm::vec3(m_renderSettings->m_RenderSettings.m_BoundingBoxColor[0], - // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[1], - // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[2])); - // ShowScaleBar.set(m_renderSettings->m_RenderSettings.m_ShowScaleBar); + ShadingType.SetValue(m_renderSettings->m_RenderSettings.m_ShadingType); + RendererType.SetValue(m_renderSettings->m_rendererType); + DensityScale.SetValue(m_renderSettings->m_RenderSettings.m_DensityScale); + GradientFactor.SetValue(m_renderSettings->m_RenderSettings.m_GradientFactor); + StepSizePrimaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactor); + StepSizeSecondaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow); + Interpolate.SetValue(m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling); + } + if (m_scene) { + BackgroundColor.SetValue(glm::vec3(m_scene->m_material.m_backgroundColor[0], + m_scene->m_material.m_backgroundColor[1], + m_scene->m_material.m_backgroundColor[2])); + ShowBoundingBox.SetValue(m_scene->m_material.m_showBoundingBox); + BoundingBoxColor.SetValue(glm::vec3(m_scene->m_material.m_boundingBoxColor[0], + m_scene->m_material.m_boundingBoxColor[1], + m_scene->m_material.m_boundingBoxColor[2])); + ShowScaleBar.SetValue(m_scene->m_showScaleBar); } } void @@ -80,21 +82,21 @@ AppearanceDataOject::update() { // update low-level object from properties if (m_renderSettings) { - m_renderSettings->m_RenderSettings.m_ShadingType = ShadingType.get(); - // m_renderSettings->m_RenderSettings.m_RendererType = RendererType.get(); - m_renderSettings->m_RenderSettings.m_DensityScale = DensityScale.get(); - m_renderSettings->m_RenderSettings.m_GradientFactor = GradientFactor.get(); - m_renderSettings->m_RenderSettings.m_StepSizeFactor = StepSizePrimaryRay.get(); - m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow = StepSizeSecondaryRay.get(); - m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling = Interpolate.get(); - // m_renderSettings->m_RenderSettings.m_BackgroundColor[0] = BackgroundColor.get().x; - // m_renderSettings->m_RenderSettings.m_BackgroundColor[1] = BackgroundColor.get().y; - // m_renderSettings->m_RenderSettings.m_BackgroundColor[2] = BackgroundColor.get().z; - // m_renderSettings->m_RenderSettings.m_ShowBoundingBox = ShowBoundingBox.get(); - // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[0] = BoundingBoxColor.get().x; - // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[1] = BoundingBoxColor.get().y; - // m_renderSettings->m_RenderSettings.m_BoundingBoxColor[2] = BoundingBoxColor.get().z; - // m_renderSettings->m_RenderSettings.m_ShowScaleBar = ShowScaleBar.get(); + m_renderSettings->m_RenderSettings.m_ShadingType = ShadingType.GetValue(); + m_renderSettings->m_rendererType = RendererType.GetValue(); + m_renderSettings->m_RenderSettings.m_DensityScale = DensityScale.GetValue(); + m_renderSettings->m_RenderSettings.m_GradientFactor = GradientFactor.GetValue(); + m_renderSettings->m_RenderSettings.m_StepSizeFactor = StepSizePrimaryRay.GetValue(); + m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow = StepSizeSecondaryRay.GetValue(); + m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling = Interpolate.GetValue(); + m_scene->m_material.m_backgroundColor[0] = BackgroundColor.GetValue().x; + m_scene->m_material.m_backgroundColor[1] = BackgroundColor.GetValue().y; + m_scene->m_material.m_backgroundColor[2] = BackgroundColor.GetValue().z; + m_scene->m_material.m_showBoundingBox = ShowBoundingBox.GetValue(); + m_scene->m_material.m_boundingBoxColor[0] = BoundingBoxColor.GetValue().x; + m_scene->m_material.m_boundingBoxColor[1] = BoundingBoxColor.GetValue().y; + m_scene->m_material.m_boundingBoxColor[2] = BoundingBoxColor.GetValue().z; + m_scene->m_showScaleBar = ShowScaleBar.GetValue(); m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); m_renderSettings->m_DirtyFlags.SetFlag(TransferFunctionDirty); diff --git a/renderlib/CameraDataObject.cpp b/renderlib/CameraDataObject.cpp index 55c206f6e..beb42004e 100644 --- a/renderlib/CameraDataObject.cpp +++ b/renderlib/CameraDataObject.cpp @@ -7,42 +7,43 @@ CameraDataObject::CameraDataObject(CCamera* camera) { updatePropsFromCamera(); // hook up properties to update the underlying camera - Exposure.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - ExposureIterations.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - NoiseReduction.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - ApertureSize.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - FieldOfView.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); - FocalDistance.addCallback([this](prtyProperty* p, bool fromUi) { - if (fromUi) - update(); - }); + // Exposure.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // ExposureIterations.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // NoiseReduction.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // ApertureSize.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // FieldOfView.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); + // FocalDistance.addCallback([this](prtyProperty* p, bool fromUi) { + // if (fromUi) + // update(); + // }); } void CameraDataObject::updatePropsFromCamera() { if (m_camera) { - Exposure.set(1.0f - m_camera->m_Film.m_Exposure); - ExposureIterations.set(m_camera->m_Film.m_ExposureIterations); + Exposure.SetValue(1.0f - m_camera->m_Film.m_Exposure); + ExposureIterations.SetValue(m_camera->m_Film.m_ExposureIterations); + // TODO this is not hooked up to the camera properly // NoiseReduction.set(m_camera->m_Film.m_NoiseReduction); - ApertureSize.set(m_camera->m_Aperture.m_Size); - FieldOfView.set(m_camera->m_FovV); - FocalDistance.set(m_camera->m_Focus.m_FocalDistance); + ApertureSize.SetValue(m_camera->m_Aperture.m_Size); + FieldOfView.SetValue(m_camera->m_FovV); + FocalDistance.SetValue(m_camera->m_Focus.m_FocalDistance); } } void @@ -50,17 +51,17 @@ CameraDataObject::update() { // update low-level camera object from properties if (m_camera) { - m_camera->m_Film.m_Exposure = 1.0f - Exposure.get(); - m_camera->m_Film.m_ExposureIterations = ExposureIterations.get(); + m_camera->m_Film.m_Exposure = 1.0f - Exposure.GetValue(); + m_camera->m_Film.m_ExposureIterations = ExposureIterations.GetValue(); // Aperture - m_camera->m_Aperture.m_Size = ApertureSize.get(); + m_camera->m_Aperture.m_Size = ApertureSize.GetValue(); // Projection - m_camera->m_FovV = FieldOfView.get(); + m_camera->m_FovV = FieldOfView.GetValue(); // Focus - m_camera->m_Focus.m_FocalDistance = FocalDistance.get(); + m_camera->m_Focus.m_FocalDistance = FocalDistance.GetValue(); m_camera->Update(); From 06b7a56f617ce87c2778bb81054a0d0539abd474 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 21 Jun 2025 17:30:07 -0700 Subject: [PATCH 08/63] WIP --- renderlib/AppearanceDataObject.cpp | 101 +-------------- renderlib/AppearanceDataObject.hpp | 38 +++--- renderlib/AppearanceUiDescription.cpp | 179 ++++++++++++++++++-------- renderlib/AppearanceUiDescription.hpp | 32 +++++ renderlib/CameraDataObject.cpp | 68 ---------- renderlib/CameraDataObject.hpp | 25 ++-- renderlib/CameraObject.cpp | 38 +----- 7 files changed, 186 insertions(+), 295 deletions(-) diff --git a/renderlib/AppearanceDataObject.cpp b/renderlib/AppearanceDataObject.cpp index 15799e8d2..82055d9a1 100644 --- a/renderlib/AppearanceDataObject.cpp +++ b/renderlib/AppearanceDataObject.cpp @@ -3,103 +3,4 @@ #include "Enumerations.h" #include "Logging.h" -AppearanceDataOject::AppearanceDataOject(RenderSettings* renderSettings) - : m_renderSettings(renderSettings) -{ - updatePropsFromRenderSettings(); - // hook up properties to update the underlying camera - // RendererType.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // ShadingType.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // DensityScale.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // GradientFactor.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // StepSizePrimaryRay.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // StepSizeSecondaryRay.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // Interpolate.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // BackgroundColor.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // ShowBoundingBox.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // BoundingBoxColor.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // ShowScaleBar.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); -} - -void -AppearanceDataOject::updatePropsFromRenderSettings() -{ - if (m_renderSettings) { - ShadingType.SetValue(m_renderSettings->m_RenderSettings.m_ShadingType); - RendererType.SetValue(m_renderSettings->m_rendererType); - DensityScale.SetValue(m_renderSettings->m_RenderSettings.m_DensityScale); - GradientFactor.SetValue(m_renderSettings->m_RenderSettings.m_GradientFactor); - StepSizePrimaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactor); - StepSizeSecondaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow); - Interpolate.SetValue(m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling); - } - if (m_scene) { - BackgroundColor.SetValue(glm::vec3(m_scene->m_material.m_backgroundColor[0], - m_scene->m_material.m_backgroundColor[1], - m_scene->m_material.m_backgroundColor[2])); - ShowBoundingBox.SetValue(m_scene->m_material.m_showBoundingBox); - BoundingBoxColor.SetValue(glm::vec3(m_scene->m_material.m_boundingBoxColor[0], - m_scene->m_material.m_boundingBoxColor[1], - m_scene->m_material.m_boundingBoxColor[2])); - ShowScaleBar.SetValue(m_scene->m_showScaleBar); - } -} -void -AppearanceDataOject::update() -{ - // update low-level object from properties - if (m_renderSettings) { - m_renderSettings->m_RenderSettings.m_ShadingType = ShadingType.GetValue(); - m_renderSettings->m_rendererType = RendererType.GetValue(); - m_renderSettings->m_RenderSettings.m_DensityScale = DensityScale.GetValue(); - m_renderSettings->m_RenderSettings.m_GradientFactor = GradientFactor.GetValue(); - m_renderSettings->m_RenderSettings.m_StepSizeFactor = StepSizePrimaryRay.GetValue(); - m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow = StepSizeSecondaryRay.GetValue(); - m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling = Interpolate.GetValue(); - m_scene->m_material.m_backgroundColor[0] = BackgroundColor.GetValue().x; - m_scene->m_material.m_backgroundColor[1] = BackgroundColor.GetValue().y; - m_scene->m_material.m_backgroundColor[2] = BackgroundColor.GetValue().z; - m_scene->m_material.m_showBoundingBox = ShowBoundingBox.GetValue(); - m_scene->m_material.m_boundingBoxColor[0] = BoundingBoxColor.GetValue().x; - m_scene->m_material.m_boundingBoxColor[1] = BoundingBoxColor.GetValue().y; - m_scene->m_material.m_boundingBoxColor[2] = BoundingBoxColor.GetValue().z; - m_scene->m_showScaleBar = ShowScaleBar.GetValue(); - - m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); - m_renderSettings->m_DirtyFlags.SetFlag(TransferFunctionDirty); - m_renderSettings->m_DirtyFlags.SetFlag(LightsDirty); - } -} \ No newline at end of file +AppearanceDataObject::AppearanceDataObject() {} diff --git a/renderlib/AppearanceDataObject.hpp b/renderlib/AppearanceDataObject.hpp index 4bf2b7497..02df6f82e 100644 --- a/renderlib/AppearanceDataObject.hpp +++ b/renderlib/AppearanceDataObject.hpp @@ -1,30 +1,26 @@ #pragma once -#include "core/prty/prtyProperty.h" -#include "RenderSettings.h" +#include "core/prty/prtyInt8.hpp" +#include "core/prty/prtyFloat.hpp" +#include "core/prty/prtyBoolean.hpp" +#include "core/prty/prtyVector3d.hpp" +#include "core/prty/prtyColor.hpp" #include "glm.h" class AppearanceDataOject { public: - AppearanceDataObject(RenderSettings* rs); + AppearanceDataObject(); - prtyProperty RendererType{ "RendererType", 0 }; - prtyProperty ShadingType{ "ShadingType", 0 }; - prtyProperty DensityScale{ "DensityScale", 1.0f }; - prtyProperty GradientFactor{ "GradientFactor", 0.5f }; - prtyProperty StepSizePrimaryRay{ "StepSizePrimaryRay", 1.0f }; - prtyProperty StepSizeSecondaryRay{ "StepSizeSecondaryRay", 1.0f }; - prtyProperty Interpolate{ "Interpolate", false }; - prtyProperty BackgroundColor{ "BackgroundColor", glm::vec3(0.0f, 0.0f, 0.0f) }; - prtyProperty ShowBoundingBox{ "ShowBoundingBox", false }; - prtyProperty BoundingBoxColor{ "BoundingBoxColor", glm::vec3(1.0f, 1.0f, 1.0f) }; - prtyProperty ShowScaleBar{ "ShowScaleBar", false }; - - RenderSettings* m_renderSettings; - - void updatePropsFromRenderSettings(); - -private: - void update(); + prtyInt8 RendererType{ "RendererType", 0 }; + prtyInt8 ShadingType{ "ShadingType", 0 }; + prtyFloat DensityScale{ "DensityScale", 1.0f }; + prtyFloat GradientFactor{ "GradientFactor", 0.5f }; + prtyFloat StepSizePrimaryRay{ "StepSizePrimaryRay", 1.0f }; + prtyFloat StepSizeSecondaryRay{ "StepSizeSecondaryRay", 1.0f }; + prtyBoolean Interpolate{ "Interpolate", false }; + prtyColor BackgroundColor{ "BackgroundColor", glm::vec4(0.0f, 0.0f, 0.0f, 1.0f) }; + prtyBoolean ShowBoundingBox{ "ShowBoundingBox", false }; + prtyColor BoundingBoxColor{ "BoundingBoxColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; + prtyBoolean ShowScaleBar{ "ShowScaleBar", false }; }; diff --git a/renderlib/AppearanceUiDescription.cpp b/renderlib/AppearanceUiDescription.cpp index c8925306c..ed1fc23ed 100644 --- a/renderlib/AppearanceUiDescription.cpp +++ b/renderlib/AppearanceUiDescription.cpp @@ -1,56 +1,127 @@ #include "AppearanceUiDescription.hpp" -ComboBoxUiInfo AppearanceUiDescription::m_rendererType("Renderer Type", - "Select volume rendering type", - "Select volume rendering type", - { "Ray march blending", "Path Traced" }); -ComboBoxUiInfo AppearanceUiDescription::m_shadingType("Shading Type", - "Select volume shading style", - "Select volume shading style", - { "BRDF Only", "Phase Function Only", "Mixed" }); -FloatSliderSpinnerUiInfo AppearanceUiDescription::m_densityScale("Scattering Density", - "Set scattering density for volume", - "Set scattering density for volume", - 0.001f, - 100.0f, - 3, - 0.01f, - 10); -FloatSliderSpinnerUiInfo AppearanceUiDescription::m_gradientFactor("Shading Type Mixture", - "Mix between BRDF and Phase shading", - "Mix between BRDF and Phase shading", - 0.0f, - 1.0f, - 3, - 0.01f, - 10); -FloatSliderSpinnerUiInfo AppearanceUiDescription::m_stepSizePrimaryRay("Primary Ray Step Size", - "Set volume ray march step size for camera rays", - "Set volume ray march step size for camera rays", - 1.0f, - 100.0f, - 3, - 0.01f, - 10); -FloatSliderSpinnerUiInfo AppearanceUiDescription::m_stepSizeSecondaryRay( - "Secondary Ray Step Size", - "Set volume ray march step size for scattered rays", - "Set volume ray march step size for scattered rays", - 1.0f, - 100.0f, - 3, - 0.01f, - 10); -CheckBoxUiInfo AppearanceUiDescription::m_interpolate("Interpolate", - "Interpolated volume sampling", - "Interpolated volume sampling"); -ColorPickerUiInfo AppearanceUiDescription::m_backgroundColor("Background Color", - "Set background color", - "Set background color"); -CheckBoxUiInfo AppearanceUiDescription::m_showBoundingBox("Show Bounding Box", - "Show/hide bounding box", - "Show/hide bounding box"); -ColorPickerUiInfo AppearanceUiDescription::m_boundingBoxColor("Bounding Box Color", - "Set bounding box color", - "Set bounding box color"); -CheckBoxUiInfo AppearanceUiDescription::m_showScaleBar("Show Scale Bar", "Show/hide scale bar", "Show/hide scale bar"); +// ComboBoxUiInfo AppearanceUiDescription::m_rendererType("Renderer Type", +// "Select volume rendering type", +// "Select volume rendering type", +// { "Ray march blending", "Path Traced" }); +// ComboBoxUiInfo AppearanceUiDescription::m_shadingType("Shading Type", +// "Select volume shading style", +// "Select volume shading style", +// { "BRDF Only", "Phase Function Only", "Mixed" }); +// FloatSliderSpinnerUiInfo AppearanceUiDescription::m_densityScale("Scattering Density", +// "Set scattering density for volume", +// "Set scattering density for volume", +// 0.001f, +// 100.0f, +// 3, +// 0.01f, +// 10); +// FloatSliderSpinnerUiInfo AppearanceUiDescription::m_gradientFactor("Shading Type Mixture", +// "Mix between BRDF and Phase shading", +// "Mix between BRDF and Phase shading", +// 0.0f, +// 1.0f, +// 3, +// 0.01f, +// 10); +// FloatSliderSpinnerUiInfo AppearanceUiDescription::m_stepSizePrimaryRay("Primary Ray Step Size", +// "Set volume ray march step size for camera +// rays", "Set volume ray march step size for +// camera rays", 1.0f, 100.0f, 3, 0.01f, 10); +// FloatSliderSpinnerUiInfo AppearanceUiDescription::m_stepSizeSecondaryRay( +// "Secondary Ray Step Size", +// "Set volume ray march step size for scattered rays", +// "Set volume ray march step size for scattered rays", +// 1.0f, +// 100.0f, +// 3, +// 0.01f, +// 10); +// CheckBoxUiInfo AppearanceUiDescription::m_interpolate("Interpolate", +// "Interpolated volume sampling", +// "Interpolated volume sampling"); +// ColorPickerUiInfo AppearanceUiDescription::m_backgroundColor("Background Color", +// "Set background color", +// "Set background color"); +// CheckBoxUiInfo AppearanceUiDescription::m_showBoundingBox("Show Bounding Box", +// "Show/hide bounding box", +// "Show/hide bounding box"); +// ColorPickerUiInfo AppearanceUiDescription::m_boundingBoxColor("Bounding Box Color", +// "Set bounding box color", +// "Set bounding box color"); +// CheckBoxUiInfo AppearanceUiDescription::m_showScaleBar("Show Scale Bar", "Show/hide scale bar", "Show/hide scale +// bar"); + +AppearanceObject::AppearanceObject() + : prtyObject() +{ + m_RenderSettings = std::make_shared(); + m_rendererType = new ComboBoxUiInfo(&m_appearanceDataObject.RendererType, "Appearance", "Renderer Type"); + m_shadingType = new ComboBoxUiInfo(&m_appearanceDataObject.ShadingType, "Appearance", "Shading Type"); + m_densityScale = new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.DensityScale, "Appearance", "Density Scale"); + m_gradientFactor = + new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.GradientFactor, "Appearance", "Gradient Factor"); + m_stepSizePrimaryRay = + new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.StepSizePrimaryRay, "Appearance", "Step Size Primary Ray"); + m_stepSizeSecondaryRay = + new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.StepSizeSecondaryRay, "Appearance", "Step Size Secondary Ray"); + m_interpolate = new CheckBoxUiInfo(&m_appearanceDataObject.Interpolate, "Appearance", "Interpolate"); + m_backgroundColor = new ColorPickerUiInfo(&m_appearanceDataObject.BackgroundColor, "Appearance", "Background Color"); + m_showBoundingBox = new CheckBoxUiInfo(&m_appearanceDataObject.ShowBoundingBox, "Appearance", "Show Bounding Box"); + m_boundingBoxColor = + new ColorPickerUiInfo(&m_appearanceDataObject.BoundingBoxColor, "Appearance", "Bounding Box Color"); + m_showScaleBar = new CheckBoxUiInfo(&m_appearanceDataObject.ShowScaleBar, "Appearance", "Show Scale Bar"); +} + +void +AppearanceObject::updatePropsFromObject() +{ + if (m_renderSettings) { + m_appearanceDataObject.ShadingType.SetValue(m_renderSettings->m_RenderSettings.m_ShadingType); + m_appearanceDataObject.RendererType.SetValue(m_renderSettings->m_rendererType); + m_appearanceDataObject.DensityScale.SetValue(m_renderSettings->m_RenderSettings.m_DensityScale); + m_appearanceDataObject.GradientFactor.SetValue(m_renderSettings->m_RenderSettings.m_GradientFactor); + m_appearanceDataObject.StepSizePrimaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactor); + m_appearanceDataObject.StepSizeSecondaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow); + m_appearanceDataObject.Interpolate.SetValue(m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling); + } + if (m_scene) { + BackgroundColor.SetValue(glm::vec4(m_scene->m_material.m_backgroundColor[0], + m_scene->m_material.m_backgroundColor[1], + m_scene->m_material.m_backgroundColor[2], + 1.0f)); + ShowBoundingBox.SetValue(m_scene->m_material.m_showBoundingBox); + BoundingBoxColor.SetValue(glm::vec4(m_scene->m_material.m_boundingBoxColor[0], + m_scene->m_material.m_boundingBoxColor[1], + m_scene->m_material.m_boundingBoxColor[2], + 1.0f)); + ShowScaleBar.SetValue(m_scene->m_showScaleBar); + } +} + +void +AppearanceObject::updateObjectFromProps() +{ + // update low-level object from properties + if (m_renderSettings) { + m_renderSettings->m_RenderSettings.m_ShadingType = m_appearanceDataObject.ShadingType.GetValue(); + m_renderSettings->m_rendererType = m_appearanceDataObject.RendererType.GetValue(); + m_renderSettings->m_RenderSettings.m_DensityScale = m_appearanceDataObject.DensityScale.GetValue(); + m_renderSettings->m_RenderSettings.m_GradientFactor = m_appearanceDataObject.GradientFactor.GetValue(); + m_renderSettings->m_RenderSettings.m_StepSizeFactor = m_appearanceDataObject.StepSizePrimaryRay.GetValue(); + m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow = m_appearanceDataObject.StepSizeSecondaryRay.GetValue(); + m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling = m_appearanceDataObject.Interpolate.GetValue(); + m_scene->m_material.m_backgroundColor[0] = m_appearanceDataObject.BackgroundColor.GetValue().x; + m_scene->m_material.m_backgroundColor[1] = m_appearanceDataObject.BackgroundColor.GetValue().y; + m_scene->m_material.m_backgroundColor[2] = m_appearanceDataObject.BackgroundColor.GetValue().z; + m_scene->m_material.m_showBoundingBox = m_appearanceDataObject.ShowBoundingBox.GetValue(); + m_scene->m_material.m_boundingBoxColor[0] = m_appearanceDataObject.BoundingBoxColor.GetValue().x; + m_scene->m_material.m_boundingBoxColor[1] = m_appearanceDataObject.BoundingBoxColor.GetValue().y; + m_scene->m_material.m_boundingBoxColor[2] = m_appearanceDataObject.BoundingBoxColor.GetValue().z; + m_scene->m_showScaleBar = m_appearanceDataObject.ShowScaleBar.GetValue(); + + m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); + m_renderSettings->m_DirtyFlags.SetFlag(TransferFunctionDirty); + m_renderSettings->m_DirtyFlags.SetFlag(LightsDirty); + } +} \ No newline at end of file diff --git a/renderlib/AppearanceUiDescription.hpp b/renderlib/AppearanceUiDescription.hpp index 6441c56a0..831cb25a8 100644 --- a/renderlib/AppearanceUiDescription.hpp +++ b/renderlib/AppearanceUiDescription.hpp @@ -1,5 +1,8 @@ #pragma once +#include "AppearanceDataObject.hpp" +#include "core/prty/prtyObject.hpp" +#include "RenderSettings.h" #include "uiInfo.hpp" struct AppearanceUiDescription @@ -16,3 +19,32 @@ struct AppearanceUiDescription static ColorPickerUiInfo m_boundingBoxColor; static CheckBoxUiInfo m_showScaleBar; }; + +class AppearanceObject : public prtyObject +{ +public: + AppearanceObject(); + + void updatePropsFromObject(); + void updateObjectFromProps(); + +private: + // the properties + AppearanceDataObject m_appearanceDataObject; + + // the actual camera + std::shared_ptr m_renderSettings; + + // the ui info + ComboBoxUiInfo* m_rendererType; + ComboBoxUiInfo* m_shadingType; + FloatSliderSpinnerUiInfo* m_densityScale; + FloatSliderSpinnerUiInfo* m_gradientFactor; + FloatSliderSpinnerUiInfo* m_stepSizePrimaryRay; + FloatSliderSpinnerUiInfo* m_stepSizeSecondaryRay; + CheckBoxUiInfo* m_interpolate; + ColorPickerUiInfo* m_backgroundColor; + CheckBoxUiInfo* m_showBoundingBox; + ColorPickerUiInfo* m_boundingBoxColor; + CheckBoxUiInfo* m_showScaleBar; +}; diff --git a/renderlib/CameraDataObject.cpp b/renderlib/CameraDataObject.cpp index beb42004e..461b8a678 100644 --- a/renderlib/CameraDataObject.cpp +++ b/renderlib/CameraDataObject.cpp @@ -1,71 +1,3 @@ #include "CameraDataObject.hpp" #include "Logging.h" - -CameraDataObject::CameraDataObject(CCamera* camera) - : m_camera(camera) -{ - updatePropsFromCamera(); - // hook up properties to update the underlying camera - // Exposure.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // ExposureIterations.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // NoiseReduction.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // ApertureSize.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // FieldOfView.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); - // FocalDistance.addCallback([this](prtyProperty* p, bool fromUi) { - // if (fromUi) - // update(); - // }); -} - -void -CameraDataObject::updatePropsFromCamera() -{ - if (m_camera) { - Exposure.SetValue(1.0f - m_camera->m_Film.m_Exposure); - ExposureIterations.SetValue(m_camera->m_Film.m_ExposureIterations); - // TODO this is not hooked up to the camera properly - // NoiseReduction.set(m_camera->m_Film.m_NoiseReduction); - ApertureSize.SetValue(m_camera->m_Aperture.m_Size); - FieldOfView.SetValue(m_camera->m_FovV); - FocalDistance.SetValue(m_camera->m_Focus.m_FocalDistance); - } -} -void -CameraDataObject::update() -{ - // update low-level camera object from properties - if (m_camera) { - m_camera->m_Film.m_Exposure = 1.0f - Exposure.GetValue(); - m_camera->m_Film.m_ExposureIterations = ExposureIterations.GetValue(); - - // Aperture - m_camera->m_Aperture.m_Size = ApertureSize.GetValue(); - - // Projection - m_camera->m_FovV = FieldOfView.GetValue(); - - // Focus - m_camera->m_Focus.m_FocalDistance = FocalDistance.GetValue(); - - m_camera->Update(); - - // renderer should pick this up and do the right thing (TM) - m_camera->m_Dirty = true; - } -} \ No newline at end of file diff --git a/renderlib/CameraDataObject.hpp b/renderlib/CameraDataObject.hpp index 6456956b1..a26c862c6 100644 --- a/renderlib/CameraDataObject.hpp +++ b/renderlib/CameraDataObject.hpp @@ -1,23 +1,18 @@ #pragma once -#include "core/prty/prtyProperty.h" -#include "CCamera.h" +#include "core/prty/prtyFloat.hpp" +#include "core/prty/prtyInt8.hpp" +#include "core/prty/prtyBoolean.hpp" class CameraDataObject { public: - CameraDataObject(CCamera* camera); + CameraDataObject() {} - prtyProperty Exposure{ "Exposure", 0.75f }; - prtyProperty ExposureIterations{ "ExposureIterations", 1 }; - prtyProperty NoiseReduction{ "NoiseReduction", false }; - prtyProperty ApertureSize{ "ApertureSize", 0.0f }; - prtyProperty FieldOfView{ "FieldOfView", 30.0f }; - prtyProperty FocalDistance{ "FocalDistance", 0.0f }; - - CCamera* m_camera; - -private: - void update(); - void updatePropsFromCamera(); + prtyFloat Exposure{ "Exposure", 0.75f }; + prtyInt8 ExposureIterations{ "ExposureIterations", 1 }; + prtyBoolean NoiseReduction{ "NoiseReduction", false }; + prtyFloat ApertureSize{ "ApertureSize", 0.0f }; + prtyFloat FieldOfView{ "FieldOfView", 30.0f }; + prtyFloat FocalDistance{ "FocalDistance", 0.0f }; }; diff --git a/renderlib/CameraObject.cpp b/renderlib/CameraObject.cpp index d45b5def9..8faa01318 100644 --- a/renderlib/CameraObject.cpp +++ b/renderlib/CameraObject.cpp @@ -95,40 +95,4 @@ CameraObject::updateObjectFromProps() // renderer should pick this up and do the right thing (TM) m_camera->m_Dirty = true; } -} - -void -CameraObject::ExposureChanged(prtyProperty* i_Property, bool i_bDirty) -{ - m_camera->m_Film.m_Exposure = 1.0f - m_cameraDataObject.Exposure.GetValue(); - m_camera->m_Dirty = true; -} -void -CameraObject::ExposureIterationsChanged(prtyProperty* i_Property, bool i_bDirty) -{ - m_camera->m_Film.m_ExposureIterations = m_cameraDataObject.ExposureIterations.GetValue(); - m_camera->m_Dirty = true; -} -void -CameraObject::NoiseReductionChanged(prtyProperty* i_Property, bool i_bDirty) -{ - LOG_ERROR << "Noise reduction is not implemented yet!"; -} -void -CameraObject::ApertureSizeChanged(prtyProperty* i_Property, bool i_bDirty) -{ - m_camera->m_Aperture.m_Size = m_cameraDataObject.ApertureSize.GetValue(); - m_camera->m_Dirty = true; -} -void -CameraObject::FieldOfViewChanged(prtyProperty* i_Property, bool i_bDirty) -{ - m_camera->m_FovV = m_cameraDataObject.FieldOfView.GetValue(); - m_camera->m_Dirty = true; -} -void -CameraObject::FocalDistanceChanged(prtyProperty* i_Property, bool i_bDirty) -{ - m_camera->m_Focus.m_FocalDistance = m_cameraDataObject.FocalDistance.GetValue(); - m_camera->m_Dirty = true; -} +} \ No newline at end of file From 203ecd03a7782b42e5c19a6a846990af746218db Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Wed, 25 Jun 2025 14:51:41 -0700 Subject: [PATCH 09/63] camera and appearance compiles but completely detached from the renderer --- agave_app/AppearanceDockWidget2.cpp | 5 +- agave_app/AppearanceDockWidget2.h | 5 +- agave_app/AppearanceWidget.h | 2 +- agave_app/CameraDockWidget.cpp | 2 +- agave_app/CameraDockWidget.h | 5 +- agave_app/CameraWidget.cpp | 167 ++---------------------- agave_app/CameraWidget.h | 11 +- agave_app/GLView3D.cpp | 11 +- agave_app/GLView3D.h | 4 +- agave_app/agaveGui.cpp | 9 +- agave_app/agaveGui.h | 6 +- agave_app/qtControls/controlFactory.cpp | 1 + agave_app/qtControls/controlFactory.h | 10 -- 13 files changed, 40 insertions(+), 198 deletions(-) diff --git a/agave_app/AppearanceDockWidget2.cpp b/agave_app/AppearanceDockWidget2.cpp index fa40c5466..7523a3853 100644 --- a/agave_app/AppearanceDockWidget2.cpp +++ b/agave_app/AppearanceDockWidget2.cpp @@ -1,6 +1,9 @@ #include "AppearanceDockWidget2.h" -QAppearanceDockWidget2::QAppearanceDockWidget2(QWidget* pParent, RenderSettings* rs, AppearanceDataObject* ado) +QAppearanceDockWidget2::QAppearanceDockWidget2(QWidget* pParent, + RenderSettings* rs, + ViewerWindow* vw, + AppearanceObject* ado) : QDockWidget(pParent) , m_AppearanceWidget(nullptr, rs, ado) { diff --git a/agave_app/AppearanceDockWidget2.h b/agave_app/AppearanceDockWidget2.h index 32c12f095..babb984ee 100644 --- a/agave_app/AppearanceDockWidget2.h +++ b/agave_app/AppearanceDockWidget2.h @@ -9,7 +9,10 @@ class QAppearanceDockWidget2 : public QDockWidget Q_OBJECT public: - QAppearanceDockWidget2(QWidget* pParent = NULL, RenderSettings* rs = NULL, AppearanceDataObject* cdo = NULL); + QAppearanceDockWidget2(QWidget* pParent = NULL, + RenderSettings* rs = NULL, + ViewerWindow* vw = NULL, + AppearanceObject* cdo = NULL); private: QAppearanceWidget2 m_AppearanceWidget; diff --git a/agave_app/AppearanceWidget.h b/agave_app/AppearanceWidget.h index 6e943119d..c059424ba 100644 --- a/agave_app/AppearanceWidget.h +++ b/agave_app/AppearanceWidget.h @@ -3,7 +3,7 @@ #include "qtControls/Controls.h" // #include "renderlib/core/prty/prtyProperty.h" -#include "renderlib/AppearanceObject.hpp" +#include "renderlib/AppearanceUiDescription.hpp" #include "renderlib/Logging.h" #include diff --git a/agave_app/CameraDockWidget.cpp b/agave_app/CameraDockWidget.cpp index 5e213175f..28a7eb772 100644 --- a/agave_app/CameraDockWidget.cpp +++ b/agave_app/CameraDockWidget.cpp @@ -1,6 +1,6 @@ #include "CameraDockWidget.h" -QCameraDockWidget::QCameraDockWidget(QWidget* pParent, QCamera* cam, RenderSettings* rs, CameraDataObject* cdo) +QCameraDockWidget::QCameraDockWidget(QWidget* pParent, RenderSettings* rs, CameraObject* cdo) : QDockWidget(pParent) , m_CameraWidget(nullptr, cam, rs, cdo) { diff --git a/agave_app/CameraDockWidget.h b/agave_app/CameraDockWidget.h index e2ac83fe2..debb462f6 100644 --- a/agave_app/CameraDockWidget.h +++ b/agave_app/CameraDockWidget.h @@ -9,10 +9,7 @@ class QCameraDockWidget : public QDockWidget Q_OBJECT public: - QCameraDockWidget(QWidget* pParent = NULL, - QCamera* cam = NULL, - RenderSettings* rs = NULL, - CameraDataObject* cdo = NULL); + QCameraDockWidget(QWidget* pParent = NULL, RenderSettings* rs = NULL, CameraObject* cdo = NULL); private: QCameraWidget m_CameraWidget; diff --git a/agave_app/CameraWidget.cpp b/agave_app/CameraWidget.cpp index c18d48cfa..c76f1001e 100644 --- a/agave_app/CameraWidget.cpp +++ b/agave_app/CameraWidget.cpp @@ -7,177 +7,26 @@ #include #include -QNumericSlider* -create(const FloatSliderSpinnerUiInfo* info, std::shared_ptr> prop) -{ - QNumericSlider* slider = new QNumericSlider(); - slider->setStatusTip(QString::fromStdString(info->statusTip)); - slider->setToolTip(QString::fromStdString(info->toolTip)); - slider->setRange(info->min, info->max); - slider->setDecimals(info->decimals); - slider->setSingleStep(info->singleStep); - slider->setNumTickMarks(info->numTickMarks); - slider->setSuffix(QString::fromStdString(info->suffix)); - - slider->setValue(prop->get(), true); - QObject::connect(slider, &QNumericSlider::valueChanged, [slider, prop](double value) { prop->set(value, true); }); - // TODO how would this capture the "previous" value, for undo? - QObject::connect(slider, &QNumericSlider::valueChangeCommit, [slider, prop]() { prop->notifyAll(true); }); - - return slider; -} -QNumericSlider* -create(const IntSliderSpinnerUiInfo* info, std::shared_ptr> prop) -{ - QNumericSlider* slider = new QNumericSlider(); - slider->setStatusTip(QString::fromStdString(info->statusTip)); - slider->setToolTip(QString::fromStdString(info->toolTip)); - slider->setRange(info->min, info->max); - slider->setSingleStep(info->singleStep); - slider->setNumTickMarks(info->numTickMarks); - slider->setSuffix(QString::fromStdString(info->suffix)); - - slider->setValue(prop->get(), true); - QObject::connect(slider, &QNumericSlider::valueChanged, [slider, prop](double value) { prop->set(value, true); }); - // TODO how would this capture the "previous" value, for undo? - QObject::connect(slider, &QNumericSlider::valueChangeCommit, [slider, prop]() { prop->notifyAll(true); }); - - return slider; -} -QCheckBox* -create(const CheckBoxUiInfo* info, std::shared_ptr> prop) -{ - QCheckBox* checkBox = new QCheckBox(); - checkBox->setStatusTip(QString::fromStdString(info->statusTip)); - checkBox->setToolTip(QString::fromStdString(info->toolTip)); - // checkBox->setText(QString::fromStdString(info->formLabel)); - checkBox->setCheckState(prop->get() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); - QObject::connect(checkBox, &QCheckBox::stateChanged, [checkBox, prop](int state) { - prop->set(state == Qt::CheckState::Checked, true); - }); - return checkBox; -} -QComboBox* -create(const ComboBoxUiInfo* info, std::shared_ptr> prop) -{ - QComboBox* comboBox = new QComboBox(); - comboBox->setStatusTip(QString::fromStdString(info->statusTip)); - comboBox->setToolTip(QString::fromStdString(info->toolTip)); - for (const auto& item : info->items) { - comboBox->addItem(QString::fromStdString(item)); - } - comboBox->setCurrentIndex(prop->get()); - QObject::connect(comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->set(index, true); }); - return comboBox; -} - -QNumericSlider* -addRow(const FloatSliderSpinnerUiInfo& info, prtyProperty* prop) -{ - QNumericSlider* slider = new QNumericSlider(); - slider->setStatusTip(QString::fromStdString(info.statusTip)); - slider->setToolTip(QString::fromStdString(info.toolTip)); - slider->setRange(info.min, info.max); - slider->setDecimals(info.decimals); - slider->setSingleStep(info.singleStep); - slider->setNumTickMarks(info.numTickMarks); - slider->setSuffix(QString::fromStdString(info.suffix)); - - slider->setValue(prop->get(), true); - QObject::connect(slider, &QNumericSlider::valueChanged, [slider, prop](double value) { prop->set(value, true); }); - // TODO how would this capture the "previous" value, for undo? - QObject::connect(slider, &QNumericSlider::valueChangeCommit, [slider, prop]() { prop->notifyAll(true); }); - - return slider; -} -QComboBox* -addRow(const ComboBoxUiInfo& info, prtyProperty* prop) -{ - QComboBox* comboBox = new QComboBox(); - comboBox->setStatusTip(QString::fromStdString(info.statusTip)); - comboBox->setToolTip(QString::fromStdString(info.toolTip)); - for (const auto& item : info.items) { - comboBox->addItem(QString::fromStdString(item)); - } - comboBox->setCurrentIndex(prop->get()); - QObject::connect(comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->set(index, true); }); - return comboBox; -} -QCheckBox* -addRow(const CheckBoxUiInfo& info, prtyProperty* prop) -{ - QCheckBox* checkBox = new QCheckBox(); - checkBox->setStatusTip(QString::fromStdString(info.statusTip)); - checkBox->setToolTip(QString::fromStdString(info.toolTip)); - // checkBox->setText(QString::fromStdString(info.formLabel)); - checkBox->setCheckState(prop->get() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); - QObject::connect(checkBox, &QCheckBox::stateChanged, [checkBox, prop](int state) { - prop->set(state == Qt::CheckState::Checked, true); - }); - return checkBox; -} - -QCameraWidget::QCameraWidget(QWidget* pParent, QCamera* cam, RenderSettings* rs, CameraDataObject* cdo) +QCameraWidget::QCameraWidget(QWidget* pParent, RenderSettings* rs, CameraObject* cameraObject) : QWidget(pParent) , m_MainLayout() , m_renderSettings(rs) - , m_cameraDataObject(cdo) + , m_cameraObject(cameraObject) { Controls::initFormLayout(m_MainLayout); setLayout(&m_MainLayout); - QNumericSlider* slider = addRow(FloatSliderSpinnerUiInfo("Exposure", - "Set Exposure", - "Set camera exposure", - 0.0f, - 1.0f, - 2, // decimals - 0.01, // singleStep - 0 // numTickMarks - ), - &m_cameraDataObject->Exposure); + QNumericSlider* slider = addRow(*m_cameraObject->getExposureUIInfo()); m_MainLayout.addRow("Exposure", slider); - QComboBox* comboBox = addRow(ComboBoxUiInfo("Exposure Time", - "Set Exposure Time", - "Set number of samples to accumulate per viewport update", - { "1", "2", "4", "8" }), - &m_cameraDataObject->ExposureIterations); + QComboBox* comboBox = addRow(*m_cameraObject->getExposureIterationsUIInfo()); m_MainLayout.addRow("Exposure Time", comboBox); - QCheckBox* checkBox = addRow(CheckBoxUiInfo("Noise Reduction", "Enable denoising pass", "Enable denoising pass"), - &m_cameraDataObject->NoiseReduction); + QCheckBox* checkBox = addRow(*m_cameraObject->getNoiseReductionUIInfo()); m_MainLayout.addRow("Noise Reduction", checkBox); - QNumericSlider* slider2 = addRow(FloatSliderSpinnerUiInfo("Aperture Size", - "Set camera aperture size", - "Set camera aperture size", - 0.0f, - 0.1f, - 2, // decimals - 0.01, // singleStep - 0, // numTickMarks - " mm"), - &m_cameraDataObject->ApertureSize); + QNumericSlider* slider2 = addRow(*m_cameraObject->getApertureSizeUIInfo()); m_MainLayout.addRow("Aperture Size", slider2); - QNumericSlider* slider3 = addRow(FloatSliderSpinnerUiInfo("Field of view", - "Set camera field of view angle", - "Set camera field of view angle", - 10.0f, - 150.0f, - 2, // decimals - 0.01, // singleStep - 0, // numTickMarks - " deg."), - &m_cameraDataObject->FieldOfView); + QNumericSlider* slider3 = addRow(*m_cameraObject->getFieldOfViewUIInfo()); m_MainLayout.addRow("Field of view", slider3); - QNumericSlider* slider4 = addRow(FloatSliderSpinnerUiInfo("Focal distance", - "Set focal distance", - "Set focal distance", - 0.0f, - 15.0f, - 2, // decimals - 0.01, // singleStep - 0, // numTickMarks - " m"), - &m_cameraDataObject->FocalDistance); + QNumericSlider* slider4 = addRow(*m_cameraObject->getFocalDistanceUIInfo()); m_MainLayout.addRow("Focal distance", slider4); #if 0 diff --git a/agave_app/CameraWidget.h b/agave_app/CameraWidget.h index 085a12477..5592afb0d 100644 --- a/agave_app/CameraWidget.h +++ b/agave_app/CameraWidget.h @@ -3,8 +3,8 @@ #include "Camera.h" #include "qtControls/Controls.h" -#include "renderlib/core/prty/prtyProperty.h" -#include "renderlib/CameraDataObject.hpp" +// #include "renderlib/core/prty/prtyProperty.h" +#include "renderlib/CameraUiDescription.hpp" #include "renderlib/Logging.h" #include @@ -19,10 +19,7 @@ class QCameraWidget : public QWidget Q_OBJECT public: - QCameraWidget(QWidget* pParent = NULL, - QCamera* cam = nullptr, - RenderSettings* rs = nullptr, - CameraDataObject* cdo = nullptr); + QCameraWidget(QWidget* pParent = NULL, RenderSettings* rs = nullptr, CameraObject* cameraObject = nullptr); virtual QSize sizeHint() const; @@ -32,5 +29,5 @@ class QCameraWidget : public QWidget RenderSettings* m_renderSettings; private: - CameraDataObject* m_cameraDataObject; + CameraObject* m_cameraObject; }; diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 481f8f256..8b619a1ea 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -45,8 +45,9 @@ GLView3D::GLView3D(QRenderSettings* qrs, RenderSettings* rs, Scene* scene, QWidg m_viewerWindow->gesture.input.setDoubleClickTime((double)QApplication::doubleClickInterval() / 1000.0); // camera is created deep down inside m_viewerWindow. - m_cameraDataObject = new CameraDataObject(&m_viewerWindow->m_CCamera); - m_appearanceDataObject = new AppearanceDataObject(rs); + m_cameraDataObject = new CameraObject(); + // m_cameraDataObject->setExternalCamera(&m_viewerWindow->m_CCamera); + m_appearanceDataObject = new AppearanceObject(); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); @@ -140,6 +141,8 @@ GLView3D::onNewImage(Scene* scene) this->OnUpdateRenderer(m_viewerWindow->m_renderSettings->m_rendererType); // would be better to preserve renderer and just change the scene data to include the new image. // how tightly coupled is renderer and scene???? + + m_appearanceDataObject->updatePropsFromObject(); } GLView3D::~GLView3D() @@ -496,9 +499,9 @@ GLView3D::fromViewerState(const Serialize::ViewerState& s) camera->m_Focus.m_FocalDistance = s.camera.focalDistance; // ASSUMES THIS IS ATTACHED TO m_viewerWindow->m_CCamera !!! - m_cameraDataObject->updatePropsFromCamera(); + m_cameraDataObject->updatePropsFromObject(); - m_appearanceDataObject->updatePropsFromRenderSettings(); + m_appearanceDataObject->updatePropsFromObject(); } QPixmap diff --git a/agave_app/GLView3D.h b/agave_app/GLView3D.h index fd665452a..1294f2ac0 100644 --- a/agave_app/GLView3D.h +++ b/agave_app/GLView3D.h @@ -68,8 +68,8 @@ class GLView3D : public QOpenGLWidget const CCamera& getCamera() { return m_viewerWindow->m_CCamera; } // tied to the above camera. CCamera must outlive this: - CameraDataObject* getCameraDataObject() { return m_cameraDataObject; } - AppearanceDataObject* getAppearanceDataObject() { return m_appearanceDataObject; } + CameraObject* getCameraDataObject() { return m_cameraDataObject; } + AppearanceObject* getAppearanceDataObject() { return m_appearanceDataObject; } void fromViewerState(const Serialize::ViewerState& s); diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index ac52673a6..2fb890c49 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -394,7 +394,7 @@ agaveGui::setupCameraDock(CameraObject* cdo) } void -agaveGui::setupAppearanceDock(AppearanceDataObject* ado) +agaveGui::setupAppearanceDock(AppearanceObject* ado) { m_appearanceDockWidget2 = new QAppearanceDockWidget2(this, &m_renderSettings, ado); m_appearanceDockWidget2->setAllowedAreas(Qt::AllDockWidgetAreas); @@ -1382,14 +1382,13 @@ agaveGui::appToViewerState() v.camera.projection = m_glView->getCamera().m_Projection == PERSPECTIVE ? Serialize::Projection_PID::PERSPECTIVE : Serialize::Projection_PID::ORTHOGRAPHIC; v.camera.orthoScale = m_glView->getCamera().m_OrthoScale; - CameraObject* cdo = m_cameraObject.get(); + CameraObject* cdo = m_glView->getCameraDataObject(); v.camera.fovY = cdo->getCameraDataObject().FieldOfView.GetValue(); v.camera.exposure = cdo->getCameraDataObject().Exposure.GetValue(); v.camera.aperture = cdo->getCameraDataObject().ApertureSize.GetValue(); v.camera.focalDistance = cdo->getCameraDataObject().FocalDistance.GetValue(); - auto rs = m_appearanceObject->getRenderSettings(); - v.density = rs->m_RenderSettings.m_DensityScale; - v.interpolate = rs->m_RenderSettings.m_InterpolatedVolumeSampling; + v.density = m_renderSettings.m_RenderSettings.m_DensityScale; + v.interpolate = m_renderSettings.m_RenderSettings.m_InterpolatedVolumeSampling; v.density = appearanceData.DensityScale.GetValue(); v.interpolate = appearanceData.Interpolate.GetValue(); diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index a5a6af638..b91ac17a9 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -101,10 +101,10 @@ private slots: void createActions(); void createMenus(); void createToolbars(); - void createDockWindows(); - void setupCameraDock(CameraDataObject* cdo); + void addDockItemsToViewMenu(); + void setupCameraDock(CameraObject* cdo); void setupTimelineDock(); - void setupAppearanceDock(AppearanceDataObject* ado); + void setupAppearanceDock(AppearanceObject* ado); void setupStatisticsDock(); void showOpenFailedMessageBox(QString path); diff --git a/agave_app/qtControls/controlFactory.cpp b/agave_app/qtControls/controlFactory.cpp index 7e4821e15..ca9ba88ab 100644 --- a/agave_app/qtControls/controlFactory.cpp +++ b/agave_app/qtControls/controlFactory.cpp @@ -241,6 +241,7 @@ addRow(const ComboBoxUiInfo& info) for (const auto& item : info.items) { comboBox->addItem(QString::fromStdString(item)); } + auto* prop = static_cast(info.GetProperty(0)); comboBox->setCurrentIndex(prop->GetValue()); auto conn = QObject::connect( comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->SetValue(index, true); }); diff --git a/agave_app/qtControls/controlFactory.h b/agave_app/qtControls/controlFactory.h index 4947b2582..24aa2b693 100644 --- a/agave_app/qtControls/controlFactory.h +++ b/agave_app/qtControls/controlFactory.h @@ -41,13 +41,3 @@ addRow(const CheckBoxUiInfo& info); QColorPushButton* addRow(const ColorPickerUiInfo& info); - -QWidget* -addGenericRow(const prtyPropertyUIInfo& info); - -template -void -createFlatList(LayoutType* mainLayout, prtyObject* object); - -void -createCategorizedSections(QFormLayout* mainLayout, prtyObject* object); From a0691b282d40f8a4f47aa40d92eca2ae9d54b72b Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Wed, 25 Jun 2025 15:47:02 -0700 Subject: [PATCH 10/63] make control creation more generic --- agave_app/CameraWidget.cpp | 122 ++---------------------- agave_app/qtControls/controlFactory.cpp | 35 +++---- agave_app/qtControls/controlFactory.h | 9 ++ 3 files changed, 26 insertions(+), 140 deletions(-) diff --git a/agave_app/CameraWidget.cpp b/agave_app/CameraWidget.cpp index c76f1001e..d413cf875 100644 --- a/agave_app/CameraWidget.cpp +++ b/agave_app/CameraWidget.cpp @@ -1,6 +1,9 @@ #include "CameraWidget.h" #include "RenderSettings.h" +#include "qtControls/controlFactory.h" +#include "qtControls/Section.h" + #include "renderlib/uiInfo.hpp" #include @@ -16,122 +19,9 @@ QCameraWidget::QCameraWidget(QWidget* pParent, RenderSettings* rs, CameraObject* Controls::initFormLayout(m_MainLayout); setLayout(&m_MainLayout); - QNumericSlider* slider = addRow(*m_cameraObject->getExposureUIInfo()); - m_MainLayout.addRow("Exposure", slider); - QComboBox* comboBox = addRow(*m_cameraObject->getExposureIterationsUIInfo()); - m_MainLayout.addRow("Exposure Time", comboBox); - QCheckBox* checkBox = addRow(*m_cameraObject->getNoiseReductionUIInfo()); - m_MainLayout.addRow("Noise Reduction", checkBox); - QNumericSlider* slider2 = addRow(*m_cameraObject->getApertureSizeUIInfo()); - m_MainLayout.addRow("Aperture Size", slider2); - QNumericSlider* slider3 = addRow(*m_cameraObject->getFieldOfViewUIInfo()); - m_MainLayout.addRow("Field of view", slider3); - QNumericSlider* slider4 = addRow(*m_cameraObject->getFocalDistanceUIInfo()); - m_MainLayout.addRow("Focal distance", slider4); - -#if 0 - // Exposure, controls how bright or dim overall scene is - m_ExposureSlider.setStatusTip(tr("Set Exposure")); - m_ExposureSlider.setToolTip(tr("Set camera exposure")); - m_ExposureSlider.setRange(0.0f, 1.0f); - m_ExposureSlider.setValue(cam->GetFilm().GetExposure()); - m_ExposureSlider.setDecimals(2); - m_ExposureSlider.setSingleStep(0.01); - - m_MainLayout.addRow("Exposure", &m_ExposureSlider); - - connect(&m_ExposureSlider, &QNumericSlider::valueChanged, this, &QCameraWidget::SetExposure); - - // Number of render iterations per viewport update - m_ExposureIterationsSpinner.setStatusTip(tr("Set Exposure Time")); - m_ExposureIterationsSpinner.setToolTip(tr("Set number of samples to accumulate per viewport update")); - m_ExposureIterationsSpinner.addItem("1", 1); - m_ExposureIterationsSpinner.addItem("2", 2); - m_ExposureIterationsSpinner.addItem("4", 4); - m_ExposureIterationsSpinner.addItem("8", 8); - m_ExposureIterationsSpinner.setCurrentIndex( - m_ExposureIterationsSpinner.findData(cam->GetFilm().GetExposureIterations())); - m_MainLayout.addRow("Exposure Time", &m_ExposureIterationsSpinner); - connect(&m_ExposureIterationsSpinner, &QComboBox::currentIndexChanged, this, &QCameraWidget::SetExposureIterations); - - m_NoiseReduction.setStatusTip(tr("Enable denoising pass")); - m_NoiseReduction.setToolTip(tr("Enable denoising pass")); - m_NoiseReduction.setCheckState(rs->m_DenoiseParams.m_Enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); - m_MainLayout.addRow("Noise Reduction", &m_NoiseReduction); - - connect(&m_NoiseReduction, &QCheckBox::stateChanged, this, &QCameraWidget::OnNoiseReduction); - - m_ApertureSizeSlider.setStatusTip(tr("Set camera aperture size")); - m_ApertureSizeSlider.setToolTip(tr("Set camera aperture size")); - m_ApertureSizeSlider.setRange(0.0, 0.1); - m_ApertureSizeSlider.setSuffix(" mm"); - m_ApertureSizeSlider.setDecimals(2); - m_ApertureSizeSlider.setValue(0.0); - m_ApertureSizeSlider.setSingleStep(0.01); - m_MainLayout.addRow("Aperture Size", &m_ApertureSizeSlider); - - connect(&m_ApertureSizeSlider, &QNumericSlider::valueChanged, this, &QCameraWidget::SetAperture); - - m_FieldOfViewSlider.setStatusTip(tr("Set camera field of view angle")); - m_FieldOfViewSlider.setToolTip(tr("Set camera field of view angle")); - m_FieldOfViewSlider.setRange(10.0, 150.0); - m_FieldOfViewSlider.setDecimals(2); - m_FieldOfViewSlider.setValue(cam->GetProjection().GetFieldOfView()); - m_FieldOfViewSlider.setSuffix(" deg."); - m_MainLayout.addRow("Field of view", &m_FieldOfViewSlider); - - connect(&m_FieldOfViewSlider, &QNumericSlider::valueChanged, this, &QCameraWidget::SetFieldOfView); - - // Focal distance - m_FocalDistanceSlider.setStatusTip(tr("Set focal distance")); - m_FocalDistanceSlider.setToolTip(tr("Set focal distance")); - m_FocalDistanceSlider.setRange(0.0, 15.0); - m_FocalDistanceSlider.setDecimals(2); - m_FocalDistanceSlider.setValue(0.0); - m_FocalDistanceSlider.setSuffix(" m"); - - m_MainLayout.addRow("Focal distance", &m_FocalDistanceSlider); - - connect(&m_FocalDistanceSlider, &QNumericSlider::valueChanged, this, &QCameraWidget::SetFocalDistance); - - QObject::connect(&cam->GetFilm(), SIGNAL(Changed(const QFilm&)), this, SLOT(OnFilmChanged())); - QObject::connect(&cam->GetAperture(), SIGNAL(Changed(const QAperture&)), this, SLOT(OnApertureChanged())); - QObject::connect(&cam->GetProjection(), SIGNAL(Changed(const QProjection&)), this, SLOT(OnProjectionChanged())); - QObject::connect(&cam->GetFocus(), SIGNAL(Changed(const QFocus&)), this, SLOT(OnFocusChanged())); -#endif -} - -void -QCameraWidget::OnFilmChanged() -{ - m_ExposureSlider.setValue(m_qcamera->GetFilm().GetExposure(), true); - m_ExposureIterationsSpinner.blockSignals(true); - m_ExposureIterationsSpinner.setCurrentIndex( - m_ExposureIterationsSpinner.findData(m_qcamera->GetFilm().GetExposureIterations())); - m_ExposureIterationsSpinner.blockSignals(false); - m_NoiseReduction.blockSignals(true); - m_NoiseReduction.setCheckState(m_renderSettings->m_DenoiseParams.m_Enabled ? Qt::CheckState::Checked - : Qt::CheckState::Unchecked); - m_NoiseReduction.blockSignals(false); - emit m_qcamera->Changed(); -} -void -QCameraWidget::OnApertureChanged() -{ - m_ApertureSizeSlider.setValue(m_qcamera->GetAperture().GetSize(), true); - emit m_qcamera->Changed(); -} -void -QCameraWidget::OnProjectionChanged() -{ - m_FieldOfViewSlider.setValue(m_qcamera->GetProjection().GetFieldOfView(), true); - emit m_qcamera->Changed(); -} -void -QCameraWidget::OnFocusChanged() -{ - m_FocalDistanceSlider.setValue(m_qcamera->GetFocus().GetFocalDistance(), true); - emit m_qcamera->Changed(); + if (m_cameraObject) { + createFlatList(&m_MainLayout, m_cameraObject); + } } QSize diff --git a/agave_app/qtControls/controlFactory.cpp b/agave_app/qtControls/controlFactory.cpp index ca9ba88ab..e4427edc1 100644 --- a/agave_app/qtControls/controlFactory.cpp +++ b/agave_app/qtControls/controlFactory.cpp @@ -309,25 +309,10 @@ addGenericRow(const prtyPropertyUIInfo& info) return addRow(*checkBoxInfo); } else if (const auto* colorPickerInfo = dynamic_cast(&info)) { return addRow(*colorPickerInfo); - } else if (const auto* colorWithIntensityInfo = dynamic_cast(&info)) { - return addRow(*colorWithIntensityInfo); } return nullptr; // or throw an exception } -template -QWidget* -addPrtyRow(LayoutType* layout, std::shared_ptr propertyInfo) -{ - QWidget* control = addGenericRow(*propertyInfo); - if (control) { - QString label = QString::fromStdString(propertyInfo->GetDescription()); - layout->addRow(label, control); - return control; - } - return nullptr; -} - void createCategorizedSections(QFormLayout* mainLayout, prtyObject* object) { @@ -354,7 +339,11 @@ createCategorizedSections(QFormLayout* mainLayout, prtyObject* object) // Add controls for each property in this category for (const auto& propertyInfo : properties) { - addPrtyRow(sectionLayout, propertyInfo); + QWidget* control = addGenericRow(*propertyInfo); + if (control) { + QString label = QString::fromStdString(propertyInfo->GetDescription()); + sectionLayout->addRow(label, control); + } } // Set the section's content layout @@ -366,20 +355,18 @@ createCategorizedSections(QFormLayout* mainLayout, prtyObject* object) } } -template void -createFlatList(LayoutType* mainLayout, prtyObject* object) +createFlatList(QFormLayout* mainLayout, prtyObject* object) { // Simple flat list of all properties const auto& propertyList = object->GetList(); for (const auto& propertyInfo : propertyList) { if (propertyInfo) { - addPrtyRow(mainLayout, propertyInfo); + QWidget* control = addGenericRow(*propertyInfo); + if (control) { + QString label = QString::fromStdString(propertyInfo->GetDescription()); + mainLayout->addRow(label, control); + } } } } - -template void -createFlatList(QFormLayout* mainLayout, prtyObject* object); -template void -createFlatList(AgaveFormLayout* mainLayout, prtyObject* object); diff --git a/agave_app/qtControls/controlFactory.h b/agave_app/qtControls/controlFactory.h index 24aa2b693..d58d83a64 100644 --- a/agave_app/qtControls/controlFactory.h +++ b/agave_app/qtControls/controlFactory.h @@ -41,3 +41,12 @@ addRow(const CheckBoxUiInfo& info); QColorPushButton* addRow(const ColorPickerUiInfo& info); + +QWidget* +addGenericRow(const prtyPropertyUIInfo& info); + +void +createFlatList(QFormLayout* mainLayout, prtyObject* object); + +void +createCategorizedSections(QFormLayout* mainLayout, prtyObject* object); \ No newline at end of file From cad4a2f0728d50b6dad027040e0ad40fad8c4dc6 Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Thu, 3 Jul 2025 16:58:19 -0700 Subject: [PATCH 11/63] wip on camera creation / lifetime --- agave_app/CameraWidget.cpp | 11 +++++++++++ agave_app/GLView3D.cpp | 2 ++ agave_app/GLView3D.h | 2 +- renderlib/CameraObject.cpp | 38 +++++++++++++++++++++++++++++++++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/agave_app/CameraWidget.cpp b/agave_app/CameraWidget.cpp index d413cf875..56aea443b 100644 --- a/agave_app/CameraWidget.cpp +++ b/agave_app/CameraWidget.cpp @@ -22,6 +22,17 @@ QCameraWidget::QCameraWidget(QWidget* pParent, RenderSettings* rs, CameraObject* if (m_cameraObject) { createFlatList(&m_MainLayout, m_cameraObject); } + // // loop over all properties in cameraobject. for each property, add a callback that updates the rendersetttings + // // cameradirty flags + // for (const auto& prop : m_cameraObject->GetList()) { + // if (prop) { + // prop->GetProperty(0)->AddCallback(new prtyCallbackLambda([this](prtyProperty* i_Property, bool i_bDirty) { + // if (i_bDirty) { + // m_renderSettings->m_DirtyFlags.SetFlag(CameraDirty); + // } + // })); + // } + // } } QSize diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 8b619a1ea..94f3a90eb 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -46,6 +46,8 @@ GLView3D::GLView3D(QRenderSettings* qrs, RenderSettings* rs, Scene* scene, QWidg // camera is created deep down inside m_viewerWindow. m_cameraDataObject = new CameraObject(); + m_viewerWindow->m_CCamera = m_cameraDataObject->getCamera(); + // m_cameraDataObject->setExternalCamera(&m_viewerWindow->m_CCamera); m_appearanceDataObject = new AppearanceObject(); diff --git a/agave_app/GLView3D.h b/agave_app/GLView3D.h index 1294f2ac0..127190b9e 100644 --- a/agave_app/GLView3D.h +++ b/agave_app/GLView3D.h @@ -66,7 +66,7 @@ class GLView3D : public QOpenGLWidget void onNewImage(Scene* scene); - const CCamera& getCamera() { return m_viewerWindow->m_CCamera; } + const CCamera& getCamera() { return *m_viewerWindow->m_CCamera; } // tied to the above camera. CCamera must outlive this: CameraObject* getCameraDataObject() { return m_cameraDataObject; } AppearanceObject* getAppearanceDataObject() { return m_appearanceDataObject; } diff --git a/renderlib/CameraObject.cpp b/renderlib/CameraObject.cpp index 8faa01318..d45b5def9 100644 --- a/renderlib/CameraObject.cpp +++ b/renderlib/CameraObject.cpp @@ -95,4 +95,40 @@ CameraObject::updateObjectFromProps() // renderer should pick this up and do the right thing (TM) m_camera->m_Dirty = true; } -} \ No newline at end of file +} + +void +CameraObject::ExposureChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_camera->m_Film.m_Exposure = 1.0f - m_cameraDataObject.Exposure.GetValue(); + m_camera->m_Dirty = true; +} +void +CameraObject::ExposureIterationsChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_camera->m_Film.m_ExposureIterations = m_cameraDataObject.ExposureIterations.GetValue(); + m_camera->m_Dirty = true; +} +void +CameraObject::NoiseReductionChanged(prtyProperty* i_Property, bool i_bDirty) +{ + LOG_ERROR << "Noise reduction is not implemented yet!"; +} +void +CameraObject::ApertureSizeChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_camera->m_Aperture.m_Size = m_cameraDataObject.ApertureSize.GetValue(); + m_camera->m_Dirty = true; +} +void +CameraObject::FieldOfViewChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_camera->m_FovV = m_cameraDataObject.FieldOfView.GetValue(); + m_camera->m_Dirty = true; +} +void +CameraObject::FocalDistanceChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_camera->m_Focus.m_FocalDistance = m_cameraDataObject.FocalDistance.GetValue(); + m_camera->m_Dirty = true; +} From 46ab1eaeb4a815ee3ae94beb90af3d8c877ea802 Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 4 Jul 2025 08:39:08 -0700 Subject: [PATCH 12/63] more object lifetime work with camera --- agave_app/GLView3D.cpp | 8 +------- agave_app/GLView3D.h | 7 ++++--- agave_app/agaveGui.cpp | 24 ++++++++---------------- agave_app/agaveGui.h | 3 +-- 4 files changed, 14 insertions(+), 28 deletions(-) diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 94f3a90eb..26a636b90 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -44,11 +44,6 @@ GLView3D::GLView3D(QRenderSettings* qrs, RenderSettings* rs, Scene* scene, QWidg m_viewerWindow = new ViewerWindow(rs); m_viewerWindow->gesture.input.setDoubleClickTime((double)QApplication::doubleClickInterval() / 1000.0); - // camera is created deep down inside m_viewerWindow. - m_cameraDataObject = new CameraObject(); - m_viewerWindow->m_CCamera = m_cameraDataObject->getCamera(); - - // m_cameraDataObject->setExternalCamera(&m_viewerWindow->m_CCamera); m_appearanceDataObject = new AppearanceObject(); setFocusPolicy(Qt::StrongFocus); @@ -149,7 +144,6 @@ GLView3D::onNewImage(Scene* scene) GLView3D::~GLView3D() { - delete m_cameraDataObject; delete m_appearanceDataObject; makeCurrent(); @@ -501,7 +495,7 @@ GLView3D::fromViewerState(const Serialize::ViewerState& s) camera->m_Focus.m_FocalDistance = s.camera.focalDistance; // ASSUMES THIS IS ATTACHED TO m_viewerWindow->m_CCamera !!! - m_cameraDataObject->updatePropsFromObject(); + m_cameraObject->updatePropsFromObject(); m_appearanceDataObject->updatePropsFromObject(); } diff --git a/agave_app/GLView3D.h b/agave_app/GLView3D.h index 127190b9e..c8018c335 100644 --- a/agave_app/GLView3D.h +++ b/agave_app/GLView3D.h @@ -68,7 +68,8 @@ class GLView3D : public QOpenGLWidget const CCamera& getCamera() { return *m_viewerWindow->m_CCamera; } // tied to the above camera. CCamera must outlive this: - CameraObject* getCameraDataObject() { return m_cameraDataObject; } + // CameraObject* getCameraDataObject() { return m_cameraObject; } + void setCameraObject(CameraObject* cameraObject); AppearanceObject* getAppearanceDataObject() { return m_appearanceDataObject; } void fromViewerState(const Serialize::ViewerState& s); @@ -112,8 +113,8 @@ public slots: void wheelEvent(QWheelEvent* event); private: - CameraDataObject* m_cameraDataObject; - AppearanceDataObject* m_appearanceDataObject; + CameraObject* m_cameraObject; + AppearanceObject* m_appearanceDataObject; QRenderSettings* m_qrendersettings; /// Rendering timer. diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 2fb890c49..05e461709 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -12,8 +12,8 @@ #include "renderlib/VolumeDimensions.h" #include "renderlib/io/FileReader.h" #include "renderlib/version.hpp" -#include "renderlib/CameraObject.hpp" -#include "renderlib/AppearanceObject.hpp" +#include "renderlib/CameraUiDescription.hpp" +#include "renderlib/AppearanceUiDescription.hpp" #include "AppearanceDockWidget.h" #include "AppearanceDockWidget2.h" @@ -85,19 +85,9 @@ QMenu#quickViewsMenu {border-radius: 2px;} agaveGui::agaveGui(QWidget* parent) : QMainWindow(parent) { - // create our document objects - // render settings - m_appearanceObject = std::make_unique(); - // scene objects + // create our two document objects m_cameraObject = std::make_unique(); - m_areaLightObject = std::make_unique(); - m_areaLightObject->getSceneLight()->m_light = Scene::defaultAreaLight(); - m_areaLightObject->setDirtyCallback( - [this]() { m_appearanceObject->getRenderSettings()->m_DirtyFlags.SetFlag(LightsDirty); }); - m_skyLightObject = std::make_unique(); - m_skyLightObject->getSceneLight()->m_light = Scene::defaultSkyLight(); - m_skyLightObject->setDirtyCallback( - [this]() { m_appearanceObject->getRenderSettings()->m_DirtyFlags.SetFlag(LightsDirty); }); + m_appearanceObject = std::make_unique(); m_ui.setupUi(this); @@ -135,8 +125,10 @@ agaveGui::agaveGui(QWidget* parent) // We need a minimum size or else the size defaults to zero. m_glView->setMinimumSize(256, 512); m_glView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_glView->setCameraObject(m_cameraObject.get()); + // create camera ui window now that there is an actual camera. - setupCameraDock(m_glView->getCameraDataObject()); + setupCameraDock(m_cameraObject.get()); setupTimelineDock(); setupStatisticsDock(); setupAppearanceDock(m_glView->getAppearanceDataObject()); @@ -1382,7 +1374,7 @@ agaveGui::appToViewerState() v.camera.projection = m_glView->getCamera().m_Projection == PERSPECTIVE ? Serialize::Projection_PID::PERSPECTIVE : Serialize::Projection_PID::ORTHOGRAPHIC; v.camera.orthoScale = m_glView->getCamera().m_OrthoScale; - CameraObject* cdo = m_glView->getCameraDataObject(); + CameraObject* cdo = m_cameraObject.get(); v.camera.fovY = cdo->getCameraDataObject().FieldOfView.GetValue(); v.camera.exposure = cdo->getCameraDataObject().Exposure.GetValue(); v.camera.aperture = cdo->getCameraDataObject().ApertureSize.GetValue(); diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index b91ac17a9..14b5391bf 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -193,8 +193,7 @@ private slots: Scene m_appScene; int m_currentScene = 0; std::unique_ptr m_cameraObject; - std::unique_ptr m_areaLightObject; - std::unique_ptr m_skyLightObject; + std::unique_ptr m_appearanceObject; std::string m_currentFilePath; // TODO remove the above m_currentFilePath and use this instead From b180b376c6e12c2bef5bd813e9b0bdac4f38167a Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 4 Jul 2025 09:55:23 -0700 Subject: [PATCH 13/63] rename --- agave_app/AppearanceWidget.h | 2 +- agave_app/CameraWidget.cpp | 1 + agave_app/CameraWidget.h | 2 +- agave_app/GLView3D.cpp | 4 +- agave_app/agaveGui.cpp | 4 +- renderlib/AppearanceObject.hpp | 20 ---- renderlib/AppearanceUiDescription.cpp | 127 -------------------------- renderlib/AppearanceUiDescription.hpp | 50 ---------- renderlib/CMakeLists.txt | 6 +- 9 files changed, 11 insertions(+), 205 deletions(-) delete mode 100644 renderlib/AppearanceUiDescription.cpp delete mode 100644 renderlib/AppearanceUiDescription.hpp diff --git a/agave_app/AppearanceWidget.h b/agave_app/AppearanceWidget.h index c059424ba..6e943119d 100644 --- a/agave_app/AppearanceWidget.h +++ b/agave_app/AppearanceWidget.h @@ -3,7 +3,7 @@ #include "qtControls/Controls.h" // #include "renderlib/core/prty/prtyProperty.h" -#include "renderlib/AppearanceUiDescription.hpp" +#include "renderlib/AppearanceObject.hpp" #include "renderlib/Logging.h" #include diff --git a/agave_app/CameraWidget.cpp b/agave_app/CameraWidget.cpp index 56aea443b..6078af462 100644 --- a/agave_app/CameraWidget.cpp +++ b/agave_app/CameraWidget.cpp @@ -5,6 +5,7 @@ #include "qtControls/Section.h" #include "renderlib/uiInfo.hpp" +#include "renderlib/CameraObject.hpp" #include #include diff --git a/agave_app/CameraWidget.h b/agave_app/CameraWidget.h index 5592afb0d..35ed6fdba 100644 --- a/agave_app/CameraWidget.h +++ b/agave_app/CameraWidget.h @@ -4,7 +4,7 @@ #include "qtControls/Controls.h" // #include "renderlib/core/prty/prtyProperty.h" -#include "renderlib/CameraUiDescription.hpp" +#include "renderlib/CameraObject.hpp" #include "renderlib/Logging.h" #include diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 26a636b90..5f95fb1d5 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -3,8 +3,8 @@ #include "QRenderSettings.h" #include "ViewerState.h" -#include "renderlib/AppearanceDataObject.hpp" -#include "renderlib/CameraDataObject.hpp" +#include "renderlib/AppearanceObject.hpp" +#include "renderlib/CameraObject.hpp" #include "renderlib/ImageXYZC.h" #include "renderlib/Logging.h" #include "renderlib/MoveTool.h" diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 05e461709..6cbd5fb2a 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -12,8 +12,8 @@ #include "renderlib/VolumeDimensions.h" #include "renderlib/io/FileReader.h" #include "renderlib/version.hpp" -#include "renderlib/CameraUiDescription.hpp" -#include "renderlib/AppearanceUiDescription.hpp" +#include "renderlib/CameraObject.hpp" +#include "renderlib/AppearanceObject.hpp" #include "AppearanceDockWidget.h" #include "AppearanceDockWidget2.h" diff --git a/renderlib/AppearanceObject.hpp b/renderlib/AppearanceObject.hpp index 49e1321cf..831cb25a8 100644 --- a/renderlib/AppearanceObject.hpp +++ b/renderlib/AppearanceObject.hpp @@ -28,26 +28,6 @@ class AppearanceObject : public prtyObject void updatePropsFromObject(); void updateObjectFromProps(); - // Getter for appearance data object - // AppearanceDataObject& getAppearanceDataObject() { return m_appearanceDataObject; } - const AppearanceDataObject& getAppearanceDataObject() const { return m_appearanceDataObject; } - - // Getters for UI info objects - ComboBoxUiInfo* getRendererTypeUiInfo() { return m_rendererType; } - ComboBoxUiInfo* getShadingTypeUiInfo() { return m_shadingType; } - FloatSliderSpinnerUiInfo* getDensityScaleUiInfo() { return m_densityScale; } - FloatSliderSpinnerUiInfo* getGradientFactorUiInfo() { return m_gradientFactor; } - FloatSliderSpinnerUiInfo* getStepSizePrimaryRayUiInfo() { return m_stepSizePrimaryRay; } - FloatSliderSpinnerUiInfo* getStepSizeSecondaryRayUiInfo() { return m_stepSizeSecondaryRay; } - CheckBoxUiInfo* getInterpolateUiInfo() { return m_interpolate; } - ColorPickerUiInfo* getBackgroundColorUiInfo() { return m_backgroundColor; } - CheckBoxUiInfo* getShowBoundingBoxUiInfo() { return m_showBoundingBox; } - ColorPickerUiInfo* getBoundingBoxColorUiInfo() { return m_boundingBoxColor; } - CheckBoxUiInfo* getShowScaleBarUiInfo() { return m_showScaleBar; } - - // Getter for the rendersettings - std::shared_ptr getRenderSettings() const { return m_renderSettings; } - private: // the properties AppearanceDataObject m_appearanceDataObject; diff --git a/renderlib/AppearanceUiDescription.cpp b/renderlib/AppearanceUiDescription.cpp deleted file mode 100644 index ed1fc23ed..000000000 --- a/renderlib/AppearanceUiDescription.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "AppearanceUiDescription.hpp" - -// ComboBoxUiInfo AppearanceUiDescription::m_rendererType("Renderer Type", -// "Select volume rendering type", -// "Select volume rendering type", -// { "Ray march blending", "Path Traced" }); -// ComboBoxUiInfo AppearanceUiDescription::m_shadingType("Shading Type", -// "Select volume shading style", -// "Select volume shading style", -// { "BRDF Only", "Phase Function Only", "Mixed" }); -// FloatSliderSpinnerUiInfo AppearanceUiDescription::m_densityScale("Scattering Density", -// "Set scattering density for volume", -// "Set scattering density for volume", -// 0.001f, -// 100.0f, -// 3, -// 0.01f, -// 10); -// FloatSliderSpinnerUiInfo AppearanceUiDescription::m_gradientFactor("Shading Type Mixture", -// "Mix between BRDF and Phase shading", -// "Mix between BRDF and Phase shading", -// 0.0f, -// 1.0f, -// 3, -// 0.01f, -// 10); -// FloatSliderSpinnerUiInfo AppearanceUiDescription::m_stepSizePrimaryRay("Primary Ray Step Size", -// "Set volume ray march step size for camera -// rays", "Set volume ray march step size for -// camera rays", 1.0f, 100.0f, 3, 0.01f, 10); -// FloatSliderSpinnerUiInfo AppearanceUiDescription::m_stepSizeSecondaryRay( -// "Secondary Ray Step Size", -// "Set volume ray march step size for scattered rays", -// "Set volume ray march step size for scattered rays", -// 1.0f, -// 100.0f, -// 3, -// 0.01f, -// 10); -// CheckBoxUiInfo AppearanceUiDescription::m_interpolate("Interpolate", -// "Interpolated volume sampling", -// "Interpolated volume sampling"); -// ColorPickerUiInfo AppearanceUiDescription::m_backgroundColor("Background Color", -// "Set background color", -// "Set background color"); -// CheckBoxUiInfo AppearanceUiDescription::m_showBoundingBox("Show Bounding Box", -// "Show/hide bounding box", -// "Show/hide bounding box"); -// ColorPickerUiInfo AppearanceUiDescription::m_boundingBoxColor("Bounding Box Color", -// "Set bounding box color", -// "Set bounding box color"); -// CheckBoxUiInfo AppearanceUiDescription::m_showScaleBar("Show Scale Bar", "Show/hide scale bar", "Show/hide scale -// bar"); - -AppearanceObject::AppearanceObject() - : prtyObject() -{ - m_RenderSettings = std::make_shared(); - m_rendererType = new ComboBoxUiInfo(&m_appearanceDataObject.RendererType, "Appearance", "Renderer Type"); - m_shadingType = new ComboBoxUiInfo(&m_appearanceDataObject.ShadingType, "Appearance", "Shading Type"); - m_densityScale = new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.DensityScale, "Appearance", "Density Scale"); - m_gradientFactor = - new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.GradientFactor, "Appearance", "Gradient Factor"); - m_stepSizePrimaryRay = - new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.StepSizePrimaryRay, "Appearance", "Step Size Primary Ray"); - m_stepSizeSecondaryRay = - new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.StepSizeSecondaryRay, "Appearance", "Step Size Secondary Ray"); - m_interpolate = new CheckBoxUiInfo(&m_appearanceDataObject.Interpolate, "Appearance", "Interpolate"); - m_backgroundColor = new ColorPickerUiInfo(&m_appearanceDataObject.BackgroundColor, "Appearance", "Background Color"); - m_showBoundingBox = new CheckBoxUiInfo(&m_appearanceDataObject.ShowBoundingBox, "Appearance", "Show Bounding Box"); - m_boundingBoxColor = - new ColorPickerUiInfo(&m_appearanceDataObject.BoundingBoxColor, "Appearance", "Bounding Box Color"); - m_showScaleBar = new CheckBoxUiInfo(&m_appearanceDataObject.ShowScaleBar, "Appearance", "Show Scale Bar"); -} - -void -AppearanceObject::updatePropsFromObject() -{ - if (m_renderSettings) { - m_appearanceDataObject.ShadingType.SetValue(m_renderSettings->m_RenderSettings.m_ShadingType); - m_appearanceDataObject.RendererType.SetValue(m_renderSettings->m_rendererType); - m_appearanceDataObject.DensityScale.SetValue(m_renderSettings->m_RenderSettings.m_DensityScale); - m_appearanceDataObject.GradientFactor.SetValue(m_renderSettings->m_RenderSettings.m_GradientFactor); - m_appearanceDataObject.StepSizePrimaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactor); - m_appearanceDataObject.StepSizeSecondaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow); - m_appearanceDataObject.Interpolate.SetValue(m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling); - } - if (m_scene) { - BackgroundColor.SetValue(glm::vec4(m_scene->m_material.m_backgroundColor[0], - m_scene->m_material.m_backgroundColor[1], - m_scene->m_material.m_backgroundColor[2], - 1.0f)); - ShowBoundingBox.SetValue(m_scene->m_material.m_showBoundingBox); - BoundingBoxColor.SetValue(glm::vec4(m_scene->m_material.m_boundingBoxColor[0], - m_scene->m_material.m_boundingBoxColor[1], - m_scene->m_material.m_boundingBoxColor[2], - 1.0f)); - ShowScaleBar.SetValue(m_scene->m_showScaleBar); - } -} - -void -AppearanceObject::updateObjectFromProps() -{ - // update low-level object from properties - if (m_renderSettings) { - m_renderSettings->m_RenderSettings.m_ShadingType = m_appearanceDataObject.ShadingType.GetValue(); - m_renderSettings->m_rendererType = m_appearanceDataObject.RendererType.GetValue(); - m_renderSettings->m_RenderSettings.m_DensityScale = m_appearanceDataObject.DensityScale.GetValue(); - m_renderSettings->m_RenderSettings.m_GradientFactor = m_appearanceDataObject.GradientFactor.GetValue(); - m_renderSettings->m_RenderSettings.m_StepSizeFactor = m_appearanceDataObject.StepSizePrimaryRay.GetValue(); - m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow = m_appearanceDataObject.StepSizeSecondaryRay.GetValue(); - m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling = m_appearanceDataObject.Interpolate.GetValue(); - m_scene->m_material.m_backgroundColor[0] = m_appearanceDataObject.BackgroundColor.GetValue().x; - m_scene->m_material.m_backgroundColor[1] = m_appearanceDataObject.BackgroundColor.GetValue().y; - m_scene->m_material.m_backgroundColor[2] = m_appearanceDataObject.BackgroundColor.GetValue().z; - m_scene->m_material.m_showBoundingBox = m_appearanceDataObject.ShowBoundingBox.GetValue(); - m_scene->m_material.m_boundingBoxColor[0] = m_appearanceDataObject.BoundingBoxColor.GetValue().x; - m_scene->m_material.m_boundingBoxColor[1] = m_appearanceDataObject.BoundingBoxColor.GetValue().y; - m_scene->m_material.m_boundingBoxColor[2] = m_appearanceDataObject.BoundingBoxColor.GetValue().z; - m_scene->m_showScaleBar = m_appearanceDataObject.ShowScaleBar.GetValue(); - - m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); - m_renderSettings->m_DirtyFlags.SetFlag(TransferFunctionDirty); - m_renderSettings->m_DirtyFlags.SetFlag(LightsDirty); - } -} \ No newline at end of file diff --git a/renderlib/AppearanceUiDescription.hpp b/renderlib/AppearanceUiDescription.hpp deleted file mode 100644 index 831cb25a8..000000000 --- a/renderlib/AppearanceUiDescription.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "AppearanceDataObject.hpp" -#include "core/prty/prtyObject.hpp" -#include "RenderSettings.h" -#include "uiInfo.hpp" - -struct AppearanceUiDescription -{ - static ComboBoxUiInfo m_rendererType; - static ComboBoxUiInfo m_shadingType; - static FloatSliderSpinnerUiInfo m_densityScale; - static FloatSliderSpinnerUiInfo m_gradientFactor; - static FloatSliderSpinnerUiInfo m_stepSizePrimaryRay; - static FloatSliderSpinnerUiInfo m_stepSizeSecondaryRay; - static CheckBoxUiInfo m_interpolate; - static ColorPickerUiInfo m_backgroundColor; - static CheckBoxUiInfo m_showBoundingBox; - static ColorPickerUiInfo m_boundingBoxColor; - static CheckBoxUiInfo m_showScaleBar; -}; - -class AppearanceObject : public prtyObject -{ -public: - AppearanceObject(); - - void updatePropsFromObject(); - void updateObjectFromProps(); - -private: - // the properties - AppearanceDataObject m_appearanceDataObject; - - // the actual camera - std::shared_ptr m_renderSettings; - - // the ui info - ComboBoxUiInfo* m_rendererType; - ComboBoxUiInfo* m_shadingType; - FloatSliderSpinnerUiInfo* m_densityScale; - FloatSliderSpinnerUiInfo* m_gradientFactor; - FloatSliderSpinnerUiInfo* m_stepSizePrimaryRay; - FloatSliderSpinnerUiInfo* m_stepSizeSecondaryRay; - CheckBoxUiInfo* m_interpolate; - ColorPickerUiInfo* m_backgroundColor; - CheckBoxUiInfo* m_showBoundingBox; - ColorPickerUiInfo* m_boundingBoxColor; - CheckBoxUiInfo* m_showScaleBar; -}; diff --git a/renderlib/CMakeLists.txt b/renderlib/CMakeLists.txt index 4309efc7b..668adafba 100644 --- a/renderlib/CMakeLists.txt +++ b/renderlib/CMakeLists.txt @@ -16,8 +16,8 @@ target_include_directories(renderlib PUBLIC target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceDataObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceDataObject.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceUiDescription.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceUiDescription.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceObject.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/AppearanceObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/AppScene.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/AppScene.h" "${CMAKE_CURRENT_SOURCE_DIR}/AreaLightObject.cpp" @@ -34,6 +34,8 @@ target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/CCamera.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraDataObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraDataObject.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.h" "${CMAKE_CURRENT_SOURCE_DIR}/Colormap.cpp" From 362e5229641ac886e46e21a1be5d27d54a1b1dfa Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 4 Jul 2025 21:32:26 -0700 Subject: [PATCH 14/63] hook up render settings --- agave_app/agaveGui.cpp | 16 +++++++++++----- agave_app/agaveGui.h | 1 - renderlib/AppearanceObject.hpp | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 6cbd5fb2a..8c23200f0 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -388,14 +388,19 @@ agaveGui::setupCameraDock(CameraObject* cdo) void agaveGui::setupAppearanceDock(AppearanceObject* ado) { - m_appearanceDockWidget2 = new QAppearanceDockWidget2(this, &m_renderSettings, ado); + // DANGER see borrowRenderer call + m_appearanceDockWidget2 = + new QAppearanceDockWidget2(this, m_appearanceObject->getRenderSettings().get(), m_glView->borrowRenderer(), ado); m_appearanceDockWidget2->setAllowedAreas(Qt::AllDockWidgetAreas); addDockWidget(Qt::LeftDockWidgetArea, m_appearanceDockWidget2); // original appearance dock widget - m_appearanceDockWidget = new QAppearanceDockWidget( - this, &m_qrendersettings, &m_renderSettings, m_toggleRotateControlsAction, m_toggleTranslateControlsAction); + m_appearanceDockWidget = new QAppearanceDockWidget(this, + &m_qrendersettings, + m_appearanceObject->getRenderSettings().get(), + m_toggleRotateControlsAction, + m_toggleTranslateControlsAction); m_appearanceDockWidget->setAllowedAreas(Qt::AllDockWidgetAreas); addDockWidget(Qt::LeftDockWidgetArea, m_appearanceDockWidget); } @@ -1379,8 +1384,9 @@ agaveGui::appToViewerState() v.camera.exposure = cdo->getCameraDataObject().Exposure.GetValue(); v.camera.aperture = cdo->getCameraDataObject().ApertureSize.GetValue(); v.camera.focalDistance = cdo->getCameraDataObject().FocalDistance.GetValue(); - v.density = m_renderSettings.m_RenderSettings.m_DensityScale; - v.interpolate = m_renderSettings.m_RenderSettings.m_InterpolatedVolumeSampling; + auto rs = m_appearanceObject->getRenderSettings(); + v.density = rs->m_RenderSettings.m_DensityScale; + v.interpolate = rs->m_RenderSettings.m_InterpolatedVolumeSampling; v.density = appearanceData.DensityScale.GetValue(); v.interpolate = appearanceData.Interpolate.GetValue(); diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index 14b5391bf..57a9350bb 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -193,7 +193,6 @@ private slots: Scene m_appScene; int m_currentScene = 0; std::unique_ptr m_cameraObject; - std::unique_ptr m_appearanceObject; std::string m_currentFilePath; // TODO remove the above m_currentFilePath and use this instead diff --git a/renderlib/AppearanceObject.hpp b/renderlib/AppearanceObject.hpp index 831cb25a8..49e1321cf 100644 --- a/renderlib/AppearanceObject.hpp +++ b/renderlib/AppearanceObject.hpp @@ -28,6 +28,26 @@ class AppearanceObject : public prtyObject void updatePropsFromObject(); void updateObjectFromProps(); + // Getter for appearance data object + // AppearanceDataObject& getAppearanceDataObject() { return m_appearanceDataObject; } + const AppearanceDataObject& getAppearanceDataObject() const { return m_appearanceDataObject; } + + // Getters for UI info objects + ComboBoxUiInfo* getRendererTypeUiInfo() { return m_rendererType; } + ComboBoxUiInfo* getShadingTypeUiInfo() { return m_shadingType; } + FloatSliderSpinnerUiInfo* getDensityScaleUiInfo() { return m_densityScale; } + FloatSliderSpinnerUiInfo* getGradientFactorUiInfo() { return m_gradientFactor; } + FloatSliderSpinnerUiInfo* getStepSizePrimaryRayUiInfo() { return m_stepSizePrimaryRay; } + FloatSliderSpinnerUiInfo* getStepSizeSecondaryRayUiInfo() { return m_stepSizeSecondaryRay; } + CheckBoxUiInfo* getInterpolateUiInfo() { return m_interpolate; } + ColorPickerUiInfo* getBackgroundColorUiInfo() { return m_backgroundColor; } + CheckBoxUiInfo* getShowBoundingBoxUiInfo() { return m_showBoundingBox; } + ColorPickerUiInfo* getBoundingBoxColorUiInfo() { return m_boundingBoxColor; } + CheckBoxUiInfo* getShowScaleBarUiInfo() { return m_showScaleBar; } + + // Getter for the rendersettings + std::shared_ptr getRenderSettings() const { return m_renderSettings; } + private: // the properties AppearanceDataObject m_appearanceDataObject; From 4549c2c007d75ddddede0d78d87f056346df0168 Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Wed, 3 Sep 2025 17:20:20 -0700 Subject: [PATCH 15/63] reinstate tensorstore version --- renderlib/io/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/renderlib/io/CMakeLists.txt b/renderlib/io/CMakeLists.txt index ef2e6d152..e9706fe9c 100644 --- a/renderlib/io/CMakeLists.txt +++ b/renderlib/io/CMakeLists.txt @@ -36,8 +36,10 @@ if(APPLE) endif(APPLE) FetchContent_Declare( tensorstore - URL "https://github.com/google/tensorstore/archive/refs/tags/v0.1.75.tar.gz" - URL_HASH SHA256=82f8a170bd6635147315036dcad4e5f32c4d5374a3faa7cdd4e958a15b9cba2e + # URL "https://github.com/google/tensorstore/archive/refs/tags/v0.1.75.tar.gz" + # URL_HASH SHA256=82f8a170bd6635147315036dcad4e5f32c4d5374a3faa7cdd4e958a15b9cba2e + URL "https://github.com/google/tensorstore/tarball/9e1feb7215ee876d3b62b8d84c03c9c2a1eec0c2" + URL_HASH SHA256=baeb9e1eafe1d8244d37c614711a8125467404801274e35979ba2896c8abdf29 ) FetchContent_MakeAvailable(tensorstore) From 006b1b51f768b90269eb1344c3bd9d0176370cb5 Mon Sep 17 00:00:00 2001 From: DMT Date: Fri, 6 Jun 2025 07:33:59 -0700 Subject: [PATCH 16/63] install the scene earlier --- agave_app/GLView3D.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 5f95fb1d5..6eab94fe4 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -44,7 +44,9 @@ GLView3D::GLView3D(QRenderSettings* qrs, RenderSettings* rs, Scene* scene, QWidg m_viewerWindow = new ViewerWindow(rs); m_viewerWindow->gesture.input.setDoubleClickTime((double)QApplication::doubleClickInterval() / 1000.0); - m_appearanceDataObject = new AppearanceObject(); + // camera is created deep down inside m_viewerWindow. + m_cameraDataObject = new CameraDataObject(&m_viewerWindow->m_CCamera); + m_appearanceDataObject = new AppearanceDataObject(rs, scene); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); @@ -139,7 +141,7 @@ GLView3D::onNewImage(Scene* scene) // would be better to preserve renderer and just change the scene data to include the new image. // how tightly coupled is renderer and scene???? - m_appearanceDataObject->updatePropsFromObject(); + m_appearanceDataObject->updatePropsFromRenderSettings(); } GLView3D::~GLView3D() From 0f01a849402aa249eb1bf4e7ce6f495eede9c5bb Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Wed, 25 Jun 2025 14:51:41 -0700 Subject: [PATCH 17/63] camera and appearance compiles but completely detached from the renderer --- agave_app/GLView3D.cpp | 2 +- agave_app/qtControls/controlFactory.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 6eab94fe4..47b5b10a3 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -141,7 +141,7 @@ GLView3D::onNewImage(Scene* scene) // would be better to preserve renderer and just change the scene data to include the new image. // how tightly coupled is renderer and scene???? - m_appearanceDataObject->updatePropsFromRenderSettings(); + m_appearanceDataObject->updatePropsFromObject(); } GLView3D::~GLView3D() diff --git a/agave_app/qtControls/controlFactory.h b/agave_app/qtControls/controlFactory.h index d58d83a64..738c3e99d 100644 --- a/agave_app/qtControls/controlFactory.h +++ b/agave_app/qtControls/controlFactory.h @@ -49,4 +49,4 @@ void createFlatList(QFormLayout* mainLayout, prtyObject* object); void -createCategorizedSections(QFormLayout* mainLayout, prtyObject* object); \ No newline at end of file +createCategorizedSections(QFormLayout* mainLayout, prtyObject* object); From ecf0ce463ede3afc1d7c55ce518d09c03252919a Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 16 Aug 2025 09:05:27 -0700 Subject: [PATCH 18/63] move lights to the right panel --- agave_app/agaveGui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 8c23200f0..51d517915 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -129,6 +129,8 @@ agaveGui::agaveGui(QWidget* parent) // create camera ui window now that there is an actual camera. setupCameraDock(m_cameraObject.get()); + setupAreaLightDock(m_areaLightObject.get()); + setupSkyLightDock(m_skyLightObject.get()); setupTimelineDock(); setupStatisticsDock(); setupAppearanceDock(m_glView->getAppearanceDataObject()); From 07e5a27c8e3470cf0a4ace75300e1c92a7b949fe Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 5 Sep 2025 12:22:31 -0700 Subject: [PATCH 19/63] fix compile errors post-rebase --- agave_app/GLView3D.cpp | 6 ------ agave_app/qtControls/controlFactory.cpp | 1 - 2 files changed, 7 deletions(-) diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 47b5b10a3..8cf085baa 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -44,10 +44,6 @@ GLView3D::GLView3D(QRenderSettings* qrs, RenderSettings* rs, Scene* scene, QWidg m_viewerWindow = new ViewerWindow(rs); m_viewerWindow->gesture.input.setDoubleClickTime((double)QApplication::doubleClickInterval() / 1000.0); - // camera is created deep down inside m_viewerWindow. - m_cameraDataObject = new CameraDataObject(&m_viewerWindow->m_CCamera); - m_appearanceDataObject = new AppearanceDataObject(rs, scene); - setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); @@ -140,8 +136,6 @@ GLView3D::onNewImage(Scene* scene) this->OnUpdateRenderer(m_viewerWindow->m_renderSettings->m_rendererType); // would be better to preserve renderer and just change the scene data to include the new image. // how tightly coupled is renderer and scene???? - - m_appearanceDataObject->updatePropsFromObject(); } GLView3D::~GLView3D() diff --git a/agave_app/qtControls/controlFactory.cpp b/agave_app/qtControls/controlFactory.cpp index e4427edc1..677b34d5d 100644 --- a/agave_app/qtControls/controlFactory.cpp +++ b/agave_app/qtControls/controlFactory.cpp @@ -241,7 +241,6 @@ addRow(const ComboBoxUiInfo& info) for (const auto& item : info.items) { comboBox->addItem(QString::fromStdString(item)); } - auto* prop = static_cast(info.GetProperty(0)); comboBox->setCurrentIndex(prop->GetValue()); auto conn = QObject::connect( comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->SetValue(index, true); }); From 60d72a8321507eb66c7a0f7fbb287b5a6cc8f468 Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 5 Sep 2025 12:40:23 -0700 Subject: [PATCH 20/63] try to generate a universal build --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b6755316a..56a8742fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,7 @@ include(FetchContent) # Needed to recognize FetchContent_Declare in renderlib if(APPLE) set(ENV{MACOSX_DEPLOYMENT_TARGET} "10.15") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE) - # Tensorstore does not support universal builds yet: - # set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") + set(CMAKE_OSX_ARCHITECTURES "arm64;x64_64") endif(APPLE) if(POLICY CMP0048) From ca6f1bf1da24efb77c6dfc693433b69ce7ca4cac Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 5 Sep 2025 13:00:08 -0700 Subject: [PATCH 21/63] ok forget it --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56a8742fa..b6755316a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,8 @@ include(FetchContent) # Needed to recognize FetchContent_Declare in renderlib if(APPLE) set(ENV{MACOSX_DEPLOYMENT_TARGET} "10.15") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE) - set(CMAKE_OSX_ARCHITECTURES "arm64;x64_64") + # Tensorstore does not support universal builds yet: + # set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") endif(APPLE) if(POLICY CMP0048) From 3b77584153e8ba5a1792fd1b5fca87cf4ea54623 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Sep 2025 21:14:15 -0700 Subject: [PATCH 22/63] hook up uiInfo for a color plus intensity control --- agave_app/AppearanceSettingsWidget.cpp | 76 +++++++++++++++++++++++-- agave_app/qtControls/Controls.cpp | 49 ++++++++++++++++ agave_app/qtControls/Controls.h | 26 +++++++++ agave_app/qtControls/controlFactory.cpp | 9 ++- renderlib/uiInfo.hpp | 31 +++++++++- 5 files changed, 182 insertions(+), 9 deletions(-) diff --git a/agave_app/AppearanceSettingsWidget.cpp b/agave_app/AppearanceSettingsWidget.cpp index 66f72f03f..37546aef2 100644 --- a/agave_app/AppearanceSettingsWidget.cpp +++ b/agave_app/AppearanceSettingsWidget.cpp @@ -356,9 +356,54 @@ QAppearanceSettingsWidget::createAreaLightingControls(QAction* pRotationAction) btnLayout->addWidget(new QWidget()); sectionLayout->addLayout(btnLayout, sectionLayout->rowCount(), 0, 1, 2); - if (m_arealightObject) { - createFlatList(sectionLayout, m_arealightObject); - } + m_lt0gui.m_thetaSlider = new QNumericSlider(); + m_lt0gui.m_thetaSlider->setStatusTip(tr("Set angle theta for area light")); + m_lt0gui.m_thetaSlider->setToolTip(tr("Set angle theta for area light")); + m_lt0gui.m_thetaSlider->setRange(0.0, TWO_PI_F); + m_lt0gui.m_thetaSlider->setSingleStep(TWO_PI_F / 100.0); + m_lt0gui.m_thetaSlider->setValue(0.0); + sectionLayout->addRow("Theta", m_lt0gui.m_thetaSlider); + QObject::connect( + m_lt0gui.m_thetaSlider, &QNumericSlider::valueChanged, this, &QAppearanceSettingsWidget::OnSetAreaLightTheta); + + m_lt0gui.m_phiSlider = new QNumericSlider(); + m_lt0gui.m_phiSlider->setStatusTip(tr("Set angle phi for area light")); + m_lt0gui.m_phiSlider->setToolTip(tr("Set angle phi for area light")); + m_lt0gui.m_phiSlider->setRange(0.0, PI_F); + m_lt0gui.m_phiSlider->setSingleStep(PI_F / 100.0); + m_lt0gui.m_phiSlider->setValue(HALF_PI_F); + sectionLayout->addRow("Phi", m_lt0gui.m_phiSlider); + QObject::connect( + m_lt0gui.m_phiSlider, &QNumericSlider::valueChanged, this, &QAppearanceSettingsWidget::OnSetAreaLightPhi); + + m_lt0gui.m_sizeSlider = new QNumericSlider(); + m_lt0gui.m_sizeSlider->setStatusTip(tr("Set size for area light")); + m_lt0gui.m_sizeSlider->setToolTip(tr("Set size for area light")); + m_lt0gui.m_sizeSlider->setRange(0.1, 5.0); + m_lt0gui.m_sizeSlider->setSingleStep(5.0 / 100.0); + m_lt0gui.m_sizeSlider->setValue(1.0); + sectionLayout->addRow("Size", m_lt0gui.m_sizeSlider); + QObject::connect( + m_lt0gui.m_sizeSlider, &QNumericSlider::valueChanged, this, &QAppearanceSettingsWidget::OnSetAreaLightSize); + + m_lt0gui.m_distSlider = new QNumericSlider(); + m_lt0gui.m_distSlider->setStatusTip(tr("Set distance for area light")); + m_lt0gui.m_distSlider->setToolTip(tr("Set distance for area light")); + m_lt0gui.m_distSlider->setRange(0.1, 10.0); + m_lt0gui.m_distSlider->setSingleStep(1.0); + m_lt0gui.m_distSlider->setValue(10.0); + sectionLayout->addRow("Distance", m_lt0gui.m_distSlider); + QObject::connect( + m_lt0gui.m_distSlider, &QNumericSlider::valueChanged, this, &QAppearanceSettingsWidget::OnSetAreaLightDistance); + + m_lt0gui.m_areaLightColor = new QColorWithIntensity(QColor(255, 255, 255), 100.0, this); + sectionLayout->addRow("Intensity", m_lt0gui.m_areaLightColor); + QObject::connect(m_lt0gui.m_areaLightColor, &QColorWithIntensity::colorChanged, [this](const QColor& c) { + this->OnSetAreaLightColor(m_lt0gui.m_areaLightColor->getIntensity(), c); + }); + QObject::connect(m_lt0gui.m_areaLightColor, &QColorWithIntensity::intensityChanged, [this](double v) { + this->OnSetAreaLightColor(v, m_lt0gui.m_areaLightColor->getColor()); + }); section->setContentLayout(*sectionLayout); return section; @@ -767,14 +812,35 @@ QAppearanceSettingsWidget::initClipPlaneControls(Scene* scene) void QAppearanceSettingsWidget::initLightingControls(Scene* scene) { - m_arealightObject->updatePropsFromSceneLight(); + m_lt0gui.m_thetaSlider->setValue(scene->AreaLight().m_Theta); + m_lt0gui.m_phiSlider->setValue(scene->AreaLight().m_Phi); + m_lt0gui.m_sizeSlider->setValue(scene->AreaLight().m_Width); + m_lt0gui.m_distSlider->setValue(scene->AreaLight().m_Distance); + // split color into color and intensity. + QColor c; + float i; + normalizeColorForGui(scene->AreaLight().m_Color, c, i); + m_lt0gui.m_areaLightColor->setIntensity(i * scene->AreaLight().m_ColorIntensity); + m_lt0gui.m_areaLightColor->setColor(c); // attach light observer to scene's area light source, to receive updates from viewport controls // TODO FIXME clean this up - it's not removed anywhere so if light(i.e. scene) outlives "this" then we have problems. // Currently in AGAVE this is not an issue.. m_arealightObject->getSceneLight()->m_observers.push_back([this](const Light& light) { // update gui controls - m_arealightObject->updatePropsFromSceneLight(); + + // bring theta into 0..2pi + m_lt0gui.m_thetaSlider->setValue(light.m_Theta < 0 ? light.m_Theta + TWO_PI_F : light.m_Theta); + // bring phi into 0..pi + m_lt0gui.m_phiSlider->setValue(light.m_Phi < 0 ? light.m_Phi + PI_F : light.m_Phi); + m_lt0gui.m_sizeSlider->setValue(light.m_Width); + m_lt0gui.m_distSlider->setValue(light.m_Distance); + // split color into color and intensity. + QColor c; + float i; + normalizeColorForGui(light.m_Color, c, i); + m_lt0gui.m_areaLightColor->setIntensity(i * light.m_ColorIntensity); + m_lt0gui.m_areaLightColor->setColor(c); }); m_skylightObject->updatePropsFromSceneLight(); diff --git a/agave_app/qtControls/Controls.cpp b/agave_app/qtControls/Controls.cpp index 700003dee..6c272e291 100644 --- a/agave_app/qtControls/Controls.cpp +++ b/agave_app/qtControls/Controls.cpp @@ -586,3 +586,52 @@ Controls::createAgaveFormLayout(QWidget* parent) layout->setColumnStretch(1, 100); return layout; } + +QColorWithIntensity::QColorWithIntensity(QColor color, float intensity, QWidget* parent) + : QWidget(parent) +{ + auto* layout = new QHBoxLayout(); + m_intensitySlider = new QNumericSlider(); + m_intensitySlider->setStatusTip(tr("Set intensity")); + m_intensitySlider->setToolTip(tr("Set intensity")); + m_intensitySlider->setRange(0.0, 1000.0); + m_intensitySlider->setSingleStep(10.0); + m_intensitySlider->setValue(intensity); + m_intensitySlider->setDecimals(1); + layout->addWidget(m_intensitySlider, 1); + m_colorButton = new QColorPushButton(); + m_colorButton->setStatusTip(tr("Set color")); + m_colorButton->setToolTip(tr("Set color")); + m_colorButton->SetColor(color); + layout->addWidget(m_colorButton, 0); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + // connect intensity slider to emit intensityChanged + connect(m_intensitySlider, &QNumericSlider::valueChanged, this, &QColorWithIntensity::intensityChanged); + connect(m_colorButton, &QColorPushButton::currentColorChanged, this, &QColorWithIntensity::colorChanged); +} + +void +QColorWithIntensity::setIntensity(float intensity) +{ + m_intensitySlider->setValue(intensity); +} + +void +QColorWithIntensity::setColor(const QColor& color) +{ + m_colorButton->SetColor(color); +} + +QColor +QColorWithIntensity::getColor() +{ + return m_colorButton->GetColor(); +} + +float +QColorWithIntensity::getIntensity() +{ + return m_intensitySlider->value(); +} diff --git a/agave_app/qtControls/Controls.h b/agave_app/qtControls/Controls.h index 4bce3125e..aa280bc44 100644 --- a/agave_app/qtControls/Controls.h +++ b/agave_app/qtControls/Controls.h @@ -245,6 +245,32 @@ private slots: QSlider m_slider; }; +class QColorWithIntensity : public QWidget +{ + Q_OBJECT +public: + QColorWithIntensity(QColor color, float intensity = 1.0f, QWidget* parent = nullptr); + + void setIntensity(float intensity); + void setColor(const QColor& color); + QColor getColor(); + float getIntensity(); + + void setRange(double min, double max) { m_intensitySlider->setRange(min, max); } + void setDecimals(int decimals) { m_intensitySlider->setDecimals(decimals); } + void setSingleStep(double step) { m_intensitySlider->setSingleStep(step); } + void setNumTickMarks(int num) { m_intensitySlider->setNumTickMarks(num); } + void setSuffix(const QString& suffix) { m_intensitySlider->setSuffix(suffix); } + +signals: + void intensityChanged(float intensity); + void colorChanged(const QColor& color); + +private: + QNumericSlider* m_intensitySlider; + QColorPushButton* m_colorButton; +}; + class AgaveFormLayout : public QGridLayout { Q_OBJECT diff --git a/agave_app/qtControls/controlFactory.cpp b/agave_app/qtControls/controlFactory.cpp index 677b34d5d..0b94f403c 100644 --- a/agave_app/qtControls/controlFactory.cpp +++ b/agave_app/qtControls/controlFactory.cpp @@ -127,8 +127,7 @@ addRow(const ColorWithIntensityUiInfo& info) { auto* propColor = static_cast(info.GetProperty(0)); glm::vec4 c = propColor->GetValue(); - QColor qc; - qc.setRgbF(c.r, c.g, c.b); + QColor qc(c.r, c.g, c.b); QColorWithIntensity* colorPicker = new QColorWithIntensity(qc); colorPicker->setStatusTip(QString::fromStdString(info.GetStatusTip())); @@ -194,7 +193,9 @@ addRow(const ColorWithIntensityUiInfo& info) if (c != newvalue) { // Prevent recursive updates // slider->setLocalChangeNoUpdate(true); - qc.setRgbF(c.r, c.g, c.b); + qc.setRedF(c.r); + qc.setGreenF(c.g); + qc.setBlueF(c.b); colorPicker->setColor(qc); // slider->setLocalChangeNoUpdate(false); } @@ -308,6 +309,8 @@ addGenericRow(const prtyPropertyUIInfo& info) return addRow(*checkBoxInfo); } else if (const auto* colorPickerInfo = dynamic_cast(&info)) { return addRow(*colorPickerInfo); + } else if (const auto* colorWithIntensityInfo = dynamic_cast(&info)) { + return addRow(*colorWithIntensityInfo); } return nullptr; // or throw an exception } diff --git a/renderlib/uiInfo.hpp b/renderlib/uiInfo.hpp index 21acacd59..5a3ac8c72 100644 --- a/renderlib/uiInfo.hpp +++ b/renderlib/uiInfo.hpp @@ -41,7 +41,36 @@ struct ComboBoxUiInfo : public GenericUIInfo { } }; -struct FloatSliderSpinnerUiInfo : public GenericUIInfo + +class ColorWithIntensityUiInfo : public prtyPropertyUIInfo +{ +public: + static constexpr const char* TYPE = "ColorWithIntensity"; + float min = 0.0f; + float max = 0.0f; + int decimals = 0; + float singleStep = 0.0f; + int numTickMarks = 0; + std::string suffix; + + ColorWithIntensityUiInfo(prtyProperty* i_pColorProperty, prtyProperty* i_pIntensityProperty) + : prtyPropertyUIInfo(i_pColorProperty) + { + AddProperty(i_pIntensityProperty); + SetControlName(TYPE); + } + ColorWithIntensityUiInfo(prtyProperty* i_pColorProperty, + prtyProperty* i_pIntensityProperty, + const std::string& i_Category, + const std::string& i_Description) + : prtyPropertyUIInfo(i_pColorProperty, i_Category, i_Description) + { + AddProperty(i_pIntensityProperty); + SetControlName(TYPE); + } +}; + +class FloatSliderSpinnerUiInfo : public prtyPropertyUIInfo { static constexpr const char* TYPE = "FloatSliderSpinner"; float min = 0.0f; From 72f029f65e209e6f9bda9869d46c936f5a1ad938 Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Fri, 7 Nov 2025 19:34:07 -0800 Subject: [PATCH 23/63] remove duplicated controls --- agave_app/AppearanceSettingsWidget.cpp | 26 ++++++++++++++++++++------ agave_app/agaveGui.cpp | 4 ++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/agave_app/AppearanceSettingsWidget.cpp b/agave_app/AppearanceSettingsWidget.cpp index 37546aef2..84f208cbc 100644 --- a/agave_app/AppearanceSettingsWidget.cpp +++ b/agave_app/AppearanceSettingsWidget.cpp @@ -356,6 +356,11 @@ QAppearanceSettingsWidget::createAreaLightingControls(QAction* pRotationAction) btnLayout->addWidget(new QWidget()); sectionLayout->addLayout(btnLayout, sectionLayout->rowCount(), 0, 1, 2); + if (m_arealightObject) { + createFlatList(sectionLayout, m_arealightObject); + } + +#if 0 m_lt0gui.m_thetaSlider = new QNumericSlider(); m_lt0gui.m_thetaSlider->setStatusTip(tr("Set angle theta for area light")); m_lt0gui.m_thetaSlider->setToolTip(tr("Set angle theta for area light")); @@ -404,7 +409,7 @@ QAppearanceSettingsWidget::createAreaLightingControls(QAction* pRotationAction) QObject::connect(m_lt0gui.m_areaLightColor, &QColorWithIntensity::intensityChanged, [this](double v) { this->OnSetAreaLightColor(v, m_lt0gui.m_areaLightColor->getColor()); }); - +#endif section->setContentLayout(*sectionLayout); return section; } @@ -812,13 +817,14 @@ QAppearanceSettingsWidget::initClipPlaneControls(Scene* scene) void QAppearanceSettingsWidget::initLightingControls(Scene* scene) { + // split color into color and intensity. + QColor c; + float i; +#if 0 m_lt0gui.m_thetaSlider->setValue(scene->AreaLight().m_Theta); m_lt0gui.m_phiSlider->setValue(scene->AreaLight().m_Phi); m_lt0gui.m_sizeSlider->setValue(scene->AreaLight().m_Width); m_lt0gui.m_distSlider->setValue(scene->AreaLight().m_Distance); - // split color into color and intensity. - QColor c; - float i; normalizeColorForGui(scene->AreaLight().m_Color, c, i); m_lt0gui.m_areaLightColor->setIntensity(i * scene->AreaLight().m_ColorIntensity); m_lt0gui.m_areaLightColor->setColor(c); @@ -842,8 +848,16 @@ QAppearanceSettingsWidget::initLightingControls(Scene* scene) m_lt0gui.m_areaLightColor->setIntensity(i * light.m_ColorIntensity); m_lt0gui.m_areaLightColor->setColor(c); }); - - m_skylightObject->updatePropsFromSceneLight(); +#endif + normalizeColorForGui(scene->SphereLight().m_ColorTop, c, i); + m_lt1gui.m_stintensitySlider->setValue(i * scene->SphereLight().m_ColorTopIntensity); + m_lt1gui.m_stColorButton->SetColor(c); + normalizeColorForGui(scene->SphereLight().m_ColorMiddle, c, i); + m_lt1gui.m_smintensitySlider->setValue(i * scene->SphereLight().m_ColorMiddleIntensity); + m_lt1gui.m_smColorButton->SetColor(c); + normalizeColorForGui(scene->SphereLight().m_ColorBottom, c, i); + m_lt1gui.m_sbintensitySlider->setValue(i * scene->SphereLight().m_ColorBottomIntensity); + m_lt1gui.m_sbColorButton->SetColor(c); } void diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 51d517915..70393c44a 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -129,8 +129,8 @@ agaveGui::agaveGui(QWidget* parent) // create camera ui window now that there is an actual camera. setupCameraDock(m_cameraObject.get()); - setupAreaLightDock(m_areaLightObject.get()); - setupSkyLightDock(m_skyLightObject.get()); + // setupAreaLightDock(m_areaLightObject.get()); + // setupSkyLightDock(m_skyLightObject.get()); setupTimelineDock(); setupStatisticsDock(); setupAppearanceDock(m_glView->getAppearanceDataObject()); From 8aad0d0efa2a7392a0f16910ec81711b4fec1dcf Mon Sep 17 00:00:00 2001 From: dmt Date: Thu, 27 Nov 2025 07:21:19 -0800 Subject: [PATCH 24/63] add some new serialize code --- renderlib/CMakeLists.txt | 1 + renderlib/serialize/CMakeLists.txt | 13 + renderlib/serialize/docWriter.h | 37 +++ renderlib/serialize/docWriterJson.cpp | 324 +++++++++++++++++++ renderlib/serialize/docWriterJson.h | 77 +++++ renderlib/serialize/docWriterYaml.cpp | 445 ++++++++++++++++++++++++++ renderlib/serialize/docWriterYaml.h | 76 +++++ test/CMakeLists.txt | 1 + 8 files changed, 974 insertions(+) create mode 100644 renderlib/serialize/CMakeLists.txt create mode 100644 renderlib/serialize/docWriter.h create mode 100644 renderlib/serialize/docWriterJson.cpp create mode 100644 renderlib/serialize/docWriterJson.h create mode 100644 renderlib/serialize/docWriterYaml.cpp create mode 100644 renderlib/serialize/docWriterYaml.h diff --git a/renderlib/CMakeLists.txt b/renderlib/CMakeLists.txt index 668adafba..bc770b2f7 100644 --- a/renderlib/CMakeLists.txt +++ b/renderlib/CMakeLists.txt @@ -106,6 +106,7 @@ add_subdirectory(graphics) add_subdirectory(io) add_subdirectory(pugixml) add_subdirectory(core) +add_subdirectory(serialize) target_link_libraries(renderlib Qt::Core Qt::OpenGL diff --git a/renderlib/serialize/CMakeLists.txt b/renderlib/serialize/CMakeLists.txt new file mode 100644 index 000000000..68b4e85eb --- /dev/null +++ b/renderlib/serialize/CMakeLists.txt @@ -0,0 +1,13 @@ +target_include_directories(renderlib PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" +) + +target_sources(renderlib PRIVATE +"${CMAKE_CURRENT_SOURCE_DIR}/docWriter.h" +"${CMAKE_CURRENT_SOURCE_DIR}/docWriterJson.h" +"${CMAKE_CURRENT_SOURCE_DIR}/docWriterJson.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/docWriterYaml.h" +"${CMAKE_CURRENT_SOURCE_DIR}/docWriterYaml.cpp" +) + + diff --git a/renderlib/serialize/docWriter.h b/renderlib/serialize/docWriter.h new file mode 100644 index 000000000..9486a861d --- /dev/null +++ b/renderlib/serialize/docWriter.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +class prtyProperty; + +class docWriter +{ +public: + docWriter() {} + virtual ~docWriter() {} + + // this must be called to start and end the document before any other writing can happen. + virtual void beginDocument(std::string filePath) = 0; + virtual void endDocument() = 0; + + // objects can contain other objects, lists, and properties. + virtual void beginObject(const char* i_name) = 0; + virtual void endObject() = 0; + + // lists can contain objects or properties. + virtual void beginList(const char* i_name) = 0; + virtual void endList() = 0; + + // properties will write their name and associated value using the primitive write methods. + virtual void writePrty(const prtyProperty* p) = 0; + + virtual size_t writeInt32(int32_t) = 0; + virtual size_t writeUint32(uint32_t) = 0; + virtual size_t writeFloat32(float) = 0; + virtual size_t writeFloat32Array(const std::vector&) = 0; + virtual size_t writeInt32Array(const std::vector&) = 0; + virtual size_t writeUint32Array(const std::vector&) = 0; + virtual size_t writeString(const std::string&) = 0; +}; diff --git a/renderlib/serialize/docWriterJson.cpp b/renderlib/serialize/docWriterJson.cpp new file mode 100644 index 000000000..a65f29c24 --- /dev/null +++ b/renderlib/serialize/docWriterJson.cpp @@ -0,0 +1,324 @@ +#include "docWriterJson.h" + +#include "core/prty/prtyProperty.hpp" +#include "Logging.h" + +#include "json/json.hpp" + +#include + +docWriterJson::docWriterJson() + : m_root(nullptr) + , m_current(nullptr) +{ +} + +docWriterJson::~docWriterJson() +{ + if (m_root) { + delete m_root; + m_root = nullptr; + } +} + +void +docWriterJson::beginDocument(std::string filePath) +{ + m_filePath = filePath; + if (m_root) { + delete m_root; + } + m_root = new nlohmann::json(nlohmann::json::object()); + m_current = m_root; + + // Clear the context stack + while (!m_contextStack.empty()) { + m_contextStack.pop(); + } +} + +void +docWriterJson::endDocument() +{ + if (!m_root) { + return; + } + + // Validate that all contexts are closed + if (!m_contextStack.empty()) { + logError("endDocument() called with " + std::to_string(m_contextStack.size()) + + " unclosed context(s). Document may be incomplete."); + } + + // Write the JSON to file + std::ofstream outFile(m_filePath); + if (outFile.is_open()) { + outFile << m_root->dump(2); // Pretty print with 2-space indentation + outFile.close(); + } +} + +void +docWriterJson::beginObject(const char* i_name) +{ + nlohmann::json* newObj = new nlohmann::json(nlohmann::json::object()); + + if (m_contextStack.empty()) { + // Root level object + (*m_current)[i_name] = *newObj; + pushContext(&(*m_current)[i_name], i_name, ContextType::Object); + } else { + const Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + // Adding object to an array + ctx.jsonObj->push_back(*newObj); + pushContext(&ctx.jsonObj->back(), i_name, ContextType::Object); + } else { + // Adding object to an object + (*ctx.jsonObj)[i_name] = *newObj; + pushContext(&(*ctx.jsonObj)[i_name], i_name, ContextType::Object); + } + } + + delete newObj; +} + +void +docWriterJson::endObject() +{ + if (m_contextStack.empty()) { + logError("endObject() called with no matching beginObject()"); + return; + } + + if (!popContext(ContextType::Object)) { + logError("endObject() called but current context is not an object"); + } +} + +void +docWriterJson::beginList(const char* i_name) +{ + nlohmann::json* newArray = new nlohmann::json(nlohmann::json::array()); + + if (m_contextStack.empty()) { + // Root level array + (*m_current)[i_name] = *newArray; + pushContext(&(*m_current)[i_name], i_name, ContextType::Array); + } else { + const Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + // Adding array to an array + ctx.jsonObj->push_back(*newArray); + pushContext(&ctx.jsonObj->back(), i_name, ContextType::Array); + } else { + // Adding array to an object + (*ctx.jsonObj)[i_name] = *newArray; + pushContext(&(*ctx.jsonObj)[i_name], i_name, ContextType::Array); + } + } + + delete newArray; +} + +void +docWriterJson::endList() +{ + if (m_contextStack.empty()) { + logError("endList() called with no matching beginList()"); + return; + } + + if (!popContext(ContextType::Array)) { + logError("endList() called but current context is not an array"); + } +} + +void +docWriterJson::writePrty(const prtyProperty* p) +{ + if (!p) { + return; + } + + // Store the property name for the next write operation + m_nextKey = p->GetPropertyName(); + + // Note: The actual value writing will be done by the specific write methods + // (writeInt32, writeFloat32, etc.) which will be called after this method +} + +size_t +docWriterJson::writeInt32(int32_t value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = value; + } else { + current->push_back(value); + } + + return sizeof(int32_t); +} + +size_t +docWriterJson::writeUint32(uint32_t value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = value; + } else { + current->push_back(value); + } + + return sizeof(uint32_t); +} + +size_t +docWriterJson::writeFloat32(float value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = value; + } else { + current->push_back(value); + } + + return sizeof(float); +} + +size_t +docWriterJson::writeFloat32Array(const std::vector& value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + nlohmann::json arr = nlohmann::json::array(); + for (float v : value) { + arr.push_back(v); + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = arr; + } else { + current->push_back(arr); + } + + return value.size() * sizeof(float); +} + +size_t +docWriterJson::writeInt32Array(const std::vector& value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + nlohmann::json arr = nlohmann::json::array(); + for (int32_t v : value) { + arr.push_back(v); + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = arr; + } else { + current->push_back(arr); + } + + return value.size() * sizeof(int32_t); +} + +size_t +docWriterJson::writeUint32Array(const std::vector& value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + nlohmann::json arr = nlohmann::json::array(); + for (uint32_t v : value) { + arr.push_back(v); + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = arr; + } else { + current->push_back(arr); + } + + return value.size() * sizeof(uint32_t); +} + +size_t +docWriterJson::writeString(const std::string& value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = value; + } else { + current->push_back(value); + } + + return value.size(); +} + +void +docWriterJson::pushContext(nlohmann::json* obj, const std::string& name, ContextType type) +{ + m_contextStack.push(Context(obj, name, type)); +} + +bool +docWriterJson::popContext(ContextType expectedType) +{ + if (m_contextStack.empty()) { + return false; + } + + const Context& ctx = m_contextStack.top(); + bool isCorrectType = (ctx.type == expectedType); + + if (!isCorrectType) { + logError("Mismatched begin/end calls: expected " + + std::string(expectedType == ContextType::Object ? "Object" : "Array") + " but found " + + std::string(ctx.type == ContextType::Object ? "Object" : "Array") + " for context '" + ctx.name + "'"); + } + + m_contextStack.pop(); + return isCorrectType; +} + +void +docWriterJson::logError(const std::string& message) +{ + LOG_ERROR << "docWriterJson: " << message; +} + +nlohmann::json* +docWriterJson::getCurrentObject() +{ + if (m_contextStack.empty()) { + return m_current; + } + return m_contextStack.top().jsonObj; +} diff --git a/renderlib/serialize/docWriterJson.h b/renderlib/serialize/docWriterJson.h new file mode 100644 index 000000000..0ed5be797 --- /dev/null +++ b/renderlib/serialize/docWriterJson.h @@ -0,0 +1,77 @@ +#pragma once + +#include "docWriter.h" + +#include +#include +#include + +namespace nlohmann { +class json; +} + +class docWriterJson : public docWriter +{ +public: + docWriterJson(); + virtual ~docWriterJson(); + + // Document lifecycle + virtual void beginDocument(std::string filePath) override; + virtual void endDocument() override; + + // Object support + virtual void beginObject(const char* i_name) override; + virtual void endObject() override; + + // List/array support + virtual void beginList(const char* i_name) override; + virtual void endList() override; + + // Property writing + virtual void writePrty(const prtyProperty* p) override; + + // Primitive type writing + virtual size_t writeInt32(int32_t value) override; + virtual size_t writeUint32(uint32_t value) override; + virtual size_t writeFloat32(float value) override; + virtual size_t writeFloat32Array(const std::vector& value) override; + virtual size_t writeInt32Array(const std::vector& value) override; + virtual size_t writeUint32Array(const std::vector& value) override; + virtual size_t writeString(const std::string& value) override; + +private: + enum class ContextType + { + Object, + Array + }; + + struct Context + { + nlohmann::json* jsonObj; + std::string name; + ContextType type; + + Context(nlohmann::json* obj, const std::string& n, ContextType t) + : jsonObj(obj) + , name(n) + , type(t) + { + } + + bool isArray() const { return type == ContextType::Array; } + bool isObject() const { return type == ContextType::Object; } + }; + + std::string m_filePath; + nlohmann::json* m_root; + std::stack m_contextStack; + nlohmann::json* m_current; + std::string m_nextKey; + + void pushContext(nlohmann::json* obj, const std::string& name, ContextType type); + bool popContext(ContextType expectedType); + nlohmann::json* getCurrentObject(); + void logError(const std::string& message); +}; diff --git a/renderlib/serialize/docWriterYaml.cpp b/renderlib/serialize/docWriterYaml.cpp new file mode 100644 index 000000000..75cbbea9e --- /dev/null +++ b/renderlib/serialize/docWriterYaml.cpp @@ -0,0 +1,445 @@ +#include "docWriterYaml.h" + +#include "core/prty/prtyProperty.hpp" +#include "Logging.h" + +#include +#include + +docWriterYaml::docWriterYaml() + : m_indentLevel(0) +{ +} + +docWriterYaml::~docWriterYaml() {} + +void +docWriterYaml::beginDocument(std::string filePath) +{ + m_filePath = filePath; + m_output.str(""); + m_output.clear(); + m_indentLevel = 0; + + // Clear the context stack + while (!m_contextStack.empty()) { + m_contextStack.pop(); + } + + // Write YAML header + m_output << "---\n"; +} + +void +docWriterYaml::endDocument() +{ + // Validate that all contexts are closed + if (!m_contextStack.empty()) { + logError("endDocument() called with " + std::to_string(m_contextStack.size()) + + " unclosed context(s). Document may be incomplete."); + } + + // Write the YAML to file + std::ofstream outFile(m_filePath); + if (outFile.is_open()) { + outFile << m_output.str(); + outFile.close(); + } +} + +void +docWriterYaml::beginObject(const char* i_name) +{ + if (m_contextStack.empty()) { + // Root level object + writeKey(i_name); + m_output << "\n"; + m_indentLevel++; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + // Adding object to an array + writeIndent(); + m_output << "- "; + if (i_name && strlen(i_name) > 0) { + m_output << i_name << ":\n"; + } else { + m_output << "\n"; + } + m_indentLevel++; + ctx.firstItem = false; + } else { + // Adding object to an object + writeIndent(); + writeKey(i_name); + m_output << "\n"; + m_indentLevel++; + ctx.firstItem = false; + } + } + + pushContext(i_name, ContextType::Object); +} + +void +docWriterYaml::endObject() +{ + if (m_contextStack.empty()) { + logError("endObject() called with no matching beginObject()"); + return; + } + + if (!popContext(ContextType::Object)) { + logError("endObject() called but current context is not an object"); + } + + m_indentLevel--; +} + +void +docWriterYaml::beginList(const char* i_name) +{ + if (m_contextStack.empty()) { + // Root level array + writeKey(i_name); + m_output << "\n"; + m_indentLevel++; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + // Adding array to an array + writeIndent(); + m_output << "- "; + if (i_name && strlen(i_name) > 0) { + m_output << i_name << ":\n"; + } else { + m_output << "\n"; + } + m_indentLevel++; + ctx.firstItem = false; + } else { + // Adding array to an object + writeIndent(); + writeKey(i_name); + m_output << "\n"; + m_indentLevel++; + ctx.firstItem = false; + } + } + + pushContext(i_name, ContextType::Array); +} + +void +docWriterYaml::endList() +{ + if (m_contextStack.empty()) { + logError("endList() called with no matching beginList()"); + return; + } + + if (!popContext(ContextType::Array)) { + logError("endList() called but current context is not an array"); + } + + m_indentLevel--; +} + +void +docWriterYaml::writePrty(const prtyProperty* p) +{ + if (!p) { + return; + } + + // Store the property name for the next write operation + m_nextKey = p->GetPropertyName(); +} + +size_t +docWriterYaml::writeInt32(int32_t value) +{ + if (m_contextStack.empty()) { + writeKey(m_nextKey); + m_output << value << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << value << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(m_nextKey); + m_output << value << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(int32_t); +} + +size_t +docWriterYaml::writeUint32(uint32_t value) +{ + if (m_contextStack.empty()) { + writeKey(m_nextKey); + m_output << value << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << value << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(m_nextKey); + m_output << value << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(uint32_t); +} + +size_t +docWriterYaml::writeFloat32(float value) +{ + if (m_contextStack.empty()) { + writeKey(m_nextKey); + m_output << std::setprecision(6) << value << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << std::setprecision(6) << value << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(m_nextKey); + m_output << std::setprecision(6) << value << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(float); +} + +size_t +docWriterYaml::writeFloat32Array(const std::vector& value) +{ + if (m_contextStack.empty()) { + writeKey(m_nextKey); + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- "; + } else { + writeIndent(); + writeKey(m_nextKey); + ctx.firstItem = false; + } + } + + // Write as inline array [x, y, z, ...] + m_output << "["; + for (size_t i = 0; i < value.size(); ++i) { + if (i > 0) { + m_output << ", "; + } + m_output << std::setprecision(6) << value[i]; + } + m_output << "]\n"; + + return value.size() * sizeof(float); +} + +size_t +docWriterYaml::writeInt32Array(const std::vector& value) +{ + if (m_contextStack.empty()) { + writeKey(m_nextKey); + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- "; + } else { + writeIndent(); + writeKey(m_nextKey); + ctx.firstItem = false; + } + } + + // Write as inline array [x, y, z, ...] + m_output << "["; + for (size_t i = 0; i < value.size(); ++i) { + if (i > 0) { + m_output << ", "; + } + m_output << value[i]; + } + m_output << "]\n"; + + return value.size() * sizeof(int32_t); +} + +size_t +docWriterYaml::writeUint32Array(const std::vector& value) +{ + if (m_contextStack.empty()) { + writeKey(m_nextKey); + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- "; + } else { + writeIndent(); + writeKey(m_nextKey); + ctx.firstItem = false; + } + } + + // Write as inline array [x, y, z, ...] + m_output << "["; + for (size_t i = 0; i < value.size(); ++i) { + if (i > 0) { + m_output << ", "; + } + m_output << value[i]; + } + m_output << "]\n"; + + return value.size() * sizeof(uint32_t); +} + +size_t +docWriterYaml::writeString(const std::string& value) +{ + std::string escaped = escapeString(value); + + if (m_contextStack.empty()) { + writeKey(m_nextKey); + m_output << escaped << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << escaped << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(m_nextKey); + m_output << escaped << "\n"; + ctx.firstItem = false; + } + } + + return value.size(); +} + +void +docWriterYaml::pushContext(const std::string& name, ContextType type) +{ + m_contextStack.push(Context(name, type)); +} + +bool +docWriterYaml::popContext(ContextType expectedType) +{ + if (m_contextStack.empty()) { + return false; + } + + const Context& ctx = m_contextStack.top(); + bool isCorrectType = (ctx.type == expectedType); + + if (!isCorrectType) { + logError("Mismatched begin/end calls: expected " + + std::string(expectedType == ContextType::Object ? "Object" : "Array") + " but found " + + std::string(ctx.type == ContextType::Object ? "Object" : "Array") + " for context '" + ctx.name + "'"); + } + + m_contextStack.pop(); + return isCorrectType; +} + +void +docWriterYaml::writeIndent() +{ + for (int i = 0; i < m_indentLevel; ++i) { + m_output << " "; // 2 spaces per indent level + } +} + +void +docWriterYaml::writeKey(const std::string& key) +{ + m_output << key << ": "; +} + +std::string +docWriterYaml::escapeString(const std::string& str) +{ + // Check if string needs quoting + bool needsQuotes = false; + + if (str.empty()) { + return "\"\""; + } + + // Check for special characters that require quoting + for (char c : str) { + if (c == ':' || c == '#' || c == '\n' || c == '\r' || c == '\t' || c == '"' || c == '\'' || c == '\\' || c == '[' || + c == ']' || c == '{' || c == '}' || c == ',' || c == '&' || c == '*' || c == '!' || c == '|' || c == '>' || + c == '@' || c == '`') { + needsQuotes = true; + break; + } + } + + // Check if it starts with special characters + if (str[0] == '-' || str[0] == '?' || str[0] == ' ') { + needsQuotes = true; + } + + if (!needsQuotes) { + return str; + } + + // Escape the string and wrap in quotes + std::string result = "\""; + for (char c : str) { + switch (c) { + case '"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + default: + result += c; + break; + } + } + result += "\""; + + return result; +} + +void +docWriterYaml::logError(const std::string& message) +{ + LOG_ERROR << "docWriterYaml: " << message; +} diff --git a/renderlib/serialize/docWriterYaml.h b/renderlib/serialize/docWriterYaml.h new file mode 100644 index 000000000..481bd6c77 --- /dev/null +++ b/renderlib/serialize/docWriterYaml.h @@ -0,0 +1,76 @@ +#pragma once + +#include "docWriter.h" + +#include +#include +#include +#include + +class docWriterYaml : public docWriter +{ +public: + docWriterYaml(); + virtual ~docWriterYaml(); + + // Document lifecycle + virtual void beginDocument(std::string filePath) override; + virtual void endDocument() override; + + // Object support + virtual void beginObject(const char* i_name) override; + virtual void endObject() override; + + // List/array support + virtual void beginList(const char* i_name) override; + virtual void endList() override; + + // Property writing + virtual void writePrty(const prtyProperty* p) override; + + // Primitive type writing + virtual size_t writeInt32(int32_t value) override; + virtual size_t writeUint32(uint32_t value) override; + virtual size_t writeFloat32(float value) override; + virtual size_t writeFloat32Array(const std::vector& value) override; + virtual size_t writeInt32Array(const std::vector& value) override; + virtual size_t writeUint32Array(const std::vector& value) override; + virtual size_t writeString(const std::string& value) override; + +private: + enum class ContextType + { + Object, + Array + }; + + struct Context + { + std::string name; + ContextType type; + bool firstItem; + + Context(const std::string& n, ContextType t) + : name(n) + , type(t) + , firstItem(true) + { + } + + bool isArray() const { return type == ContextType::Array; } + bool isObject() const { return type == ContextType::Object; } + }; + + std::string m_filePath; + std::ostringstream m_output; + std::stack m_contextStack; + std::string m_nextKey; + int m_indentLevel; + + void pushContext(const std::string& name, ContextType type); + bool popContext(ContextType expectedType); + void writeIndent(); + void writeKey(const std::string& key); + std::string escapeString(const std::string& str); + void logError(const std::string& message); +}; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 876648632..eb1c013c8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,6 +15,7 @@ target_include_directories(agave_test PUBLIC ) target_sources(agave_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/test_commands.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/test_docWriter.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_histogram.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_main.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_mathUtil.cpp" From 4cdd5ae2c4dd47d933da0220a945c2d7e0977a02 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 29 Nov 2025 11:14:27 -0800 Subject: [PATCH 25/63] add test file --- renderlib/serialize/docWriterJson.cpp | 1 + renderlib/serialize/docWriterJson.h | 6 +- test/test_docWriter.cpp | 244 ++++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 test/test_docWriter.cpp diff --git a/renderlib/serialize/docWriterJson.cpp b/renderlib/serialize/docWriterJson.cpp index a65f29c24..13470f094 100644 --- a/renderlib/serialize/docWriterJson.cpp +++ b/renderlib/serialize/docWriterJson.cpp @@ -10,6 +10,7 @@ docWriterJson::docWriterJson() : m_root(nullptr) , m_current(nullptr) + , m_nextKey("") { } diff --git a/renderlib/serialize/docWriterJson.h b/renderlib/serialize/docWriterJson.h index 0ed5be797..23b5048b9 100644 --- a/renderlib/serialize/docWriterJson.h +++ b/renderlib/serialize/docWriterJson.h @@ -2,14 +2,12 @@ #include "docWriter.h" +#include "json/json.hpp" + #include #include #include -namespace nlohmann { -class json; -} - class docWriterJson : public docWriter { public: diff --git a/test/test_docWriter.cpp b/test/test_docWriter.cpp new file mode 100644 index 000000000..90465b7d0 --- /dev/null +++ b/test/test_docWriter.cpp @@ -0,0 +1,244 @@ +#include + +#include "renderlib/serialize/docWriter.h" +#include "renderlib/serialize/docWriterJson.h" +#include "renderlib/serialize/docWriterYaml.h" +#include "renderlib/AppearanceObject.hpp" +#include "renderlib/core/prty/prtyObject.hpp" +#include "renderlib/core/prty/prtyPropertyUIInfo.hpp" +#include "renderlib/core/prty/prtyProperty.hpp" +#include "renderlib/core/prty/prtyInt8.hpp" +#include "renderlib/core/prty/prtyInt32.hpp" +#include "renderlib/core/prty/prtyFloat.hpp" +#include "renderlib/core/prty/prtyText.hpp" +#include "renderlib/core/prty/prtyBoolean.hpp" +#include "renderlib/Logging.h" + +#include +#include +#include +#include + +// Helper function to write a prtyObject to a docWriter +void +writePrtyObject(docWriter& writer, prtyObject* obj, const std::string& name) +{ + if (!obj) { + return; + } + + writer.beginObject(name.c_str()); + + const PropertyUIIList& propList = obj->GetList(); + std::cout << "Property list size: " << propList.size() << std::endl; + + for (const auto& propUIInfo : propList) { + int numProps = propUIInfo->GetNumberOfProperties(); + std::cout << "Number of properties in UIInfo: " << numProps << std::endl; + + prtyProperty* prop = propUIInfo->GetProperty(0); + if (!prop) { + std::cout << "Property is null!" << std::endl; + continue; + } + + const char* type = prop->GetType(); + std::string propName = prop->GetPropertyName(); + std::cout << "Writing property: " << propName << " of type: " << type << std::endl; + + // Set up the property name for writing + writer.writePrty(prop); + + if (strcmp(type, "Int8") == 0) { + auto* p = static_cast(prop); + writer.writeInt32(p->GetValue()); + } else if (strcmp(type, "Int32") == 0) { + auto* p = static_cast(prop); + writer.writeInt32(p->GetValue()); + } else if (strcmp(type, "Float") == 0) { + auto* p = static_cast(prop); + writer.writeFloat32(p->GetValue()); + } else if (strcmp(type, "Text") == 0) { + auto* p = static_cast(prop); + writer.writeString(p->GetValue()); + } else if (strcmp(type, "Boolean") == 0) { + auto* p = static_cast(prop); + writer.writeInt32(p->GetValue() ? 1 : 0); + } + } + + writer.endObject(); +} + +TEST_CASE("Serialize prtyObject to JSON", "[serialize][docWriter]") +{ + // Create a simple prtyObject with some properties + prtyObject obj; + + auto intProp = std::make_shared(new prtyInt32("testInt", 42), "", "Test Integer"); + auto floatProp = std::make_shared(new prtyFloat("testFloat", 3.14f), "", "Test Float"); + auto stringProp = std::make_shared(new prtyText("testString", "Hello World"), "", "Test String"); + auto boolProp = std::make_shared(new prtyBoolean("testBool", true), "", "Test Boolean"); + + obj.AddProperty(intProp); + obj.AddProperty(floatProp); + obj.AddProperty(stringProp); + obj.AddProperty(boolProp); + + // Write to JSON + docWriterJson jsonWriter; + std::string jsonPath = "test_output.json"; + + jsonWriter.beginDocument(jsonPath); + writePrtyObject(jsonWriter, &obj, "testObject"); + jsonWriter.endDocument(); + + // Verify the file was created + REQUIRE(std::filesystem::exists(jsonPath)); + + // Read and verify contents + std::ifstream file(jsonPath); + REQUIRE(file.is_open()); + + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + file.close(); + + // Check that the JSON contains expected values + REQUIRE(content.find("testObject") != std::string::npos); + REQUIRE(content.find("testInt") != std::string::npos); + REQUIRE(content.find("42") != std::string::npos); + REQUIRE(content.find("testFloat") != std::string::npos); + REQUIRE(content.find("3.14") != std::string::npos); + REQUIRE(content.find("testString") != std::string::npos); + REQUIRE(content.find("Hello World") != std::string::npos); + REQUIRE(content.find("testBool") != std::string::npos); + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("Serialize prtyObject to YAML", "[serialize][docWriter]") +{ + // Create a simple prtyObject with some properties + prtyObject obj; + + auto intProp = std::make_shared(new prtyInt32("testInt", 42), "", "Test Integer"); + auto floatProp = std::make_shared(new prtyFloat("testFloat", 3.14f), "", "Test Float"); + auto stringProp = std::make_shared(new prtyText("testString", "Hello World"), "", "Test String"); + auto boolProp = std::make_shared(new prtyBoolean("testBool", true), "", "Test Boolean"); + + obj.AddProperty(intProp); + obj.AddProperty(floatProp); + obj.AddProperty(stringProp); + obj.AddProperty(boolProp); + + // Write to YAML + docWriterYaml yamlWriter; + std::string yamlPath = "test_output.yaml"; + + yamlWriter.beginDocument(yamlPath); + writePrtyObject(yamlWriter, &obj, "testObject"); + yamlWriter.endDocument(); + + // Verify the file was created + REQUIRE(std::filesystem::exists(yamlPath)); + + // Read and verify contents + std::ifstream file(yamlPath); + REQUIRE(file.is_open()); + + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + file.close(); + + // Check that the YAML contains expected values + REQUIRE(content.find("---") != std::string::npos); // YAML header + REQUIRE(content.find("testObject:") != std::string::npos); + REQUIRE(content.find("testInt:") != std::string::npos); + REQUIRE(content.find("42") != std::string::npos); + REQUIRE(content.find("testFloat:") != std::string::npos); + REQUIRE(content.find("3.14") != std::string::npos); + REQUIRE(content.find("testString:") != std::string::npos); + REQUIRE(content.find("Hello World") != std::string::npos); + REQUIRE(content.find("testBool:") != std::string::npos); + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("Serialize AppearanceObject", "[serialize][docWriter][AppearanceObject]") +{ + AppearanceObject appearance; + + // Write to JSON + docWriterJson jsonWriter; + std::string jsonPath = "test_appearance.json"; + + jsonWriter.beginDocument(jsonPath); + writePrtyObject(jsonWriter, &appearance, "appearance"); + jsonWriter.endDocument(); + + // Verify the file was created + REQUIRE(std::filesystem::exists(jsonPath)); + + // Read and verify contents + std::ifstream file(jsonPath); + REQUIRE(file.is_open()); + + std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + file.close(); + + // Check that the JSON contains the appearance object + REQUIRE(content.find("appearance") != std::string::npos); + + // Cleanup + std::filesystem::remove(jsonPath); + + // Write to YAML + docWriterYaml yamlWriter; + std::string yamlPath = "test_appearance.yaml"; + + yamlWriter.beginDocument(yamlPath); + writePrtyObject(yamlWriter, &appearance, "appearance"); + yamlWriter.endDocument(); + + // Verify the file was created + REQUIRE(std::filesystem::exists(yamlPath)); + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("Validate nesting protection in docWriter", "[serialize][docWriter]") +{ + SECTION("Extra endObject call") + { + docWriterJson writer; + writer.beginDocument("test_nesting_error.json"); + writer.beginObject("obj1"); + writer.endObject(); + writer.endObject(); // Extra end - should log error but not crash + writer.endDocument(); + std::filesystem::remove("test_nesting_error.json"); + } + + SECTION("Mismatched begin/end types") + { + docWriterJson writer; + writer.beginDocument("test_mismatch_error.json"); + writer.beginObject("obj1"); + writer.endList(); // Wrong type - should log error + writer.endDocument(); + std::filesystem::remove("test_mismatch_error.json"); + } + + SECTION("Unclosed contexts") + { + docWriterJson writer; + writer.beginDocument("test_unclosed_error.json"); + writer.beginObject("obj1"); + writer.beginObject("obj2"); + // Missing endObject calls - should log error on endDocument + writer.endDocument(); + std::filesystem::remove("test_unclosed_error.json"); + } +} From be84168e287fe3c39bd4746f77de3ab878fc38be Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 29 Nov 2025 13:27:48 -0800 Subject: [PATCH 26/63] implement property writing --- renderlib/core/CMakeLists.txt | 4 ++ renderlib/core/prty/CMakeLists.txt | 4 ++ renderlib/core/prty/prtyBoolean.cpp | 8 ++-- renderlib/core/prty/prtyBoolean.hpp | 2 +- renderlib/core/prty/prtyColor.cpp | 5 ++- renderlib/core/prty/prtyColor.hpp | 2 +- renderlib/core/prty/prtyFloat.cpp | 7 ++-- renderlib/core/prty/prtyFloat.hpp | 2 +- renderlib/core/prty/prtyInt32.cpp | 6 +-- renderlib/core/prty/prtyInt32.hpp | 2 +- renderlib/core/prty/prtyInt8.cpp | 7 ++-- renderlib/core/prty/prtyInt8.hpp | 2 +- renderlib/core/prty/prtyProperty.hpp | 4 +- renderlib/core/prty/prtyRotation.cpp | 5 ++- renderlib/core/prty/prtyRotation.hpp | 2 +- renderlib/core/prty/prtyText.cpp | 4 +- renderlib/core/prty/prtyText.hpp | 2 +- renderlib/core/prty/prtyVector3d.cpp | 5 ++- renderlib/core/prty/prtyVector3d.hpp | 2 +- renderlib/serialize/CMakeLists.txt | 1 + renderlib/serialize/docWriter.cpp | 40 ++++++++++++++++++ renderlib/serialize/docWriter.h | 6 +++ renderlib/serialize/docWriterJson.cpp | 49 +++++++++++++++++++--- renderlib/serialize/docWriterJson.h | 3 ++ renderlib/serialize/docWriterYaml.cpp | 59 +++++++++++++++++++++++++-- renderlib/serialize/docWriterYaml.h | 3 ++ test/test_docWriter.cpp | 38 +---------------- test/test_prty.cpp | 2 +- 28 files changed, 198 insertions(+), 78 deletions(-) create mode 100644 renderlib/serialize/docWriter.cpp diff --git a/renderlib/core/CMakeLists.txt b/renderlib/core/CMakeLists.txt index c5301ab1e..31f9428c7 100644 --- a/renderlib/core/CMakeLists.txt +++ b/renderlib/core/CMakeLists.txt @@ -1,2 +1,6 @@ +target_include_directories(renderlib PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" +) + add_subdirectory(prty) add_subdirectory(undo) diff --git a/renderlib/core/prty/CMakeLists.txt b/renderlib/core/prty/CMakeLists.txt index 03edfb9be..0447f8f85 100644 --- a/renderlib/core/prty/CMakeLists.txt +++ b/renderlib/core/prty/CMakeLists.txt @@ -1,3 +1,7 @@ +target_include_directories(renderlib PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}" +) + target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/prtyBoolean.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyBoolean.hpp" diff --git a/renderlib/core/prty/prtyBoolean.cpp b/renderlib/core/prty/prtyBoolean.cpp index aaacb84ae..5b09c8da4 100644 --- a/renderlib/core/prty/prtyBoolean.cpp +++ b/renderlib/core/prty/prtyBoolean.cpp @@ -1,7 +1,7 @@ #include "core/prty/prtyBoolean.hpp" // #include "core/ch/chReader.hpp" -// #include "core/ch/chWriter.hpp" +#include "serialize/docWriter.h" //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- @@ -83,7 +83,7 @@ void prtyBoolean::Read(chReader& io_Reader) { // bool temp; - // io_Reader.Read(temp); + // io_Reader.readBool(temp); // SetValue(temp); } @@ -91,7 +91,7 @@ prtyBoolean::Read(chReader& io_Reader) //-------------------------------------------------------------------- // virtual void -prtyBoolean::Write(chWriter& io_Writer) const +prtyBoolean::Write(docWriter& io_Writer) const { - // io_Writer.Write(GetValue()); + io_Writer.writeBool(GetValue()); } diff --git a/renderlib/core/prty/prtyBoolean.hpp b/renderlib/core/prty/prtyBoolean.hpp index 8ba2ecdcd..83ec71fc2 100644 --- a/renderlib/core/prty/prtyBoolean.hpp +++ b/renderlib/core/prty/prtyBoolean.hpp @@ -44,5 +44,5 @@ class prtyBoolean : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const; + virtual void Write(docWriter& io_Writer) const; }; diff --git a/renderlib/core/prty/prtyColor.cpp b/renderlib/core/prty/prtyColor.cpp index 6e302065d..a1a6acb5d 100644 --- a/renderlib/core/prty/prtyColor.cpp +++ b/renderlib/core/prty/prtyColor.cpp @@ -2,6 +2,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docWriter.h" //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- @@ -91,7 +92,7 @@ prtyColor::Read(chReader& io_Reader) //-------------------------------------------------------------------- // virtual void -prtyColor::Write(chWriter& io_Writer) const +prtyColor::Write(docWriter& io_Writer) const { - // chChunkParserUtil::Write(io_Writer, GetValue()); + io_Writer.writeFloat32Array(4, glm::value_ptr(GetValue())); } diff --git a/renderlib/core/prty/prtyColor.hpp b/renderlib/core/prty/prtyColor.hpp index 822e4781f..1c0e5bcae 100644 --- a/renderlib/core/prty/prtyColor.hpp +++ b/renderlib/core/prty/prtyColor.hpp @@ -46,5 +46,5 @@ class prtyColor : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const; + virtual void Write(docWriter& io_Writer) const; }; diff --git a/renderlib/core/prty/prtyFloat.cpp b/renderlib/core/prty/prtyFloat.cpp index 53d52b5b1..7cf575bfe 100644 --- a/renderlib/core/prty/prtyFloat.cpp +++ b/renderlib/core/prty/prtyFloat.cpp @@ -2,7 +2,7 @@ #include "core/prty/prtyUnits.hpp" // #include "core/ch/chReader.hpp" -// #include "core/ch/chWriter.hpp" +#include "serialize/docWriter.h" //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- @@ -155,8 +155,7 @@ prtyFloat::Read(chReader& io_Reader) //-------------------------------------------------------------------- // virtual void -prtyFloat::Write(chWriter& io_Writer) const +prtyFloat::Write(docWriter& io_Writer) const { - // float temp = GetValue(); - // io_Writer.Write(temp); + io_Writer.writeFloat32(GetValue()); } diff --git a/renderlib/core/prty/prtyFloat.hpp b/renderlib/core/prty/prtyFloat.hpp index 350e3494b..575b3052b 100644 --- a/renderlib/core/prty/prtyFloat.hpp +++ b/renderlib/core/prty/prtyFloat.hpp @@ -67,7 +67,7 @@ class prtyFloat : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const; + virtual void Write(docWriter& io_Writer) const; private: bool m_bUseUnits; diff --git a/renderlib/core/prty/prtyInt32.cpp b/renderlib/core/prty/prtyInt32.cpp index c14b0c06b..d4a0fdf80 100644 --- a/renderlib/core/prty/prtyInt32.cpp +++ b/renderlib/core/prty/prtyInt32.cpp @@ -2,6 +2,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docWriter.h" //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- @@ -135,8 +136,7 @@ prtyInt32::Read(chReader& io_Reader) //-------------------------------------------------------------------- // virtual void -prtyInt32::Write(chWriter& io_Writer) const +prtyInt32::Write(docWriter& io_Writer) const { - // int32_t temp = GetValue(); - // io_Writer.Write(temp); + io_Writer.writeInt32(GetValue()); } diff --git a/renderlib/core/prty/prtyInt32.hpp b/renderlib/core/prty/prtyInt32.hpp index a7abfcdef..e0a4cae7d 100644 --- a/renderlib/core/prty/prtyInt32.hpp +++ b/renderlib/core/prty/prtyInt32.hpp @@ -56,5 +56,5 @@ class prtyInt32 : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const; + virtual void Write(docWriter& io_Writer) const; }; diff --git a/renderlib/core/prty/prtyInt8.cpp b/renderlib/core/prty/prtyInt8.cpp index bbe85620d..9db8d598f 100644 --- a/renderlib/core/prty/prtyInt8.cpp +++ b/renderlib/core/prty/prtyInt8.cpp @@ -1,7 +1,7 @@ #include "core/prty/prtyInt8.hpp" // #include "core/ch/chReader.hpp" -// #include "core/ch/chWriter.hpp" +#include "serialize/docWriter.h" //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- @@ -135,8 +135,7 @@ prtyInt8::Read(chReader& io_Reader) //-------------------------------------------------------------------- // virtual void -prtyInt8::Write(chWriter& io_Writer) const +prtyInt8::Write(docWriter& io_Writer) const { - // int8_t temp = GetValue(); - // io_Writer.Write(temp); + io_Writer.writeInt8(GetValue()); } diff --git a/renderlib/core/prty/prtyInt8.hpp b/renderlib/core/prty/prtyInt8.hpp index 182ce1649..cbef84cc0 100644 --- a/renderlib/core/prty/prtyInt8.hpp +++ b/renderlib/core/prty/prtyInt8.hpp @@ -56,5 +56,5 @@ class prtyInt8 : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const; + virtual void Write(docWriter& io_Writer) const; }; diff --git a/renderlib/core/prty/prtyProperty.hpp b/renderlib/core/prty/prtyProperty.hpp index 0d6c29b8f..b9deb46a9 100644 --- a/renderlib/core/prty/prtyProperty.hpp +++ b/renderlib/core/prty/prtyProperty.hpp @@ -14,7 +14,7 @@ class prtyProperty; class prtyPropertyReference; class chReader; -class chWriter; +class docWriter; //============================================================================ // typedefs @@ -191,7 +191,7 @@ class prtyProperty //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const = 0; + virtual void Write(docWriter& io_Writer) const = 0; //-------------------------------------------------------------------- // In some cases we want the form builder to check whether or not diff --git a/renderlib/core/prty/prtyRotation.cpp b/renderlib/core/prty/prtyRotation.cpp index f833516ce..693069b35 100644 --- a/renderlib/core/prty/prtyRotation.cpp +++ b/renderlib/core/prty/prtyRotation.cpp @@ -5,6 +5,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" // #include "core/ma/maConstants.hpp" +#include "serialize/docWriter.h" #include "core/undo/undoUndoMgr.hpp" namespace { @@ -203,7 +204,7 @@ prtyRotation::Read(chReader& io_Reader) //-------------------------------------------------------------------- // virtual void -prtyRotation::Write(chWriter& io_Writer) const +prtyRotation::Write(docWriter& io_Writer) const { - // chChunkParserUtil::Write(io_Writer, m_Quaternion); + io_Writer.writeFloat32Array(4, glm::value_ptr(GetQuaternion())); } diff --git a/renderlib/core/prty/prtyRotation.hpp b/renderlib/core/prty/prtyRotation.hpp index 7aad33271..6582e8e31 100644 --- a/renderlib/core/prty/prtyRotation.hpp +++ b/renderlib/core/prty/prtyRotation.hpp @@ -85,7 +85,7 @@ class prtyRotation : public prtyProperty //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const; + virtual void Write(docWriter& io_Writer) const; private: glm::quat m_Quaternion; diff --git a/renderlib/core/prty/prtyText.cpp b/renderlib/core/prty/prtyText.cpp index 74b87231a..4cec7cc1c 100644 --- a/renderlib/core/prty/prtyText.cpp +++ b/renderlib/core/prty/prtyText.cpp @@ -2,6 +2,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docWriter.h" //-------------------------------------------------------------------- //-------------------------------------------------------------------- @@ -97,7 +98,8 @@ prtyText::Read(chReader& io_Reader) //-------------------------------------------------------------------- // virtual void -prtyText::Write(chWriter& io_Writer) const +prtyText::Write(docWriter& io_Writer) const { + io_Writer.writeString(GetValue()); // chChunkParserUtil::Write(io_Writer, GetValue()); } diff --git a/renderlib/core/prty/prtyText.hpp b/renderlib/core/prty/prtyText.hpp index 1e2fa90e0..5ecaf97f2 100644 --- a/renderlib/core/prty/prtyText.hpp +++ b/renderlib/core/prty/prtyText.hpp @@ -45,5 +45,5 @@ class prtyText : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const; + virtual void Write(docWriter& io_Writer) const; }; diff --git a/renderlib/core/prty/prtyVector3d.cpp b/renderlib/core/prty/prtyVector3d.cpp index ca0bfa8ca..8fc22dadb 100644 --- a/renderlib/core/prty/prtyVector3d.cpp +++ b/renderlib/core/prty/prtyVector3d.cpp @@ -3,6 +3,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docWriter.h" //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- @@ -143,7 +144,7 @@ prtyVector3d::Read(chReader& io_Reader) //-------------------------------------------------------------------- // virtual void -prtyVector3d::Write(chWriter& io_Writer) const +prtyVector3d::Write(docWriter& io_Writer) const { - // chChunkParserUtil::Write(io_Writer, GetValue()); + io_Writer.writeFloat32Array(3, glm::value_ptr(GetValue())); } diff --git a/renderlib/core/prty/prtyVector3d.hpp b/renderlib/core/prty/prtyVector3d.hpp index 80a34873f..0e75d3108 100644 --- a/renderlib/core/prty/prtyVector3d.hpp +++ b/renderlib/core/prty/prtyVector3d.hpp @@ -69,7 +69,7 @@ class prtyVector3d : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Write(chWriter& io_Writer) const; + virtual void Write(docWriter& io_Writer) const; private: bool m_bUseUnits; diff --git a/renderlib/serialize/CMakeLists.txt b/renderlib/serialize/CMakeLists.txt index 68b4e85eb..7271e3fc0 100644 --- a/renderlib/serialize/CMakeLists.txt +++ b/renderlib/serialize/CMakeLists.txt @@ -4,6 +4,7 @@ target_include_directories(renderlib PUBLIC target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/docWriter.h" +"${CMAKE_CURRENT_SOURCE_DIR}/docWriter.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/docWriterJson.h" "${CMAKE_CURRENT_SOURCE_DIR}/docWriterJson.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/docWriterYaml.h" diff --git a/renderlib/serialize/docWriter.cpp b/renderlib/serialize/docWriter.cpp new file mode 100644 index 000000000..897c7ce15 --- /dev/null +++ b/renderlib/serialize/docWriter.cpp @@ -0,0 +1,40 @@ +#include "docWriter.h" + +#include "core/prty/prtyProperty.hpp" +#include "core/prty/prtyObject.hpp" + +// assumes a current named object context. +// if objects had ONLY properties, and no children, +// then this could create the object context itself using beginObject/endObject. +void +docWriter::writeProperties(prtyObject* obj) +{ + if (!obj) { + return; + } + + // beginObject(name.c_str()); + + const PropertyUIIList& propList = obj->GetList(); + // std::cout << "Property list size: " << propList.size() << std::endl; + + for (const auto& propUIInfo : propList) { + int numProps = propUIInfo->GetNumberOfProperties(); + // std::cout << "Number of properties in UIInfo: " << numProps << std::endl; + + prtyProperty* prop = propUIInfo->GetProperty(0); + if (!prop) { + // std::cout << "Property is null!" << std::endl; + continue; + } + + const char* type = prop->GetType(); + std::string propName = prop->GetPropertyName(); + // std::cout << "Writing property: " << propName << " of type: " << type << std::endl; + + // Set up the property name for writing + writePrty(prop); + } + + // endObject(); +} diff --git a/renderlib/serialize/docWriter.h b/renderlib/serialize/docWriter.h index 9486a861d..ac57d874e 100644 --- a/renderlib/serialize/docWriter.h +++ b/renderlib/serialize/docWriter.h @@ -5,6 +5,7 @@ #include class prtyProperty; +class prtyObject; class docWriter { @@ -27,11 +28,16 @@ class docWriter // properties will write their name and associated value using the primitive write methods. virtual void writePrty(const prtyProperty* p) = 0; + virtual size_t writeBool(bool) = 0; + virtual size_t writeInt8(int8_t) = 0; virtual size_t writeInt32(int32_t) = 0; virtual size_t writeUint32(uint32_t) = 0; virtual size_t writeFloat32(float) = 0; virtual size_t writeFloat32Array(const std::vector&) = 0; + virtual size_t writeFloat32Array(size_t count, const float* values) = 0; virtual size_t writeInt32Array(const std::vector&) = 0; virtual size_t writeUint32Array(const std::vector&) = 0; virtual size_t writeString(const std::string&) = 0; + + void writeProperties(prtyObject* obj); }; diff --git a/renderlib/serialize/docWriterJson.cpp b/renderlib/serialize/docWriterJson.cpp index 13470f094..6135608a7 100644 --- a/renderlib/serialize/docWriterJson.cpp +++ b/renderlib/serialize/docWriterJson.cpp @@ -145,8 +145,41 @@ docWriterJson::writePrty(const prtyProperty* p) // Store the property name for the next write operation m_nextKey = p->GetPropertyName(); - // Note: The actual value writing will be done by the specific write methods - // (writeInt32, writeFloat32, etc.) which will be called after this method + p->Write(*this); +} + +size_t +docWriterJson::writeBool(bool value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = value; + } else { + current->push_back(value); + } + + return sizeof(bool); +} + +size_t +docWriterJson::writeInt8(int8_t value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[m_nextKey] = value; + } else { + current->push_back(value); + } + + return sizeof(int8_t); } size_t @@ -202,6 +235,12 @@ docWriterJson::writeFloat32(float value) size_t docWriterJson::writeFloat32Array(const std::vector& value) +{ + return writeFloat32Array(value.size(), value.data()); +} + +size_t +docWriterJson::writeFloat32Array(size_t count, const float* values) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -209,8 +248,8 @@ docWriterJson::writeFloat32Array(const std::vector& value) } nlohmann::json arr = nlohmann::json::array(); - for (float v : value) { - arr.push_back(v); + for (size_t i = 0; i < count; ++i) { + arr.push_back(values[i]); } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { @@ -219,7 +258,7 @@ docWriterJson::writeFloat32Array(const std::vector& value) current->push_back(arr); } - return value.size() * sizeof(float); + return count * sizeof(float); } size_t diff --git a/renderlib/serialize/docWriterJson.h b/renderlib/serialize/docWriterJson.h index 23b5048b9..1e71428b1 100644 --- a/renderlib/serialize/docWriterJson.h +++ b/renderlib/serialize/docWriterJson.h @@ -30,10 +30,13 @@ class docWriterJson : public docWriter virtual void writePrty(const prtyProperty* p) override; // Primitive type writing + virtual size_t writeBool(bool) override; + virtual size_t writeInt8(int8_t value) override; virtual size_t writeInt32(int32_t value) override; virtual size_t writeUint32(uint32_t value) override; virtual size_t writeFloat32(float value) override; virtual size_t writeFloat32Array(const std::vector& value) override; + virtual size_t writeFloat32Array(size_t count, const float* values) override; virtual size_t writeInt32Array(const std::vector& value) override; virtual size_t writeUint32Array(const std::vector& value) override; virtual size_t writeString(const std::string& value) override; diff --git a/renderlib/serialize/docWriterYaml.cpp b/renderlib/serialize/docWriterYaml.cpp index 75cbbea9e..7b2179d07 100644 --- a/renderlib/serialize/docWriterYaml.cpp +++ b/renderlib/serialize/docWriterYaml.cpp @@ -154,6 +154,53 @@ docWriterYaml::writePrty(const prtyProperty* p) // Store the property name for the next write operation m_nextKey = p->GetPropertyName(); + p->Write(*this); +} + +size_t +docWriterYaml::writeBool(bool value) +{ + if (m_contextStack.empty()) { + writeKey(m_nextKey); + m_output << (value ? "true" : "false") << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << (value ? "true" : "false") << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(m_nextKey); + m_output << (value ? "true" : "false") << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(bool); +} + +size_t +docWriterYaml::writeInt8(int8_t value) +{ + if (m_contextStack.empty()) { + writeKey(m_nextKey); + m_output << static_cast(value) << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << static_cast(value) << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(m_nextKey); + m_output << static_cast(value) << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(int8_t); } size_t @@ -227,6 +274,12 @@ docWriterYaml::writeFloat32(float value) size_t docWriterYaml::writeFloat32Array(const std::vector& value) +{ + return writeFloat32Array(value.size(), value.data()); +} + +size_t +docWriterYaml::writeFloat32Array(size_t count, const float* values) { if (m_contextStack.empty()) { writeKey(m_nextKey); @@ -244,15 +297,15 @@ docWriterYaml::writeFloat32Array(const std::vector& value) // Write as inline array [x, y, z, ...] m_output << "["; - for (size_t i = 0; i < value.size(); ++i) { + for (size_t i = 0; i < count; ++i) { if (i > 0) { m_output << ", "; } - m_output << std::setprecision(6) << value[i]; + m_output << std::setprecision(6) << values[i]; } m_output << "]\n"; - return value.size() * sizeof(float); + return count * sizeof(float); } size_t diff --git a/renderlib/serialize/docWriterYaml.h b/renderlib/serialize/docWriterYaml.h index 481bd6c77..d07634861 100644 --- a/renderlib/serialize/docWriterYaml.h +++ b/renderlib/serialize/docWriterYaml.h @@ -29,10 +29,13 @@ class docWriterYaml : public docWriter virtual void writePrty(const prtyProperty* p) override; // Primitive type writing + virtual size_t writeBool(bool) override; + virtual size_t writeInt8(int8_t value) override; virtual size_t writeInt32(int32_t value) override; virtual size_t writeUint32(uint32_t value) override; virtual size_t writeFloat32(float value) override; virtual size_t writeFloat32Array(const std::vector& value) override; + virtual size_t writeFloat32Array(size_t count, const float* values) override; virtual size_t writeInt32Array(const std::vector& value) override; virtual size_t writeUint32Array(const std::vector& value) override; virtual size_t writeString(const std::string& value) override; diff --git a/test/test_docWriter.cpp b/test/test_docWriter.cpp index 90465b7d0..2f8bd909f 100644 --- a/test/test_docWriter.cpp +++ b/test/test_docWriter.cpp @@ -29,43 +29,7 @@ writePrtyObject(docWriter& writer, prtyObject* obj, const std::string& name) writer.beginObject(name.c_str()); - const PropertyUIIList& propList = obj->GetList(); - std::cout << "Property list size: " << propList.size() << std::endl; - - for (const auto& propUIInfo : propList) { - int numProps = propUIInfo->GetNumberOfProperties(); - std::cout << "Number of properties in UIInfo: " << numProps << std::endl; - - prtyProperty* prop = propUIInfo->GetProperty(0); - if (!prop) { - std::cout << "Property is null!" << std::endl; - continue; - } - - const char* type = prop->GetType(); - std::string propName = prop->GetPropertyName(); - std::cout << "Writing property: " << propName << " of type: " << type << std::endl; - - // Set up the property name for writing - writer.writePrty(prop); - - if (strcmp(type, "Int8") == 0) { - auto* p = static_cast(prop); - writer.writeInt32(p->GetValue()); - } else if (strcmp(type, "Int32") == 0) { - auto* p = static_cast(prop); - writer.writeInt32(p->GetValue()); - } else if (strcmp(type, "Float") == 0) { - auto* p = static_cast(prop); - writer.writeFloat32(p->GetValue()); - } else if (strcmp(type, "Text") == 0) { - auto* p = static_cast(prop); - writer.writeString(p->GetValue()); - } else if (strcmp(type, "Boolean") == 0) { - auto* p = static_cast(prop); - writer.writeInt32(p->GetValue() ? 1 : 0); - } - } + writer.writeProperties(obj); writer.endObject(); } diff --git a/test/test_prty.cpp b/test/test_prty.cpp index 63d0f3b6c..baa5b2464 100644 --- a/test/test_prty.cpp +++ b/test/test_prty.cpp @@ -106,7 +106,7 @@ TEST_CASE("prtyProperty struct", "[prtyProperty]") { // Implement reading from a reader if needed } - virtual void Write(chWriter& io_Writer) const override + virtual void Write(docWriter& io_Writer) const override { // Implement writing to a writer if needed } From 81619729441ec0a75bafc323b84f664882f8eedb Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 29 Nov 2025 13:39:09 -0800 Subject: [PATCH 27/63] fix writer for multi-prop uiinfos --- renderlib/serialize/docWriter.cpp | 24 +++++++++++++----------- renderlib/serialize/docWriterYaml.cpp | 1 + 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/renderlib/serialize/docWriter.cpp b/renderlib/serialize/docWriter.cpp index 897c7ce15..efee4d40c 100644 --- a/renderlib/serialize/docWriter.cpp +++ b/renderlib/serialize/docWriter.cpp @@ -22,18 +22,20 @@ docWriter::writeProperties(prtyObject* obj) int numProps = propUIInfo->GetNumberOfProperties(); // std::cout << "Number of properties in UIInfo: " << numProps << std::endl; - prtyProperty* prop = propUIInfo->GetProperty(0); - if (!prop) { - // std::cout << "Property is null!" << std::endl; - continue; + for (int i = 0; i < numProps; ++i) { + prtyProperty* prop = propUIInfo->GetProperty(i); + if (!prop) { + // std::cout << "Property is null!" << std::endl; + continue; + } + + const char* type = prop->GetType(); + std::string propName = prop->GetPropertyName(); + // std::cout << "Writing property: " << propName << " of type: " << type << std::endl; + + // Set up the property name for writing + writePrty(prop); } - - const char* type = prop->GetType(); - std::string propName = prop->GetPropertyName(); - // std::cout << "Writing property: " << propName << " of type: " << type << std::endl; - - // Set up the property name for writing - writePrty(prop); } // endObject(); diff --git a/renderlib/serialize/docWriterYaml.cpp b/renderlib/serialize/docWriterYaml.cpp index 7b2179d07..5051597c2 100644 --- a/renderlib/serialize/docWriterYaml.cpp +++ b/renderlib/serialize/docWriterYaml.cpp @@ -154,6 +154,7 @@ docWriterYaml::writePrty(const prtyProperty* p) // Store the property name for the next write operation m_nextKey = p->GetPropertyName(); + p->Write(*this); } From eecb036b43c19e698eb9747703113751eba7b1b3 Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 30 Nov 2025 17:16:10 -0800 Subject: [PATCH 28/63] initial add of docReader --- agave_app/agaveGui.cpp | 5 + renderlib/serialize/CMakeLists.txt | 7 + renderlib/serialize/SerializationConstants.h | 9 + renderlib/serialize/docReader.cpp | 35 + renderlib/serialize/docReader.h | 49 ++ renderlib/serialize/docReaderJson.cpp | 495 +++++++++++++ renderlib/serialize/docReaderJson.h | 85 +++ renderlib/serialize/docReaderYaml.cpp | 723 +++++++++++++++++++ renderlib/serialize/docReaderYaml.h | 122 ++++ renderlib/serialize/docWriter.cpp | 13 +- test/CMakeLists.txt | 1 + test/test_docReader.cpp | 411 +++++++++++ 12 files changed, 1943 insertions(+), 12 deletions(-) create mode 100644 renderlib/serialize/SerializationConstants.h create mode 100644 renderlib/serialize/docReader.cpp create mode 100644 renderlib/serialize/docReader.h create mode 100644 renderlib/serialize/docReaderJson.cpp create mode 100644 renderlib/serialize/docReaderJson.h create mode 100644 renderlib/serialize/docReaderYaml.cpp create mode 100644 renderlib/serialize/docReaderYaml.h create mode 100644 test/test_docReader.cpp diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 70393c44a..f0d8d94b2 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -14,6 +14,7 @@ #include "renderlib/version.hpp" #include "renderlib/CameraObject.hpp" #include "renderlib/AppearanceObject.hpp" +#include "renderlib/serialize/docWriterJson.h" #include "AppearanceDockWidget.h" #include "AppearanceDockWidget2.h" @@ -735,6 +736,10 @@ agaveGui::saveJson() nlohmann::json doc = st; std::string str = doc.dump(); saveFile.write(str.c_str()); // QString::fromStdString(str)); + + docWriterJson writer; + writer.beginDocument(file.toStdString()); + writer.endDocument(); } } diff --git a/renderlib/serialize/CMakeLists.txt b/renderlib/serialize/CMakeLists.txt index 7271e3fc0..fa083d8a1 100644 --- a/renderlib/serialize/CMakeLists.txt +++ b/renderlib/serialize/CMakeLists.txt @@ -3,12 +3,19 @@ target_include_directories(renderlib PUBLIC ) target_sources(renderlib PRIVATE +"${CMAKE_CURRENT_SOURCE_DIR}/SerializationConstants.h" "${CMAKE_CURRENT_SOURCE_DIR}/docWriter.h" "${CMAKE_CURRENT_SOURCE_DIR}/docWriter.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/docWriterJson.h" "${CMAKE_CURRENT_SOURCE_DIR}/docWriterJson.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/docWriterYaml.h" "${CMAKE_CURRENT_SOURCE_DIR}/docWriterYaml.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/docReader.h" +"${CMAKE_CURRENT_SOURCE_DIR}/docReader.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/docReaderJson.h" +"${CMAKE_CURRENT_SOURCE_DIR}/docReaderJson.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/docReaderYaml.h" +"${CMAKE_CURRENT_SOURCE_DIR}/docReaderYaml.cpp" ) diff --git a/renderlib/serialize/SerializationConstants.h b/renderlib/serialize/SerializationConstants.h new file mode 100644 index 000000000..9a319919b --- /dev/null +++ b/renderlib/serialize/SerializationConstants.h @@ -0,0 +1,9 @@ +#pragma once + +namespace SerializationConstants { + +// Reserved keys for serialization metadata +constexpr const char* TYPE_KEY = "_type"; +constexpr const char* VERSION_KEY = "_version"; + +} // namespace SerializationConstants diff --git a/renderlib/serialize/docReader.cpp b/renderlib/serialize/docReader.cpp new file mode 100644 index 000000000..8b17401a4 --- /dev/null +++ b/renderlib/serialize/docReader.cpp @@ -0,0 +1,35 @@ +#include "docReader.h" + +#include "core/prty/prtyProperty.hpp" +#include "core/prty/prtyObject.hpp" + +// Reads all properties from the current object context +// Assumes we're already inside the object (after beginObject was called) +void +docReader::readProperties(prtyObject* obj) +{ + if (!obj) { + return; + } + + const PropertyUIIList& propList = obj->GetList(); + + for (const auto& propUIInfo : propList) { + int numProps = propUIInfo->GetNumberOfProperties(); + + for (int i = 0; i < numProps; ++i) { + prtyProperty* prop = propUIInfo->GetProperty(i); + if (!prop) { + continue; + } + + std::string propName = prop->GetPropertyName(); + + // Check if the property exists in the document + if (hasKey(propName.c_str())) { + // Set up the property name for reading and let the property read itself + readPrty(prop); + } + } + } +} diff --git a/renderlib/serialize/docReader.h b/renderlib/serialize/docReader.h new file mode 100644 index 000000000..4e8ae90fa --- /dev/null +++ b/renderlib/serialize/docReader.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +class prtyProperty; +class prtyObject; + +class docReader +{ +public: + docReader() {} + virtual ~docReader() {} + + // this must be called to start and end the document before any other reading can happen. + virtual bool beginDocument(std::string filePath) = 0; + virtual void endDocument() = 0; + + // objects can contain other objects, lists, and properties. + virtual bool beginObject(const char* i_name) = 0; + virtual void endObject() = 0; + + // lists can contain objects or properties. + virtual bool beginList(const char* i_name) = 0; + virtual void endList() = 0; + + // Check if a key exists at the current level + virtual bool hasKey(const char* key) = 0; + + // Peek at object type and version without consuming + virtual std::string peekObjectType() = 0; + virtual int peekVersion() = 0; + + // properties will read their name and associated value using the primitive read methods. + virtual void readPrty(prtyProperty* p) = 0; + + virtual bool readBool() = 0; + virtual int8_t readInt8() = 0; + virtual int32_t readInt32() = 0; + virtual uint32_t readUint32() = 0; + virtual float readFloat32() = 0; + virtual std::vector readFloat32Array() = 0; + virtual std::vector readInt32Array() = 0; + virtual std::vector readUint32Array() = 0; + virtual std::string readString() = 0; + + void readProperties(prtyObject* obj); +}; diff --git a/renderlib/serialize/docReaderJson.cpp b/renderlib/serialize/docReaderJson.cpp new file mode 100644 index 000000000..7b6afe726 --- /dev/null +++ b/renderlib/serialize/docReaderJson.cpp @@ -0,0 +1,495 @@ +#include "docReaderJson.h" + +#include "SerializationConstants.h" +#include "core/prty/prtyProperty.hpp" +#include "Logging.h" + +#include "json/json.hpp" + +#include + +docReaderJson::docReaderJson() + : m_root(nullptr) + , m_current(nullptr) + , m_nextKey("") +{ +} + +docReaderJson::~docReaderJson() +{ + if (m_root) { + delete m_root; + m_root = nullptr; + } +} + +bool +docReaderJson::beginDocument(std::string filePath) +{ + m_filePath = filePath; + if (m_root) { + delete m_root; + } + + // Read the JSON file + std::ifstream inFile(m_filePath); + if (!inFile.is_open()) { + LOG_ERROR << "Failed to open file for reading: " << m_filePath; + return false; + } + + try { + m_root = new nlohmann::json(); + inFile >> *m_root; + m_current = m_root; + } catch (const nlohmann::json::exception& e) { + LOG_ERROR << "JSON parse error: " << e.what(); + delete m_root; + m_root = nullptr; + return false; + } + + inFile.close(); + + // Clear the context stack + while (!m_contextStack.empty()) { + m_contextStack.pop(); + } + + return true; +} + +void +docReaderJson::endDocument() +{ + if (!m_contextStack.empty()) { + LOG_ERROR << "endDocument() called with " << m_contextStack.size() + << " unclosed context(s). Document may be incomplete."; + } +} + +bool +docReaderJson::beginObject(const char* i_name) +{ + if (!m_current) { + LOG_ERROR << "beginObject() called with null current object"; + return false; + } + + nlohmann::json* targetObj = nullptr; + + if (m_contextStack.empty()) { + // Root level object + if (m_current->contains(i_name) && (*m_current)[i_name].is_object()) { + targetObj = &(*m_current)[i_name]; + } else { + LOG_ERROR << "beginObject() - key not found or not an object: " << i_name; + return false; + } + } else { + const Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + // Reading object from an array + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_object()) { + targetObj = &(*ctx.jsonObj)[ctx.arrayIndex]; + } else { + LOG_ERROR << "beginObject() - array index out of bounds or not an object"; + return false; + } + } else { + // Reading object from an object + if (ctx.jsonObj->contains(i_name) && (*ctx.jsonObj)[i_name].is_object()) { + targetObj = &(*ctx.jsonObj)[i_name]; + } else { + LOG_ERROR << "beginObject() - key not found or not an object: " << i_name; + return false; + } + } + } + + pushContext(targetObj, i_name, ContextType::Object); + return true; +} + +void +docReaderJson::endObject() +{ + if (m_contextStack.empty()) { + LOG_ERROR << "endObject() called with no matching beginObject()"; + return; + } + + if (!popContext(ContextType::Object)) { + LOG_ERROR << "endObject() called but current context is not an object"; + } +} + +bool +docReaderJson::beginList(const char* i_name) +{ + if (!m_current) { + LOG_ERROR << "beginList() called with null current object"; + return false; + } + + nlohmann::json* targetArray = nullptr; + + if (m_contextStack.empty()) { + // Root level array + if (m_current->contains(i_name) && (*m_current)[i_name].is_array()) { + targetArray = &(*m_current)[i_name]; + } else { + LOG_ERROR << "beginList() - key not found or not an array: " << i_name; + return false; + } + } else { + const Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + // Reading array from an array + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_array()) { + targetArray = &(*ctx.jsonObj)[ctx.arrayIndex]; + } else { + LOG_ERROR << "beginList() - array index out of bounds or not an array"; + return false; + } + } else { + // Reading array from an object + if (ctx.jsonObj->contains(i_name) && (*ctx.jsonObj)[i_name].is_array()) { + targetArray = &(*ctx.jsonObj)[i_name]; + } else { + LOG_ERROR << "beginList() - key not found or not an array: " << i_name; + return false; + } + } + } + + pushContext(targetArray, i_name, ContextType::Array); + return true; +} + +void +docReaderJson::endList() +{ + if (m_contextStack.empty()) { + LOG_ERROR << "endList() called with no matching beginList()"; + return; + } + + if (!popContext(ContextType::Array)) { + LOG_ERROR << "endList() called but current context is not an array"; + } +} + +bool +docReaderJson::hasKey(const char* key) +{ + nlohmann::json* current = getCurrentObject(); + if (!current || !current->is_object()) { + return false; + } + return current->contains(key); +} + +std::string +docReaderJson::peekObjectType() +{ + nlohmann::json* current = getCurrentObject(); + if (!current || !current->is_object()) { + return ""; + } + + // Look for a "_type" key in the current object + if (current->contains(SerializationConstants::TYPE_KEY) && (*current)[SerializationConstants::TYPE_KEY].is_string()) { + return (*current)[SerializationConstants::TYPE_KEY].get(); + } + + return ""; +} + +int +docReaderJson::peekVersion() +{ + nlohmann::json* current = getCurrentObject(); + if (!current || !current->is_object()) { + return 0; + } + + // Look for a "_version" key in the current object + if (current->contains(SerializationConstants::VERSION_KEY) && + (*current)[SerializationConstants::VERSION_KEY].is_number_integer()) { + return (*current)[SerializationConstants::VERSION_KEY].get(); + } + + return 0; +} + +void +docReaderJson::readPrty(prtyProperty* p) +{ + if (!p) { + return; + } + + // Store the property name for the next read operation + m_nextKey = p->GetPropertyName(); + + // Check if the key exists + nlohmann::json* current = getCurrentObject(); + if (!current || !current->contains(m_nextKey)) { + LOG_ERROR << "readPrty() - property key not found: " << m_nextKey; + return; + } + + // Let the property read itself + // p->Read(*this); +} + +bool +docReaderJson::readBool() +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return false; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_boolean()) { + return (*current)[m_nextKey].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_boolean()) { + bool value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return false; +} + +int8_t +docReaderJson::readInt8() +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_number_integer()) { + return (*current)[m_nextKey].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { + int8_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; +} + +int32_t +docReaderJson::readInt32() +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_number_integer()) { + return (*current)[m_nextKey].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { + int32_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; +} + +uint32_t +docReaderJson::readUint32() +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_number_unsigned()) { + return (*current)[m_nextKey].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { + uint32_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; +} + +float +docReaderJson::readFloat32() +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0.0f; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_number()) { + return (*current)[m_nextKey].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number()) { + float value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0.0f; +} + +std::vector +docReaderJson::readFloat32Array() +{ + std::vector result; + nlohmann::json* current = getCurrentObject(); + if (!current) { + return result; + } + + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_array()) { + const nlohmann::json& arr = (*current)[m_nextKey]; + for (const auto& elem : arr) { + if (elem.is_number()) { + result.push_back(elem.get()); + } + } + } + + return result; +} + +std::vector +docReaderJson::readInt32Array() +{ + std::vector result; + nlohmann::json* current = getCurrentObject(); + if (!current) { + return result; + } + + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_array()) { + const nlohmann::json& arr = (*current)[m_nextKey]; + for (const auto& elem : arr) { + if (elem.is_number_integer()) { + result.push_back(elem.get()); + } + } + } + + return result; +} + +std::vector +docReaderJson::readUint32Array() +{ + std::vector result; + nlohmann::json* current = getCurrentObject(); + if (!current) { + return result; + } + + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_array()) { + const nlohmann::json& arr = (*current)[m_nextKey]; + for (const auto& elem : arr) { + if (elem.is_number_unsigned()) { + result.push_back(elem.get()); + } + } + } + + return result; +} + +std::string +docReaderJson::readString() +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return ""; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(m_nextKey) && (*current)[m_nextKey].is_string()) { + return (*current)[m_nextKey].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_string()) { + std::string value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return ""; +} + +void +docReaderJson::pushContext(nlohmann::json* obj, const std::string& name, ContextType type) +{ + m_contextStack.push(Context(obj, name, type)); +} + +bool +docReaderJson::popContext(ContextType expectedType) +{ + if (m_contextStack.empty()) { + return false; + } + + const Context& ctx = m_contextStack.top(); + if (ctx.type != expectedType) { + return false; + } + + m_contextStack.pop(); + return true; +} + +nlohmann::json* +docReaderJson::getCurrentObject() +{ + if (m_contextStack.empty()) { + return m_current; + } + return m_contextStack.top().jsonObj; +} diff --git a/renderlib/serialize/docReaderJson.h b/renderlib/serialize/docReaderJson.h new file mode 100644 index 000000000..c96f3546a --- /dev/null +++ b/renderlib/serialize/docReaderJson.h @@ -0,0 +1,85 @@ +#pragma once + +#include "docReader.h" + +#include "json/json.hpp" + +#include +#include +#include + +class docReaderJson : public docReader +{ +public: + docReaderJson(); + virtual ~docReaderJson(); + + // Document lifecycle + virtual bool beginDocument(std::string filePath) override; + virtual void endDocument() override; + + // Object support + virtual bool beginObject(const char* i_name) override; + virtual void endObject() override; + + // List/array support + virtual bool beginList(const char* i_name) override; + virtual void endList() override; + + // Key checking + virtual bool hasKey(const char* key) override; + + // Peek operations + virtual std::string peekObjectType() override; + virtual int peekVersion() override; + + // Property reading + virtual void readPrty(prtyProperty* p) override; + + // Primitive type reading + virtual bool readBool() override; + virtual int8_t readInt8() override; + virtual int32_t readInt32() override; + virtual uint32_t readUint32() override; + virtual float readFloat32() override; + virtual std::vector readFloat32Array() override; + virtual std::vector readInt32Array() override; + virtual std::vector readUint32Array() override; + virtual std::string readString() override; + +private: + enum class ContextType + { + Object, + Array + }; + + struct Context + { + nlohmann::json* jsonObj; + std::string name; + ContextType type; + size_t arrayIndex; // For tracking position in arrays + + Context(nlohmann::json* obj, const std::string& n, ContextType t) + : jsonObj(obj) + , name(n) + , type(t) + , arrayIndex(0) + { + } + + bool isArray() const { return type == ContextType::Array; } + bool isObject() const { return type == ContextType::Object; } + }; + + void pushContext(nlohmann::json* obj, const std::string& name, ContextType type); + bool popContext(ContextType expectedType); + nlohmann::json* getCurrentObject(); + + nlohmann::json* m_root; + nlohmann::json* m_current; + std::stack m_contextStack; + std::string m_nextKey; + std::string m_filePath; +}; diff --git a/renderlib/serialize/docReaderYaml.cpp b/renderlib/serialize/docReaderYaml.cpp new file mode 100644 index 000000000..afc3534f7 --- /dev/null +++ b/renderlib/serialize/docReaderYaml.cpp @@ -0,0 +1,723 @@ +#include "docReaderYaml.h" + +#include "SerializationConstants.h" +#include "core/prty/prtyProperty.hpp" +#include "Logging.h" + +#include +#include +#include + +docReaderYaml::docReaderYaml() + : m_current(nullptr) + , m_nextKey("") +{ + m_root.data = YamlObject(); +} + +docReaderYaml::~docReaderYaml() {} + +bool +docReaderYaml::beginDocument(std::string filePath) +{ + m_filePath = filePath; + m_root.data = YamlObject(); + + if (!parseYaml(filePath)) { + LOG_ERROR << "Failed to parse YAML file: " << filePath; + return false; + } + + m_current = &m_root; + + // Clear the context stack + while (!m_contextStack.empty()) { + m_contextStack.pop(); + } + + return true; +} + +void +docReaderYaml::endDocument() +{ + if (!m_contextStack.empty()) { + LOG_ERROR << "endDocument() called with " << m_contextStack.size() + << " unclosed context(s). Document may be incomplete."; + } +} + +bool +docReaderYaml::beginObject(const char* i_name) +{ + if (!m_current) { + LOG_ERROR << "beginObject() called with null current value"; + return false; + } + + YamlValue* targetObj = nullptr; + + if (m_contextStack.empty()) { + // Root level object + if (m_current->isObject()) { + YamlObject& obj = m_current->asObject(); + if (obj.find(i_name) != obj.end() && obj[i_name].isObject()) { + targetObj = &obj[i_name]; + } + } + } else { + const Context& ctx = m_contextStack.top(); + if (ctx.isArray() && ctx.value->isArray()) { + // Reading object from an array + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isObject()) { + targetObj = &arr[ctx.arrayIndex]; + } + } else if (ctx.isObject() && ctx.value->isObject()) { + // Reading object from an object + YamlObject& obj = ctx.value->asObject(); + if (obj.find(i_name) != obj.end() && obj[i_name].isObject()) { + targetObj = &obj[i_name]; + } + } + } + + if (!targetObj) { + LOG_ERROR << "beginObject() - object not found: " << i_name; + return false; + } + + pushContext(targetObj, i_name, ContextType::Object); + return true; +} + +void +docReaderYaml::endObject() +{ + if (m_contextStack.empty()) { + LOG_ERROR << "endObject() called with no matching beginObject()"; + return; + } + + if (!popContext(ContextType::Object)) { + LOG_ERROR << "endObject() called but current context is not an object"; + } +} + +bool +docReaderYaml::beginList(const char* i_name) +{ + if (!m_current) { + LOG_ERROR << "beginList() called with null current value"; + return false; + } + + YamlValue* targetArray = nullptr; + + if (m_contextStack.empty()) { + // Root level array + if (m_current->isObject()) { + YamlObject& obj = m_current->asObject(); + if (obj.find(i_name) != obj.end() && obj[i_name].isArray()) { + targetArray = &obj[i_name]; + } + } + } else { + const Context& ctx = m_contextStack.top(); + if (ctx.isArray() && ctx.value->isArray()) { + // Reading array from an array + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isArray()) { + targetArray = &arr[ctx.arrayIndex]; + } + } else if (ctx.isObject() && ctx.value->isObject()) { + // Reading array from an object + YamlObject& obj = ctx.value->asObject(); + if (obj.find(i_name) != obj.end() && obj[i_name].isArray()) { + targetArray = &obj[i_name]; + } + } + } + + if (!targetArray) { + LOG_ERROR << "beginList() - array not found: " << i_name; + return false; + } + + pushContext(targetArray, i_name, ContextType::Array); + return true; +} + +void +docReaderYaml::endList() +{ + if (m_contextStack.empty()) { + LOG_ERROR << "endList() called with no matching beginList()"; + return; + } + + if (!popContext(ContextType::Array)) { + LOG_ERROR << "endList() called but current context is not an array"; + } +} + +bool +docReaderYaml::hasKey(const char* key) +{ + YamlValue* current = getCurrentValue(); + if (!current || !current->isObject()) { + return false; + } + YamlObject& obj = current->asObject(); + return obj.find(key) != obj.end(); +} + +std::string +docReaderYaml::peekObjectType() +{ + YamlValue* current = getCurrentValue(); + if (!current || !current->isObject()) { + return ""; + } + + YamlObject& obj = current->asObject(); + if (obj.find(SerializationConstants::TYPE_KEY) != obj.end() && obj[SerializationConstants::TYPE_KEY].isString()) { + return obj[SerializationConstants::TYPE_KEY].asString(); + } + + return ""; +} + +int +docReaderYaml::peekVersion() +{ + YamlValue* current = getCurrentValue(); + if (!current || !current->isObject()) { + return 0; + } + + YamlObject& obj = current->asObject(); + if (obj.find(SerializationConstants::VERSION_KEY) != obj.end() && + obj[SerializationConstants::VERSION_KEY].isString()) { + return stringToInt(obj[SerializationConstants::VERSION_KEY].asString()); + } + + return 0; +} + +void +docReaderYaml::readPrty(prtyProperty* p) +{ + if (!p) { + return; + } + + // Store the property name for the next read operation + m_nextKey = p->GetPropertyName(); + + // Check if the key exists + if (!hasKey(m_nextKey.c_str())) { + LOG_ERROR << "readPrty() - property key not found: " << m_nextKey; + return; + } +} + +bool +docReaderYaml::readBool() +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return false; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { + return stringToBool(obj[m_nextKey].asString()); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + bool value = stringToBool(arr[ctx.arrayIndex].asString()); + ctx.arrayIndex++; + return value; + } + } + } + + return false; +} + +int8_t +docReaderYaml::readInt8() +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { + return static_cast(stringToInt(obj[m_nextKey].asString())); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + int8_t value = static_cast(stringToInt(arr[ctx.arrayIndex].asString())); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; +} + +int32_t +docReaderYaml::readInt32() +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { + return stringToInt(obj[m_nextKey].asString()); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + int32_t value = stringToInt(arr[ctx.arrayIndex].asString()); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; +} + +uint32_t +docReaderYaml::readUint32() +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { + return static_cast(stringToInt(obj[m_nextKey].asString())); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + uint32_t value = static_cast(stringToInt(arr[ctx.arrayIndex].asString())); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; +} + +float +docReaderYaml::readFloat32() +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0.0f; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { + return stringToFloat(obj[m_nextKey].asString()); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + float value = stringToFloat(arr[ctx.arrayIndex].asString()); + ctx.arrayIndex++; + return value; + } + } + } + + return 0.0f; +} + +std::vector +docReaderYaml::readFloat32Array() +{ + std::vector result; + YamlValue* current = getCurrentValue(); + if (!current || !current->isObject()) { + return result; + } + + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isArray()) { + YamlArray& arr = obj[m_nextKey].asArray(); + for (const auto& elem : arr) { + if (elem.isString()) { + result.push_back(stringToFloat(elem.asString())); + } + } + } + + return result; +} + +std::vector +docReaderYaml::readInt32Array() +{ + std::vector result; + YamlValue* current = getCurrentValue(); + if (!current || !current->isObject()) { + return result; + } + + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isArray()) { + YamlArray& arr = obj[m_nextKey].asArray(); + for (const auto& elem : arr) { + if (elem.isString()) { + result.push_back(stringToInt(elem.asString())); + } + } + } + + return result; +} + +std::vector +docReaderYaml::readUint32Array() +{ + std::vector result; + YamlValue* current = getCurrentValue(); + if (!current || !current->isObject()) { + return result; + } + + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isArray()) { + YamlArray& arr = obj[m_nextKey].asArray(); + for (const auto& elem : arr) { + if (elem.isString()) { + result.push_back(static_cast(stringToInt(elem.asString()))); + } + } + } + + return result; +} + +std::string +docReaderYaml::readString() +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return ""; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { + return obj[m_nextKey].asString(); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + std::string value = arr[ctx.arrayIndex].asString(); + ctx.arrayIndex++; + return value; + } + } + } + + return ""; +} + +bool +docReaderYaml::parseYaml(const std::string& filePath) +{ + std::ifstream file(filePath); + if (!file.is_open()) { + return false; + } + + // Skip YAML header if present + std::string line; + if (std::getline(file, line) && line != "---") { + file.seekg(0); // Reset if not a header + } + + int currentIndent = 0; + m_root.data = parseObject(file, currentIndent, 0); + + file.close(); + return true; +} + +docReaderYaml::YamlObject +docReaderYaml::parseObject(std::ifstream& file, int& currentIndent, int baseIndent) +{ + YamlObject obj; + std::string line; + std::streampos lastPos = file.tellg(); + + while (std::getline(file, line)) { + if (line.empty() || line.find_first_not_of(" \t") == std::string::npos) { + continue; // Skip empty lines + } + + std::string key; + int indent = getIndent(line); + std::string value = parseLine(line, key, indent); + + if (indent < baseIndent) { + // We've dedented, go back and return + file.seekg(lastPos); + currentIndent = indent; + break; + } + + if (indent == baseIndent && !key.empty()) { + if (value.empty()) { + // Check next line for nested content + lastPos = file.tellg(); + std::string nextLine; + if (std::getline(file, nextLine)) { + int nextIndent = getIndent(nextLine); + file.seekg(lastPos); + + if (nextIndent > indent) { + // Parse nested object + int nestedIndent = 0; + YamlValue nestedValue; + + if (nextLine.find('-') != std::string::npos && nextLine.find_first_not_of(" \t") == nextLine.find('-')) { + // It's an array + nestedValue.data = parseArray(file, nestedIndent, indent + 2); + } else { + // It's an object + nestedValue.data = parseObject(file, nestedIndent, indent + 2); + } + obj[key] = nestedValue; + } else { + // Empty value + YamlValue emptyValue; + emptyValue.data = std::string(""); + obj[key] = emptyValue; + } + } + } else { + // Simple key-value pair + YamlValue yamlValue; + yamlValue.data = value; + obj[key] = yamlValue; + } + } + + lastPos = file.tellg(); + } + + return obj; +} + +docReaderYaml::YamlArray +docReaderYaml::parseArray(std::ifstream& file, int& currentIndent, int baseIndent) +{ + YamlArray arr; + std::string line; + std::streampos lastPos = file.tellg(); + + while (std::getline(file, line)) { + if (line.empty() || line.find_first_not_of(" \t") == std::string::npos) { + continue; + } + + int indent = getIndent(line); + + if (indent < baseIndent) { + file.seekg(lastPos); + currentIndent = indent; + break; + } + + if (indent == baseIndent) { + size_t dashPos = line.find('-'); + if (dashPos != std::string::npos && dashPos == line.find_first_not_of(" \t")) { + std::string value = trimString(line.substr(dashPos + 1)); + + if (value.empty()) { + // Check for nested content + lastPos = file.tellg(); + std::string nextLine; + if (std::getline(file, nextLine)) { + int nextIndent = getIndent(nextLine); + file.seekg(lastPos); + + if (nextIndent > indent) { + YamlValue nestedValue; + int nestedIndent = 0; + nestedValue.data = parseObject(file, nestedIndent, indent + 2); + arr.push_back(nestedValue); + } + } + } else { + YamlValue yamlValue; + yamlValue.data = value; + arr.push_back(yamlValue); + } + } + } + + lastPos = file.tellg(); + } + + return arr; +} + +std::string +docReaderYaml::parseLine(const std::string& line, std::string& key, int& indent) +{ + indent = getIndent(line); + size_t colonPos = line.find(':'); + + if (colonPos != std::string::npos) { + key = trimString(line.substr(0, colonPos)); + std::string value = trimString(line.substr(colonPos + 1)); + return value; + } + + return ""; +} + +std::string +docReaderYaml::trimString(const std::string& str) +{ + size_t start = str.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) { + return ""; + } + size_t end = str.find_last_not_of(" \t\r\n"); + return str.substr(start, end - start + 1); +} + +int +docReaderYaml::getIndent(const std::string& line) +{ + int indent = 0; + for (char c : line) { + if (c == ' ') { + indent++; + } else if (c == '\t') { + indent += 2; // Treat tab as 2 spaces + } else { + break; + } + } + return indent; +} + +bool +docReaderYaml::stringToBool(const std::string& str) +{ + std::string lower = str; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + return (lower == "true" || lower == "yes" || lower == "1"); +} + +int +docReaderYaml::stringToInt(const std::string& str) +{ + try { + return std::stoi(str); + } catch (...) { + return 0; + } +} + +float +docReaderYaml::stringToFloat(const std::string& str) +{ + try { + return std::stof(str); + } catch (...) { + return 0.0f; + } +} + +void +docReaderYaml::pushContext(YamlValue* val, const std::string& name, ContextType type) +{ + m_contextStack.push(Context(val, name, type)); +} + +bool +docReaderYaml::popContext(ContextType expectedType) +{ + if (m_contextStack.empty()) { + return false; + } + + const Context& ctx = m_contextStack.top(); + if (ctx.type != expectedType) { + return false; + } + + m_contextStack.pop(); + return true; +} + +docReaderYaml::YamlValue* +docReaderYaml::getCurrentValue() +{ + if (m_contextStack.empty()) { + return m_current; + } + return m_contextStack.top().value; +} diff --git a/renderlib/serialize/docReaderYaml.h b/renderlib/serialize/docReaderYaml.h new file mode 100644 index 000000000..8abb86ea4 --- /dev/null +++ b/renderlib/serialize/docReaderYaml.h @@ -0,0 +1,122 @@ +#pragma once + +#include "docReader.h" + +#include +#include +#include +#include +#include +#include + +class docReaderYaml : public docReader +{ +public: + docReaderYaml(); + virtual ~docReaderYaml(); + + // Document lifecycle + virtual bool beginDocument(std::string filePath) override; + virtual void endDocument() override; + + // Object support + virtual bool beginObject(const char* i_name) override; + virtual void endObject() override; + + // List/array support + virtual bool beginList(const char* i_name) override; + virtual void endList() override; + + // Key checking + virtual bool hasKey(const char* key) override; + + // Peek operations + virtual std::string peekObjectType() override; + virtual int peekVersion() override; + + // Property reading + virtual void readPrty(prtyProperty* p) override; + + // Primitive type reading + virtual bool readBool() override; + virtual int8_t readInt8() override; + virtual int32_t readInt32() override; + virtual uint32_t readUint32() override; + virtual float readFloat32() override; + virtual std::vector readFloat32Array() override; + virtual std::vector readInt32Array() override; + virtual std::vector readUint32Array() override; + virtual std::string readString() override; + +private: + // Simple YAML value types + struct YamlValue; + using YamlObject = std::map; + using YamlArray = std::vector; + + struct YamlValue + { + std::variant data; + + bool isString() const { return std::holds_alternative(data); } + bool isObject() const { return std::holds_alternative(data); } + bool isArray() const { return std::holds_alternative(data); } + + std::string& asString() { return std::get(data); } + YamlObject& asObject() { return std::get(data); } + YamlArray& asArray() { return std::get(data); } + + const std::string& asString() const { return std::get(data); } + const YamlObject& asObject() const { return std::get(data); } + const YamlArray& asArray() const { return std::get(data); } + }; + + enum class ContextType + { + Object, + Array + }; + + struct Context + { + YamlValue* value; + std::string name; + ContextType type; + size_t arrayIndex; // For tracking position in arrays + + Context(YamlValue* val, const std::string& n, ContextType t) + : value(val) + , name(n) + , type(t) + , arrayIndex(0) + { + } + + bool isArray() const { return type == ContextType::Array; } + bool isObject() const { return type == ContextType::Object; } + }; + + // Parsing helpers + bool parseYaml(const std::string& filePath); + YamlValue parseValue(std::ifstream& file, int& currentIndent, int expectedIndent); + YamlObject parseObject(std::ifstream& file, int& currentIndent, int baseIndent); + YamlArray parseArray(std::ifstream& file, int& currentIndent, int baseIndent); + std::string parseLine(const std::string& line, std::string& key, int& indent); + std::string trimString(const std::string& str); + int getIndent(const std::string& line); + + // Type conversion helpers + bool stringToBool(const std::string& str); + int stringToInt(const std::string& str); + float stringToFloat(const std::string& str); + + void pushContext(YamlValue* val, const std::string& name, ContextType type); + bool popContext(ContextType expectedType); + YamlValue* getCurrentValue(); + + YamlValue m_root; + YamlValue* m_current; + std::stack m_contextStack; + std::string m_nextKey; + std::string m_filePath; +}; diff --git a/renderlib/serialize/docWriter.cpp b/renderlib/serialize/docWriter.cpp index efee4d40c..4cd8c958e 100644 --- a/renderlib/serialize/docWriter.cpp +++ b/renderlib/serialize/docWriter.cpp @@ -3,7 +3,7 @@ #include "core/prty/prtyProperty.hpp" #include "core/prty/prtyObject.hpp" -// assumes a current named object context. +// assumes a current named object context (i.e. beginObject was already called). // if objects had ONLY properties, and no children, // then this could create the object context itself using beginObject/endObject. void @@ -13,14 +13,10 @@ docWriter::writeProperties(prtyObject* obj) return; } - // beginObject(name.c_str()); - const PropertyUIIList& propList = obj->GetList(); - // std::cout << "Property list size: " << propList.size() << std::endl; for (const auto& propUIInfo : propList) { int numProps = propUIInfo->GetNumberOfProperties(); - // std::cout << "Number of properties in UIInfo: " << numProps << std::endl; for (int i = 0; i < numProps; ++i) { prtyProperty* prop = propUIInfo->GetProperty(i); @@ -29,14 +25,7 @@ docWriter::writeProperties(prtyObject* obj) continue; } - const char* type = prop->GetType(); - std::string propName = prop->GetPropertyName(); - // std::cout << "Writing property: " << propName << " of type: " << type << std::endl; - - // Set up the property name for writing writePrty(prop); } } - - // endObject(); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index eb1c013c8..3d88e01be 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,6 +16,7 @@ target_include_directories(agave_test PUBLIC target_sources(agave_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/test_commands.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_docWriter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/test_docReader.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_histogram.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_main.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_mathUtil.cpp" diff --git a/test/test_docReader.cpp b/test/test_docReader.cpp new file mode 100644 index 000000000..3c25b0ea1 --- /dev/null +++ b/test/test_docReader.cpp @@ -0,0 +1,411 @@ +#include +#include + +#include "renderlib/serialize/docReader.h" +#include "renderlib/serialize/docReaderJson.h" +#include "renderlib/serialize/docReaderYaml.h" +#include "renderlib/serialize/docWriter.h" +#include "renderlib/serialize/docWriterJson.h" +#include "renderlib/serialize/docWriterYaml.h" +#include "renderlib/serialize/SerializationConstants.h" +#include "renderlib/core/prty/prtyObject.hpp" +#include "renderlib/core/prty/prtyPropertyUIInfo.hpp" +#include "renderlib/core/prty/prtyProperty.hpp" +#include "renderlib/core/prty/prtyInt8.hpp" +#include "renderlib/core/prty/prtyInt32.hpp" +#include "renderlib/core/prty/prtyFloat.hpp" +#include "renderlib/core/prty/prtyText.hpp" +#include "renderlib/core/prty/prtyBoolean.hpp" +#include "renderlib/Logging.h" + +#include +#include +#include + +// Helper function to create a test prtyObject +prtyObject* +createTestObject() +{ + prtyObject* obj = new prtyObject(); + + auto intProp = std::make_shared(new prtyInt32("testInt", 42), "", "Test Integer"); + auto floatProp = std::make_shared(new prtyFloat("testFloat", 3.14f), "", "Test Float"); + auto stringProp = std::make_shared(new prtyText("testString", "Hello World"), "", "Test String"); + auto boolProp = std::make_shared(new prtyBoolean("testBool", true), "", "Test Boolean"); + auto int8Prop = std::make_shared(new prtyInt8("testInt8", 127), "", "Test Int8"); + + obj->AddProperty(intProp); + obj->AddProperty(floatProp); + obj->AddProperty(stringProp); + obj->AddProperty(boolProp); + obj->AddProperty(int8Prop); + + return obj; +} + +// Helper function to write a test JSON file +void +createTestJsonFile(const std::string& filePath) +{ + prtyObject* obj = createTestObject(); + + docWriterJson writer; + writer.beginDocument(filePath); + writer.beginObject("testObject"); + writer.writeProperties(obj); + writer.endObject(); + writer.endDocument(); + + delete obj; +} + +// Helper function to write a test YAML file +void +createTestYamlFile(const std::string& filePath) +{ + prtyObject* obj = createTestObject(); + + docWriterYaml writer; + writer.beginDocument(filePath); + writer.beginObject("testObject"); + writer.writeProperties(obj); + writer.endObject(); + writer.endDocument(); + + delete obj; +} + +TEST_CASE("Read prtyObject from JSON", "[serialize][docReader]") +{ + std::string jsonPath = "test_read.json"; + + // Create a test file + createTestJsonFile(jsonPath); + REQUIRE(std::filesystem::exists(jsonPath)); + + // Read the file + docReaderJson reader; + REQUIRE(reader.beginDocument(jsonPath)); + + // Navigate to the object + REQUIRE(reader.beginObject("testObject")); + + // Read individual properties + prtyInt32 testInt("testInt"); + reader.readPrty(&testInt); + int32_t intValue = reader.readInt32(); + REQUIRE(intValue == 42); + + prtyFloat testFloat("testFloat"); + reader.readPrty(&testFloat); + float floatValue = reader.readFloat32(); + REQUIRE(floatValue == Catch::Approx(3.14f)); + + prtyText testString("testString"); + reader.readPrty(&testString); + std::string stringValue = reader.readString(); + REQUIRE(stringValue == "Hello World"); + + prtyBoolean testBool("testBool"); + reader.readPrty(&testBool); + bool boolValue = reader.readBool(); + REQUIRE(boolValue == true); + + prtyInt8 testInt8("testInt8"); + reader.readPrty(&testInt8); + int8_t int8Value = reader.readInt8(); + REQUIRE(int8Value == 127); + + reader.endObject(); + reader.endDocument(); + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("Read prtyObject from YAML", "[serialize][docReader]") +{ + std::string yamlPath = "test_read.yaml"; + + // Create a test file + createTestYamlFile(yamlPath); + REQUIRE(std::filesystem::exists(yamlPath)); + + // Read the file + docReaderYaml reader; + REQUIRE(reader.beginDocument(yamlPath)); + + // Navigate to the object + REQUIRE(reader.beginObject("testObject")); + + // Read individual properties + prtyInt32 testInt("testInt"); + reader.readPrty(&testInt); + int32_t intValue = reader.readInt32(); + REQUIRE(intValue == 42); + + prtyFloat testFloat("testFloat"); + reader.readPrty(&testFloat); + float floatValue = reader.readFloat32(); + REQUIRE(floatValue == Catch::Approx(3.14f)); + + prtyText testString("testString"); + reader.readPrty(&testString); + std::string stringValue = reader.readString(); + REQUIRE(stringValue == "Hello World"); + + prtyBoolean testBool("testBool"); + reader.readPrty(&testBool); + bool boolValue = reader.readBool(); + REQUIRE(boolValue == true); + + prtyInt8 testInt8("testInt8"); + reader.readPrty(&testInt8); + int8_t int8Value = reader.readInt8(); + REQUIRE(int8Value == 127); + + reader.endObject(); + reader.endDocument(); + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("Read and write roundtrip JSON", "[serialize][docReader]") +{ + std::string jsonPath = "test_roundtrip.json"; + + // Create original object and write + prtyObject* originalObj = createTestObject(); + docWriterJson writer; + writer.beginDocument(jsonPath); + writer.beginObject("testObject"); + writer.writeProperties(originalObj); + writer.endObject(); + writer.endDocument(); + + // Read back + docReaderJson reader; + REQUIRE(reader.beginDocument(jsonPath)); + REQUIRE(reader.beginObject("testObject")); + + // Create a new object and read properties into it + prtyObject* readObj = new prtyObject(); + + auto intProp = std::make_shared(new prtyInt32("testInt", 0), "", "Test Integer"); + auto floatProp = std::make_shared(new prtyFloat("testFloat", 0.0f), "", "Test Float"); + auto stringProp = std::make_shared(new prtyText("testString", ""), "", "Test String"); + auto boolProp = std::make_shared(new prtyBoolean("testBool", false), "", "Test Boolean"); + auto int8Prop = std::make_shared(new prtyInt8("testInt8", 0), "", "Test Int8"); + + readObj->AddProperty(intProp); + readObj->AddProperty(floatProp); + readObj->AddProperty(stringProp); + readObj->AddProperty(boolProp); + readObj->AddProperty(int8Prop); + + // Read properties + reader.readPrty(intProp->GetProperty(0)); + static_cast(intProp->GetProperty(0))->SetValue(reader.readInt32()); + + reader.readPrty(floatProp->GetProperty(0)); + static_cast(floatProp->GetProperty(0))->SetValue(reader.readFloat32()); + + reader.readPrty(stringProp->GetProperty(0)); + static_cast(stringProp->GetProperty(0))->SetValue(reader.readString()); + + reader.readPrty(boolProp->GetProperty(0)); + static_cast(boolProp->GetProperty(0))->SetValue(reader.readBool()); + + reader.readPrty(int8Prop->GetProperty(0)); + static_cast(int8Prop->GetProperty(0))->SetValue(reader.readInt8()); + + reader.endObject(); + reader.endDocument(); + + // Verify values match + REQUIRE(static_cast(intProp->GetProperty(0))->GetValue() == 42); + REQUIRE(static_cast(floatProp->GetProperty(0))->GetValue() == Catch::Approx(3.14f)); + REQUIRE(static_cast(stringProp->GetProperty(0))->GetValue() == "Hello World"); + REQUIRE(static_cast(boolProp->GetProperty(0))->GetValue() == true); + REQUIRE(static_cast(int8Prop->GetProperty(0))->GetValue() == 127); + + // Cleanup + delete originalObj; + delete readObj; + std::filesystem::remove(jsonPath); +} + +TEST_CASE("Read and write roundtrip YAML", "[serialize][docReader]") +{ + std::string yamlPath = "test_roundtrip.yaml"; + + // Create original object and write + prtyObject* originalObj = createTestObject(); + docWriterYaml writer; + writer.beginDocument(yamlPath); + writer.beginObject("testObject"); + writer.writeProperties(originalObj); + writer.endObject(); + writer.endDocument(); + + // Read back + docReaderYaml reader; + REQUIRE(reader.beginDocument(yamlPath)); + REQUIRE(reader.beginObject("testObject")); + + // Create a new object and read properties into it + prtyObject* readObj = new prtyObject(); + + auto intProp = std::make_shared(new prtyInt32("testInt", 0), "", "Test Integer"); + auto floatProp = std::make_shared(new prtyFloat("testFloat", 0.0f), "", "Test Float"); + auto stringProp = std::make_shared(new prtyText("testString", ""), "", "Test String"); + auto boolProp = std::make_shared(new prtyBoolean("testBool", false), "", "Test Boolean"); + auto int8Prop = std::make_shared(new prtyInt8("testInt8", 0), "", "Test Int8"); + + readObj->AddProperty(intProp); + readObj->AddProperty(floatProp); + readObj->AddProperty(stringProp); + readObj->AddProperty(boolProp); + readObj->AddProperty(int8Prop); + + // Read properties + reader.readPrty(intProp->GetProperty(0)); + static_cast(intProp->GetProperty(0))->SetValue(reader.readInt32()); + + reader.readPrty(floatProp->GetProperty(0)); + static_cast(floatProp->GetProperty(0))->SetValue(reader.readFloat32()); + + reader.readPrty(stringProp->GetProperty(0)); + static_cast(stringProp->GetProperty(0))->SetValue(reader.readString()); + + reader.readPrty(boolProp->GetProperty(0)); + static_cast(boolProp->GetProperty(0))->SetValue(reader.readBool()); + + reader.readPrty(int8Prop->GetProperty(0)); + static_cast(int8Prop->GetProperty(0))->SetValue(reader.readInt8()); + + reader.endObject(); + reader.endDocument(); + + // Verify values match + REQUIRE(static_cast(intProp->GetProperty(0))->GetValue() == 42); + REQUIRE(static_cast(floatProp->GetProperty(0))->GetValue() == Catch::Approx(3.14f)); + REQUIRE(static_cast(stringProp->GetProperty(0))->GetValue() == "Hello World"); + REQUIRE(static_cast(boolProp->GetProperty(0))->GetValue() == true); + REQUIRE(static_cast(int8Prop->GetProperty(0))->GetValue() == 127); + + // Cleanup + delete originalObj; + delete readObj; + std::filesystem::remove(yamlPath); +} + +TEST_CASE("Test peek operations JSON", "[serialize][docReader]") +{ + std::string jsonPath = "test_peek.json"; + + // Create a JSON file with type and version + std::ofstream file(jsonPath); + file << "{\n"; + file << " \"" << SerializationConstants::TYPE_KEY << "\": \"Camera\",\n"; + file << " \"" << SerializationConstants::VERSION_KEY << "\": 2,\n"; + file << " \"testData\": 123\n"; + file << "}\n"; + file.close(); + + // Read and peek + docReaderJson reader; + REQUIRE(reader.beginDocument(jsonPath)); + + std::string type = reader.peekObjectType(); + REQUIRE(type == "Camera"); + + int version = reader.peekVersion(); + REQUIRE(version == 2); + + reader.endDocument(); + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("Test peek operations YAML", "[serialize][docReader]") +{ + std::string yamlPath = "test_peek.yaml"; + + // Create a YAML file with type and version + std::ofstream file(yamlPath); + file << "---\n"; + file << SerializationConstants::TYPE_KEY << ": Camera\n"; + file << SerializationConstants::VERSION_KEY << ": 2\n"; + file << "testData: 123\n"; + file.close(); + + // Read and peek + docReaderYaml reader; + REQUIRE(reader.beginDocument(yamlPath)); + + std::string type = reader.peekObjectType(); + REQUIRE(type == "Camera"); + + int version = reader.peekVersion(); + REQUIRE(version == 2); + + reader.endDocument(); + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("Test hasKey operation JSON", "[serialize][docReader]") +{ + std::string jsonPath = "test_haskey.json"; + + createTestJsonFile(jsonPath); + + docReaderJson reader; + REQUIRE(reader.beginDocument(jsonPath)); + REQUIRE(reader.beginObject("testObject")); + + // Check for existing keys + REQUIRE(reader.hasKey("testInt")); + REQUIRE(reader.hasKey("testFloat")); + REQUIRE(reader.hasKey("testString")); + REQUIRE(reader.hasKey("testBool")); + + // Check for non-existing key + REQUIRE_FALSE(reader.hasKey("nonExistentKey")); + + reader.endObject(); + reader.endDocument(); + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("Test hasKey operation YAML", "[serialize][docReader]") +{ + std::string yamlPath = "test_haskey.yaml"; + + createTestYamlFile(yamlPath); + + docReaderYaml reader; + REQUIRE(reader.beginDocument(yamlPath)); + REQUIRE(reader.beginObject("testObject")); + + // Check for existing keys + REQUIRE(reader.hasKey("testInt")); + REQUIRE(reader.hasKey("testFloat")); + REQUIRE(reader.hasKey("testString")); + REQUIRE(reader.hasKey("testBool")); + + // Check for non-existing key + REQUIRE_FALSE(reader.hasKey("nonExistentKey")); + + reader.endObject(); + reader.endDocument(); + + // Cleanup + std::filesystem::remove(yamlPath); +} From c517037735a381c0a80444bd777e84e6d82bbd7d Mon Sep 17 00:00:00 2001 From: dmt Date: Mon, 1 Dec 2025 19:17:10 -0800 Subject: [PATCH 29/63] wip --- agave_app/agaveGui.cpp | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index f0d8d94b2..c6d61a9cb 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -739,6 +739,41 @@ agaveGui::saveJson() docWriterJson writer; writer.beginDocument(file.toStdString()); + writer.beginObject("_AGAVE"); + // write agave version at least + writer.endObject(); + writer.beginList("_camera"); + // list of all cameras + writer.endList(); + writer.beginList("_light"); + // list of all lights + writer.endList(); + writer.beginList("_clipPlane"); + // list of all clip planes + writer.endList(); + writer.beginList("_renderSettings"); + // list of all render settings objects + writer.endList(); + writer.beginList("_captureSettings"); + // list of capture settings objects (only one?) + writer.endList(); + + writer.beginObject("_geometry"); + writer.beginList("_volume"); + // list of all volumes + writer.endList(); + writer.beginList("_mesh"); + // list of all meshes + writer.endList(); + writer.endObject(); + + writer.beginObject("_scene"); + // one and only active scene. + // this includes references to selections of the above objects. + // other objects should not be cross referencing each other. + // so scene needs to be set up after other objects are defined. + writer.endObject(); + writer.endDocument(); } } From 2fe73ecccc1e5dc392ab73a4c03a9c69aac961b9 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Dec 2025 15:29:27 -0800 Subject: [PATCH 30/63] fix cmakelists --- agave_app/CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/agave_app/CMakeLists.txt b/agave_app/CMakeLists.txt index 5ca90c0e9..e05e471aa 100644 --- a/agave_app/CMakeLists.txt +++ b/agave_app/CMakeLists.txt @@ -37,10 +37,6 @@ target_sources(agaveapp PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/cgiparser.h" "${CMAKE_CURRENT_SOURCE_DIR}/commandBuffer.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/commandBuffer.h" - "${CMAKE_CURRENT_SOURCE_DIR}/Film.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/Film.h" - "${CMAKE_CURRENT_SOURCE_DIR}/Focus.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/Focus.h" "${CMAKE_CURRENT_SOURCE_DIR}/GLView3D.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/GLView3D.h" "${CMAKE_CURRENT_SOURCE_DIR}/loadDialog.cpp" From e48a21b01075db97894fcad86ba6a27d404ada09 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Dec 2025 16:12:20 -0800 Subject: [PATCH 31/63] fix uiinfo --- renderlib/uiInfo.hpp | 141 +++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 73 deletions(-) diff --git a/renderlib/uiInfo.hpp b/renderlib/uiInfo.hpp index 5a3ac8c72..5e344432a 100644 --- a/renderlib/uiInfo.hpp +++ b/renderlib/uiInfo.hpp @@ -1,77 +1,62 @@ #pragma once +#include "core/prty/prtyPropertyUIInfo.hpp" + #include #include -struct GenericUIInfo +class CheckBoxUiInfo : public prtyPropertyUIInfo { - std::string type; - std::string formLabel; - std::string statusTip; - std::string toolTip; - - GenericUIInfo() = default; - GenericUIInfo(std::string type, std::string formLabel, std::string statusTip, std::string toolTip) - : type(type) - , formLabel(formLabel) - , statusTip(statusTip) - , toolTip(toolTip) +public: + static constexpr const char* TYPE = "CheckBox"; + CheckBoxUiInfo(prtyProperty* i_pProperty) + : prtyPropertyUIInfo(i_pProperty) { + SetControlName(TYPE); } -}; -struct CheckBoxUiInfo : public GenericUIInfo -{ - static constexpr const char* TYPE = "CheckBox"; - - CheckBoxUiInfo() { type = CheckBoxUiInfo::TYPE; } - CheckBoxUiInfo(std::string formLabel, std::string statusTip, std::string toolTip) - : GenericUIInfo(CheckBoxUiInfo::TYPE, formLabel, statusTip, toolTip) + CheckBoxUiInfo(prtyProperty* i_pProperty, const std::string& i_Category, const std::string& i_Description) + : prtyPropertyUIInfo(i_pProperty, i_Category, i_Description) { + SetControlName(TYPE); } }; -struct ComboBoxUiInfo : public GenericUIInfo -{ - static constexpr const char* TYPE = "ComboBox"; - std::vector items; - ComboBoxUiInfo() { type = ComboBoxUiInfo::TYPE; } - ComboBoxUiInfo(std::string formLabel, std::string statusTip, std::string toolTip, std::vector items) - : GenericUIInfo(ComboBoxUiInfo::TYPE, formLabel, statusTip, toolTip) - , items(items) +class ColorPickerUiInfo : public prtyPropertyUIInfo +{ +public: + static constexpr const char* TYPE = "ColorPicker"; + ColorPickerUiInfo(prtyProperty* i_pProperty) + : prtyPropertyUIInfo(i_pProperty) { + SetControlName(TYPE); + } + ColorPickerUiInfo(prtyProperty* i_pProperty, const std::string& i_Category, const std::string& i_Description) + : prtyPropertyUIInfo(i_pProperty, i_Category, i_Description) + { + SetControlName(TYPE); } }; -class ColorWithIntensityUiInfo : public prtyPropertyUIInfo +class ComboBoxUiInfo : public prtyPropertyUIInfo { public: - static constexpr const char* TYPE = "ColorWithIntensity"; - float min = 0.0f; - float max = 0.0f; - int decimals = 0; - float singleStep = 0.0f; - int numTickMarks = 0; - std::string suffix; + static constexpr const char* TYPE = "ComboBox"; + std::vector items; - ColorWithIntensityUiInfo(prtyProperty* i_pColorProperty, prtyProperty* i_pIntensityProperty) - : prtyPropertyUIInfo(i_pColorProperty) + ComboBoxUiInfo(prtyProperty* i_pProperty) + : prtyPropertyUIInfo(i_pProperty) { - AddProperty(i_pIntensityProperty); SetControlName(TYPE); } - ColorWithIntensityUiInfo(prtyProperty* i_pColorProperty, - prtyProperty* i_pIntensityProperty, - const std::string& i_Category, - const std::string& i_Description) - : prtyPropertyUIInfo(i_pColorProperty, i_Category, i_Description) + ComboBoxUiInfo(prtyProperty* i_pProperty, const std::string& i_Category, const std::string& i_Description) + : prtyPropertyUIInfo(i_pProperty, i_Category, i_Description) { - AddProperty(i_pIntensityProperty); SetControlName(TYPE); } }; - class FloatSliderSpinnerUiInfo : public prtyPropertyUIInfo { +public: static constexpr const char* TYPE = "FloatSliderSpinner"; float min = 0.0f; float max = 0.0f; @@ -80,28 +65,39 @@ class FloatSliderSpinnerUiInfo : public prtyPropertyUIInfo int numTickMarks = 0; std::string suffix; - FloatSliderSpinnerUiInfo() { type = FloatSliderSpinnerUiInfo::TYPE; } - FloatSliderSpinnerUiInfo(std::string formLabel, - std::string statusTip, - std::string toolTip, - float min, - float max, - int decimals, - float singleStep, - int numTickMarks = 0, - std::string suffix = "") - : GenericUIInfo(FloatSliderSpinnerUiInfo::TYPE, formLabel, statusTip, toolTip) - , min(min) - , max(max) - , decimals(decimals) - , singleStep(singleStep) - , numTickMarks(numTickMarks) - , suffix(suffix) + FloatSliderSpinnerUiInfo(prtyProperty* i_pProperty) + : prtyPropertyUIInfo(i_pProperty) + { + SetControlName(TYPE); + } + FloatSliderSpinnerUiInfo(prtyProperty* i_pProperty, const std::string& i_Category, const std::string& i_Description) + : prtyPropertyUIInfo(i_pProperty, i_Category, i_Description) { + SetControlName(TYPE); } }; -struct IntSliderSpinnerUiInfo : public GenericUIInfo +// std::string statusTip, +// std::string toolTip, +// float min, +// float max, +// int decimals, +// float singleStep, +// int numTickMarks = 0, +// std::string suffix = "") +// : GenericUIInfo(FloatSliderSpinnerUiInfo::TYPE, formLabel, statusTip, toolTip) +// , min(min) +// , max(max) +// , decimals(decimals) +// , singleStep(singleStep) +// , numTickMarks(numTickMarks) +// , suffix(suffix) +// { +// } +// }; + +class IntSliderSpinnerUiInfo : public prtyPropertyUIInfo { +public: static constexpr const char* TYPE = "IntSliderSpinner"; int min; int max; @@ -109,16 +105,15 @@ struct IntSliderSpinnerUiInfo : public GenericUIInfo int numTickMarks; std::string suffix; - IntSliderSpinnerUiInfo() { type = IntSliderSpinnerUiInfo::TYPE; } -}; - -struct ColorPickerUiInfo : public GenericUIInfo -{ - static constexpr const char* TYPE = "ColorPicker"; + IntSliderSpinnerUiInfo(prtyProperty* i_pProperty) + : prtyPropertyUIInfo(i_pProperty) + { + SetControlName(TYPE); + } - ColorPickerUiInfo() { type = ColorPickerUiInfo::TYPE; } - ColorPickerUiInfo(std::string formLabel, std::string statusTip, std::string toolTip) - : GenericUIInfo(ColorPickerUiInfo::TYPE, formLabel, statusTip, toolTip) + IntSliderSpinnerUiInfo(prtyProperty* i_pProperty, const std::string& i_Category, const std::string& i_Description) + : prtyPropertyUIInfo(i_pProperty, i_Category, i_Description) { + SetControlName(TYPE); } }; From c930d96c0c8d6a2e0bd530d218fe56d440e9f60a Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Dec 2025 16:12:35 -0800 Subject: [PATCH 32/63] fix uiinfo part 2 --- renderlib/uiInfo.hpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/renderlib/uiInfo.hpp b/renderlib/uiInfo.hpp index 5e344432a..8c4030c61 100644 --- a/renderlib/uiInfo.hpp +++ b/renderlib/uiInfo.hpp @@ -41,7 +41,6 @@ class ComboBoxUiInfo : public prtyPropertyUIInfo { public: static constexpr const char* TYPE = "ComboBox"; - std::vector items; ComboBoxUiInfo(prtyProperty* i_pProperty) : prtyPropertyUIInfo(i_pProperty) @@ -54,6 +53,34 @@ class ComboBoxUiInfo : public prtyPropertyUIInfo SetControlName(TYPE); } }; +class ColorWithIntensityUiInfo : public prtyPropertyUIInfo +{ +public: + static constexpr const char* TYPE = "ColorWithIntensity"; + float min = 0.0f; + float max = 0.0f; + int decimals = 0; + float singleStep = 0.0f; + int numTickMarks = 0; + std::string suffix; + + ColorWithIntensityUiInfo(prtyProperty* i_pColorProperty, prtyProperty* i_pIntensityProperty) + : prtyPropertyUIInfo(i_pColorProperty) + { + AddProperty(i_pIntensityProperty); + SetControlName(TYPE); + } + ColorWithIntensityUiInfo(prtyProperty* i_pColorProperty, + prtyProperty* i_pIntensityProperty, + const std::string& i_Category, + const std::string& i_Description) + : prtyPropertyUIInfo(i_pColorProperty, i_Category, i_Description) + { + AddProperty(i_pIntensityProperty); + SetControlName(TYPE); + } +}; + class FloatSliderSpinnerUiInfo : public prtyPropertyUIInfo { public: From 2b9fadd48499dab0550381f60a90a4f3cdcd9142 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Dec 2025 16:12:48 -0800 Subject: [PATCH 33/63] fix typo --- renderlib/AppearanceDataObject.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renderlib/AppearanceDataObject.hpp b/renderlib/AppearanceDataObject.hpp index 02df6f82e..35774b7fc 100644 --- a/renderlib/AppearanceDataObject.hpp +++ b/renderlib/AppearanceDataObject.hpp @@ -7,7 +7,7 @@ #include "core/prty/prtyColor.hpp" #include "glm.h" -class AppearanceDataOject +class AppearanceDataObject { public: AppearanceDataObject(); From 2c6950682c0a1624f5ad8633a22ba9c3aff4cb89 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Dec 2025 16:24:57 -0800 Subject: [PATCH 34/63] fix viewerwindow --- renderlib/ViewerWindow.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/renderlib/ViewerWindow.cpp b/renderlib/ViewerWindow.cpp index 4968cd23e..ee7dc20c0 100644 --- a/renderlib/ViewerWindow.cpp +++ b/renderlib/ViewerWindow.cpp @@ -137,10 +137,6 @@ ViewerWindow::updateCamera() // let renderer know camera is dirty m_renderSettings->m_DirtyFlags.SetFlag(CameraDirty); } - if (m_CCamera.m_Dirty) { - m_renderSettings->m_DirtyFlags.SetFlag(CameraDirty); - m_CCamera.m_Dirty = false; - } sceneView.camera = renderCamera; sceneView.camera.Update(); From 3e9d951dcc05b07c0414ab87969c59181cf8ac1b Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Dec 2025 16:35:25 -0800 Subject: [PATCH 35/63] fix appearanceobject.cpp --- renderlib/AppearanceObject.cpp | 231 ++++++++++++++++++++++++++++----- 1 file changed, 195 insertions(+), 36 deletions(-) diff --git a/renderlib/AppearanceObject.cpp b/renderlib/AppearanceObject.cpp index ed1fc23ed..a641cb328 100644 --- a/renderlib/AppearanceObject.cpp +++ b/renderlib/AppearanceObject.cpp @@ -1,5 +1,6 @@ -#include "AppearanceUiDescription.hpp" +#include "AppearanceObject.hpp" +#include "Logging.h" // ComboBoxUiInfo AppearanceUiDescription::m_rendererType("Renderer Type", // "Select volume rendering type", // "Select volume rendering type", @@ -55,22 +56,101 @@ AppearanceObject::AppearanceObject() : prtyObject() { - m_RenderSettings = std::make_shared(); - m_rendererType = new ComboBoxUiInfo(&m_appearanceDataObject.RendererType, "Appearance", "Renderer Type"); - m_shadingType = new ComboBoxUiInfo(&m_appearanceDataObject.ShadingType, "Appearance", "Shading Type"); - m_densityScale = new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.DensityScale, "Appearance", "Density Scale"); - m_gradientFactor = - new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.GradientFactor, "Appearance", "Gradient Factor"); + std::string category("Rendering"); + m_renderSettings = std::make_shared(); + m_rendererType = new ComboBoxUiInfo(&m_appearanceDataObject.RendererType, category, "Renderer Type"); + m_rendererType->SetToolTip("Select volume rendering type"); + m_rendererType->SetStatusTip("Select volume rendering type"); + AddProperty(m_rendererType); + m_shadingType = new ComboBoxUiInfo(&m_appearanceDataObject.ShadingType, category, "Shading Type"); + m_shadingType->SetToolTip("Select volume shading style"); + m_shadingType->SetStatusTip("Select volume shading style"); + AddProperty(m_shadingType); + m_densityScale = new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.DensityScale, category, "Density Scale"); + m_densityScale->SetToolTip("Set scattering density for volume"); + m_densityScale->SetStatusTip("Set scattering density for volume"); + m_densityScale->min = 0.001f; + m_densityScale->max = 100.0f; + m_densityScale->decimals = 3; // decimals + m_densityScale->singleStep = 0.01f; // singleStep + m_densityScale->numTickMarks = 10; // numTickMarks + m_densityScale->suffix = ""; // suffix + AddProperty(m_densityScale); + m_gradientFactor = new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.GradientFactor, category, "Gradient Factor"); + m_gradientFactor->SetToolTip("Mix between BRDF and Phase shading"); + m_gradientFactor->SetStatusTip("Mix between BRDF and Phase shading"); + m_gradientFactor->min = 0.0f; + m_gradientFactor->max = 1.0f; + m_gradientFactor->decimals = 3; // decimals + m_gradientFactor->singleStep = 0.01f; // singleStep + m_gradientFactor->numTickMarks = 10; // numTickMarks + m_gradientFactor->suffix = ""; // suffix + AddProperty(m_gradientFactor); m_stepSizePrimaryRay = - new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.StepSizePrimaryRay, "Appearance", "Step Size Primary Ray"); + new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.StepSizePrimaryRay, category, "Step Size Primary Ray"); + m_stepSizePrimaryRay->SetToolTip("Set volume ray march step size for camera rays"); + m_stepSizePrimaryRay->SetStatusTip("Set volume ray march step size for camera rays"); + m_stepSizePrimaryRay->min = 1.0f; + m_stepSizePrimaryRay->max = 100.0f; + m_stepSizePrimaryRay->decimals = 3; // decimals + m_stepSizePrimaryRay->singleStep = 0.01f; // singleStep + m_stepSizePrimaryRay->numTickMarks = 10; // numTickMarks + m_stepSizePrimaryRay->suffix = ""; // suffix + AddProperty(m_stepSizePrimaryRay); m_stepSizeSecondaryRay = - new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.StepSizeSecondaryRay, "Appearance", "Step Size Secondary Ray"); - m_interpolate = new CheckBoxUiInfo(&m_appearanceDataObject.Interpolate, "Appearance", "Interpolate"); - m_backgroundColor = new ColorPickerUiInfo(&m_appearanceDataObject.BackgroundColor, "Appearance", "Background Color"); - m_showBoundingBox = new CheckBoxUiInfo(&m_appearanceDataObject.ShowBoundingBox, "Appearance", "Show Bounding Box"); - m_boundingBoxColor = - new ColorPickerUiInfo(&m_appearanceDataObject.BoundingBoxColor, "Appearance", "Bounding Box Color"); - m_showScaleBar = new CheckBoxUiInfo(&m_appearanceDataObject.ShowScaleBar, "Appearance", "Show Scale Bar"); + new FloatSliderSpinnerUiInfo(&m_appearanceDataObject.StepSizeSecondaryRay, category, "Step Size Secondary Ray"); + m_stepSizeSecondaryRay->SetToolTip("Set volume ray march step size for scattered rays"); + m_stepSizeSecondaryRay->SetStatusTip("Set volume ray march step size for scattered rays"); + m_stepSizeSecondaryRay->min = 1.0f; + m_stepSizeSecondaryRay->max = 100.0f; + m_stepSizeSecondaryRay->decimals = 3; // decimals + m_stepSizeSecondaryRay->singleStep = 0.01f; // singleStep + m_stepSizeSecondaryRay->numTickMarks = 10; // numTickMarks + m_stepSizeSecondaryRay->suffix = ""; // suffix + AddProperty(m_stepSizeSecondaryRay); + m_interpolate = new CheckBoxUiInfo(&m_appearanceDataObject.Interpolate, category, "Interpolate"); + m_interpolate->SetToolTip("Interpolated volume sampling"); + m_interpolate->SetStatusTip("Interpolated volume sampling"); + AddProperty(m_interpolate); + m_backgroundColor = new ColorPickerUiInfo(&m_appearanceDataObject.BackgroundColor, category, "Background Color"); + m_backgroundColor->SetToolTip("Set background color"); + m_backgroundColor->SetStatusTip("Set background color"); + AddProperty(m_backgroundColor); + m_showBoundingBox = new CheckBoxUiInfo(&m_appearanceDataObject.ShowBoundingBox, category, "Show Bounding Box"); + m_showBoundingBox->SetToolTip("Show/hide bounding box"); + m_showBoundingBox->SetStatusTip("Show/hide bounding box"); + AddProperty(m_showBoundingBox); + m_boundingBoxColor = new ColorPickerUiInfo(&m_appearanceDataObject.BoundingBoxColor, category, "Bounding Box Color"); + m_boundingBoxColor->SetToolTip("Set bounding box color"); + m_boundingBoxColor->SetStatusTip("Set bounding box color"); + AddProperty(m_boundingBoxColor); + m_showScaleBar = new CheckBoxUiInfo(&m_appearanceDataObject.ShowScaleBar, category, "Show Scale Bar"); + m_showScaleBar->SetToolTip("Show/hide scale bar"); + m_showScaleBar->SetStatusTip("Show/hide scale bar"); + AddProperty(m_showScaleBar); + + m_appearanceDataObject.RendererType.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::RendererTypeChanged)); + m_appearanceDataObject.ShadingType.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::ShadingTypeChanged)); + m_appearanceDataObject.DensityScale.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::DensityScaleChanged)); + m_appearanceDataObject.GradientFactor.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::GradientFactorChanged)); + m_appearanceDataObject.StepSizePrimaryRay.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::StepSizePrimaryRayChanged)); + m_appearanceDataObject.StepSizeSecondaryRay.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::StepSizeSecondaryRayChanged)); + m_appearanceDataObject.Interpolate.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::InterpolateChanged)); + m_appearanceDataObject.BackgroundColor.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::BackgroundColorChanged)); + m_appearanceDataObject.ShowBoundingBox.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::ShowBoundingBoxChanged)); + m_appearanceDataObject.BoundingBoxColor.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::BoundingBoxColorChanged)); + m_appearanceDataObject.ShowScaleBar.AddCallback( + new prtyCallbackWrapper(this, &AppearanceObject::ShowScaleBarChanged)); } void @@ -85,17 +165,17 @@ AppearanceObject::updatePropsFromObject() m_appearanceDataObject.StepSizeSecondaryRay.SetValue(m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow); m_appearanceDataObject.Interpolate.SetValue(m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling); } - if (m_scene) { - BackgroundColor.SetValue(glm::vec4(m_scene->m_material.m_backgroundColor[0], - m_scene->m_material.m_backgroundColor[1], - m_scene->m_material.m_backgroundColor[2], - 1.0f)); - ShowBoundingBox.SetValue(m_scene->m_material.m_showBoundingBox); - BoundingBoxColor.SetValue(glm::vec4(m_scene->m_material.m_boundingBoxColor[0], - m_scene->m_material.m_boundingBoxColor[1], - m_scene->m_material.m_boundingBoxColor[2], - 1.0f)); - ShowScaleBar.SetValue(m_scene->m_showScaleBar); + if (auto scene = m_scene.lock()) { + m_appearanceDataObject.BackgroundColor.SetValue(glm::vec4(scene->m_material.m_backgroundColor[0], + scene->m_material.m_backgroundColor[1], + scene->m_material.m_backgroundColor[2], + 1.0f)); + m_appearanceDataObject.ShowBoundingBox.SetValue(scene->m_material.m_showBoundingBox); + m_appearanceDataObject.BoundingBoxColor.SetValue(glm::vec4(scene->m_material.m_boundingBoxColor[0], + scene->m_material.m_boundingBoxColor[1], + scene->m_material.m_boundingBoxColor[2], + 1.0f)); + m_appearanceDataObject.ShowScaleBar.SetValue(scene->m_showScaleBar); } } @@ -111,17 +191,96 @@ AppearanceObject::updateObjectFromProps() m_renderSettings->m_RenderSettings.m_StepSizeFactor = m_appearanceDataObject.StepSizePrimaryRay.GetValue(); m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow = m_appearanceDataObject.StepSizeSecondaryRay.GetValue(); m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling = m_appearanceDataObject.Interpolate.GetValue(); - m_scene->m_material.m_backgroundColor[0] = m_appearanceDataObject.BackgroundColor.GetValue().x; - m_scene->m_material.m_backgroundColor[1] = m_appearanceDataObject.BackgroundColor.GetValue().y; - m_scene->m_material.m_backgroundColor[2] = m_appearanceDataObject.BackgroundColor.GetValue().z; - m_scene->m_material.m_showBoundingBox = m_appearanceDataObject.ShowBoundingBox.GetValue(); - m_scene->m_material.m_boundingBoxColor[0] = m_appearanceDataObject.BoundingBoxColor.GetValue().x; - m_scene->m_material.m_boundingBoxColor[1] = m_appearanceDataObject.BoundingBoxColor.GetValue().y; - m_scene->m_material.m_boundingBoxColor[2] = m_appearanceDataObject.BoundingBoxColor.GetValue().z; - m_scene->m_showScaleBar = m_appearanceDataObject.ShowScaleBar.GetValue(); - + if (auto scene = m_scene.lock()) { + scene->m_material.m_backgroundColor[0] = m_appearanceDataObject.BackgroundColor.GetValue().x; + scene->m_material.m_backgroundColor[1] = m_appearanceDataObject.BackgroundColor.GetValue().y; + scene->m_material.m_backgroundColor[2] = m_appearanceDataObject.BackgroundColor.GetValue().z; + scene->m_material.m_showBoundingBox = m_appearanceDataObject.ShowBoundingBox.GetValue(); + scene->m_material.m_boundingBoxColor[0] = m_appearanceDataObject.BoundingBoxColor.GetValue().x; + scene->m_material.m_boundingBoxColor[1] = m_appearanceDataObject.BoundingBoxColor.GetValue().y; + scene->m_material.m_boundingBoxColor[2] = m_appearanceDataObject.BoundingBoxColor.GetValue().z; + scene->m_showScaleBar = m_appearanceDataObject.ShowScaleBar.GetValue(); + } m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); m_renderSettings->m_DirtyFlags.SetFlag(TransferFunctionDirty); m_renderSettings->m_DirtyFlags.SetFlag(LightsDirty); } -} \ No newline at end of file +} + +void +AppearanceObject::RendererTypeChanged(prtyProperty* i_Property, bool i_bDirty) +{ + LOG_ERROR << "Renderer type changed, but not implemented yet!"; +} +void +AppearanceObject::ShadingTypeChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_renderSettings->m_RenderSettings.m_ShadingType = m_appearanceDataObject.ShadingType.GetValue(); + m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); +} +void +AppearanceObject::DensityScaleChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_renderSettings->m_RenderSettings.m_DensityScale = m_appearanceDataObject.DensityScale.GetValue(); + m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); +} +void +AppearanceObject::GradientFactorChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_renderSettings->m_RenderSettings.m_GradientFactor = m_appearanceDataObject.GradientFactor.GetValue(); + m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); +} +void +AppearanceObject::StepSizePrimaryRayChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_renderSettings->m_RenderSettings.m_StepSizeFactor = m_appearanceDataObject.StepSizePrimaryRay.GetValue(); + m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); +} +void +AppearanceObject::StepSizeSecondaryRayChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_renderSettings->m_RenderSettings.m_StepSizeFactorShadow = m_appearanceDataObject.StepSizeSecondaryRay.GetValue(); + m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); +} +void +AppearanceObject::InterpolateChanged(prtyProperty* i_Property, bool i_bDirty) +{ + m_renderSettings->m_RenderSettings.m_InterpolatedVolumeSampling = m_appearanceDataObject.Interpolate.GetValue(); + m_renderSettings->m_DirtyFlags.SetFlag(RenderParamsDirty); +} +void +AppearanceObject::BackgroundColorChanged(prtyProperty* i_Property, bool i_bDirty) +{ + if (auto scene = m_scene.lock()) { + scene->m_material.m_backgroundColor[0] = m_appearanceDataObject.BackgroundColor.GetValue().x; + scene->m_material.m_backgroundColor[1] = m_appearanceDataObject.BackgroundColor.GetValue().y; + scene->m_material.m_backgroundColor[2] = m_appearanceDataObject.BackgroundColor.GetValue().z; + m_renderSettings->m_DirtyFlags.SetFlag(EnvironmentDirty); + } +} +void +AppearanceObject::ShowBoundingBoxChanged(prtyProperty* i_Property, bool i_bDirty) +{ + if (auto scene = m_scene.lock()) { + scene->m_material.m_showBoundingBox = m_appearanceDataObject.ShowBoundingBox.GetValue(); + m_renderSettings->m_DirtyFlags.SetFlag(EnvironmentDirty); + } +} +void +AppearanceObject::BoundingBoxColorChanged(prtyProperty* i_Property, bool i_bDirty) +{ + if (auto scene = m_scene.lock()) { + scene->m_material.m_boundingBoxColor[0] = m_appearanceDataObject.BoundingBoxColor.GetValue().x; + scene->m_material.m_boundingBoxColor[1] = m_appearanceDataObject.BoundingBoxColor.GetValue().y; + scene->m_material.m_boundingBoxColor[2] = m_appearanceDataObject.BoundingBoxColor.GetValue().z; + m_renderSettings->m_DirtyFlags.SetFlag(EnvironmentDirty); + } +} +void +AppearanceObject::ShowScaleBarChanged(prtyProperty* i_Property, bool i_bDirty) +{ + if (auto scene = m_scene.lock()) { + scene->m_showScaleBar = m_appearanceDataObject.ShowScaleBar.GetValue(); + m_renderSettings->m_DirtyFlags.SetFlag(EnvironmentDirty); + } +} From 5700931bfca6faf24cf61eaac70cf4cc7fd191b9 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Dec 2025 16:38:14 -0800 Subject: [PATCH 36/63] fix AppearanceObject.hpp --- renderlib/AppearanceObject.hpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/renderlib/AppearanceObject.hpp b/renderlib/AppearanceObject.hpp index 49e1321cf..367748468 100644 --- a/renderlib/AppearanceObject.hpp +++ b/renderlib/AppearanceObject.hpp @@ -3,6 +3,7 @@ #include "AppearanceDataObject.hpp" #include "core/prty/prtyObject.hpp" #include "RenderSettings.h" +#include "AppScene.h" #include "uiInfo.hpp" struct AppearanceUiDescription @@ -29,7 +30,7 @@ class AppearanceObject : public prtyObject void updateObjectFromProps(); // Getter for appearance data object - // AppearanceDataObject& getAppearanceDataObject() { return m_appearanceDataObject; } + AppearanceDataObject& appearanceDataObject() { return m_appearanceDataObject; } const AppearanceDataObject& getAppearanceDataObject() const { return m_appearanceDataObject; } // Getters for UI info objects @@ -52,8 +53,9 @@ class AppearanceObject : public prtyObject // the properties AppearanceDataObject m_appearanceDataObject; - // the actual camera + // the actual settings std::shared_ptr m_renderSettings; + std::weak_ptr m_scene; // the ui info ComboBoxUiInfo* m_rendererType; @@ -67,4 +69,16 @@ class AppearanceObject : public prtyObject CheckBoxUiInfo* m_showBoundingBox; ColorPickerUiInfo* m_boundingBoxColor; CheckBoxUiInfo* m_showScaleBar; + + void RendererTypeChanged(prtyProperty* i_Property, bool i_bDirty); + void ShadingTypeChanged(prtyProperty* i_Property, bool i_bDirty); + void DensityScaleChanged(prtyProperty* i_Property, bool i_bDirty); + void GradientFactorChanged(prtyProperty* i_Property, bool i_bDirty); + void StepSizePrimaryRayChanged(prtyProperty* i_Property, bool i_bDirty); + void StepSizeSecondaryRayChanged(prtyProperty* i_Property, bool i_bDirty); + void InterpolateChanged(prtyProperty* i_Property, bool i_bDirty); + void BackgroundColorChanged(prtyProperty* i_Property, bool i_bDirty); + void ShowBoundingBoxChanged(prtyProperty* i_Property, bool i_bDirty); + void BoundingBoxColorChanged(prtyProperty* i_Property, bool i_bDirty); + void ShowScaleBarChanged(prtyProperty* i_Property, bool i_bDirty); }; From 0741ffea846512ca8b6a49fecca24bd04d78c12c Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 6 Dec 2025 18:02:24 -0800 Subject: [PATCH 37/63] wip fixups, after terrible rebase catastrophe. more coming --- agave_app/AppearanceSettingsWidget.cpp | 89 +-- agave_app/CameraWidget.h | 1 - agave_app/GLView3D.h | 6 +- agave_app/agaveGui.h | 5 + agave_app/qtControls/controlFactory.cpp | 41 +- agave_app/qtControls/controlFactory.h | 3 +- agave_app/tfeditor/gradients.cpp | 904 +++++++++++++++++------- renderlib/CameraDataObject.hpp | 27 +- renderlib/CameraObject.cpp | 135 +++- renderlib/CameraObject.hpp | 13 +- 10 files changed, 854 insertions(+), 370 deletions(-) diff --git a/agave_app/AppearanceSettingsWidget.cpp b/agave_app/AppearanceSettingsWidget.cpp index 84f208cbc..b8f8b423b 100644 --- a/agave_app/AppearanceSettingsWidget.cpp +++ b/agave_app/AppearanceSettingsWidget.cpp @@ -3,6 +3,7 @@ #include "QRenderSettings.h" #include "RangeWidget.h" #include "qtControls/Section.h" +#include "qtControls/controlFactory.h" #include "ImageXYZC.h" #include "renderlib/AppScene.h" @@ -360,56 +361,6 @@ QAppearanceSettingsWidget::createAreaLightingControls(QAction* pRotationAction) createFlatList(sectionLayout, m_arealightObject); } -#if 0 - m_lt0gui.m_thetaSlider = new QNumericSlider(); - m_lt0gui.m_thetaSlider->setStatusTip(tr("Set angle theta for area light")); - m_lt0gui.m_thetaSlider->setToolTip(tr("Set angle theta for area light")); - m_lt0gui.m_thetaSlider->setRange(0.0, TWO_PI_F); - m_lt0gui.m_thetaSlider->setSingleStep(TWO_PI_F / 100.0); - m_lt0gui.m_thetaSlider->setValue(0.0); - sectionLayout->addRow("Theta", m_lt0gui.m_thetaSlider); - QObject::connect( - m_lt0gui.m_thetaSlider, &QNumericSlider::valueChanged, this, &QAppearanceSettingsWidget::OnSetAreaLightTheta); - - m_lt0gui.m_phiSlider = new QNumericSlider(); - m_lt0gui.m_phiSlider->setStatusTip(tr("Set angle phi for area light")); - m_lt0gui.m_phiSlider->setToolTip(tr("Set angle phi for area light")); - m_lt0gui.m_phiSlider->setRange(0.0, PI_F); - m_lt0gui.m_phiSlider->setSingleStep(PI_F / 100.0); - m_lt0gui.m_phiSlider->setValue(HALF_PI_F); - sectionLayout->addRow("Phi", m_lt0gui.m_phiSlider); - QObject::connect( - m_lt0gui.m_phiSlider, &QNumericSlider::valueChanged, this, &QAppearanceSettingsWidget::OnSetAreaLightPhi); - - m_lt0gui.m_sizeSlider = new QNumericSlider(); - m_lt0gui.m_sizeSlider->setStatusTip(tr("Set size for area light")); - m_lt0gui.m_sizeSlider->setToolTip(tr("Set size for area light")); - m_lt0gui.m_sizeSlider->setRange(0.1, 5.0); - m_lt0gui.m_sizeSlider->setSingleStep(5.0 / 100.0); - m_lt0gui.m_sizeSlider->setValue(1.0); - sectionLayout->addRow("Size", m_lt0gui.m_sizeSlider); - QObject::connect( - m_lt0gui.m_sizeSlider, &QNumericSlider::valueChanged, this, &QAppearanceSettingsWidget::OnSetAreaLightSize); - - m_lt0gui.m_distSlider = new QNumericSlider(); - m_lt0gui.m_distSlider->setStatusTip(tr("Set distance for area light")); - m_lt0gui.m_distSlider->setToolTip(tr("Set distance for area light")); - m_lt0gui.m_distSlider->setRange(0.1, 10.0); - m_lt0gui.m_distSlider->setSingleStep(1.0); - m_lt0gui.m_distSlider->setValue(10.0); - sectionLayout->addRow("Distance", m_lt0gui.m_distSlider); - QObject::connect( - m_lt0gui.m_distSlider, &QNumericSlider::valueChanged, this, &QAppearanceSettingsWidget::OnSetAreaLightDistance); - - m_lt0gui.m_areaLightColor = new QColorWithIntensity(QColor(255, 255, 255), 100.0, this); - sectionLayout->addRow("Intensity", m_lt0gui.m_areaLightColor); - QObject::connect(m_lt0gui.m_areaLightColor, &QColorWithIntensity::colorChanged, [this](const QColor& c) { - this->OnSetAreaLightColor(m_lt0gui.m_areaLightColor->getIntensity(), c); - }); - QObject::connect(m_lt0gui.m_areaLightColor, &QColorWithIntensity::intensityChanged, [this](double v) { - this->OnSetAreaLightColor(v, m_lt0gui.m_areaLightColor->getColor()); - }); -#endif section->setContentLayout(*sectionLayout); return section; } @@ -817,47 +768,17 @@ QAppearanceSettingsWidget::initClipPlaneControls(Scene* scene) void QAppearanceSettingsWidget::initLightingControls(Scene* scene) { - // split color into color and intensity. - QColor c; - float i; -#if 0 - m_lt0gui.m_thetaSlider->setValue(scene->AreaLight().m_Theta); - m_lt0gui.m_phiSlider->setValue(scene->AreaLight().m_Phi); - m_lt0gui.m_sizeSlider->setValue(scene->AreaLight().m_Width); - m_lt0gui.m_distSlider->setValue(scene->AreaLight().m_Distance); - normalizeColorForGui(scene->AreaLight().m_Color, c, i); - m_lt0gui.m_areaLightColor->setIntensity(i * scene->AreaLight().m_ColorIntensity); - m_lt0gui.m_areaLightColor->setColor(c); + m_arealightObject->updatePropsFromSceneLight(); // attach light observer to scene's area light source, to receive updates from viewport controls // TODO FIXME clean this up - it's not removed anywhere so if light(i.e. scene) outlives "this" then we have problems. // Currently in AGAVE this is not an issue.. m_arealightObject->getSceneLight()->m_observers.push_back([this](const Light& light) { // update gui controls - - // bring theta into 0..2pi - m_lt0gui.m_thetaSlider->setValue(light.m_Theta < 0 ? light.m_Theta + TWO_PI_F : light.m_Theta); - // bring phi into 0..pi - m_lt0gui.m_phiSlider->setValue(light.m_Phi < 0 ? light.m_Phi + PI_F : light.m_Phi); - m_lt0gui.m_sizeSlider->setValue(light.m_Width); - m_lt0gui.m_distSlider->setValue(light.m_Distance); - // split color into color and intensity. - QColor c; - float i; - normalizeColorForGui(light.m_Color, c, i); - m_lt0gui.m_areaLightColor->setIntensity(i * light.m_ColorIntensity); - m_lt0gui.m_areaLightColor->setColor(c); + m_arealightObject->updatePropsFromSceneLight(); }); -#endif - normalizeColorForGui(scene->SphereLight().m_ColorTop, c, i); - m_lt1gui.m_stintensitySlider->setValue(i * scene->SphereLight().m_ColorTopIntensity); - m_lt1gui.m_stColorButton->SetColor(c); - normalizeColorForGui(scene->SphereLight().m_ColorMiddle, c, i); - m_lt1gui.m_smintensitySlider->setValue(i * scene->SphereLight().m_ColorMiddleIntensity); - m_lt1gui.m_smColorButton->SetColor(c); - normalizeColorForGui(scene->SphereLight().m_ColorBottom, c, i); - m_lt1gui.m_sbintensitySlider->setValue(i * scene->SphereLight().m_ColorBottomIntensity); - m_lt1gui.m_sbColorButton->SetColor(c); + + m_skylightObject->updatePropsFromSceneLight(); } void diff --git a/agave_app/CameraWidget.h b/agave_app/CameraWidget.h index 35ed6fdba..2bb56e310 100644 --- a/agave_app/CameraWidget.h +++ b/agave_app/CameraWidget.h @@ -1,6 +1,5 @@ #pragma once -#include "Camera.h" #include "qtControls/Controls.h" // #include "renderlib/core/prty/prtyProperty.h" diff --git a/agave_app/GLView3D.h b/agave_app/GLView3D.h index c8018c335..91b7b0c35 100644 --- a/agave_app/GLView3D.h +++ b/agave_app/GLView3D.h @@ -12,8 +12,8 @@ #include #include -class AppearanceDataObject; -class CameraDataObject; +class AppearanceObject; +class CameraObject; class CStatus; class ImageXYZC; class IRenderWindow; @@ -70,7 +70,6 @@ class GLView3D : public QOpenGLWidget // tied to the above camera. CCamera must outlive this: // CameraObject* getCameraDataObject() { return m_cameraObject; } void setCameraObject(CameraObject* cameraObject); - AppearanceObject* getAppearanceDataObject() { return m_appearanceDataObject; } void fromViewerState(const Serialize::ViewerState& s); @@ -114,7 +113,6 @@ public slots: private: CameraObject* m_cameraObject; - AppearanceObject* m_appearanceDataObject; QRenderSettings* m_qrendersettings; /// Rendering timer. diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index 57a9350bb..10e4b67fa 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -15,6 +15,7 @@ class QAppearanceDockWidget; class QAppearanceDockWidget2; +class QAreaLightDockWidget; class QCameraDockWidget; class QSkyLightDockWidget; class QStatisticsDockWidget; @@ -102,6 +103,8 @@ private slots: void createMenus(); void createToolbars(); void addDockItemsToViewMenu(); + void setupAreaLightDock(AreaLightObject* light); + void setupSkyLightDock(SkyLightObject* light); void setupCameraDock(CameraObject* cdo); void setupTimelineDock(); void setupAppearanceDock(AppearanceObject* ado); @@ -193,6 +196,8 @@ private slots: Scene m_appScene; int m_currentScene = 0; std::unique_ptr m_cameraObject; + std::unique_ptr m_areaLightObject; + std::unique_ptr m_skyLightObject; std::string m_currentFilePath; // TODO remove the above m_currentFilePath and use this instead diff --git a/agave_app/qtControls/controlFactory.cpp b/agave_app/qtControls/controlFactory.cpp index 0b94f403c..ca9ba88ab 100644 --- a/agave_app/qtControls/controlFactory.cpp +++ b/agave_app/qtControls/controlFactory.cpp @@ -127,7 +127,8 @@ addRow(const ColorWithIntensityUiInfo& info) { auto* propColor = static_cast(info.GetProperty(0)); glm::vec4 c = propColor->GetValue(); - QColor qc(c.r, c.g, c.b); + QColor qc; + qc.setRgbF(c.r, c.g, c.b); QColorWithIntensity* colorPicker = new QColorWithIntensity(qc); colorPicker->setStatusTip(QString::fromStdString(info.GetStatusTip())); @@ -193,9 +194,7 @@ addRow(const ColorWithIntensityUiInfo& info) if (c != newvalue) { // Prevent recursive updates // slider->setLocalChangeNoUpdate(true); - qc.setRedF(c.r); - qc.setGreenF(c.g); - qc.setBlueF(c.b); + qc.setRgbF(c.r, c.g, c.b); colorPicker->setColor(qc); // slider->setLocalChangeNoUpdate(false); } @@ -242,6 +241,7 @@ addRow(const ComboBoxUiInfo& info) for (const auto& item : info.items) { comboBox->addItem(QString::fromStdString(item)); } + auto* prop = static_cast(info.GetProperty(0)); comboBox->setCurrentIndex(prop->GetValue()); auto conn = QObject::connect( comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->SetValue(index, true); }); @@ -315,6 +315,19 @@ addGenericRow(const prtyPropertyUIInfo& info) return nullptr; // or throw an exception } +template +QWidget* +addPrtyRow(LayoutType* layout, std::shared_ptr propertyInfo) +{ + QWidget* control = addGenericRow(*propertyInfo); + if (control) { + QString label = QString::fromStdString(propertyInfo->GetDescription()); + layout->addRow(label, control); + return control; + } + return nullptr; +} + void createCategorizedSections(QFormLayout* mainLayout, prtyObject* object) { @@ -341,11 +354,7 @@ createCategorizedSections(QFormLayout* mainLayout, prtyObject* object) // Add controls for each property in this category for (const auto& propertyInfo : properties) { - QWidget* control = addGenericRow(*propertyInfo); - if (control) { - QString label = QString::fromStdString(propertyInfo->GetDescription()); - sectionLayout->addRow(label, control); - } + addPrtyRow(sectionLayout, propertyInfo); } // Set the section's content layout @@ -357,18 +366,20 @@ createCategorizedSections(QFormLayout* mainLayout, prtyObject* object) } } +template void -createFlatList(QFormLayout* mainLayout, prtyObject* object) +createFlatList(LayoutType* mainLayout, prtyObject* object) { // Simple flat list of all properties const auto& propertyList = object->GetList(); for (const auto& propertyInfo : propertyList) { if (propertyInfo) { - QWidget* control = addGenericRow(*propertyInfo); - if (control) { - QString label = QString::fromStdString(propertyInfo->GetDescription()); - mainLayout->addRow(label, control); - } + addPrtyRow(mainLayout, propertyInfo); } } } + +template void +createFlatList(QFormLayout* mainLayout, prtyObject* object); +template void +createFlatList(AgaveFormLayout* mainLayout, prtyObject* object); diff --git a/agave_app/qtControls/controlFactory.h b/agave_app/qtControls/controlFactory.h index 738c3e99d..4947b2582 100644 --- a/agave_app/qtControls/controlFactory.h +++ b/agave_app/qtControls/controlFactory.h @@ -45,8 +45,9 @@ addRow(const ColorPickerUiInfo& info); QWidget* addGenericRow(const prtyPropertyUIInfo& info); +template void -createFlatList(QFormLayout* mainLayout, prtyObject* object); +createFlatList(LayoutType* mainLayout, prtyObject* object); void createCategorizedSections(QFormLayout* mainLayout, prtyObject* object); diff --git a/agave_app/tfeditor/gradients.cpp b/agave_app/tfeditor/gradients.cpp index 13bf7b534..a48f2d0c8 100644 --- a/agave_app/tfeditor/gradients.cpp +++ b/agave_app/tfeditor/gradients.cpp @@ -1,59 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the demonstration applications of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - #include "gradients.h" -#include "hoverpoints.h" -#include "qtControls/Controls.h" -#include "Defines.h" -#include "Logging.h" +#include "Controls.h" +#include "qcustomplot.h" +#include "renderlib/Defines.h" +#include "renderlib/Logging.h" +#include "renderlib/MathUtil.h" #include @@ -78,193 +29,464 @@ vectorToGradientStops(std::vector& v) return stops; } -ShadeWidget::ShadeWidget(const Histogram& histogram, ShadeType type, QWidget* parent) +static void +bound_point(double x, double y, const QRectF& bounds, int lock, double& out_x, double& out_y) +{ + qreal left = bounds.left(); + qreal right = bounds.right(); + // notice top/bottom switch here. + qreal bottom = bounds.top(); + qreal top = bounds.bottom(); + + out_x = x; + out_y = y; + + if (x <= left || (lock & GradientEditor::LockToLeft)) + out_x = left; + else if (x >= right || (lock & GradientEditor::LockToRight)) + out_x = right; + + if (y >= top || (lock & GradientEditor::LockToTop)) + out_y = top; + else if (y <= bottom || (lock & GradientEditor::LockToBottom)) + out_y = bottom; +} + +static constexpr double SCATTERSIZE = 10.0; + +GradientEditor::GradientEditor(const Histogram& histogram, QWidget* parent) : QWidget(parent) - , m_shade_type(type) - , m_alpha_gradient(QLinearGradient(0, 0, 0, 0)) , m_histogram(histogram) { - // Checkers background - if (m_shade_type == ARGBShade) { - QPixmap pm(20, 20); - QPainter pmp(&pm); - pmp.fillRect(0, 0, 10, 10, Qt::lightGray); - pmp.fillRect(10, 10, 10, 10, Qt::lightGray); - pmp.fillRect(0, 10, 10, 10, Qt::darkGray); - pmp.fillRect(10, 0, 10, 10, Qt::darkGray); - pmp.end(); - QPalette pal = palette(); - pal.setBrush(backgroundRole(), QBrush(pm)); - setAutoFillBackground(true); - setPalette(pal); + QVBoxLayout* vbox = new QVBoxLayout(this); + vbox->setSpacing(1); - } else { - setAttribute(Qt::WA_OpaquePaintEvent); + m_customPlot = new QCustomPlot(this); + + // first graph will be histogram + QPalette pal = m_customPlot->palette(); + QColor histFillColor = pal.color(QPalette::Link).lighter(150); + m_histogramBars = new QCPBars(m_customPlot->xAxis, m_customPlot->yAxis); + QBrush barBrush = m_histogramBars->brush(); + barBrush.setColor(histFillColor); + m_histogramBars->setBrush(barBrush); + m_histogramBars->setPen(Qt::NoPen); + m_histogramBars->setWidthType(QCPBars::wtPlotCoords); + float firstBinCenter, lastBinCenter, binSize; + histogram.bin_range(histogram._bins.size(), firstBinCenter, lastBinCenter, binSize); + m_histogramBars->setWidth(binSize); + QVector keyData; + QVector valueData; + static constexpr double MIN_BAR_HEIGHT = 0.01; // Minimum height for nonzero bins (0.1% of max) + for (size_t i = 0; i < histogram._bins.size(); ++i) { + keyData << firstBinCenter + i * binSize; + if (histogram._bins[i] == 0) { + // Zero bins get zero height + valueData << 0.0; + } else { + // Nonzero bins get at least the minimum height + double normalizedHeight = (double)histogram._bins[i] / (double)histogram._bins[histogram._maxBin]; + valueData << std::max(normalizedHeight, MIN_BAR_HEIGHT); + } } - - QPolygonF points; - points << QPointF(0.0f, 0.0f) << QPointF(1.0f, 1.0f); - - m_hoverPoints = new HoverPoints(this, HoverPoints::CircleShape); - // m_hoverPoints->setConnectionType(HoverPoints::LineConnection); - m_hoverPoints->setPoints(points); - m_hoverPoints->setPointLock(0, HoverPoints::LockToLeft); - m_hoverPoints->setPointLock(1, HoverPoints::LockToRight); - m_hoverPoints->setSortType(HoverPoints::XSort); - - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - - connect(m_hoverPoints, &HoverPoints::pointsChanged, this, &ShadeWidget::colorsChanged); -} - -QPolygonF -ShadeWidget::points() const -{ - return m_hoverPoints->points(); + m_histogramBars->setData(keyData, valueData); + m_histogramBars->setSelectable(QCP::stNone); + + // first added graph will the the piecewise linear transfer function + m_customPlot->addGraph(); + m_customPlot->graph(0)->setPen(QPen(Qt::black)); + QPen scatterPen(Qt::black); + scatterPen.setWidthF(1.0); + m_customPlot->graph(0)->setScatterStyle( + QCPScatterStyle(QCPScatterStyle::ssCircle, scatterPen, Qt::NoBrush, SCATTERSIZE)); + m_customPlot->graph(0)->setSelectable(QCP::stSingleData); + + // give the axes some labels: + m_customPlot->xAxis->setLabel(""); + m_customPlot->yAxis->setLabel(""); + + // set axes ranges, so we see all data: + m_customPlot->xAxis->setRange(histogram._dataMin, histogram._dataMax); + m_customPlot->xAxis->ticker()->setTickCount(4); + m_customPlot->xAxis->ticker()->setTickOrigin(histogram._dataMin); + auto tickLabelFont = m_customPlot->xAxis->tickLabelFont(); + tickLabelFont.setPointSize((float)tickLabelFont.pointSize() * 0.75); + m_customPlot->xAxis->setTickLabelFont(tickLabelFont); + QPen penx = m_customPlot->xAxis->basePen(); + penx.setWidthF(1.0); + m_customPlot->xAxis->setBasePen(penx); + + // increasing this will extend the Y axis up and down, so that the scatter handles are not clipped. + static constexpr double AXIS_OFFSET_FRACTION = 0.0; + m_customPlot->xAxis->setOffset(AXIS_OFFSET_FRACTION); + + m_customPlot->yAxis->setRange(0 - AXIS_OFFSET_FRACTION, 1 + AXIS_OFFSET_FRACTION); + m_customPlot->yAxis->ticker()->setTickCount(1); + tickLabelFont = m_customPlot->yAxis->tickLabelFont(); + tickLabelFont.setPointSize((float)tickLabelFont.pointSize() * 0.75); + m_customPlot->yAxis->setTickLabelFont(tickLabelFont); + QPen peny = m_customPlot->yAxis->basePen(); + peny.setWidthF(1.0); + m_customPlot->yAxis->setBasePen(peny); + + m_customPlot->xAxis->grid()->setVisible(true); + m_customPlot->xAxis->grid()->setSubGridVisible(true); + m_customPlot->yAxis->grid()->setVisible(true); + m_customPlot->yAxis->grid()->setSubGridVisible(true); + + m_customPlot->setInteractions( + QCP::iRangeDrag | QCP::iRangeZoom | + QCP::iSelectPlottables); // allow user to drag axis ranges with mouse, zoom with mouse wheel + m_customPlot->axisRect()->setRangeDrag(Qt::Horizontal); + m_customPlot->axisRect()->setRangeZoom(Qt::Horizontal); + + m_customPlot->replot(); + + connect(m_customPlot, &QCustomPlot::mousePress, this, &GradientEditor::onPlotMousePress); + connect(m_customPlot, &QCustomPlot::mouseMove, this, &GradientEditor::onPlotMouseMove); + connect(m_customPlot, &QCustomPlot::mouseRelease, this, &GradientEditor::onPlotMouseRelease); + connect(m_customPlot, &QCustomPlot::mouseWheel, this, &GradientEditor::onPlotMouseWheel); + connect(m_customPlot, &QCustomPlot::mouseDoubleClick, this, &GradientEditor::onPlotMouseDoubleClick); + + vbox->addWidget(m_customPlot); } void -ShadeWidget::setEditable(bool editable) +GradientEditor::changeEvent(QEvent* event) { - m_hoverPoints->setEditable(editable); + // This might be too many event types to check, but ThemeChange only seems to work on the QMainWindow. + // At least on Windows, changing dark mode to light mode incurs StyleChange and PaletteChange events too. + if (event->type() == QEvent::ThemeChange || event->type() == QEvent::ApplicationPaletteChange || + event->type() == QEvent::StyleChange || event->type() == QEvent::PaletteChange) { + // check for dark or light mode + auto sh = QGuiApplication::styleHints(); + auto colorScheme = sh->colorScheme(); + QColor plotLineColor; + QColor backgroundColor; + QColor barsColor; + QColor gridColor; + QColor subgridColor; + if (colorScheme == Qt::ColorScheme::Dark) { + barsColor = Qt::magenta; + barsColor.setAlphaF(0.5); + plotLineColor = this->palette().color(QPalette::Text); + backgroundColor = this->palette().color(QPalette::Window); + gridColor = plotLineColor.darker(150); + subgridColor = plotLineColor.darker(170); + } else if (colorScheme == Qt::ColorScheme::Light) { + barsColor = Qt::magenta; + barsColor.setAlphaF(0.25); + plotLineColor = this->palette().color(QPalette::Text); + backgroundColor = this->palette().color(QPalette::Window); + gridColor = plotLineColor.lighter(150); + subgridColor = plotLineColor.lighter(170); + } + m_customPlot->graph(0)->setPen(QPen(plotLineColor)); + QPen scatterPen(plotLineColor); + scatterPen.setWidthF(1.0); + m_customPlot->graph(0)->setScatterStyle( + QCPScatterStyle(QCPScatterStyle::ssCircle, scatterPen, Qt::NoBrush, SCATTERSIZE)); + m_customPlot->setBackground(QBrush(backgroundColor)); + m_customPlot->axisRect(); + + QPen basepen = m_customPlot->xAxis->basePen(); + basepen.setColor(plotLineColor); + m_customPlot->xAxis->setBasePen(basepen); + m_customPlot->yAxis->setBasePen(basepen); + + QPen gridpen = m_customPlot->xAxis->grid()->pen(); + gridpen.setColor(gridColor); + m_customPlot->xAxis->grid()->setPen(gridpen); + m_customPlot->yAxis->grid()->setPen(gridpen); + QPen subgridpen = m_customPlot->xAxis->grid()->subGridPen(); + subgridpen.setColor(subgridColor); + m_customPlot->xAxis->grid()->setSubGridPen(subgridpen); + m_customPlot->yAxis->grid()->setSubGridPen(subgridpen); + m_customPlot->xAxis->grid()->setAntialiasedSubGrid(true); + m_customPlot->yAxis->grid()->setAntialiasedSubGrid(true); + + QPen axisTickPen = m_customPlot->xAxis->tickPen(); + axisTickPen.setColor(plotLineColor); + m_customPlot->xAxis->setTickPen(axisTickPen); + m_customPlot->yAxis->setTickPen(axisTickPen); + m_customPlot->xAxis->setTickLabelColor(plotLineColor); + m_customPlot->yAxis->setTickLabelColor(plotLineColor); + QPen axisSubTickPen = m_customPlot->xAxis->subTickPen(); + axisSubTickPen.setColor(plotLineColor); + m_customPlot->xAxis->setSubTickPen(axisSubTickPen); + m_customPlot->yAxis->setSubTickPen(axisSubTickPen); + + m_histogramBars->setPen(Qt::NoPen); // QPen(barsColor)); + m_histogramBars->setBrush(QBrush(barsColor)); + + m_customPlot->replot(); + } + QWidget::changeEvent(event); } -uint -ShadeWidget::colorAt(int x) +void +GradientEditor::onPlotMousePress(QMouseEvent* event) { - generateShade(); - if (m_shade.isNull()) { - return 0; + // in custom mode, any click is either ON a point or creating a new point? + bool isCustomMode = (m_currentEditMode == GradientEditMode::CUSTOM); + bool isMinMaxMode = (m_currentEditMode == GradientEditMode::MINMAX); + bool isWindowLevelMode = (m_currentEditMode == GradientEditMode::WINDOW_LEVEL); + bool isPercentileMode = (m_currentEditMode == GradientEditMode::PERCENTILE); + bool isInteractiveMode = isCustomMode || isMinMaxMode || isWindowLevelMode || isPercentileMode; + + if (!isInteractiveMode) { + return; } - QPolygonF pts = m_hoverPoints->points(); - for (int i = 1; i < pts.size(); ++i) { - if (pts.at(i - 1).x() <= x && pts.at(i).x() >= x) { - QLineF l(pts.at(i - 1), pts.at(i)); - l.setLength(l.length() * ((x - l.x1()) / l.dx())); - return m_shade.pixel(qRound(qMin(l.x2(), (qreal(m_shade.width() - 1)))), - qRound(qMin(l.y2(), qreal(m_shade.height() - 1)))); + // let's look to see if a data point was clicked. + + int indexOfDataPoint = -1; + double dist = 1E+9; + + auto graph = m_customPlot->graph(0); + for (int n = 0; n < (graph->data()->size()); n++) { + // get xy of each data pt in pixels. compare with scattersize. + // first hit wins. + double x = (graph->data()->begin() + n)->key; + double y = (graph->data()->begin() + n)->value; + double px = m_customPlot->xAxis->coordToPixel(x); + double py = m_customPlot->yAxis->coordToPixel(y); + double dx = (px - (double)event->pos().x()); + double dy = (py - (double)event->pos().y()); + dist = sqrt(dx * dx + dy * dy); + if (dist < SCATTERSIZE / 2.0) { + indexOfDataPoint = n; + // remember dist! + break; } } - return 0; -} - -void -ShadeWidget::setGradientStops(const QGradientStops& stops) -{ - if (m_shade_type == ARGBShade) { - m_alpha_gradient = QLinearGradient(0, 0, width(), 0); - for (int i = 0; i < stops.size(); ++i) { - QColor c = stops.at(i).second; - m_alpha_gradient.setColorAt(stops.at(i).first, QColor(c.red(), c.green(), c.blue())); + if (event->button() == Qt::LeftButton) { + + // if we didn't click on a point, then we could add a point (only in custom mode): + if (indexOfDataPoint == -1 && isCustomMode) { + // this checks to see if user clicked along the line anywhere close. + QCPGraph* plottable = m_customPlot->plottableAt(event->pos(), true, &indexOfDataPoint); + if (plottable != nullptr && indexOfDataPoint > -1) { + // create a new point at x, y + double x = m_customPlot->xAxis->pixelToCoord(event->pos().x()); + double y = m_customPlot->yAxis->pixelToCoord(event->pos().y()); + // find first point above x to know the index? + for (int n = 0; n < (graph->data()->size()); n++) { + // get xy of each data pt in pixels. compare with scattersize. + // first hit wins. + double xn = (graph->data()->begin() + n)->key; + if (x < xn) { + // the index of x will be n. + indexOfDataPoint = n; + break; + } + } + m_locks.insert(indexOfDataPoint, 0); + graph->addData(x, y); + graph->data()->sort(); + m_customPlot->replot(); + } } - m_shade = QImage(); - generateShade(); - update(); - } -} + if (indexOfDataPoint > -1) { + // In MINMAX, Window/Level, and Percentile modes, only allow dragging of second and third points (threshold + // points) + if (isMinMaxMode || isWindowLevelMode || isPercentileMode) { + int dataSize = graph->data()->size(); + if (indexOfDataPoint != 1 && indexOfDataPoint != 2) { + // Not the second or third point (threshold points), don't allow dragging + return; + } + } -void -ShadeWidget::paintEvent(QPaintEvent*) -{ - generateShade(); - - QPainter p(this); - p.drawImage(0, 0, m_shade); - /* - qreal barWidth = width() / (qreal)m_histogram.size(); - - for (int i = 0; i < m_histogram.size(); ++i) { - qreal h = m_histogram[i] * height(); - // draw level - painter.fillRect(barWidth * i, height() - h, barWidth * (i + 1), height(), Qt::red); - // clear the rest of the control - painter.fillRect(barWidth * i, 0, barWidth * (i + 1), height() - h, Qt::black); + m_isDraggingPoint = true; + m_currentPointIndex = indexOfDataPoint; + // turn off axis dragging while we are dragging a point + m_customPlot->axisRect()->setRangeDrag((Qt::Orientations)0); + // swallow this event so it doesn't propagate to the plot + event->accept(); + } + } else if (event->button() == Qt::RightButton) { + // Only allow point deletion in custom mode + if (indexOfDataPoint >= 0 && isCustomMode) { + if (m_locks[indexOfDataPoint] == 0) { + m_locks.remove(indexOfDataPoint); + // remove data pt from plot + graph->data()->remove((graph->data()->begin() + indexOfDataPoint)->key); + + // update the stuff because we did something + emit gradientStopsChanged(this->buildStopsFromPlot()); + m_customPlot->replot(); + event->accept(); } - */ - p.setPen(QColor(146, 146, 146)); - p.drawRect(0, 0, width() - 1, height() - 1); + } + } } - void -ShadeWidget::drawHistogram(QPainter& p, int w, int h) +GradientEditor::onPlotMouseMove(QMouseEvent* event) { - size_t nbins = m_histogram._bins.size(); - int maxbinsize = m_histogram._bins[m_histogram._maxBin]; - for (size_t i = 0; i < nbins; ++i) { - float binheight = (float)m_histogram._bins[i] * (float)(h - 1) / (float)maxbinsize; - p.fillRect( - QRectF((float)i * (float)(w - 1) / (float)nbins, h - 1 - binheight, (float)(w - 1) / (float)nbins, binheight), - QColor(0, 0, 0, 255)); + bool isCustomMode = (m_currentEditMode == GradientEditMode::CUSTOM); + bool isMinMaxMode = (m_currentEditMode == GradientEditMode::MINMAX); + bool isWindowLevelMode = (m_currentEditMode == GradientEditMode::WINDOW_LEVEL); + bool isPercentileMode = (m_currentEditMode == GradientEditMode::PERCENTILE); + bool isInteractiveMode = isCustomMode || isMinMaxMode || isWindowLevelMode || isPercentileMode; + + if (!isInteractiveMode) { + return; } -} -void -ShadeWidget::generateShade() -{ - if (m_shade.isNull() || m_shade.size() != size()) { - - QRect qrect = rect(); - QSize qsize = size(); - if (m_shade_type == ARGBShade) { - m_shade = QImage(qsize, QImage::Format_ARGB32_Premultiplied); - if (m_shade.isNull()) { - return; + if (m_isDraggingPoint && m_currentPointIndex >= 0) { + if (event->buttons() & Qt::LeftButton) { + auto graph = m_customPlot->graph(0); + double evx = m_customPlot->xAxis->pixelToCoord(event->pos().x()); + double evy = m_customPlot->yAxis->pixelToCoord(event->pos().y()); + + // Handle threshold-based modes (MINMAX, Window/Level, Percentile) differently + if (isMinMaxMode || isWindowLevelMode || isPercentileMode) { + // In threshold-based modes, keep Y value constant and only allow horizontal movement + double originalY = (graph->data()->begin() + m_currentPointIndex)->value; + evy = originalY; // Keep Y constant + + static constexpr double OVERLAP_EPSILON = 0.001; + // Apply additional constraints for min/max threshold points + if (m_currentPointIndex == 1) { + // This is the min threshold point - don't let it go past the max threshold point + if (graph->data()->size() > 2) { + double maxThresholdX = (graph->data()->begin() + 2)->key; + evx = std::min(evx, maxThresholdX - OVERLAP_EPSILON); // Small epsilon to prevent overlap + } + // Also don't let it go before the first fixed point + if (graph->data()->size() > 0) { + double firstPointX = (graph->data()->begin())->key; + evx = std::max(evx, firstPointX + OVERLAP_EPSILON); + } + } else if (m_currentPointIndex == 2) { + // This is the max threshold point - don't let it go past the min threshold point + if (graph->data()->size() > 1) { + double minThresholdX = (graph->data()->begin() + 1)->key; + evx = std::max(evx, minThresholdX + OVERLAP_EPSILON); // Small epsilon to prevent overlap + } + // Also don't let it go past the last fixed point + if (graph->data()->size() > 3) { + double lastPointX = (graph->data()->begin() + 3)->key; + evx = std::min(evx, lastPointX - OVERLAP_EPSILON); + } + } } - m_shade.fill(0); - QPainter p(&m_shade); - p.fillRect(qrect, m_alpha_gradient); + // see hoverpoints.cpp. + // this will make sure we don't move past locked edges in the bounding rectangle. + double px = evx, py = evy; + bound_point(evx, + evy, + // we really want clipRect() here? to capture zoomed region + QRectF(m_histogram._dataMin, 0.0f, m_histogram._dataMax - m_histogram._dataMin, 1.0f), + m_locks.at(m_currentPointIndex), + px, + py); + + // if we are dragging a point then move it + (graph->data()->begin() + m_currentPointIndex)->value = py; + (graph->data()->begin() + m_currentPointIndex)->key = px; + + // In MINMAX mode, don't sort - keep points in their fixed positions + if (!isMinMaxMode) { + // The point may have moved past other points, so sort, + // and account for current point index possibly changing. + // TODO should we always sort on every move? Or can we tell if we crossed another point? + graph->data().data()->sort(); + // find new index of current point + // find first point above x to know the index? + int indexOfDataPoint = m_currentPointIndex; + for (int n = 0; n < (graph->data()->size()); n++) { + // get xy of each data pt in pixels. compare with scattersize. + // first hit wins. + double xn = (graph->data()->begin() + n)->key; + if (px == xn) { + // the index of x will be n. + indexOfDataPoint = n; + break; + } + } + if (indexOfDataPoint != m_currentPointIndex) { + m_currentPointIndex = indexOfDataPoint; + } + } - p.setCompositionMode(QPainter::CompositionMode_DestinationIn); - QLinearGradient fade(0, 0, 0, height() - 1); - fade.setColorAt(0, QColor(255, 255, 255, 255)); - fade.setColorAt(1, QColor(0, 0, 0, 0)); - p.fillRect(qrect, fade); + // In threshold-based modes, emit interactivePointsChanged for real-time slider updates + // Use the second and third points (indices 1 and 2) as the threshold points + if ((isMinMaxMode || isWindowLevelMode || isPercentileMode) && graph->data()->size() >= 4) { + double minThresholdX = (graph->data()->begin() + 1)->key; + double maxThresholdX = (graph->data()->begin() + 2)->key; + emit interactivePointsChanged(minThresholdX, maxThresholdX); + } - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + // emit( DataChanged() ); - drawHistogram(p, qsize.width(), qsize.height()); + emit gradientStopsChanged(this->buildStopsFromPlot()); - } else { - m_shade = QImage(qsize, QImage::Format_RGB32); - if (m_shade.isNull()) { - return; - } - QLinearGradient shade(0, 0, 0, height()); - shade.setColorAt(1, Qt::black); + m_customPlot->replot(); + } + } +} - if (m_shade_type == RedShade) - shade.setColorAt(0, Qt::red); - else if (m_shade_type == GreenShade) - shade.setColorAt(0, Qt::green); - else - shade.setColorAt(0, Qt::blue); +QGradientStops +GradientEditor::buildStopsFromPlot() +{ + // build up coords from the customplot into the form of gradient stops + QGradientStops stops; - QPainter p(&m_shade); - p.fillRect(qrect, shade); + auto graph = m_customPlot->graph(0); + for (int n = 0; n < (graph->data()->size()); n++) { + auto dataIter = graph->data()->begin() + n; + double x = dataIter->key; + // skip duplicates? + if (n + 1 < graph->data()->size() && x == graph->data()->at(n + 1)->key) + continue; - p.setCompositionMode(QPainter::CompositionMode_SourceOver); + // rescale x to 0-1 range. + x = (x - m_histogram._dataMin) / (m_histogram._dataMax - m_histogram._dataMin); + double y = dataIter->value; - drawHistogram(p, qsize.width(), qsize.height()); + QColor color = QColor::fromRgbF(y, y, y, y); + if (x > 1.0) { + LOG_ERROR << "control point x greater than 1"; + return stops; } + + stops << QGradientStop(x, color); } + return stops; } -GradientEditor::GradientEditor(const Histogram& histogram, QWidget* parent) - : QWidget(parent) +void +GradientEditor::onPlotMouseRelease(QMouseEvent* event) { - QVBoxLayout* vbox = new QVBoxLayout(this); - vbox->setSpacing(1); - // vbox->setMargin(1); - - m_alpha_shade = new ShadeWidget(histogram, ShadeWidget::ARGBShade, this); + Q_UNUSED(event); + if (m_currentEditMode != GradientEditMode::CUSTOM && m_currentEditMode != GradientEditMode::MINMAX && + m_currentEditMode != GradientEditMode::WINDOW_LEVEL && m_currentEditMode != GradientEditMode::PERCENTILE) { + return; + } + // if we were dragging a point then stop + m_isDraggingPoint = false; + m_currentPointIndex = -1; + // re-enable axis dragging + m_customPlot->axisRect()->setRangeDrag(Qt::Horizontal); +} - vbox->addWidget(m_alpha_shade); +void +GradientEditor::onPlotMouseDoubleClick(QMouseEvent* event) +{ + // double click should reset zoom + this->m_customPlot->rescaleAxes(); + this->m_customPlot->replot(); +} - connect(m_alpha_shade, &ShadeWidget::colorsChanged, this, &GradientEditor::pointsUpdated); +void +GradientEditor::onPlotMouseWheel(QWheelEvent* event) +{ + Q_UNUSED(event); } inline static bool @@ -290,18 +512,11 @@ pointsToGradientStops(QPolygonF points) if (i + 1 < points.size() && x == points.at(i + 1).x()) continue; float pixelvalue = points.at(i).y(); - // TODO future: let each point in m_alpha_shade have a full RGBA color and use a color picker to assign it via dbl + // TODO future: let each point have a full RGBA color and use a color picker to assign it via dbl // click or some other means - // unsigned int pixelvalue = m_alpha_shade->colorAt(int(x)); - // unsigned int r = (0x00ff0000 & pixelvalue) >> 16; - // unsigned int g = (0x0000ff00 & pixelvalue) >> 8; - // unsigned int b = (0x000000ff & pixelvalue); - // unsigned int a = (0xff000000 & pixelvalue) >> 24; - // QColor color(r, g, b, a); QColor color = QColor::fromRgbF(pixelvalue, pixelvalue, pixelvalue, pixelvalue); if (x > 1) { - LOG_ERROR << "control point x greater than 1"; return stops; } @@ -311,31 +526,31 @@ pointsToGradientStops(QPolygonF points) } void -GradientEditor::pointsUpdated() -{ - // qreal w = m_alpha_shade->width(); - - QGradientStops stops = pointsToGradientStops(m_alpha_shade->points()); - - m_alpha_shade->setGradientStops(stops); - - emit gradientStopsChanged(stops); -} - -static void -set_shade_points(const QPolygonF& points, ShadeWidget* shade) +GradientEditor::set_shade_points(const QPolygonF& points, QCustomPlot* plot, const Histogram& histogram) { if (points.size() < 2) { return; } QGradientStops stops = pointsToGradientStops(points); - shade->setGradientStops(stops); - shade->hoverPoints()->setPoints(points); - shade->hoverPoints()->setPointLock(0, HoverPoints::LockToLeft); - shade->hoverPoints()->setPointLock(points.size() - 1, HoverPoints::LockToRight); - shade->update(); + m_locks.clear(); + if (points.size() > 0) { + m_locks.resize(points.size()); + m_locks.fill(0); + } + m_locks[0] = GradientEditor::LockToLeft; + m_locks[points.size() - 1] = GradientEditor::LockToRight; + + QVector x, y; + for (int i = 0; i < points.size(); ++i) { + // incoming points x values are in 0-1 range which is normalized to histogram data range + float dx = histogram._dataMin + points.at(i).x() * (histogram._dataMax - histogram._dataMin); + x << dx; + y << points.at(i).y(); + } + plot->graph(0)->setData(x, y); + plot->replot(); } void @@ -347,7 +562,7 @@ GradientEditor::setControlPoints(const std::vector& points) pts_alpha << QPointF(p.first, p.second); } - set_shade_points(pts_alpha, m_alpha_shade); + set_shade_points(pts_alpha, m_customPlot, m_histogram); } void @@ -364,10 +579,6 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj { QVBoxLayout* mainGroupLayout = new QVBoxLayout(this); - // setWindowTitle(tr("Gradients")); - - // QGroupBox* editorGroup = new QGroupBox(this); - // editorGroup->setTitle(tr("Color Editor")); m_editor = new GradientEditor(m_histogram, this); mainGroupLayout->addWidget(m_editor); @@ -461,7 +672,8 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj int initialStackedPageIndex = btnIdToStackedPage[initialButtonId]; stackedLayout->setCurrentIndex(initialStackedPageIndex); // if this is not custom mode, then disable the gradient editor - m_editor->setEditable(m == GradientEditMode::CUSTOM); + // m_editor->setEditable(m == GradientEditMode::CUSTOM); + m_editor->setEditMode(m); connect(btnGroup, QOverload::of(&QButtonGroup::buttonClicked), @@ -477,35 +689,36 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj stackedLayout->setCurrentIndex(btnIdToStackedPage[id]); // if this is not custom mode, then disable the gradient editor - m_editor->setEditable(modeToSet == GradientEditMode::CUSTOM); + // m_editor->setEditable(modeToSet == GradientEditMode::CUSTOM); + m_editor->setEditMode(modeToSet); this->forceDataUpdate(); }); - QIntSlider* minu16Slider = new QIntSlider(); + minu16Slider = new QIntSlider(); minu16Slider->setStatusTip(tr("Minimum u16 value")); minu16Slider->setToolTip(tr("Set minimum u16 value")); - minu16Slider->setRange(0, 65535); + minu16Slider->setRange(m_histogram._dataMin, m_histogram._dataMax); minu16Slider->setSingleStep(1); minu16Slider->setValue(m_gradientData->m_minu16); section0Layout->addRow("Min u16", minu16Slider); - QIntSlider* maxu16Slider = new QIntSlider(); + maxu16Slider = new QIntSlider(); maxu16Slider->setStatusTip(tr("Maximum u16 value")); maxu16Slider->setToolTip(tr("Set maximum u16 value")); - maxu16Slider->setRange(0, 65535); + maxu16Slider->setRange(m_histogram._dataMin, m_histogram._dataMax); maxu16Slider->setSingleStep(1); maxu16Slider->setValue(m_gradientData->m_maxu16); section0Layout->addRow("Max u16", maxu16Slider); - connect(minu16Slider, &QIntSlider::valueChanged, [this, maxu16Slider](int i) { + connect(minu16Slider, &QIntSlider::valueChanged, [this](int i) { this->m_gradientData->m_minu16 = i; this->onSetMinMax(i, this->m_gradientData->m_maxu16); }); - connect(maxu16Slider, &QIntSlider::valueChanged, [this, minu16Slider](int i) { + connect(maxu16Slider, &QIntSlider::valueChanged, [this](int i) { this->m_gradientData->m_maxu16 = i; this->onSetMinMax(this->m_gradientData->m_minu16, i); }); - QNumericSlider* windowSlider = new QNumericSlider(); + windowSlider = new QNumericSlider(); windowSlider->setStatusTip(tr("Window")); windowSlider->setToolTip(tr("Set size of range of intensities")); windowSlider->setRange(0.0, 1.0); @@ -513,7 +726,7 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj windowSlider->setDecimals(3); windowSlider->setValue(m_gradientData->m_window); section1Layout->addRow("Window", windowSlider); - QNumericSlider* levelSlider = new QNumericSlider(); + levelSlider = new QNumericSlider(); levelSlider->setStatusTip(tr("Level")); levelSlider->setToolTip(tr("Set level of mid intensity")); levelSlider->setRange(0.0, 1.0); @@ -521,16 +734,16 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj levelSlider->setDecimals(3); levelSlider->setValue(m_gradientData->m_level); section1Layout->addRow("Level", levelSlider); - connect(windowSlider, &QNumericSlider::valueChanged, [this, levelSlider](double d) { + connect(windowSlider, &QNumericSlider::valueChanged, [this](double d) { this->m_gradientData->m_window = d; this->onSetWindowLevel(d, levelSlider->value()); }); - connect(levelSlider, &QNumericSlider::valueChanged, [this, windowSlider](double d) { + connect(levelSlider, &QNumericSlider::valueChanged, [this](double d) { this->m_gradientData->m_level = d; this->onSetWindowLevel(windowSlider->value(), d); }); - QNumericSlider* isovalueSlider = new QNumericSlider(); + isovalueSlider = new QNumericSlider(); isovalueSlider->setStatusTip(tr("Isovalue")); isovalueSlider->setToolTip(tr("Set Isovalue")); isovalueSlider->setRange(0.0, 1.0); @@ -538,7 +751,7 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj isovalueSlider->setDecimals(3); isovalueSlider->setValue(m_gradientData->m_isovalue); section2Layout->addRow("Isovalue", isovalueSlider); - QNumericSlider* isorangeSlider = new QNumericSlider(); + isorangeSlider = new QNumericSlider(); isorangeSlider->setStatusTip(tr("Isovalue range")); isorangeSlider->setToolTip(tr("Set range above and below isovalue")); isorangeSlider->setRange(0.0, 1.0); @@ -546,16 +759,16 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj isorangeSlider->setDecimals(3); isorangeSlider->setValue(m_gradientData->m_isorange); section2Layout->addRow("Iso-range", isorangeSlider); - connect(isovalueSlider, &QNumericSlider::valueChanged, [this, isorangeSlider](double d) { + connect(isovalueSlider, &QNumericSlider::valueChanged, [this](double d) { this->m_gradientData->m_isovalue = d; this->onSetIsovalue(d, isorangeSlider->value()); }); - connect(isorangeSlider, &QNumericSlider::valueChanged, [this, isovalueSlider](double d) { + connect(isorangeSlider, &QNumericSlider::valueChanged, [this](double d) { this->m_gradientData->m_isorange = d; this->onSetIsovalue(isovalueSlider->value(), d); }); - QNumericSlider* pctLowSlider = new QNumericSlider(); + pctLowSlider = new QNumericSlider(); pctLowSlider->setStatusTip(tr("Low percentile")); pctLowSlider->setToolTip(tr("Set bottom percentile")); pctLowSlider->setRange(0.0, 1.0); @@ -563,7 +776,7 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj pctLowSlider->setDecimals(3); pctLowSlider->setValue(m_gradientData->m_pctLow); section3Layout->addRow("Pct Min", pctLowSlider); - QNumericSlider* pctHighSlider = new QNumericSlider(); + pctHighSlider = new QNumericSlider(); pctHighSlider->setStatusTip(tr("High percentile")); pctHighSlider->setToolTip(tr("Set top percentile")); pctHighSlider->setRange(0.0, 1.0); @@ -571,11 +784,11 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj pctHighSlider->setDecimals(3); pctHighSlider->setValue(m_gradientData->m_pctHigh); section3Layout->addRow("Pct Max", pctHighSlider); - connect(pctLowSlider, &QNumericSlider::valueChanged, [this, pctHighSlider](double d) { + connect(pctLowSlider, &QNumericSlider::valueChanged, [this](double d) { this->m_gradientData->m_pctLow = d; this->onSetHistogramPercentiles(d, pctHighSlider->value()); }); - connect(pctHighSlider, &QNumericSlider::valueChanged, [this, pctLowSlider](double d) { + connect(pctHighSlider, &QNumericSlider::valueChanged, [this](double d) { this->m_gradientData->m_pctHigh = d; this->onSetHistogramPercentiles(pctLowSlider->value(), d); }); @@ -584,6 +797,7 @@ GradientWidget::GradientWidget(const Histogram& histogram, GradientData* dataObj mainGroupLayout->addStretch(1); connect(m_editor, &GradientEditor::gradientStopsChanged, this, &GradientWidget::onGradientStopsChanged); + connect(m_editor, &GradientEditor::interactivePointsChanged, this, &GradientWidget::onInteractivePointsChanged); forceDataUpdate(); } @@ -621,12 +835,89 @@ void GradientWidget::onGradientStopsChanged(const QGradientStops& stops) { // update the data stored in m_gradientData - m_gradientData->m_customControlPoints.clear(); - for (int i = 0; i < stops.size(); ++i) { - m_gradientData->m_customControlPoints.push_back(LutControlPoint(stops.at(i).first, stops.at(i).second.alphaF())); + // depending on our mode: + if (m_gradientData->m_activeMode == GradientEditMode::CUSTOM) { + m_gradientData->m_customControlPoints.clear(); + for (int i = 0; i < stops.size(); ++i) { + m_gradientData->m_customControlPoints.push_back(LutControlPoint(stops.at(i).first, stops.at(i).second.alphaF())); + } + emit gradientStopsChanged(stops); + } else if (m_gradientData->m_activeMode == GradientEditMode::WINDOW_LEVEL) { + // extract window and level from the stops - use second and third points (threshold points) + std::vector points = gradientStopsToVector(const_cast(stops)); + if (points.size() < 4) { + return; + } + std::sort(points.begin(), points.end(), controlpoint_x_less_than); + float low = points[1].first; // Second point (low threshold) + float high = points[2].first; // Third point (high threshold) + float window = high - low; + float level = (high + low) * 0.5f; + m_gradientData->m_window = window; + m_gradientData->m_level = level; + + // update the sliders to match: + windowSlider->setValue(window); + levelSlider->setValue(level); + + } else if (m_gradientData->m_activeMode == GradientEditMode::ISOVALUE) { + // extract isovalue and range from the stops + std::vector points = gradientStopsToVector(const_cast(stops)); + if (points.size() < 2) { + return; + } + std::sort(points.begin(), points.end(), controlpoint_x_less_than); + float low = points[0].first; + float high = points[1].first; + float isovalue = (high + low) * 0.5f; + float isorange = high - low; + m_gradientData->m_isovalue = isovalue; + m_gradientData->m_isorange = isorange; + + // update the sliders to match: + isovalueSlider->setValue(isovalue); + isorangeSlider->setValue(isorange); + } else if (m_gradientData->m_activeMode == GradientEditMode::PERCENTILE) { + // get percentiles from the stops and histogram - use second and third points (threshold points) + std::vector points = gradientStopsToVector(const_cast(stops)); + if (points.size() < 4) { + return; + } + std::sort(points.begin(), points.end(), controlpoint_x_less_than); + float low = points[1].first; // Second point (low threshold) + float high = points[2].first; // Third point (high threshold) + // calculate percentiles from the histogram: + uint16_t ulow = m_histogram._dataMin + static_cast(low * (m_histogram._dataMax - m_histogram._dataMin)); + uint16_t uhigh = m_histogram._dataMin + static_cast(high * (m_histogram._dataMax - m_histogram._dataMin)); + float pctLow = 0.0f, pctHigh = 1.0f; + m_histogram.computePercentile(ulow, pctLow); + m_histogram.computePercentile(uhigh, pctHigh); + m_gradientData->m_pctLow = pctLow; + m_gradientData->m_pctHigh = pctHigh; + + // update the sliders to match: + pctLowSlider->setValue(pctLow); + pctHighSlider->setValue(pctHigh); + } else if (m_gradientData->m_activeMode == GradientEditMode::MINMAX) { + // get absolute min/max from the stops - use second and third points (threshold points) + std::vector points = gradientStopsToVector(const_cast(stops)); + if (points.size() < 4) { + return; + } + std::sort(points.begin(), points.end(), controlpoint_x_less_than); + // turn the second and third points' x values into u16 intensities from the histogram range: + float low = points[1].first; // Second point (min threshold) + float high = points[2].first; // Third point (max threshold) + // calculate percentiles from the histogram: + uint16_t ulow = m_histogram._dataMin + static_cast(low * (m_histogram._dataMax - m_histogram._dataMin)); + uint16_t uhigh = m_histogram._dataMin + static_cast(high * (m_histogram._dataMax - m_histogram._dataMin)); + m_gradientData->m_minu16 = ulow; + m_gradientData->m_maxu16 = uhigh; + + // update the sliders to match: + minu16Slider->setValue(ulow); + maxu16Slider->setValue(uhigh); } - - emit gradientStopsChanged(stops); } void @@ -666,8 +957,9 @@ GradientWidget::onSetWindowLevel(float window, float level) void GradientWidget::onSetMinMax(uint16_t minu16, uint16_t maxu16) { - float relativeMin = normalizeInt(minu16); - float relativeMax = normalizeInt(maxu16); + // these need to be relative to the data range of the channel, not absolute! + float relativeMin = normalizeInt(minu16, m_histogram._dataMin, m_histogram._dataMax); + float relativeMax = normalizeInt(maxu16, m_histogram._dataMin, m_histogram._dataMax); relativeMin = std::max(relativeMin, 0.0f); relativeMax = std::min(relativeMax, 1.0f); if (relativeMin >= relativeMax) { @@ -696,3 +988,103 @@ GradientWidget::onSetIsovalue(float isovalue, float width) m_editor->setControlPoints(points); emit gradientStopsChanged(vectorToGradientStops(points)); } + +void +GradientWidget::onInteractivePointsChanged(float minIntensity, float maxIntensity) +{ + // Handle different modes appropriately + if (m_gradientData->m_activeMode == GradientEditMode::MINMAX) { + // Convert from graph coordinates (histogram data range) to u16 intensity values + uint16_t minu16 = static_cast(minIntensity); + uint16_t maxu16 = static_cast(maxIntensity); + + // Ensure values are within valid range + minu16 = std::max(minu16, static_cast(m_histogram._dataMin)); + maxu16 = std::min(maxu16, static_cast(m_histogram._dataMax)); + + // Update the data + m_gradientData->m_minu16 = minu16; + m_gradientData->m_maxu16 = maxu16; + + // Update sliders without triggering their signals (to avoid feedback loops) + if (minu16Slider) { + minu16Slider->blockSignals(true); + minu16Slider->setValue(minu16); + minu16Slider->blockSignals(false); + } + if (maxu16Slider) { + maxu16Slider->blockSignals(true); + maxu16Slider->setValue(maxu16); + maxu16Slider->blockSignals(false); + } + } else if (m_gradientData->m_activeMode == GradientEditMode::WINDOW_LEVEL) { + // Convert intensities to normalized values (0-1 range) + uint16_t minInt = static_cast(minIntensity); + uint16_t maxInt = static_cast(maxIntensity); + float relativeMin = normalizeInt(minInt, m_histogram._dataMin, m_histogram._dataMax); + float relativeMax = normalizeInt(maxInt, m_histogram._dataMin, m_histogram._dataMax); + + // Calculate window and level from the threshold points + float window = relativeMax - relativeMin; + float level = (relativeMax + relativeMin) * 0.5f; + + // Update the data + m_gradientData->m_window = window; + m_gradientData->m_level = level; + + // Update sliders without triggering their signals + if (windowSlider) { + windowSlider->blockSignals(true); + windowSlider->setValue(window); + windowSlider->blockSignals(false); + } + if (levelSlider) { + levelSlider->blockSignals(true); + levelSlider->setValue(level); + levelSlider->blockSignals(false); + } + } else if (m_gradientData->m_activeMode == GradientEditMode::PERCENTILE) { + // Convert intensities to u16 values first + uint16_t minu16 = static_cast(std::max(minIntensity, (float)m_histogram._dataMin)); + uint16_t maxu16 = static_cast(std::min(maxIntensity, (float)m_histogram._dataMax)); + + // Calculate percentiles by converting intensity to bin index and using cumulative counts + float pctLow = 0.0f, pctHigh = 1.0f; + + if (m_histogram._pixelCount > 0 && !m_histogram._ccounts.empty()) { + // For low percentile + if (minu16 <= m_histogram._dataMin) { + pctLow = 0.0f; + } else if (minu16 >= m_histogram._dataMax) { + pctLow = 1.0f; + } else { + m_histogram.computePercentile(minu16, pctLow); + } + + // For high percentile + if (maxu16 <= m_histogram._dataMin) { + pctHigh = 0.0f; + } else if (maxu16 >= m_histogram._dataMax) { + pctHigh = 1.0f; + } else { + m_histogram.computePercentile(maxu16, pctHigh); + } + } + + // Update the data + m_gradientData->m_pctLow = pctLow; + m_gradientData->m_pctHigh = pctHigh; + + // Update sliders without triggering their signals + if (pctLowSlider) { + pctLowSlider->blockSignals(true); + pctLowSlider->setValue(pctLow); + pctLowSlider->blockSignals(false); + } + if (pctHighSlider) { + pctHighSlider->blockSignals(true); + pctHighSlider->setValue(pctHigh); + pctHighSlider->blockSignals(false); + } + } +} diff --git a/renderlib/CameraDataObject.hpp b/renderlib/CameraDataObject.hpp index a26c862c6..1e4eb0952 100644 --- a/renderlib/CameraDataObject.hpp +++ b/renderlib/CameraDataObject.hpp @@ -1,18 +1,37 @@ #pragma once #include "core/prty/prtyFloat.hpp" -#include "core/prty/prtyInt8.hpp" +#include "core/prty/prtyEnum.hpp" #include "core/prty/prtyBoolean.hpp" +#include "core/prty/prtyVector3d.hpp" class CameraDataObject { public: - CameraDataObject() {} + CameraDataObject() + { + ExposureIterations.SetEnumTag(0, "1"); + ExposureIterations.SetEnumTag(1, "2"); + ExposureIterations.SetEnumTag(2, "4"); + ExposureIterations.SetEnumTag(3, "8"); + + ProjectionMode.SetEnumTag(0, "Perspective"); + ProjectionMode.SetEnumTag(1, "Orthographic"); + } prtyFloat Exposure{ "Exposure", 0.75f }; - prtyInt8 ExposureIterations{ "ExposureIterations", 1 }; + prtyEnum ExposureIterations{ "ExposureIterations", 0 }; prtyBoolean NoiseReduction{ "NoiseReduction", false }; prtyFloat ApertureSize{ "ApertureSize", 0.0f }; - prtyFloat FieldOfView{ "FieldOfView", 30.0f }; + prtyFloat FieldOfView{ "FieldOfView", 30.0f }; // degrees prtyFloat FocalDistance{ "FocalDistance", 0.0f }; + + prtyVector3d Position{ "Position", glm::vec3(0.0f, 0.0f, 0.0f) }; + prtyVector3d Target{ "Target", glm::vec3(0.0f, 0.0f, -1.0f) }; + prtyFloat NearPlane{ "NearPlane", 0.1f }; + prtyFloat FarPlane{ "FarPlane", 1000.0f }; + prtyFloat Roll{ "Roll", 0.0f }; // tilt angle in degrees + + prtyFloat OrthoScale{ "OrthoScale", 1.0f }; // orthographic scale for orthographic projection + prtyEnum ProjectionMode{ "ProjectionMode", 0 }; // 0 = perspective, 1 = orthographic }; diff --git a/renderlib/CameraObject.cpp b/renderlib/CameraObject.cpp index d45b5def9..1d3614cc6 100644 --- a/renderlib/CameraObject.cpp +++ b/renderlib/CameraObject.cpp @@ -1,4 +1,7 @@ -#include "CameraUiDescription.hpp" +#include "CameraObject.hpp" + +#include "Logging.h" +#include "glm.h" // FloatSliderSpinnerUiInfo CameraUiDescription::m_exposure("Exposure", // "Set Exposure", @@ -49,20 +52,102 @@ CameraObject::CameraObject() { m_camera = std::make_shared(); m_ExposureUIInfo = new FloatSliderSpinnerUiInfo(&m_cameraDataObject.Exposure, "Camera", "Exposure"); + m_ExposureUIInfo->SetToolTip("Set Exposure"); + m_ExposureUIInfo->SetStatusTip("Set camera exposure"); + m_ExposureUIInfo->min = 0.0f; + m_ExposureUIInfo->max = 1.0f; + m_ExposureUIInfo->decimals = 2; // decimals + m_ExposureUIInfo->singleStep = 0.01f; // singleStep + m_ExposureUIInfo->numTickMarks = 0; // numTickMarks + AddProperty(m_ExposureUIInfo); m_ExposureIterationsUIInfo = new ComboBoxUiInfo(&m_cameraDataObject.ExposureIterations, "Camera", "Exposure Iterations"); + m_ExposureIterationsUIInfo->SetToolTip("Set Exposure Iterations"); + m_ExposureIterationsUIInfo->SetStatusTip("Set number of samples to accumulate per viewport update"); + AddProperty(m_ExposureIterationsUIInfo); m_NoiseReductionUIInfo = new CheckBoxUiInfo(&m_cameraDataObject.NoiseReduction, "Camera", "Noise Reduction"); + m_NoiseReductionUIInfo->SetToolTip("Enable Noise Reduction"); + m_NoiseReductionUIInfo->SetStatusTip("Enable denoising pass"); + AddProperty(m_NoiseReductionUIInfo); m_ApertureSizeUIInfo = new FloatSliderSpinnerUiInfo(&m_cameraDataObject.ApertureSize, "Camera", "Aperture Size"); + m_ApertureSizeUIInfo->SetToolTip("Set Aperture Size"); + m_ApertureSizeUIInfo->SetStatusTip("Set camera aperture size"); + m_ApertureSizeUIInfo->min = 0.0f; + m_ApertureSizeUIInfo->max = 0.1f; + m_ApertureSizeUIInfo->decimals = 2; // decimals + m_ApertureSizeUIInfo->singleStep = 0.01f; // + m_ApertureSizeUIInfo->numTickMarks = 0; // numTickMarks + m_ApertureSizeUIInfo->suffix = " mm"; // suffix + AddProperty(m_ApertureSizeUIInfo); m_FieldOfViewUIInfo = new FloatSliderSpinnerUiInfo(&m_cameraDataObject.FieldOfView, "Camera", "Field of View"); + m_FieldOfViewUIInfo->SetToolTip("Set Field of View"); + m_FieldOfViewUIInfo->SetStatusTip("Set camera field of view angle"); + m_FieldOfViewUIInfo->min = 10.0f; + m_FieldOfViewUIInfo->max = 150.0f; + m_FieldOfViewUIInfo->decimals = 2; // decimals + m_FieldOfViewUIInfo->singleStep = 0.01f; // single + m_FieldOfViewUIInfo->numTickMarks = 0; // numTickMarks + m_FieldOfViewUIInfo->suffix = " deg"; // suffix + AddProperty(m_FieldOfViewUIInfo); m_FocalDistanceUIInfo = new FloatSliderSpinnerUiInfo(&m_cameraDataObject.FocalDistance, "Camera", "Focal Distance"); + m_FocalDistanceUIInfo->SetToolTip("Set Focal Distance"); + m_FocalDistanceUIInfo->SetStatusTip("Set focal distance"); + m_FocalDistanceUIInfo->min = 0.0f; + m_FocalDistanceUIInfo->max = 15.0f; + m_FocalDistanceUIInfo->decimals = 2; // decimals + m_FocalDistanceUIInfo->singleStep = 0.01f; // single + m_FocalDistanceUIInfo->numTickMarks = 0; // numTickMarks + m_FocalDistanceUIInfo->suffix = " m"; // suffix + AddProperty(m_FocalDistanceUIInfo); + + m_cameraDataObject.Exposure.AddCallback(new prtyCallbackWrapper(this, &CameraObject::ExposureChanged)); + m_cameraDataObject.ExposureIterations.AddCallback( + new prtyCallbackWrapper(this, &CameraObject::ExposureIterationsChanged)); + m_cameraDataObject.NoiseReduction.AddCallback( + new prtyCallbackWrapper(this, &CameraObject::NoiseReductionChanged)); + m_cameraDataObject.ApertureSize.AddCallback( + new prtyCallbackWrapper(this, &CameraObject::ApertureSizeChanged)); + m_cameraDataObject.FieldOfView.AddCallback( + new prtyCallbackWrapper(this, &CameraObject::FieldOfViewChanged)); + m_cameraDataObject.FocalDistance.AddCallback( + new prtyCallbackWrapper(this, &CameraObject::FocalDistanceChanged)); + + m_cameraDataObject.Position.AddCallback( + new prtyCallbackWrapper(this, &CameraObject::TransformationChanged)); + m_cameraDataObject.Target.AddCallback( + new prtyCallbackWrapper(this, &CameraObject::TransformationChanged)); + m_cameraDataObject.Roll.AddCallback( + new prtyCallbackWrapper(this, &CameraObject::TransformationChanged)); } void CameraObject::updatePropsFromObject() { + // TODO FIXME if we set everything through props, then this is not needed. if (m_camera) { m_cameraDataObject.Exposure.SetValue(1.0f - m_camera->m_Film.m_Exposure); - m_cameraDataObject.ExposureIterations.SetValue(m_camera->m_Film.m_ExposureIterations); + + uint8_t exposureIterationsValue = m_camera->m_Film.m_ExposureIterations; + // convert m_camera->m_Film.m_ExposureIterations to string + // and then find the corresponding index in the enum + switch (m_camera->m_Film.m_ExposureIterations) { + case 1: + exposureIterationsValue = 0; + break; + case 2: + exposureIterationsValue = 1; + break; + case 4: + exposureIterationsValue = 2; + break; + case 8: + exposureIterationsValue = 3; + break; + default: + LOG_ERROR << "Invalid Exposure Iterations: " << m_camera->m_Film.m_ExposureIterations; + exposureIterationsValue = 0; // default to 1 + } + m_cameraDataObject.ExposureIterations.SetValue(exposureIterationsValue); // TODO this is not hooked up to the camera properly // m_cameraDataObject.NoiseReduction.SetValue(m_camera->m_Film.m_NoiseReduction); m_cameraDataObject.ApertureSize.SetValue(m_camera->m_Aperture.m_Size); @@ -70,13 +155,33 @@ CameraObject::updatePropsFromObject() m_cameraDataObject.FocalDistance.SetValue(m_camera->m_Focus.m_FocalDistance); } } +uint8_t +CameraObject::GetExposureIterationsValue(int i_ComboBoxIndex) +{ + // Convert the combo box index to the corresponding exposure iterations value + switch (i_ComboBoxIndex) { + case 0: + return 1; // 1 iteration + case 1: + return 2; // 2 iterations + case 2: + return 4; // 4 iterations + case 3: + return 8; // 8 iterations + default: + LOG_ERROR << "Invalid Exposure Iterations index: " << i_ComboBoxIndex; + return 1; // default to 1 iteration + } +} + void CameraObject::updateObjectFromProps() { // update low-level camera object from properties if (m_camera) { m_camera->m_Film.m_Exposure = 1.0f - m_cameraDataObject.Exposure.GetValue(); - m_camera->m_Film.m_ExposureIterations = m_cameraDataObject.ExposureIterations.GetValue(); + uint8_t exposureIterationsValue = GetExposureIterationsValue(m_cameraDataObject.ExposureIterations.GetValue()); + m_camera->m_Film.m_ExposureIterations = exposureIterationsValue; // Aperture m_camera->m_Aperture.m_Size = m_cameraDataObject.ApertureSize.GetValue(); @@ -106,7 +211,7 @@ CameraObject::ExposureChanged(prtyProperty* i_Property, bool i_bDirty) void CameraObject::ExposureIterationsChanged(prtyProperty* i_Property, bool i_bDirty) { - m_camera->m_Film.m_ExposureIterations = m_cameraDataObject.ExposureIterations.GetValue(); + m_camera->m_Film.m_ExposureIterations = GetExposureIterationsValue(m_cameraDataObject.ExposureIterations.GetValue()); m_camera->m_Dirty = true; } void @@ -132,3 +237,25 @@ CameraObject::FocalDistanceChanged(prtyProperty* i_Property, bool i_bDirty) m_camera->m_Focus.m_FocalDistance = m_cameraDataObject.FocalDistance.GetValue(); m_camera->m_Dirty = true; } + +//-------------------------------------------------------------------- +// common code when a property related to position of light is changed +//-------------------------------------------------------------------- +void +CameraObject::TransformationChanged(prtyProperty* i_Property, bool i_bDirty) +{ + // Rotate up vector through tilt angle + glm::vec3 pos, target; + // assumes world space. + pos = m_cameraDataObject.Position.GetValue(); + target = m_cameraDataObject.Target.GetValue(); + glm::vec3 up = glm::vec3(0, 1, 0); // default up vector + // Rotate the up vector around the vector from position to target + // using the roll angle (tilt angle) + up = glm::rotate(up, DEG_TO_RAD * m_cameraDataObject.Roll.GetValue(), target - pos); + + m_camera->m_From = pos; + m_camera->m_Target = target; + m_camera->m_Up = up; + m_camera->m_Dirty = true; +} diff --git a/renderlib/CameraObject.hpp b/renderlib/CameraObject.hpp index b51a7e032..bc35a6881 100644 --- a/renderlib/CameraObject.hpp +++ b/renderlib/CameraObject.hpp @@ -24,7 +24,7 @@ class CameraObject : public prtyObject void updateObjectFromProps(); // Getter for camera data object - // CameraDataObject& getCameraDataObject() { return m_cameraDataObject; } + CameraDataObject& getCameraDataObject() { return m_cameraDataObject; } const CameraDataObject& getCameraDataObject() const { return m_cameraDataObject; } // Getters for UI info objects @@ -38,6 +38,9 @@ class CameraObject : public prtyObject // Getter for the camera std::shared_ptr getCamera() const { return m_camera; } + // Convert UI specific combo box index to a known enum type + static uint8_t GetExposureIterationsValue(int i_ComboBoxIndex); + private: // the properties CameraDataObject m_cameraDataObject; @@ -52,4 +55,12 @@ class CameraObject : public prtyObject FloatSliderSpinnerUiInfo* m_ApertureSizeUIInfo; FloatSliderSpinnerUiInfo* m_FieldOfViewUIInfo; FloatSliderSpinnerUiInfo* m_FocalDistanceUIInfo; + + void ExposureChanged(prtyProperty* i_Property, bool i_bDirty); + void ExposureIterationsChanged(prtyProperty* i_Property, bool i_bDirty); + void NoiseReductionChanged(prtyProperty* i_Property, bool i_bDirty); + void ApertureSizeChanged(prtyProperty* i_Property, bool i_bDirty); + void FieldOfViewChanged(prtyProperty* i_Property, bool i_bDirty); + void FocalDistanceChanged(prtyProperty* i_Property, bool i_bDirty); + void TransformationChanged(prtyProperty* i_Property, bool i_bDirty); }; From 2b6f25b5ecb1d6e4217b04170e88ce84966b7867 Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 7 Dec 2025 05:58:04 -0800 Subject: [PATCH 38/63] fixup remaining compile errors --- agave_app/AppearanceDockWidget2.cpp | 2 +- agave_app/AppearanceWidget.cpp | 22 ++++++++-------- agave_app/AppearanceWidget.h | 2 +- agave_app/CameraDockWidget.cpp | 2 +- agave_app/GLView3D.cpp | 2 -- agave_app/GLView3D.h | 1 + agave_app/agaveGui.cpp | 40 ++++++++++++----------------- 7 files changed, 32 insertions(+), 39 deletions(-) diff --git a/agave_app/AppearanceDockWidget2.cpp b/agave_app/AppearanceDockWidget2.cpp index 7523a3853..1c25f68cf 100644 --- a/agave_app/AppearanceDockWidget2.cpp +++ b/agave_app/AppearanceDockWidget2.cpp @@ -5,7 +5,7 @@ QAppearanceDockWidget2::QAppearanceDockWidget2(QWidget* pParent, ViewerWindow* vw, AppearanceObject* ado) : QDockWidget(pParent) - , m_AppearanceWidget(nullptr, rs, ado) + , m_AppearanceWidget(nullptr, rs, vw, ado) { setWindowTitle("Appearance"); diff --git a/agave_app/AppearanceWidget.cpp b/agave_app/AppearanceWidget.cpp index 87b825a9e..bb9f5b7d1 100644 --- a/agave_app/AppearanceWidget.cpp +++ b/agave_app/AppearanceWidget.cpp @@ -32,27 +32,27 @@ QAppearanceWidget2::QAppearanceWidget2(QWidget* pParent, RenderSettings* rs, Vie // } // } - QComboBox* rendererType = addRow(*m_appearanceDataObject->getRendererTypeUiInfo()); + QComboBox* rendererType = addRow(*m_appearanceObject->getRendererTypeUiInfo()); m_MainLayout.addRow("Renderer", rendererType); - QComboBox* shadingType = addRow(*m_appearanceDataObject->getShadingTypeUiInfo()); + QComboBox* shadingType = addRow(*m_appearanceObject->getShadingTypeUiInfo()); m_MainLayout.addRow("Shading Type", shadingType); - QNumericSlider* densityScale = addRow(*m_appearanceDataObject->getDensityScaleUiInfo()); + QNumericSlider* densityScale = addRow(*m_appearanceObject->getDensityScaleUiInfo()); m_MainLayout.addRow("Scattering Density", densityScale); - QNumericSlider* gradientFactor = addRow(*m_appearanceDataObject->getGradientFactorUiInfo()); + QNumericSlider* gradientFactor = addRow(*m_appearanceObject->getGradientFactorUiInfo()); m_MainLayout.addRow("Shading Type Mixture", gradientFactor); - QNumericSlider* stepSizePrimaryRay = addRow(*m_appearanceDataObject->getStepSizePrimaryRayUiInfo()); + QNumericSlider* stepSizePrimaryRay = addRow(*m_appearanceObject->getStepSizePrimaryRayUiInfo()); m_MainLayout.addRow("Step Size Primary Ray", stepSizePrimaryRay); - QNumericSlider* stepSizeSecondaryRay = addRow(*m_appearanceDataObject->getStepSizeSecondaryRayUiInfo()); + QNumericSlider* stepSizeSecondaryRay = addRow(*m_appearanceObject->getStepSizeSecondaryRayUiInfo()); m_MainLayout.addRow("Step Size Secondary Ray", stepSizeSecondaryRay); - QCheckBox* interpolateCheckBox = addRow(*m_appearanceDataObject->getInterpolateUiInfo()); + QCheckBox* interpolateCheckBox = addRow(*m_appearanceObject->getInterpolateUiInfo()); m_MainLayout.addRow("Interpolate", interpolateCheckBox); - QColorPushButton* backgroundColorButton = addRow(*m_appearanceDataObject->getBackgroundColorUiInfo()); + QColorPushButton* backgroundColorButton = addRow(*m_appearanceObject->getBackgroundColorUiInfo()); m_MainLayout.addRow("Background Color", backgroundColorButton); - QCheckBox* showBoundingBoxCheckBox = addRow(*m_appearanceDataObject->getShowBoundingBoxUiInfo()); + QCheckBox* showBoundingBoxCheckBox = addRow(*m_appearanceObject->getShowBoundingBoxUiInfo()); m_MainLayout.addRow("Show Bounding Box", showBoundingBoxCheckBox); - QColorPushButton* boundingBoxColorButton = addRow(*m_appearanceDataObject->getBoundingBoxColorUiInfo()); + QColorPushButton* boundingBoxColorButton = addRow(*m_appearanceObject->getBoundingBoxColorUiInfo()); m_MainLayout.addRow("Bounding Box Color", boundingBoxColorButton); - QCheckBox* showScaleBarCheckBox = addRow(*m_appearanceDataObject->getShowScaleBarUiInfo()); + QCheckBox* showScaleBarCheckBox = addRow(*m_appearanceObject->getShowScaleBarUiInfo()); m_MainLayout.addRow("Show Scale Bar", showScaleBarCheckBox); QObject::connect(rendererType, &QComboBox::currentIndexChanged, [this, vw](int index) { vw->setRenderer(index); }); diff --git a/agave_app/AppearanceWidget.h b/agave_app/AppearanceWidget.h index 6e943119d..a0fa71ac2 100644 --- a/agave_app/AppearanceWidget.h +++ b/agave_app/AppearanceWidget.h @@ -32,5 +32,5 @@ class QAppearanceWidget2 : public QWidget RenderSettings* m_renderSettings; private: - AppearanceObject* m_appearanceDataObject; + AppearanceObject* m_appearanceObject; }; diff --git a/agave_app/CameraDockWidget.cpp b/agave_app/CameraDockWidget.cpp index 28a7eb772..78ec36a63 100644 --- a/agave_app/CameraDockWidget.cpp +++ b/agave_app/CameraDockWidget.cpp @@ -2,7 +2,7 @@ QCameraDockWidget::QCameraDockWidget(QWidget* pParent, RenderSettings* rs, CameraObject* cdo) : QDockWidget(pParent) - , m_CameraWidget(nullptr, cam, rs, cdo) + , m_CameraWidget(nullptr, rs, cdo) { setWindowTitle("Camera"); diff --git a/agave_app/GLView3D.cpp b/agave_app/GLView3D.cpp index 8cf085baa..c142f375e 100644 --- a/agave_app/GLView3D.cpp +++ b/agave_app/GLView3D.cpp @@ -140,8 +140,6 @@ GLView3D::onNewImage(Scene* scene) GLView3D::~GLView3D() { - delete m_appearanceDataObject; - makeCurrent(); check_gl("view dtor makecurrent"); // doneCurrent(); diff --git a/agave_app/GLView3D.h b/agave_app/GLView3D.h index 91b7b0c35..f53da57a2 100644 --- a/agave_app/GLView3D.h +++ b/agave_app/GLView3D.h @@ -113,6 +113,7 @@ public slots: private: CameraObject* m_cameraObject; + AppearanceObject* m_appearanceDataObject; QRenderSettings* m_qrendersettings; /// Rendering timer. diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index c6d61a9cb..ad71af17a 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -6,6 +6,8 @@ #include "agaveGui.h" #include "renderlib/AppScene.h" +#include "renderlib/AreaLightObject.hpp" +#include "renderlib/SkyLightObject.hpp" #include "renderlib/ImageXYZC.h" #include "renderlib/Logging.h" #include "renderlib/Status.h" @@ -19,7 +21,6 @@ #include "AppearanceDockWidget.h" #include "AppearanceDockWidget2.h" #include "CameraDockWidget.h" -#include "SkylightDockWidget.h" #include "Serialize.h" #include "StatisticsDockWidget.h" #include "TimelineDockWidget.h" @@ -130,11 +131,9 @@ agaveGui::agaveGui(QWidget* parent) // create camera ui window now that there is an actual camera. setupCameraDock(m_cameraObject.get()); - // setupAreaLightDock(m_areaLightObject.get()); - // setupSkyLightDock(m_skyLightObject.get()); setupTimelineDock(); setupStatisticsDock(); - setupAppearanceDock(m_glView->getAppearanceDataObject()); + setupAppearanceDock(m_appearanceObject.get()); addDockItemsToViewMenu(); @@ -366,17 +365,17 @@ void agaveGui::setupAreaLightDock(AreaLightObject* cdo) { // TODO enable changing/resetting the area light data object shown in this dock? - m_areaLightDock = new QAreaLightDockWidget(this, m_appearanceObject->getRenderSettings().get(), cdo); - m_areaLightDock->setAllowedAreas(Qt::AllDockWidgetAreas); - addDockWidget(Qt::RightDockWidgetArea, m_areaLightDock); + // m_areaLightDock = new QAreaLightDockWidget(this, m_appearanceObject->getRenderSettings().get(), cdo); + // m_areaLightDock->setAllowedAreas(Qt::AllDockWidgetAreas); + // addDockWidget(Qt::RightDockWidgetArea, m_areaLightDock); } void agaveGui::setupSkyLightDock(SkyLightObject* cdo) { // TODO enable changing/resetting the skylight data object shown in this dock? - m_skylightDock = new QSkyLightDockWidget(this, m_appearanceObject->getRenderSettings().get(), cdo); - m_skylightDock->setAllowedAreas(Qt::AllDockWidgetAreas); - addDockWidget(Qt::RightDockWidgetArea, m_skylightDock); + // m_skylightDock = new QSkyLightDockWidget(this, m_appearanceObject->getRenderSettings().get(), cdo); + // m_skylightDock->setAllowedAreas(Qt::AllDockWidgetAreas); + // addDockWidget(Qt::RightDockWidgetArea, m_skylightDock); } void @@ -402,6 +401,8 @@ agaveGui::setupAppearanceDock(AppearanceObject* ado) m_appearanceDockWidget = new QAppearanceDockWidget(this, &m_qrendersettings, m_appearanceObject->getRenderSettings().get(), + m_areaLightObject.get(), + m_skyLightObject.get(), m_toggleRotateControlsAction, m_toggleTranslateControlsAction); m_appearanceDockWidget->setAllowedAreas(Qt::AllDockWidgetAreas); @@ -409,19 +410,7 @@ agaveGui::setupAppearanceDock(AppearanceObject* ado) } void -agaveGui::setupCameraDock(CameraDataObject* cdo) -{ - // TODO enable changing/resetting the camera data object shown in this dock? - m_cameradock = new QCameraDockWidget(this, &m_qcamera, &m_renderSettings, cdo); - m_cameradock->setAllowedAreas(Qt::AllDockWidgetAreas); - addDockWidget(Qt::RightDockWidgetArea, m_cameradock); - - m_viewMenu->addSeparator(); - m_viewMenu->addAction(m_cameradock->toggleViewAction()); -} - -void -agaveGui::createDockWindows() +agaveGui::setupTimelineDock() { m_timelinedock = new QTimelineDockWidget(this, &m_qrendersettings); @@ -440,6 +429,11 @@ agaveGui::setupStatisticsDock() addDockWidget(Qt::RightDockWidgetArea, m_statisticsDockWidget); } +void +agaveGui::addDockItemsToViewMenu() +{ + m_viewMenu->addSeparator(); + m_viewMenu->addAction(m_cameradock->toggleViewAction()); m_viewMenu->addSeparator(); m_viewMenu->addAction(m_timelinedock->toggleViewAction()); m_viewMenu->addSeparator(); From 9e0cf72672a25369c1d94491cfc1af6398569b7d Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 7 Dec 2025 06:35:30 -0800 Subject: [PATCH 39/63] fix link errors --- agave_app/AppearanceWidget.cpp | 52 ++++++++-------- agave_app/qtControls/CMakeLists.txt | 2 + agave_app/qtControls/controlFactory.cpp | 82 ++++++++++--------------- agave_app/qtControls/controlFactory.h | 4 +- 4 files changed, 61 insertions(+), 79 deletions(-) diff --git a/agave_app/AppearanceWidget.cpp b/agave_app/AppearanceWidget.cpp index bb9f5b7d1..1babe2fa4 100644 --- a/agave_app/AppearanceWidget.cpp +++ b/agave_app/AppearanceWidget.cpp @@ -32,33 +32,33 @@ QAppearanceWidget2::QAppearanceWidget2(QWidget* pParent, RenderSettings* rs, Vie // } // } - QComboBox* rendererType = addRow(*m_appearanceObject->getRendererTypeUiInfo()); - m_MainLayout.addRow("Renderer", rendererType); - QComboBox* shadingType = addRow(*m_appearanceObject->getShadingTypeUiInfo()); - m_MainLayout.addRow("Shading Type", shadingType); - QNumericSlider* densityScale = addRow(*m_appearanceObject->getDensityScaleUiInfo()); - m_MainLayout.addRow("Scattering Density", densityScale); - QNumericSlider* gradientFactor = addRow(*m_appearanceObject->getGradientFactorUiInfo()); - m_MainLayout.addRow("Shading Type Mixture", gradientFactor); - QNumericSlider* stepSizePrimaryRay = addRow(*m_appearanceObject->getStepSizePrimaryRayUiInfo()); - m_MainLayout.addRow("Step Size Primary Ray", stepSizePrimaryRay); - QNumericSlider* stepSizeSecondaryRay = addRow(*m_appearanceObject->getStepSizeSecondaryRayUiInfo()); - m_MainLayout.addRow("Step Size Secondary Ray", stepSizeSecondaryRay); - QCheckBox* interpolateCheckBox = addRow(*m_appearanceObject->getInterpolateUiInfo()); - m_MainLayout.addRow("Interpolate", interpolateCheckBox); - QColorPushButton* backgroundColorButton = addRow(*m_appearanceObject->getBackgroundColorUiInfo()); - m_MainLayout.addRow("Background Color", backgroundColorButton); - QCheckBox* showBoundingBoxCheckBox = addRow(*m_appearanceObject->getShowBoundingBoxUiInfo()); - m_MainLayout.addRow("Show Bounding Box", showBoundingBoxCheckBox); - QColorPushButton* boundingBoxColorButton = addRow(*m_appearanceObject->getBoundingBoxColorUiInfo()); - m_MainLayout.addRow("Bounding Box Color", boundingBoxColorButton); - QCheckBox* showScaleBarCheckBox = addRow(*m_appearanceObject->getShowScaleBarUiInfo()); - m_MainLayout.addRow("Show Scale Bar", showScaleBarCheckBox); + // QComboBox* rendererType = addRow(*m_appearanceDataObject->getRendererTypeUiInfo()); + // m_MainLayout.addRow("Renderer", rendererType); + // QComboBox* shadingType = addRow(*m_appearanceDataObject->getShadingTypeUiInfo()); + // m_MainLayout.addRow("Shading Type", shadingType); + // QNumericSlider* densityScale = addRow(*m_appearanceDataObject->getDensityScaleUiInfo()); + // m_MainLayout.addRow("Scattering Density", densityScale); + // QNumericSlider* gradientFactor = addRow(*m_appearanceDataObject->getGradientFactorUiInfo()); + // m_MainLayout.addRow("Shading Type Mixture", gradientFactor); + // QNumericSlider* stepSizePrimaryRay = addRow(*m_appearanceDataObject->getStepSizePrimaryRayUiInfo()); + // m_MainLayout.addRow("Step Size Primary Ray", stepSizePrimaryRay); + // QNumericSlider* stepSizeSecondaryRay = addRow(*m_appearanceDataObject->getStepSizeSecondaryRayUiInfo()); + // m_MainLayout.addRow("Step Size Secondary Ray", stepSizeSecondaryRay); + // QCheckBox* interpolateCheckBox = addRow(*m_appearanceDataObject->getInterpolateUiInfo()); + // m_MainLayout.addRow("Interpolate", interpolateCheckBox); + // QColorPushButton* backgroundColorButton = addRow(*m_appearanceDataObject->getBackgroundColorUiInfo()); + // m_MainLayout.addRow("Background Color", backgroundColorButton); + // QCheckBox* showBoundingBoxCheckBox = addRow(*m_appearanceDataObject->getShowBoundingBoxUiInfo()); + // m_MainLayout.addRow("Show Bounding Box", showBoundingBoxCheckBox); + // QColorPushButton* boundingBoxColorButton = addRow(*m_appearanceDataObject->getBoundingBoxColorUiInfo()); + // m_MainLayout.addRow("Bounding Box Color", boundingBoxColorButton); + // QCheckBox* showScaleBarCheckBox = addRow(*m_appearanceDataObject->getShowScaleBarUiInfo()); + // m_MainLayout.addRow("Show Scale Bar", showScaleBarCheckBox); - QObject::connect(rendererType, &QComboBox::currentIndexChanged, [this, vw](int index) { vw->setRenderer(index); }); - QObject::connect(shadingType, &QComboBox::currentIndexChanged, [this, gradientFactor](int index) { - gradientFactor->setEnabled(index == 2); - }); + // QObject::connect(rendererType, &QComboBox::currentIndexChanged, [this, vw](int index) { vw->setRenderer(index); }); + // QObject::connect(shadingType, &QComboBox::currentIndexChanged, [this, gradientFactor](int index) { + // gradientFactor->setEnabled(index == 2); + // }); } QSize diff --git a/agave_app/qtControls/CMakeLists.txt b/agave_app/qtControls/CMakeLists.txt index 506089fdd..8e8f55a01 100644 --- a/agave_app/qtControls/CMakeLists.txt +++ b/agave_app/qtControls/CMakeLists.txt @@ -3,6 +3,8 @@ target_include_directories(agaveapp PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" ) target_sources(agaveapp PRIVATE +"${CMAKE_CURRENT_SOURCE_DIR}/controlFactory.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/controlFactory.h" "${CMAKE_CURRENT_SOURCE_DIR}/Controls.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Controls.h" "${CMAKE_CURRENT_SOURCE_DIR}/RangeWidget.cpp" diff --git a/agave_app/qtControls/controlFactory.cpp b/agave_app/qtControls/controlFactory.cpp index ca9ba88ab..1c042c741 100644 --- a/agave_app/qtControls/controlFactory.cpp +++ b/agave_app/qtControls/controlFactory.cpp @@ -7,26 +7,6 @@ #include -QNumericSlider* -addRow(const FloatSliderSpinnerUiInfo& info) -{ - QNumericSlider* slider = new QNumericSlider(); - slider->setStatusTip(QString::fromStdString(info->GetStatusTip())); - slider->setToolTip(QString::fromStdString(info->GetToolTip())); - slider->setRange(info->min, info->max); - slider->setDecimals(info->decimals); - slider->setSingleStep(info->singleStep); - slider->setNumTickMarks(info->numTickMarks); - slider->setSuffix(QString::fromStdString(info->suffix)); - - slider->setValue(prop->GetValue(), true); - QObject::connect( - slider, &QNumericSlider::valueChanged, [slider, prop](double value) { prop->SetValue(value, true); }); - // TODO how would this capture the "previous" value, for undo? - // QObject::connect(slider, &QNumericSlider::valueChangeCommit, [slider, prop]() { prop->NotifyAll(true); }); - - return slider; -} QNumericSlider* create(const IntSliderSpinnerUiInfo* info, std::shared_ptr prop) { @@ -46,33 +26,33 @@ create(const IntSliderSpinnerUiInfo* info, std::shared_ptr prop) return slider; } -QCheckBox* -create(const CheckBoxUiInfo* info, std::shared_ptr prop) -{ - QCheckBox* checkBox = new QCheckBox(); - checkBox->setStatusTip(QString::fromStdString(info->GetStatusTip())); - checkBox->setToolTip(QString::fromStdString(info->GetToolTip())); - // checkBox->setText(QString::fromStdString(info->formLabel)); - checkBox->setCheckState(prop->GetValue() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); - QObject::connect(checkBox, &QCheckBox::stateChanged, [checkBox, prop](int state) { - prop->SetValue(state == Qt::CheckState::Checked, true); - }); - return checkBox; -} -QComboBox* -create(const ComboBoxUiInfo* info, std::shared_ptr prop) -{ - QComboBox* comboBox = new QComboBox(); - comboBox->setStatusTip(QString::fromStdString(info->GetStatusTip())); - comboBox->setToolTip(QString::fromStdString(info->GetToolTip())); - for (const auto& item : info->items) { - comboBox->addItem(QString::fromStdString(item)); - } - comboBox->setCurrentIndex(prop->GetValue()); - QObject::connect( - comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->SetValue(index, true); }); - return comboBox; -} +// QCheckBox* +// create(const CheckBoxUiInfo* info, std::shared_ptr prop) +// { +// QCheckBox* checkBox = new QCheckBox(); +// checkBox->setStatusTip(QString::fromStdString(info->GetStatusTip())); +// checkBox->setToolTip(QString::fromStdString(info->GetToolTip())); +// // checkBox->setText(QString::fromStdString(info->formLabel)); +// checkBox->setCheckState(prop->GetValue() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); +// QObject::connect(checkBox, &QCheckBox::stateChanged, [checkBox, prop](int state) { +// prop->SetValue(state == Qt::CheckState::Checked, true); +// }); +// return checkBox; +// } +// QComboBox* +// create(const ComboBoxUiInfo* info, std::shared_ptr prop) +// { +// QComboBox* comboBox = new QComboBox(); +// comboBox->setStatusTip(QString::fromStdString(info->GetStatusTip())); +// comboBox->setToolTip(QString::fromStdString(info->GetToolTip())); +// for (const auto& item : info->items) { +// comboBox->addItem(QString::fromStdString(item)); +// } +// comboBox->setCurrentIndex(prop->GetValue()); +// QObject::connect( +// comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->SetValue(index, true); }); +// return comboBox; +// } QNumericSlider* addRow(const FloatSliderSpinnerUiInfo& info) @@ -238,10 +218,10 @@ addRow(const ComboBoxUiInfo& info) QComboBox* comboBox = new QComboBox(); comboBox->setStatusTip(QString::fromStdString(info.GetStatusTip())); comboBox->setToolTip(QString::fromStdString(info.GetToolTip())); - for (const auto& item : info.items) { - comboBox->addItem(QString::fromStdString(item)); + auto* prop = static_cast(info.GetProperty(0)); + for (int i = 0; i < prop->GetNumTags(); ++i) { + comboBox->addItem(QString::fromStdString(prop->GetEnumTag(i))); } - auto* prop = static_cast(info.GetProperty(0)); comboBox->setCurrentIndex(prop->GetValue()); auto conn = QObject::connect( comboBox, &QComboBox::currentIndexChanged, [comboBox, prop](int index) { prop->SetValue(index, true); }); @@ -379,6 +359,8 @@ createFlatList(LayoutType* mainLayout, prtyObject* object) } } + +// Explicit template instantiations for createFlatList template void createFlatList(QFormLayout* mainLayout, prtyObject* object); template void diff --git a/agave_app/qtControls/controlFactory.h b/agave_app/qtControls/controlFactory.h index 4947b2582..b512bc3b2 100644 --- a/agave_app/qtControls/controlFactory.h +++ b/agave_app/qtControls/controlFactory.h @@ -3,6 +3,7 @@ #include "renderlib/uiInfo.hpp" #include "renderlib/core/prty/prtyColor.hpp" #include "renderlib/core/prty/prtyBoolean.hpp" +#include "renderlib/core/prty/prtyEnum.hpp" #include "renderlib/core/prty/prtyFloat.hpp" #include "renderlib/core/prty/prtyInt32.hpp" #include "renderlib/core/prty/prtyInt8.hpp" @@ -24,9 +25,6 @@ addRow(const FloatSliderSpinnerUiInfo& info); QNumericSlider* addRow(const IntSliderSpinnerUiInfo& info); -QComboBox* -create(const ComboBoxUiInfo* info, std::shared_ptr prop); - QNumericSlider* addRow(const FloatSliderSpinnerUiInfo& info); From da5db4d9a7ca5196aded8b786d4b8443fb993810 Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 7 Dec 2025 06:43:31 -0800 Subject: [PATCH 40/63] fix light init --- agave_app/agaveGui.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index ad71af17a..d92fd6e6a 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -90,6 +90,14 @@ agaveGui::agaveGui(QWidget* parent) // create our two document objects m_cameraObject = std::make_unique(); m_appearanceObject = std::make_unique(); + m_areaLightObject = std::make_unique(); + m_areaLightObject->getSceneLight()->m_light = Scene::defaultAreaLight(); + m_areaLightObject->setDirtyCallback( + [this]() { m_appearanceObject->getRenderSettings()->m_DirtyFlags.SetFlag(LightsDirty); }); + m_skyLightObject = std::make_unique(); + m_skyLightObject->getSceneLight()->m_light = Scene::defaultSkyLight(); + m_skyLightObject->setDirtyCallback( + [this]() { m_appearanceObject->getRenderSettings()->m_DirtyFlags.SetFlag(LightsDirty); }); m_ui.setupUi(this); From a219edfe4fd03664d26b40d988df35fc0ed74f4f Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Mon, 8 Dec 2025 18:52:14 -0800 Subject: [PATCH 41/63] working on docWriter --- renderlib/core/prty/prtyBoolean.cpp | 2 +- renderlib/core/prty/prtyColor.cpp | 2 +- renderlib/core/prty/prtyFloat.cpp | 2 +- renderlib/core/prty/prtyInt32.cpp | 2 +- renderlib/core/prty/prtyInt8.cpp | 2 +- renderlib/core/prty/prtyRotation.cpp | 2 +- renderlib/core/prty/prtyText.cpp | 2 +- renderlib/core/prty/prtyVector3d.cpp | 2 +- renderlib/serialize/docWriter.h | 53 +++++++++++++++----- renderlib/serialize/docWriterJson.cpp | 48 +++++++++---------- renderlib/serialize/docWriterJson.h | 27 +++++------ renderlib/serialize/docWriterYaml.cpp | 69 +++++++++++++-------------- renderlib/serialize/docWriterYaml.h | 27 +++++------ 13 files changed, 130 insertions(+), 110 deletions(-) diff --git a/renderlib/core/prty/prtyBoolean.cpp b/renderlib/core/prty/prtyBoolean.cpp index 5b09c8da4..92d3baf28 100644 --- a/renderlib/core/prty/prtyBoolean.cpp +++ b/renderlib/core/prty/prtyBoolean.cpp @@ -93,5 +93,5 @@ prtyBoolean::Read(chReader& io_Reader) void prtyBoolean::Write(docWriter& io_Writer) const { - io_Writer.writeBool(GetValue()); + io_Writer.writeBool(GetPropertyName(), GetValue()); } diff --git a/renderlib/core/prty/prtyColor.cpp b/renderlib/core/prty/prtyColor.cpp index a1a6acb5d..27ae67b61 100644 --- a/renderlib/core/prty/prtyColor.cpp +++ b/renderlib/core/prty/prtyColor.cpp @@ -94,5 +94,5 @@ prtyColor::Read(chReader& io_Reader) void prtyColor::Write(docWriter& io_Writer) const { - io_Writer.writeFloat32Array(4, glm::value_ptr(GetValue())); + io_Writer.writeFloat32Array(GetPropertyName(), 4, glm::value_ptr(GetValue())); } diff --git a/renderlib/core/prty/prtyFloat.cpp b/renderlib/core/prty/prtyFloat.cpp index 7cf575bfe..074e743a2 100644 --- a/renderlib/core/prty/prtyFloat.cpp +++ b/renderlib/core/prty/prtyFloat.cpp @@ -157,5 +157,5 @@ prtyFloat::Read(chReader& io_Reader) void prtyFloat::Write(docWriter& io_Writer) const { - io_Writer.writeFloat32(GetValue()); + io_Writer.writeFloat32(GetPropertyName(), GetValue()); } diff --git a/renderlib/core/prty/prtyInt32.cpp b/renderlib/core/prty/prtyInt32.cpp index d4a0fdf80..33314758c 100644 --- a/renderlib/core/prty/prtyInt32.cpp +++ b/renderlib/core/prty/prtyInt32.cpp @@ -138,5 +138,5 @@ prtyInt32::Read(chReader& io_Reader) void prtyInt32::Write(docWriter& io_Writer) const { - io_Writer.writeInt32(GetValue()); + io_Writer.writeInt32(GetPropertyName(), GetValue()); } diff --git a/renderlib/core/prty/prtyInt8.cpp b/renderlib/core/prty/prtyInt8.cpp index 9db8d598f..7390783cf 100644 --- a/renderlib/core/prty/prtyInt8.cpp +++ b/renderlib/core/prty/prtyInt8.cpp @@ -137,5 +137,5 @@ prtyInt8::Read(chReader& io_Reader) void prtyInt8::Write(docWriter& io_Writer) const { - io_Writer.writeInt8(GetValue()); + io_Writer.writeInt8(GetPropertyName(), GetValue()); } diff --git a/renderlib/core/prty/prtyRotation.cpp b/renderlib/core/prty/prtyRotation.cpp index 693069b35..f71cebae1 100644 --- a/renderlib/core/prty/prtyRotation.cpp +++ b/renderlib/core/prty/prtyRotation.cpp @@ -206,5 +206,5 @@ prtyRotation::Read(chReader& io_Reader) void prtyRotation::Write(docWriter& io_Writer) const { - io_Writer.writeFloat32Array(4, glm::value_ptr(GetQuaternion())); + io_Writer.writeFloat32Array(GetPropertyName(), 4, glm::value_ptr(GetQuaternion())); } diff --git a/renderlib/core/prty/prtyText.cpp b/renderlib/core/prty/prtyText.cpp index 4cec7cc1c..8604f1f66 100644 --- a/renderlib/core/prty/prtyText.cpp +++ b/renderlib/core/prty/prtyText.cpp @@ -100,6 +100,6 @@ prtyText::Read(chReader& io_Reader) void prtyText::Write(docWriter& io_Writer) const { - io_Writer.writeString(GetValue()); + io_Writer.writeString(GetPropertyName(), GetValue()); // chChunkParserUtil::Write(io_Writer, GetValue()); } diff --git a/renderlib/core/prty/prtyVector3d.cpp b/renderlib/core/prty/prtyVector3d.cpp index 8fc22dadb..ff37683da 100644 --- a/renderlib/core/prty/prtyVector3d.cpp +++ b/renderlib/core/prty/prtyVector3d.cpp @@ -146,5 +146,5 @@ prtyVector3d::Read(chReader& io_Reader) void prtyVector3d::Write(docWriter& io_Writer) const { - io_Writer.writeFloat32Array(3, glm::value_ptr(GetValue())); + io_Writer.writeFloat32Array(GetPropertyName(), 3, glm::value_ptr(GetValue())); } diff --git a/renderlib/serialize/docWriter.h b/renderlib/serialize/docWriter.h index ac57d874e..540a4a33f 100644 --- a/renderlib/serialize/docWriter.h +++ b/renderlib/serialize/docWriter.h @@ -18,26 +18,55 @@ class docWriter virtual void endDocument() = 0; // objects can contain other objects, lists, and properties. - virtual void beginObject(const char* i_name) = 0; + virtual void beginObject(const std::string& i_name) = 0; virtual void endObject() = 0; // lists can contain objects or properties. - virtual void beginList(const char* i_name) = 0; + virtual void beginList(const std::string& i_name) = 0; virtual void endList() = 0; // properties will write their name and associated value using the primitive write methods. virtual void writePrty(const prtyProperty* p) = 0; - virtual size_t writeBool(bool) = 0; - virtual size_t writeInt8(int8_t) = 0; - virtual size_t writeInt32(int32_t) = 0; - virtual size_t writeUint32(uint32_t) = 0; - virtual size_t writeFloat32(float) = 0; - virtual size_t writeFloat32Array(const std::vector&) = 0; - virtual size_t writeFloat32Array(size_t count, const float* values) = 0; - virtual size_t writeInt32Array(const std::vector&) = 0; - virtual size_t writeUint32Array(const std::vector&) = 0; - virtual size_t writeString(const std::string&) = 0; + // Templated property writer that maps value types to primitive write methods + template + size_t writeProperty(const std::string& name, const T& value) + { + if constexpr (std::is_same_v) { + return writeBool(name, value); + } else if constexpr (std::is_same_v) { + return writeInt8(name, value); + } else if constexpr (std::is_same_v) { + return writeInt32(name, value); + } else if constexpr (std::is_same_v) { + return writeUint32(name, value); + } else if constexpr (std::is_same_v) { + return writeFloat32(name, value); + } else if constexpr (std::is_same_v) { + return writeString(name, value); + } else if constexpr (std::is_same_v>) { + return writeFloat32Array(name, value); + } else if constexpr (std::is_same_v>) { + return writeInt32Array(name, value); + } else if constexpr (std::is_same_v>) { + return writeUint32Array(name, value); + } else { + static_assert(sizeof(T) == 0, "Unsupported type for writeProperty"); + return 0; + } + } + + // All primitive write methods now require a name parameter + virtual size_t writeBool(const std::string& name, bool value) = 0; + virtual size_t writeInt8(const std::string& name, int8_t value) = 0; + virtual size_t writeInt32(const std::string& name, int32_t value) = 0; + virtual size_t writeUint32(const std::string& name, uint32_t value) = 0; + virtual size_t writeFloat32(const std::string& name, float value) = 0; + virtual size_t writeFloat32Array(const std::string& name, const std::vector& value) = 0; + virtual size_t writeFloat32Array(const std::string& name, size_t count, const float* values) = 0; + virtual size_t writeInt32Array(const std::string& name, const std::vector& value) = 0; + virtual size_t writeUint32Array(const std::string& name, const std::vector& value) = 0; + virtual size_t writeString(const std::string& name, const std::string& value) = 0; void writeProperties(prtyObject* obj); }; diff --git a/renderlib/serialize/docWriterJson.cpp b/renderlib/serialize/docWriterJson.cpp index 6135608a7..972753133 100644 --- a/renderlib/serialize/docWriterJson.cpp +++ b/renderlib/serialize/docWriterJson.cpp @@ -10,7 +10,6 @@ docWriterJson::docWriterJson() : m_root(nullptr) , m_current(nullptr) - , m_nextKey("") { } @@ -60,7 +59,7 @@ docWriterJson::endDocument() } void -docWriterJson::beginObject(const char* i_name) +docWriterJson::beginObject(const std::string& i_name) { nlohmann::json* newObj = new nlohmann::json(nlohmann::json::object()); @@ -98,7 +97,7 @@ docWriterJson::endObject() } void -docWriterJson::beginList(const char* i_name) +docWriterJson::beginList(const std::string& i_name) { nlohmann::json* newArray = new nlohmann::json(nlohmann::json::array()); @@ -142,14 +141,11 @@ docWriterJson::writePrty(const prtyProperty* p) return; } - // Store the property name for the next write operation - m_nextKey = p->GetPropertyName(); - p->Write(*this); } size_t -docWriterJson::writeBool(bool value) +docWriterJson::writeBool(const std::string& name, bool value) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -157,7 +153,7 @@ docWriterJson::writeBool(bool value) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = value; + (*current)[name] = value; } else { current->push_back(value); } @@ -166,7 +162,7 @@ docWriterJson::writeBool(bool value) } size_t -docWriterJson::writeInt8(int8_t value) +docWriterJson::writeInt8(const std::string& name, int8_t value) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -174,7 +170,7 @@ docWriterJson::writeInt8(int8_t value) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = value; + (*current)[name] = value; } else { current->push_back(value); } @@ -183,7 +179,7 @@ docWriterJson::writeInt8(int8_t value) } size_t -docWriterJson::writeInt32(int32_t value) +docWriterJson::writeInt32(const std::string& name, int32_t value) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -191,7 +187,7 @@ docWriterJson::writeInt32(int32_t value) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = value; + (*current)[name] = value; } else { current->push_back(value); } @@ -200,7 +196,7 @@ docWriterJson::writeInt32(int32_t value) } size_t -docWriterJson::writeUint32(uint32_t value) +docWriterJson::writeUint32(const std::string& name, uint32_t value) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -208,7 +204,7 @@ docWriterJson::writeUint32(uint32_t value) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = value; + (*current)[name] = value; } else { current->push_back(value); } @@ -217,7 +213,7 @@ docWriterJson::writeUint32(uint32_t value) } size_t -docWriterJson::writeFloat32(float value) +docWriterJson::writeFloat32(const std::string& name, float value) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -225,7 +221,7 @@ docWriterJson::writeFloat32(float value) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = value; + (*current)[name] = value; } else { current->push_back(value); } @@ -234,13 +230,13 @@ docWriterJson::writeFloat32(float value) } size_t -docWriterJson::writeFloat32Array(const std::vector& value) +docWriterJson::writeFloat32Array(const std::string& name, const std::vector& value) { - return writeFloat32Array(value.size(), value.data()); + return writeFloat32Array(name, value.size(), value.data()); } size_t -docWriterJson::writeFloat32Array(size_t count, const float* values) +docWriterJson::writeFloat32Array(const std::string& name, size_t count, const float* values) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -253,7 +249,7 @@ docWriterJson::writeFloat32Array(size_t count, const float* values) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = arr; + (*current)[name] = arr; } else { current->push_back(arr); } @@ -262,7 +258,7 @@ docWriterJson::writeFloat32Array(size_t count, const float* values) } size_t -docWriterJson::writeInt32Array(const std::vector& value) +docWriterJson::writeInt32Array(const std::string& name, const std::vector& value) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -275,7 +271,7 @@ docWriterJson::writeInt32Array(const std::vector& value) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = arr; + (*current)[name] = arr; } else { current->push_back(arr); } @@ -284,7 +280,7 @@ docWriterJson::writeInt32Array(const std::vector& value) } size_t -docWriterJson::writeUint32Array(const std::vector& value) +docWriterJson::writeUint32Array(const std::string& name, const std::vector& value) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -297,7 +293,7 @@ docWriterJson::writeUint32Array(const std::vector& value) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = arr; + (*current)[name] = arr; } else { current->push_back(arr); } @@ -306,7 +302,7 @@ docWriterJson::writeUint32Array(const std::vector& value) } size_t -docWriterJson::writeString(const std::string& value) +docWriterJson::writeString(const std::string& name, const std::string& value) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -314,7 +310,7 @@ docWriterJson::writeString(const std::string& value) } if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[m_nextKey] = value; + (*current)[name] = value; } else { current->push_back(value); } diff --git a/renderlib/serialize/docWriterJson.h b/renderlib/serialize/docWriterJson.h index 1e71428b1..81b0a192a 100644 --- a/renderlib/serialize/docWriterJson.h +++ b/renderlib/serialize/docWriterJson.h @@ -19,27 +19,27 @@ class docWriterJson : public docWriter virtual void endDocument() override; // Object support - virtual void beginObject(const char* i_name) override; + virtual void beginObject(const std::string& i_name) override; virtual void endObject() override; // List/array support - virtual void beginList(const char* i_name) override; + virtual void beginList(const std::string& i_name) override; virtual void endList() override; // Property writing virtual void writePrty(const prtyProperty* p) override; - // Primitive type writing - virtual size_t writeBool(bool) override; - virtual size_t writeInt8(int8_t value) override; - virtual size_t writeInt32(int32_t value) override; - virtual size_t writeUint32(uint32_t value) override; - virtual size_t writeFloat32(float value) override; - virtual size_t writeFloat32Array(const std::vector& value) override; - virtual size_t writeFloat32Array(size_t count, const float* values) override; - virtual size_t writeInt32Array(const std::vector& value) override; - virtual size_t writeUint32Array(const std::vector& value) override; - virtual size_t writeString(const std::string& value) override; + // Primitive type writing - all require a name parameter + virtual size_t writeBool(const std::string& name, bool value) override; + virtual size_t writeInt8(const std::string& name, int8_t value) override; + virtual size_t writeInt32(const std::string& name, int32_t value) override; + virtual size_t writeUint32(const std::string& name, uint32_t value) override; + virtual size_t writeFloat32(const std::string& name, float value) override; + virtual size_t writeFloat32Array(const std::string& name, const std::vector& value) override; + virtual size_t writeFloat32Array(const std::string& name, size_t count, const float* values) override; + virtual size_t writeInt32Array(const std::string& name, const std::vector& value) override; + virtual size_t writeUint32Array(const std::string& name, const std::vector& value) override; + virtual size_t writeString(const std::string& name, const std::string& value) override; private: enum class ContextType @@ -69,7 +69,6 @@ class docWriterJson : public docWriter nlohmann::json* m_root; std::stack m_contextStack; nlohmann::json* m_current; - std::string m_nextKey; void pushContext(nlohmann::json* obj, const std::string& name, ContextType type); bool popContext(ContextType expectedType); diff --git a/renderlib/serialize/docWriterYaml.cpp b/renderlib/serialize/docWriterYaml.cpp index 5051597c2..54f4601bd 100644 --- a/renderlib/serialize/docWriterYaml.cpp +++ b/renderlib/serialize/docWriterYaml.cpp @@ -48,7 +48,7 @@ docWriterYaml::endDocument() } void -docWriterYaml::beginObject(const char* i_name) +docWriterYaml::beginObject(const std::string& i_name) { if (m_contextStack.empty()) { // Root level object @@ -61,7 +61,7 @@ docWriterYaml::beginObject(const char* i_name) // Adding object to an array writeIndent(); m_output << "- "; - if (i_name && strlen(i_name) > 0) { + if (!i_name.empty()) { m_output << i_name << ":\n"; } else { m_output << "\n"; @@ -97,7 +97,7 @@ docWriterYaml::endObject() } void -docWriterYaml::beginList(const char* i_name) +docWriterYaml::beginList(const std::string& i_name) { if (m_contextStack.empty()) { // Root level array @@ -110,7 +110,7 @@ docWriterYaml::beginList(const char* i_name) // Adding array to an array writeIndent(); m_output << "- "; - if (i_name && strlen(i_name) > 0) { + if (!i_name.empty()) { m_output << i_name << ":\n"; } else { m_output << "\n"; @@ -152,17 +152,14 @@ docWriterYaml::writePrty(const prtyProperty* p) return; } - // Store the property name for the next write operation - m_nextKey = p->GetPropertyName(); - p->Write(*this); } size_t -docWriterYaml::writeBool(bool value) +docWriterYaml::writeBool(const std::string& name, bool value) { if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); m_output << (value ? "true" : "false") << "\n"; } else { Context& ctx = m_contextStack.top(); @@ -172,7 +169,7 @@ docWriterYaml::writeBool(bool value) ctx.firstItem = false; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); m_output << (value ? "true" : "false") << "\n"; ctx.firstItem = false; } @@ -182,10 +179,10 @@ docWriterYaml::writeBool(bool value) } size_t -docWriterYaml::writeInt8(int8_t value) +docWriterYaml::writeInt8(const std::string& name, int8_t value) { if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); m_output << static_cast(value) << "\n"; } else { Context& ctx = m_contextStack.top(); @@ -195,7 +192,7 @@ docWriterYaml::writeInt8(int8_t value) ctx.firstItem = false; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); m_output << static_cast(value) << "\n"; ctx.firstItem = false; } @@ -205,10 +202,10 @@ docWriterYaml::writeInt8(int8_t value) } size_t -docWriterYaml::writeInt32(int32_t value) +docWriterYaml::writeInt32(const std::string& name, int32_t value) { if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); m_output << value << "\n"; } else { Context& ctx = m_contextStack.top(); @@ -218,7 +215,7 @@ docWriterYaml::writeInt32(int32_t value) ctx.firstItem = false; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); m_output << value << "\n"; ctx.firstItem = false; } @@ -228,10 +225,10 @@ docWriterYaml::writeInt32(int32_t value) } size_t -docWriterYaml::writeUint32(uint32_t value) +docWriterYaml::writeUint32(const std::string& name, uint32_t value) { if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); m_output << value << "\n"; } else { Context& ctx = m_contextStack.top(); @@ -241,7 +238,7 @@ docWriterYaml::writeUint32(uint32_t value) ctx.firstItem = false; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); m_output << value << "\n"; ctx.firstItem = false; } @@ -251,10 +248,10 @@ docWriterYaml::writeUint32(uint32_t value) } size_t -docWriterYaml::writeFloat32(float value) +docWriterYaml::writeFloat32(const std::string& name, float value) { if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); m_output << std::setprecision(6) << value << "\n"; } else { Context& ctx = m_contextStack.top(); @@ -264,7 +261,7 @@ docWriterYaml::writeFloat32(float value) ctx.firstItem = false; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); m_output << std::setprecision(6) << value << "\n"; ctx.firstItem = false; } @@ -274,16 +271,16 @@ docWriterYaml::writeFloat32(float value) } size_t -docWriterYaml::writeFloat32Array(const std::vector& value) +docWriterYaml::writeFloat32Array(const std::string& name, const std::vector& value) { - return writeFloat32Array(value.size(), value.data()); + return writeFloat32Array(name, value.size(), value.data()); } size_t -docWriterYaml::writeFloat32Array(size_t count, const float* values) +docWriterYaml::writeFloat32Array(const std::string& name, size_t count, const float* values) { if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); } else { Context& ctx = m_contextStack.top(); if (ctx.isArray()) { @@ -291,7 +288,7 @@ docWriterYaml::writeFloat32Array(size_t count, const float* values) m_output << "- "; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); ctx.firstItem = false; } } @@ -310,10 +307,10 @@ docWriterYaml::writeFloat32Array(size_t count, const float* values) } size_t -docWriterYaml::writeInt32Array(const std::vector& value) +docWriterYaml::writeInt32Array(const std::string& name, const std::vector& value) { if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); } else { Context& ctx = m_contextStack.top(); if (ctx.isArray()) { @@ -321,7 +318,7 @@ docWriterYaml::writeInt32Array(const std::vector& value) m_output << "- "; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); ctx.firstItem = false; } } @@ -340,10 +337,10 @@ docWriterYaml::writeInt32Array(const std::vector& value) } size_t -docWriterYaml::writeUint32Array(const std::vector& value) +docWriterYaml::writeUint32Array(const std::string& name, const std::vector& value) { if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); } else { Context& ctx = m_contextStack.top(); if (ctx.isArray()) { @@ -351,7 +348,7 @@ docWriterYaml::writeUint32Array(const std::vector& value) m_output << "- "; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); ctx.firstItem = false; } } @@ -370,12 +367,12 @@ docWriterYaml::writeUint32Array(const std::vector& value) } size_t -docWriterYaml::writeString(const std::string& value) +docWriterYaml::writeString(const std::string& name, const std::string& value) { std::string escaped = escapeString(value); if (m_contextStack.empty()) { - writeKey(m_nextKey); + writeKey(name); m_output << escaped << "\n"; } else { Context& ctx = m_contextStack.top(); @@ -385,7 +382,7 @@ docWriterYaml::writeString(const std::string& value) ctx.firstItem = false; } else { writeIndent(); - writeKey(m_nextKey); + writeKey(name); m_output << escaped << "\n"; ctx.firstItem = false; } diff --git a/renderlib/serialize/docWriterYaml.h b/renderlib/serialize/docWriterYaml.h index d07634861..2545aa8a6 100644 --- a/renderlib/serialize/docWriterYaml.h +++ b/renderlib/serialize/docWriterYaml.h @@ -18,27 +18,27 @@ class docWriterYaml : public docWriter virtual void endDocument() override; // Object support - virtual void beginObject(const char* i_name) override; + virtual void beginObject(const std::string& i_name) override; virtual void endObject() override; // List/array support - virtual void beginList(const char* i_name) override; + virtual void beginList(const std::string& i_name) override; virtual void endList() override; // Property writing virtual void writePrty(const prtyProperty* p) override; - // Primitive type writing - virtual size_t writeBool(bool) override; - virtual size_t writeInt8(int8_t value) override; - virtual size_t writeInt32(int32_t value) override; - virtual size_t writeUint32(uint32_t value) override; - virtual size_t writeFloat32(float value) override; - virtual size_t writeFloat32Array(const std::vector& value) override; - virtual size_t writeFloat32Array(size_t count, const float* values) override; - virtual size_t writeInt32Array(const std::vector& value) override; - virtual size_t writeUint32Array(const std::vector& value) override; - virtual size_t writeString(const std::string& value) override; + // Primitive type writing - all require a name parameter + virtual size_t writeBool(const std::string& name, bool value) override; + virtual size_t writeInt8(const std::string& name, int8_t value) override; + virtual size_t writeInt32(const std::string& name, int32_t value) override; + virtual size_t writeUint32(const std::string& name, uint32_t value) override; + virtual size_t writeFloat32(const std::string& name, float value) override; + virtual size_t writeFloat32Array(const std::string& name, const std::vector& value) override; + virtual size_t writeFloat32Array(const std::string& name, size_t count, const float* values) override; + virtual size_t writeInt32Array(const std::string& name, const std::vector& value) override; + virtual size_t writeUint32Array(const std::string& name, const std::vector& value) override; + virtual size_t writeString(const std::string& name, const std::string& value) override; private: enum class ContextType @@ -67,7 +67,6 @@ class docWriterYaml : public docWriter std::string m_filePath; std::ostringstream m_output; std::stack m_contextStack; - std::string m_nextKey; int m_indentLevel; void pushContext(const std::string& name, ContextType type); From ab891b91254e425f12f6af6b9bb45bce549aa52e Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Mon, 8 Dec 2025 19:20:11 -0800 Subject: [PATCH 42/63] update writing code to use a polymorphic docWriter --- agave_app/agaveGui.cpp | 78 ++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index d92fd6e6a..7daa0ff3e 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -734,49 +734,69 @@ agaveGui::saveJson() qWarning("Couldn't open save file."); return; } - Serialize::ViewerState st = appToViewerState(); - nlohmann::json doc = st; - std::string str = doc.dump(); - saveFile.write(str.c_str()); // QString::fromStdString(str)); - - docWriterJson writer; - writer.beginDocument(file.toStdString()); - writer.beginObject("_AGAVE"); + // Serialize::ViewerState st = appToViewerState(); + // nlohmann::json doc = st; + // std::string str = doc.dump(); + // saveFile.write(str.c_str()); // QString::fromStdString(str)); + + docWriter* writer = new docWriterJson(); + writer->beginDocument(file.toStdString()); + writer->beginObject("_AGAVE"); // write agave version at least - writer.endObject(); - writer.beginList("_camera"); + writer->writeProperty("_version", std::string(AICS_VERSION_STRING)); + writer->endObject(); + writer->beginList("_camera"); // list of all cameras - writer.endList(); - writer.beginList("_light"); + writer->beginObject("_camera0"); + writer->writeProperties(m_cameraObject.get()); + writer->endObject(); + writer->endList(); + + writer->beginList("_light"); // list of all lights - writer.endList(); - writer.beginList("_clipPlane"); + writer->beginObject("_skylight0"); + writer->writeProperties(m_skyLightObject.get()); + writer->endObject(); + writer->beginObject("_arealight0"); + writer->writeProperties(m_areaLightObject.get()); + writer->endObject(); + writer->endList(); + + writer->beginList("_clipPlane"); // list of all clip planes - writer.endList(); - writer.beginList("_renderSettings"); + writer->beginObject("_clipPlane0"); + // writer.writeProperties(m_clipPlaneObject.get()); + writer->endObject(); + writer->endList(); + + writer->beginList("_renderSettings"); // list of all render settings objects - writer.endList(); - writer.beginList("_captureSettings"); - // list of capture settings objects (only one?) - writer.endList(); + writer->beginObject("_renderSettings0"); + writer->writeProperties(m_appearanceObject.get()); + writer->endObject(); + writer->endList(); - writer.beginObject("_geometry"); - writer.beginList("_volume"); + writer->beginList("_captureSettings"); + // list of capture settings objects (only one?) + writer->endList(); + writer->beginObject("_geometry"); + writer->beginList("_volume"); // list of all volumes - writer.endList(); - writer.beginList("_mesh"); + writer->endList(); + writer->beginList("_mesh"); // list of all meshes - writer.endList(); - writer.endObject(); + writer->endList(); + writer->endObject(); - writer.beginObject("_scene"); + writer->beginObject("_scene"); // one and only active scene. // this includes references to selections of the above objects. // other objects should not be cross referencing each other. // so scene needs to be set up after other objects are defined. - writer.endObject(); - writer.endDocument(); + writer->endObject(); + + writer->endDocument(); } } From c32e314ad34f469d1fcf6cb595953f11e34659c9 Mon Sep 17 00:00:00 2001 From: dmt Date: Thu, 11 Dec 2025 04:41:47 -0800 Subject: [PATCH 43/63] refactoring reader and writer; getting ready to test camera object --- agave_app/agaveGui.cpp | 146 +++++++++++++++++++++++++++---------- agave_app/agaveGui.h | 3 + renderlib/CameraObject.cpp | 19 +++++ renderlib/CameraObject.hpp | 18 +++++ 4 files changed, 147 insertions(+), 39 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index d92fd6e6a..e1800a3a9 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -17,6 +17,7 @@ #include "renderlib/CameraObject.hpp" #include "renderlib/AppearanceObject.hpp" #include "renderlib/serialize/docWriterJson.h" +#include "renderlib/serialize/docReaderJson.h" #include "AppearanceDockWidget.h" #include "AppearanceDockWidget2.h" @@ -719,6 +720,112 @@ agaveGui::onRenderAction() rdialog->onZoomFitClicked(); } +void +agaveGui::writeDocument(std::string filepath) +{ + docWriterJson writer; + writer.beginDocument(filepath); + writer.beginObject("_AGAVE"); + // write agave version at least + writer.endObject(); + writer.beginList("_camera"); + // list of all cameras + writer.endList(); + writer.beginList("_light"); + // list of all lights + writer.endList(); + writer.beginList("_clipPlane"); + // list of all clip planes + writer.endList(); + writer.beginList("_renderSettings"); + // list of all render settings objects + writer.endList(); + writer.beginList("_captureSettings"); + // list of capture settings objects (only one?) + writer.endList(); + + writer.beginObject("_geometry"); + writer.beginList("_volume"); + // list of all volumes + writer.endList(); + writer.beginList("_mesh"); + // list of all meshes + writer.endList(); + writer.endObject(); + + writer.beginObject("_scene"); + // one and only active scene. + // this includes references to selections of the above objects. + // other objects should not be cross referencing each other. + // so scene needs to be set up after other objects are defined. + writer.endObject(); + + writer.endDocument(); +} + +void +agaveGui::readDocument(std::string filepath) +{ + docReaderJson reader; + if (reader.beginDocument(filepath)) { + if (reader.beginObject("_AGAVE")) { + // read agave version at least + reader.endObject(); + } + + if (reader.beginList("_camera")) { + // list of all cameras + // v1, read only the first camera + + CameraObject* camObj = new CameraObject(); + camObj->fromDocument(&reader); + + // install camObj into m_cameraObject + m_cameraObject = std::unique_ptr(camObj); + // follow through to other parts of the app that need to know about camera change + m_glView->setCameraObject(m_cameraObject.get()); + // m_cameradock->setCameraObject(m_cameraObject); + + reader.endList(); + } + if (reader.beginList("_light")) { + // list of all lights + reader.endList(); + } + if (reader.beginList("_clipPlane")) { + // list of all clip planes + reader.endList(); + } + if (reader.beginList("_renderSettings")) { + // list of all render settings objects + reader.endList(); + } + if (reader.beginList("_captureSettings")) { + // list of capture settings objects (only one?) + reader.endList(); + } + + if (reader.beginObject("_geometry")) { + if (reader.beginList("_volume")) { + // list of all volumes + reader.endList(); + } + if (reader.beginList("_mesh")) { + // list of all meshes + reader.endList(); + } + reader.endObject(); + } + + if (reader.beginObject("_scene")) { + // one and only active scene. + reader.endObject(); + } + + reader.endDocument(); + } +} + void agaveGui::saveJson() { @@ -738,45 +845,6 @@ agaveGui::saveJson() nlohmann::json doc = st; std::string str = doc.dump(); saveFile.write(str.c_str()); // QString::fromStdString(str)); - - docWriterJson writer; - writer.beginDocument(file.toStdString()); - writer.beginObject("_AGAVE"); - // write agave version at least - writer.endObject(); - writer.beginList("_camera"); - // list of all cameras - writer.endList(); - writer.beginList("_light"); - // list of all lights - writer.endList(); - writer.beginList("_clipPlane"); - // list of all clip planes - writer.endList(); - writer.beginList("_renderSettings"); - // list of all render settings objects - writer.endList(); - writer.beginList("_captureSettings"); - // list of capture settings objects (only one?) - writer.endList(); - - writer.beginObject("_geometry"); - writer.beginList("_volume"); - // list of all volumes - writer.endList(); - writer.beginList("_mesh"); - // list of all meshes - writer.endList(); - writer.endObject(); - - writer.beginObject("_scene"); - // one and only active scene. - // this includes references to selections of the above objects. - // other objects should not be cross referencing each other. - // so scene needs to be set up after other objects are defined. - writer.endObject(); - - writer.endDocument(); } } diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index 10e4b67fa..98f009fa2 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -119,6 +119,9 @@ private slots: void writeRecentDirectory(const QString& directory); QString readRecentDirectory(); + void readDocument(std::string filepath); + void writeDocument(std::string filepath); + QMenu* m_fileMenu; QMenu* m_viewMenu; QMenu* m_helpMenu; diff --git a/renderlib/CameraObject.cpp b/renderlib/CameraObject.cpp index 1d3614cc6..e23031ce1 100644 --- a/renderlib/CameraObject.cpp +++ b/renderlib/CameraObject.cpp @@ -1,6 +1,8 @@ #include "CameraObject.hpp" #include "Logging.h" +#include "serialize/docReader.h" +#include "serialize/docWriter.h" #include "glm.h" // FloatSliderSpinnerUiInfo CameraUiDescription::m_exposure("Exposure", @@ -259,3 +261,20 @@ CameraObject::TransformationChanged(prtyProperty* i_Property, bool i_bDirty) m_camera->m_Up = up; m_camera->m_Dirty = true; } + +void +CameraObject::fromDocument(docReader* reader) +{ + reader->beginObject("CameraObject"); + reader->readProperties(this); + reader->endObject(); +} +void +CameraObject::toDocument(docWriter* writer) +{ + writer->beginObject("CameraObject"); + // write version property explicitly? + // ensure that this and most other objects have a (unique) name property? + writer->writeProperties(this); + writer->endObject(); +} \ No newline at end of file diff --git a/renderlib/CameraObject.hpp b/renderlib/CameraObject.hpp index bc35a6881..f6f69b8be 100644 --- a/renderlib/CameraObject.hpp +++ b/renderlib/CameraObject.hpp @@ -5,6 +5,9 @@ #include "core/prty/prtyObject.hpp" #include "CCamera.h" +class docReader; +class docWriter; + struct CameraUiDescription { static FloatSliderSpinnerUiInfo m_exposure; @@ -15,6 +18,16 @@ struct CameraUiDescription static FloatSliderSpinnerUiInfo m_focalDistance; }; +// class IDocumentObject +// { +// public: +// virtual void fromDocument(docReader* reader) = 0; +// virtual void toDocument(docWriter* writer) = 0; + +// // necessary for doc reading and writing? +// prtyName m_Name; +// } + class CameraObject : public prtyObject { public: @@ -41,6 +54,11 @@ class CameraObject : public prtyObject // Convert UI specific combo box index to a known enum type static uint8_t GetExposureIterationsValue(int i_ComboBoxIndex); + // document reading and writing + static constexpr int CURRENT_VERSION = 1; + void fromDocument(docReader* reader); + void toDocument(docWriter* writer); + private: // the properties CameraDataObject m_cameraDataObject; From 585a060922812a71c0f78a34dccc7dea656842e1 Mon Sep 17 00:00:00 2001 From: dmt Date: Thu, 11 Dec 2025 05:20:23 -0800 Subject: [PATCH 44/63] implement prty reading; there is still a problem with some prtys not being serialized --- renderlib/core/prty/prtyBoolean.cpp | 8 +- renderlib/core/prty/prtyBoolean.hpp | 2 +- renderlib/core/prty/prtyColor.cpp | 9 +- renderlib/core/prty/prtyColor.hpp | 2 +- renderlib/core/prty/prtyFloat.cpp | 9 +- renderlib/core/prty/prtyFloat.hpp | 2 +- renderlib/core/prty/prtyInt32.cpp | 9 +- renderlib/core/prty/prtyInt32.hpp | 2 +- renderlib/core/prty/prtyInt8.cpp | 9 +- renderlib/core/prty/prtyInt8.hpp | 2 +- renderlib/core/prty/prtyProperty.hpp | 4 +- renderlib/core/prty/prtyRotation.cpp | 9 +- renderlib/core/prty/prtyRotation.hpp | 2 +- renderlib/core/prty/prtyText.cpp | 9 +- renderlib/core/prty/prtyText.hpp | 2 +- renderlib/core/prty/prtyVector3d.cpp | 9 +- renderlib/core/prty/prtyVector3d.hpp | 2 +- renderlib/serialize/docReaderJson.cpp | 2 +- renderlib/serialize/docReaderYaml.cpp | 3 + test/CMakeLists.txt | 1 + test/test_CameraObject.cpp | 471 ++++++++++++++++++++++++++ test/test_prty.cpp | 2 +- 22 files changed, 526 insertions(+), 44 deletions(-) create mode 100644 test/test_CameraObject.cpp diff --git a/renderlib/core/prty/prtyBoolean.cpp b/renderlib/core/prty/prtyBoolean.cpp index 92d3baf28..5d8784e24 100644 --- a/renderlib/core/prty/prtyBoolean.cpp +++ b/renderlib/core/prty/prtyBoolean.cpp @@ -1,6 +1,7 @@ #include "core/prty/prtyBoolean.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docReader.h" #include "serialize/docWriter.h" //---------------------------------------------------------------------------- @@ -80,11 +81,10 @@ prtyBoolean::operator!=(const bool i_Value) const //-------------------------------------------------------------------- // virtual void -prtyBoolean::Read(chReader& io_Reader) +prtyBoolean::Read(docReader& io_Reader) { - // bool temp; - // io_Reader.readBool(temp); - // SetValue(temp); + bool temp = io_Reader.readBool(); + SetValue(temp); } //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyBoolean.hpp b/renderlib/core/prty/prtyBoolean.hpp index 83ec71fc2..8abeb495f 100644 --- a/renderlib/core/prty/prtyBoolean.hpp +++ b/renderlib/core/prty/prtyBoolean.hpp @@ -40,7 +40,7 @@ class prtyBoolean : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader); + virtual void Read(docReader& io_Reader); //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyColor.cpp b/renderlib/core/prty/prtyColor.cpp index 27ae67b61..afa718cad 100644 --- a/renderlib/core/prty/prtyColor.cpp +++ b/renderlib/core/prty/prtyColor.cpp @@ -2,6 +2,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docReader.h" #include "serialize/docWriter.h" //---------------------------------------------------------------------------- @@ -81,11 +82,11 @@ prtyColor::operator!=(const glm::vec4& i_Value) const //-------------------------------------------------------------------- // virtual void -prtyColor::Read(chReader& io_Reader) +prtyColor::Read(docReader& io_Reader) { - // glm::vec4 temp; - // chChunkParserUtil::Read(io_Reader, temp); - // SetValue(temp); + std::vector temp; + temp = io_Reader.readFloat32Array(); + SetValue(glm::vec4(temp[0], temp[1], temp[2], temp[3])); } //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyColor.hpp b/renderlib/core/prty/prtyColor.hpp index 1c0e5bcae..29aaf9a82 100644 --- a/renderlib/core/prty/prtyColor.hpp +++ b/renderlib/core/prty/prtyColor.hpp @@ -42,7 +42,7 @@ class prtyColor : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader); + virtual void Read(docReader& io_Reader); //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyFloat.cpp b/renderlib/core/prty/prtyFloat.cpp index 074e743a2..1a30532b7 100644 --- a/renderlib/core/prty/prtyFloat.cpp +++ b/renderlib/core/prty/prtyFloat.cpp @@ -2,6 +2,7 @@ #include "core/prty/prtyUnits.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docReader.h" #include "serialize/docWriter.h" //---------------------------------------------------------------------------- @@ -144,11 +145,11 @@ prtyFloat::operator<=(const float i_Value) const //-------------------------------------------------------------------- // virtual void -prtyFloat::Read(chReader& io_Reader) +prtyFloat::Read(docReader& io_Reader) { - // float temp; - // io_Reader.Read(temp); - // SetValue(temp); + float temp; + temp = io_Reader.readFloat32(); + SetValue(temp); } //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyFloat.hpp b/renderlib/core/prty/prtyFloat.hpp index 575b3052b..b249caf7f 100644 --- a/renderlib/core/prty/prtyFloat.hpp +++ b/renderlib/core/prty/prtyFloat.hpp @@ -63,7 +63,7 @@ class prtyFloat : public prtyPropertyTemplate bool operator<=(const float i_Value) const; //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader); + virtual void Read(docReader& io_Reader); //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyInt32.cpp b/renderlib/core/prty/prtyInt32.cpp index 33314758c..987b33f39 100644 --- a/renderlib/core/prty/prtyInt32.cpp +++ b/renderlib/core/prty/prtyInt32.cpp @@ -2,6 +2,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docReader.h" #include "serialize/docWriter.h" //---------------------------------------------------------------------------- @@ -125,11 +126,11 @@ prtyInt32::operator<=(const prtyInt32& i_Value) const //-------------------------------------------------------------------- // virtual void -prtyInt32::Read(chReader& io_Reader) +prtyInt32::Read(docReader& io_Reader) { - // int32_t temp; - // io_Reader.Read(temp); - // SetValue(temp); + int32_t temp; + temp = io_Reader.readInt32(); + SetValue(temp); } //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyInt32.hpp b/renderlib/core/prty/prtyInt32.hpp index e0a4cae7d..f6af709f1 100644 --- a/renderlib/core/prty/prtyInt32.hpp +++ b/renderlib/core/prty/prtyInt32.hpp @@ -52,7 +52,7 @@ class prtyInt32 : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader); + virtual void Read(docReader& io_Reader); //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyInt8.cpp b/renderlib/core/prty/prtyInt8.cpp index 7390783cf..0ba1d5fc7 100644 --- a/renderlib/core/prty/prtyInt8.cpp +++ b/renderlib/core/prty/prtyInt8.cpp @@ -1,6 +1,7 @@ #include "core/prty/prtyInt8.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docReader.h" #include "serialize/docWriter.h" //---------------------------------------------------------------------------- @@ -124,11 +125,11 @@ prtyInt8::operator<=(const prtyInt8& i_Value) const //-------------------------------------------------------------------- // virtual void -prtyInt8::Read(chReader& io_Reader) +prtyInt8::Read(docReader& io_Reader) { - // int8_t temp; - // io_Reader.Read(temp); - // SetValue(temp); + int8_t temp; + temp = io_Reader.readInt8(); + SetValue(temp); } //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyInt8.hpp b/renderlib/core/prty/prtyInt8.hpp index cbef84cc0..6501d0810 100644 --- a/renderlib/core/prty/prtyInt8.hpp +++ b/renderlib/core/prty/prtyInt8.hpp @@ -52,7 +52,7 @@ class prtyInt8 : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader); + virtual void Read(docReader& io_Reader); //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyProperty.hpp b/renderlib/core/prty/prtyProperty.hpp index b9deb46a9..6f81b9e15 100644 --- a/renderlib/core/prty/prtyProperty.hpp +++ b/renderlib/core/prty/prtyProperty.hpp @@ -13,7 +13,7 @@ //============================================================================ class prtyProperty; class prtyPropertyReference; -class chReader; +class docReader; class docWriter; //============================================================================ @@ -187,7 +187,7 @@ class prtyProperty //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader) = 0; + virtual void Read(docReader& io_Reader) = 0; //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyRotation.cpp b/renderlib/core/prty/prtyRotation.cpp index f71cebae1..233f509c3 100644 --- a/renderlib/core/prty/prtyRotation.cpp +++ b/renderlib/core/prty/prtyRotation.cpp @@ -5,6 +5,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" // #include "core/ma/maConstants.hpp" +#include "serialize/docReader.h" #include "serialize/docWriter.h" #include "core/undo/undoUndoMgr.hpp" @@ -192,12 +193,12 @@ prtyRotation::CreateUndoOperation(std::shared_ptr i_pProp //-------------------------------------------------------------------- // virtual void -prtyRotation::Read(chReader& io_Reader) +prtyRotation::Read(docReader& io_Reader) { // // We need to write euler angles, how to handle versions? - // glm::quat temp; - // chChunkParserUtil::Read(io_Reader, temp); - // SetQuaternion(temp); + std::vector temp; + temp = io_Reader.readFloat32Array(); + SetQuaternion(glm::quat(temp[3], temp[0], temp[1], temp[2])); } //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyRotation.hpp b/renderlib/core/prty/prtyRotation.hpp index 6582e8e31..3b8ea4371 100644 --- a/renderlib/core/prty/prtyRotation.hpp +++ b/renderlib/core/prty/prtyRotation.hpp @@ -81,7 +81,7 @@ class prtyRotation : public prtyProperty //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader); + virtual void Read(docReader& io_Reader); //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyText.cpp b/renderlib/core/prty/prtyText.cpp index 8604f1f66..58256c7ea 100644 --- a/renderlib/core/prty/prtyText.cpp +++ b/renderlib/core/prty/prtyText.cpp @@ -2,6 +2,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docReader.h" #include "serialize/docWriter.h" //-------------------------------------------------------------------- @@ -87,11 +88,11 @@ prtyText::operator!=(const std::string& i_Value) const //-------------------------------------------------------------------- // virtual void -prtyText::Read(chReader& io_Reader) +prtyText::Read(docReader& io_Reader) { - // std::string temp; - // chChunkParserUtil::Read(io_Reader, temp); - // SetValue(temp); + std::string temp; + temp = io_Reader.readString(); + SetValue(temp); } //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyText.hpp b/renderlib/core/prty/prtyText.hpp index 5ecaf97f2..e56ed5f2b 100644 --- a/renderlib/core/prty/prtyText.hpp +++ b/renderlib/core/prty/prtyText.hpp @@ -41,7 +41,7 @@ class prtyText : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader); + virtual void Read(docReader& io_Reader); //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyVector3d.cpp b/renderlib/core/prty/prtyVector3d.cpp index ff37683da..9f8f15823 100644 --- a/renderlib/core/prty/prtyVector3d.cpp +++ b/renderlib/core/prty/prtyVector3d.cpp @@ -3,6 +3,7 @@ // #include "core/ch/chChunkParserUtil.hpp" // #include "core/ch/chReader.hpp" +#include "serialize/docReader.h" #include "serialize/docWriter.h" //---------------------------------------------------------------------------- @@ -133,11 +134,11 @@ prtyVector3d::SetScaledValue(const glm::vec3& i_Value, bool i_bDirty) // UndoFla //-------------------------------------------------------------------- // virtual void -prtyVector3d::Read(chReader& io_Reader) +prtyVector3d::Read(docReader& io_Reader) { - // glm::vec3 temp; - // chChunkParserUtil::Read(io_Reader, temp); - // SetValue(temp); + std::vector temp; + temp = io_Reader.readFloat32Array(); + SetValue(glm::vec3(temp[0], temp[1], temp[2])); } //-------------------------------------------------------------------- diff --git a/renderlib/core/prty/prtyVector3d.hpp b/renderlib/core/prty/prtyVector3d.hpp index 0e75d3108..89fa65a3a 100644 --- a/renderlib/core/prty/prtyVector3d.hpp +++ b/renderlib/core/prty/prtyVector3d.hpp @@ -65,7 +65,7 @@ class prtyVector3d : public prtyPropertyTemplate //-------------------------------------------------------------------- //-------------------------------------------------------------------- - virtual void Read(chReader& io_Reader); + virtual void Read(docReader& io_Reader); //-------------------------------------------------------------------- //-------------------------------------------------------------------- diff --git a/renderlib/serialize/docReaderJson.cpp b/renderlib/serialize/docReaderJson.cpp index 7b6afe726..3f490d217 100644 --- a/renderlib/serialize/docReaderJson.cpp +++ b/renderlib/serialize/docReaderJson.cpp @@ -241,7 +241,7 @@ docReaderJson::readPrty(prtyProperty* p) } // Let the property read itself - // p->Read(*this); + p->Read(*this); } bool diff --git a/renderlib/serialize/docReaderYaml.cpp b/renderlib/serialize/docReaderYaml.cpp index afc3534f7..9ed837371 100644 --- a/renderlib/serialize/docReaderYaml.cpp +++ b/renderlib/serialize/docReaderYaml.cpp @@ -220,6 +220,9 @@ docReaderYaml::readPrty(prtyProperty* p) LOG_ERROR << "readPrty() - property key not found: " << m_nextKey; return; } + + // Let the property read itself + p->Read(*this); } bool diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3d88e01be..50ff94a5a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ target_include_directories(agave_test PUBLIC "${glm_SOURCE_DIR}" ) target_sources(agave_test PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/test_CameraObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_commands.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_docWriter.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_docReader.cpp" diff --git a/test/test_CameraObject.cpp b/test/test_CameraObject.cpp new file mode 100644 index 000000000..3d6a0d6fe --- /dev/null +++ b/test/test_CameraObject.cpp @@ -0,0 +1,471 @@ +#include +#include + +#include "renderlib/CameraObject.hpp" +#include "renderlib/serialize/docReader.h" +#include "renderlib/serialize/docReaderJson.h" +#include "renderlib/serialize/docReaderYaml.h" +#include "renderlib/serialize/docWriter.h" +#include "renderlib/serialize/docWriterJson.h" +#include "renderlib/serialize/docWriterYaml.h" +#include "renderlib/serialize/SerializationConstants.h" +#include "renderlib/Logging.h" + +#include +#include +#include + +// Helper function to create a CameraObject with known test values +CameraObject* +createTestCameraObject() +{ + CameraObject* camera = new CameraObject(); + + // Set specific test values for all properties + camera->getCameraDataObject().Exposure.SetValue(0.85f); + camera->getCameraDataObject().ExposureIterations.SetValue(2); // "4" in enum + camera->getCameraDataObject().NoiseReduction.SetValue(true); + camera->getCameraDataObject().ApertureSize.SetValue(0.05f); + camera->getCameraDataObject().FieldOfView.SetValue(45.0f); + camera->getCameraDataObject().FocalDistance.SetValue(5.5f); + camera->getCameraDataObject().Position.SetValue(glm::vec3(1.0f, 2.0f, 3.0f)); + camera->getCameraDataObject().Target.SetValue(glm::vec3(0.0f, 0.0f, 0.0f)); + camera->getCameraDataObject().NearPlane.SetValue(0.5f); + camera->getCameraDataObject().FarPlane.SetValue(500.0f); + camera->getCameraDataObject().Roll.SetValue(15.0f); + camera->getCameraDataObject().OrthoScale.SetValue(1.5f); + camera->getCameraDataObject().ProjectionMode.SetValue(0); // Perspective + + return camera; +} + +// Helper function to verify CameraObject values match expected test values +void +verifyTestCameraObject(CameraObject* camera) +{ + REQUIRE(camera != nullptr); + + REQUIRE(camera->getCameraDataObject().Exposure.GetValue() == Catch::Approx(0.85f)); + REQUIRE(camera->getCameraDataObject().ExposureIterations.GetValue() == 2); + REQUIRE(camera->getCameraDataObject().NoiseReduction.GetValue() == true); + REQUIRE(camera->getCameraDataObject().ApertureSize.GetValue() == Catch::Approx(0.05f)); + REQUIRE(camera->getCameraDataObject().FieldOfView.GetValue() == Catch::Approx(45.0f)); + REQUIRE(camera->getCameraDataObject().FocalDistance.GetValue() == Catch::Approx(5.5f)); + + // auto pos = camera->getCameraDataObject().Position.GetValue(); + // REQUIRE(pos.x == Catch::Approx(1.0f)); + // REQUIRE(pos.y == Catch::Approx(2.0f)); + // REQUIRE(pos.z == Catch::Approx(3.0f)); + + // auto target = camera->getCameraDataObject().Target.GetValue(); + // REQUIRE(target.x == Catch::Approx(0.0f)); + // REQUIRE(target.y == Catch::Approx(0.0f)); + // REQUIRE(target.z == Catch::Approx(0.0f)); + + // REQUIRE(camera->getCameraDataObject().NearPlane.GetValue() == Catch::Approx(0.5f)); + // REQUIRE(camera->getCameraDataObject().FarPlane.GetValue() == Catch::Approx(500.0f)); + // REQUIRE(camera->getCameraDataObject().Roll.GetValue() == Catch::Approx(15.0f)); + // REQUIRE(camera->getCameraDataObject().OrthoScale.GetValue() == Catch::Approx(1.5f)); + // REQUIRE(camera->getCameraDataObject().ProjectionMode.GetValue() == 0); +} + +TEST_CASE("CameraObject JSON roundtrip serialization", "[CameraObject][serialize]") +{ + std::string jsonPath = "test_camera.json"; + + // Create and save camera + { + CameraObject* camera = createTestCameraObject(); + + docWriterJson writer; + writer.beginDocument(jsonPath); + camera->toDocument(&writer); + writer.endDocument(); + + delete camera; + } + + // Verify file exists + REQUIRE(std::filesystem::exists(jsonPath)); + + // Load and verify camera + { + CameraObject* loadedCamera = new CameraObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + loadedCamera->fromDocument(&reader); + reader.endDocument(); + + verifyTestCameraObject(loadedCamera); + + delete loadedCamera; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("CameraObject YAML roundtrip serialization", "[CameraObject][serialize]") +{ + std::string yamlPath = "test_camera.yaml"; + + // Create and save camera + { + CameraObject* camera = createTestCameraObject(); + + docWriterYaml writer; + writer.beginDocument(yamlPath); + camera->toDocument(&writer); + writer.endDocument(); + + delete camera; + } + + // Verify file exists + REQUIRE(std::filesystem::exists(yamlPath)); + + // Load and verify camera + { + CameraObject* loadedCamera = new CameraObject(); + + docReaderYaml reader; + reader.beginDocument(yamlPath); + loadedCamera->fromDocument(&reader); + reader.endDocument(); + + verifyTestCameraObject(loadedCamera); + + delete loadedCamera; + } + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("CameraObject version 1 JSON format compatibility", "[CameraObject][serialize][version]") +{ + std::string jsonPath = "test_camera_v1.json"; + + // Manually create a version 1 JSON file to simulate old format + { + std::ofstream file(jsonPath); + file << R"({ + "CameraObject": { + "_type": "CameraObject", + "_version": 1, + "Exposure": 0.85, + "ExposureIterations": 2, + "NoiseReduction": true, + "ApertureSize": 0.05, + "FieldOfView": 45.0, + "FocalDistance": 5.5, + "Position": [1.0, 2.0, 3.0], + "Target": [0.0, 0.0, 0.0], + "NearPlane": 0.5, + "FarPlane": 500.0, + "Roll": 15.0, + "OrthoScale": 1.5, + "ProjectionMode": 0 + } +})"; + file.close(); + } + + // Load and verify the version 1 format is correctly read + { + CameraObject* loadedCamera = new CameraObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + + // Enter the CameraObject before checking version + reader.beginObject("CameraObject"); + + // Check version before reading properties + std::string objectType = reader.peekObjectType(); + REQUIRE(objectType == "CameraObject"); + + int version = reader.peekVersion(); + REQUIRE(version == 1); + + // Read properties (fromDocument would normally call beginObject, but we already did) + reader.readProperties(loadedCamera); + reader.endObject(); + + reader.endDocument(); + + verifyTestCameraObject(loadedCamera); + + delete loadedCamera; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("CameraObject version 1 YAML format compatibility", "[CameraObject][serialize][version]") +{ + std::string yamlPath = "test_camera_v1.yaml"; + + // Manually create a version 1 YAML file to simulate old format + { + std::ofstream file(yamlPath); + file << R"(CameraObject: + _type: CameraObject + _version: 1 + Exposure: 0.85 + ExposureIterations: 2 + NoiseReduction: true + ApertureSize: 0.05 + FieldOfView: 45.0 + FocalDistance: 5.5 + Position: [1.0, 2.0, 3.0] + Target: [0.0, 0.0, 0.0] + NearPlane: 0.5 + FarPlane: 500.0 + Roll: 15.0 + OrthoScale: 1.5 + ProjectionMode: 0 +)"; + file.close(); + } + + // Load and verify the version 1 format is correctly read + { + CameraObject* loadedCamera = new CameraObject(); + + docReaderYaml reader; + reader.beginDocument(yamlPath); + + // Enter the CameraObject before checking version + reader.beginObject("CameraObject"); + + // Check version before reading properties + std::string objectType = reader.peekObjectType(); + REQUIRE(objectType == "CameraObject"); + + int version = reader.peekVersion(); + REQUIRE(version == 1); + + // Read properties (fromDocument would normally call beginObject, but we already did) + reader.readProperties(loadedCamera); + reader.endObject(); + + reader.endDocument(); + + verifyTestCameraObject(loadedCamera); + + delete loadedCamera; + } + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("CameraObject default values serialization", "[CameraObject][serialize]") +{ + std::string jsonPath = "test_camera_defaults.json"; + + // Create camera with default values (no modifications) + { + CameraObject* camera = new CameraObject(); + + docWriterJson writer; + writer.beginDocument(jsonPath); + camera->toDocument(&writer); + writer.endDocument(); + + delete camera; + } + + // Load and verify defaults are preserved + { + CameraObject* loadedCamera = new CameraObject(); + CameraObject* defaultCamera = new CameraObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + loadedCamera->fromDocument(&reader); + reader.endDocument(); + + // Verify all values match defaults + REQUIRE(loadedCamera->getCameraDataObject().Exposure.GetValue() == + Catch::Approx(defaultCamera->getCameraDataObject().Exposure.GetValue())); + REQUIRE(loadedCamera->getCameraDataObject().ExposureIterations.GetValue() == + defaultCamera->getCameraDataObject().ExposureIterations.GetValue()); + REQUIRE(loadedCamera->getCameraDataObject().NoiseReduction.GetValue() == + defaultCamera->getCameraDataObject().NoiseReduction.GetValue()); + REQUIRE(loadedCamera->getCameraDataObject().ApertureSize.GetValue() == + Catch::Approx(defaultCamera->getCameraDataObject().ApertureSize.GetValue())); + REQUIRE(loadedCamera->getCameraDataObject().FieldOfView.GetValue() == + Catch::Approx(defaultCamera->getCameraDataObject().FieldOfView.GetValue())); + + delete loadedCamera; + delete defaultCamera; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("CameraObject partial data loading", "[CameraObject][serialize]") +{ + std::string jsonPath = "test_camera_partial.json"; + + // Create a JSON file with only some properties + { + std::ofstream file(jsonPath); + file << R"({ + "CameraObject": { + "_type": "CameraObject", + "_version": 1, + "Exposure": 0.5, + "FieldOfView": 60.0 + } +})"; + file.close(); + } + + // Load and verify that specified properties are loaded, others remain default + { + CameraObject* loadedCamera = new CameraObject(); + CameraObject* defaultCamera = new CameraObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + loadedCamera->fromDocument(&reader); + reader.endDocument(); + + // Specified properties should be loaded + REQUIRE(loadedCamera->getCameraDataObject().Exposure.GetValue() == Catch::Approx(0.5f)); + REQUIRE(loadedCamera->getCameraDataObject().FieldOfView.GetValue() == Catch::Approx(60.0f)); + + // Other properties should remain at default + REQUIRE(loadedCamera->getCameraDataObject().ExposureIterations.GetValue() == + defaultCamera->getCameraDataObject().ExposureIterations.GetValue()); + REQUIRE(loadedCamera->getCameraDataObject().ApertureSize.GetValue() == + Catch::Approx(defaultCamera->getCameraDataObject().ApertureSize.GetValue())); + + delete loadedCamera; + delete defaultCamera; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("CameraObject invalid version handling", "[CameraObject][serialize][version]") +{ + std::string jsonPath = "test_camera_invalid_version.json"; + + // Create a JSON file with a future version number + { + std::ofstream file(jsonPath); + file << R"({ + "CameraObject": { + "_type": "CameraObject", + "_version": 999, + "Exposure": 0.85, + "FieldOfView": 45.0 + } +})"; + file.close(); + } + + // Load - should still work, just log a warning + { + CameraObject* loadedCamera = new CameraObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + + // Enter the CameraObject before checking version + reader.beginObject("CameraObject"); + + int version = reader.peekVersion(); + REQUIRE(version == 999); + + // Read properties (fromDocument would normally call beginObject, but we already did) + reader.readProperties(loadedCamera); + reader.endObject(); + + reader.endDocument(); + + REQUIRE(loadedCamera->getCameraDataObject().Exposure.GetValue() == Catch::Approx(0.85f)); + REQUIRE(loadedCamera->getCameraDataObject().FieldOfView.GetValue() == Catch::Approx(45.0f)); + + delete loadedCamera; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("CameraObject enum property serialization", "[CameraObject][serialize]") +{ + std::string jsonPath = "test_camera_enum.json"; + + // Test different enum values + for (int i = 0; i < 4; ++i) { + // Create and save camera with specific enum value + { + CameraObject* camera = new CameraObject(); + camera->getCameraDataObject().ExposureIterations.SetValue(i); + + docWriterJson writer; + writer.beginDocument(jsonPath); + camera->toDocument(&writer); + writer.endDocument(); + + delete camera; + } + + // Load and verify enum value + { + CameraObject* loadedCamera = new CameraObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + loadedCamera->fromDocument(&reader); + reader.endDocument(); + + REQUIRE(loadedCamera->getCameraDataObject().ExposureIterations.GetValue() == i); + + delete loadedCamera; + } + } + + // Test ProjectionMode enum + for (int i = 0; i < 2; ++i) { + // Create and save camera with specific projection mode + { + CameraObject* camera = new CameraObject(); + camera->getCameraDataObject().ProjectionMode.SetValue(i); + + docWriterJson writer; + writer.beginDocument(jsonPath); + camera->toDocument(&writer); + writer.endDocument(); + + delete camera; + } + + // Load and verify projection mode + { + CameraObject* loadedCamera = new CameraObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + loadedCamera->fromDocument(&reader); + reader.endDocument(); + + REQUIRE(loadedCamera->getCameraDataObject().ProjectionMode.GetValue() == i); + + delete loadedCamera; + } + } + + // Cleanup + std::filesystem::remove(jsonPath); +} diff --git a/test/test_prty.cpp b/test/test_prty.cpp index baa5b2464..3f65cac92 100644 --- a/test/test_prty.cpp +++ b/test/test_prty.cpp @@ -102,7 +102,7 @@ TEST_CASE("prtyProperty struct", "[prtyProperty]") } virtual const char* GetType() override { return "Foo"; } - virtual void Read(chReader& io_Reader) override + virtual void Read(docReader& io_Reader) override { // Implement reading from a reader if needed } From bcd0af02a3151440dafb06b8c5703e5b9a8b3385 Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Thu, 11 Dec 2025 09:36:22 -0800 Subject: [PATCH 45/63] wip toward writing out document --- agave_app/agaveGui.cpp | 166 ++++++++++++++++++++++++++ agave_app/agaveGui.h | 1 + renderlib/serialize/docReader.h | 6 +- renderlib/serialize/docReaderJson.cpp | 6 +- renderlib/serialize/docReaderJson.h | 6 +- renderlib/serialize/docReaderYaml.cpp | 6 +- renderlib/serialize/docReaderYaml.h | 6 +- 7 files changed, 182 insertions(+), 15 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 7daa0ff3e..cab0e85f6 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -17,6 +17,7 @@ #include "renderlib/CameraObject.hpp" #include "renderlib/AppearanceObject.hpp" #include "renderlib/serialize/docWriterJson.h" +#include "renderlib/serialize/docReaderJson.h" #include "AppearanceDockWidget.h" #include "AppearanceDockWidget2.h" @@ -748,6 +749,7 @@ agaveGui::saveJson() writer->beginList("_camera"); // list of all cameras writer->beginObject("_camera0"); + writer->writeProperty("_version", (uint32_t)1); // Camera object version writer->writeProperties(m_cameraObject.get()); writer->endObject(); writer->endList(); @@ -755,9 +757,11 @@ agaveGui::saveJson() writer->beginList("_light"); // list of all lights writer->beginObject("_skylight0"); + writer->writeProperty("_version", (uint32_t)1); // SkyLight object version writer->writeProperties(m_skyLightObject.get()); writer->endObject(); writer->beginObject("_arealight0"); + writer->writeProperty("_version", (uint32_t)1); // AreaLight object version writer->writeProperties(m_areaLightObject.get()); writer->endObject(); writer->endList(); @@ -765,6 +769,7 @@ agaveGui::saveJson() writer->beginList("_clipPlane"); // list of all clip planes writer->beginObject("_clipPlane0"); + writer->writeProperty("_version", (uint32_t)1); // ClipPlane object version // writer.writeProperties(m_clipPlaneObject.get()); writer->endObject(); writer->endList(); @@ -772,6 +777,7 @@ agaveGui::saveJson() writer->beginList("_renderSettings"); // list of all render settings objects writer->beginObject("_renderSettings0"); + writer->writeProperty("_version", (uint32_t)1); // RenderSettings object version writer->writeProperties(m_appearanceObject.get()); writer->endObject(); writer->endList(); @@ -780,6 +786,7 @@ agaveGui::saveJson() // list of capture settings objects (only one?) writer->endList(); writer->beginObject("_geometry"); + writer->writeProperty("_version", (uint32_t)1); // Geometry object version writer->beginList("_volume"); // list of all volumes writer->endList(); @@ -789,6 +796,7 @@ agaveGui::saveJson() writer->endObject(); writer->beginObject("_scene"); + writer->writeProperty("_version", (uint32_t)1); // Scene object version // one and only active scene. // this includes references to selections of the above objects. // other objects should not be cross referencing each other. @@ -797,6 +805,164 @@ agaveGui::saveJson() writer->endObject(); writer->endDocument(); + delete writer; + } +} + +void +agaveGui::loadJson() +{ + QFileDialog::Options options; +#ifdef __linux__ + options |= QFileDialog::DontUseNativeDialog; +#endif + QString file = QFileDialog::getOpenFileName(this, tr("Load JSON"), QString(), tr("JSON (*.json)"), nullptr, options); + if (!file.isEmpty()) { + + QFile loadFile(file); + if (!loadFile.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open load file."); + return; + } + + docReader* reader = new docReaderJson(); + if (!reader->beginDocument(file.toStdString())) { + qWarning("Failed to parse JSON document."); + delete reader; + return; + } + + // Read AGAVE metadata + if (reader->beginObject("_AGAVE")) { + if (reader->hasKey("_version")) { + std::string agaveVersion = reader->readString(); + LOG_INFO << "Loading AGAVE file version: " << agaveVersion; + } + reader->endObject(); + } + + // Read cameras + if (reader->beginList("_camera")) { + if (reader->beginObject("_camera0")) { + // Check version + if (reader->hasKey("_version")) { + uint32_t version = reader->readUint32(); + LOG_INFO << "Camera object version: " << version; + if (version != 1) { + LOG_WARNING << "Unknown camera version " << version << ", attempting to read anyway"; + } + } + // Read all camera properties + reader->readProperties(m_cameraObject.get()); + // Update the actual camera from properties + m_cameraObject->updateObjectFromProps(); + reader->endObject(); + } + reader->endList(); + } + + // Read lights + if (reader->beginList("_light")) { + // Sky light + if (reader->beginObject("_skylight0")) { + if (reader->hasKey("_version")) { + uint32_t version = reader->readUint32(); + LOG_INFO << "SkyLight object version: " << version; + if (version != 1) { + LOG_WARNING << "Unknown skylight version " << version << ", attempting to read anyway"; + } + } + reader->readProperties(m_skyLightObject.get()); + m_skyLightObject->updateObjectFromProps(); + reader->endObject(); + } + + // Area light + if (reader->beginObject("_arealight0")) { + if (reader->hasKey("_version")) { + uint32_t version = reader->readUint32(); + LOG_INFO << "AreaLight object version: " << version; + if (version != 1) { + LOG_WARNING << "Unknown arealight version " << version << ", attempting to read anyway"; + } + } + reader->readProperties(m_areaLightObject.get()); + m_areaLightObject->updateObjectFromProps(); + reader->endObject(); + } + reader->endList(); + } + + // Read clip planes + if (reader->beginList("_clipPlane")) { + if (reader->beginObject("_clipPlane0")) { + if (reader->hasKey("_version")) { + uint32_t version = reader->readUint32(); + LOG_INFO << "ClipPlane object version: " << version; + } + // TODO: read clip plane properties when implemented + reader->endObject(); + } + reader->endList(); + } + + // Read render settings + if (reader->beginList("_renderSettings")) { + if (reader->beginObject("_renderSettings0")) { + if (reader->hasKey("_version")) { + uint32_t version = reader->readUint32(); + LOG_INFO << "RenderSettings object version: " << version; + if (version != 1) { + LOG_WARNING << "Unknown render settings version " << version << ", attempting to read anyway"; + } + } + reader->readProperties(m_appearanceObject.get()); + m_appearanceObject->updateObjectFromProps(); + reader->endObject(); + } + reader->endList(); + } + + // Read capture settings + if (reader->beginList("_captureSettings")) { + // TODO: implement capture settings + reader->endList(); + } + + // Read geometry + if (reader->beginObject("_geometry")) { + if (reader->hasKey("_version")) { + uint32_t version = reader->readUint32(); + LOG_INFO << "Geometry object version: " << version; + } + + if (reader->beginList("_volume")) { + // TODO: implement volume loading + reader->endList(); + } + + if (reader->beginList("_mesh")) { + // TODO: implement mesh loading + reader->endList(); + } + + reader->endObject(); + } + + // Read scene + if (reader->beginObject("_scene")) { + if (reader->hasKey("_version")) { + uint32_t version = reader->readUint32(); + LOG_INFO << "Scene object version: " << version; + } + // TODO: implement scene state loading + reader->endObject(); + } + + reader->endDocument(); + delete reader; + + LOG_INFO << "Successfully loaded JSON file: " << file.toStdString(); } } diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index 10e4b67fa..5bcb5e3fc 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -78,6 +78,7 @@ private slots: void openMesh(const QString& file); void saveImage(); void saveJson(); + void loadJson(); void savePython(); void onRenderAction(); void OnUpdateRenderer(); diff --git a/renderlib/serialize/docReader.h b/renderlib/serialize/docReader.h index 4e8ae90fa..82fb34c20 100644 --- a/renderlib/serialize/docReader.h +++ b/renderlib/serialize/docReader.h @@ -18,15 +18,15 @@ class docReader virtual void endDocument() = 0; // objects can contain other objects, lists, and properties. - virtual bool beginObject(const char* i_name) = 0; + virtual bool beginObject(const std::string& i_name) = 0; virtual void endObject() = 0; // lists can contain objects or properties. - virtual bool beginList(const char* i_name) = 0; + virtual bool beginList(const std::string& i_name) = 0; virtual void endList() = 0; // Check if a key exists at the current level - virtual bool hasKey(const char* key) = 0; + virtual bool hasKey(const std::string& key) = 0; // Peek at object type and version without consuming virtual std::string peekObjectType() = 0; diff --git a/renderlib/serialize/docReaderJson.cpp b/renderlib/serialize/docReaderJson.cpp index 7b6afe726..aa655b661 100644 --- a/renderlib/serialize/docReaderJson.cpp +++ b/renderlib/serialize/docReaderJson.cpp @@ -69,7 +69,7 @@ docReaderJson::endDocument() } bool -docReaderJson::beginObject(const char* i_name) +docReaderJson::beginObject(const std::string& i_name) { if (!m_current) { LOG_ERROR << "beginObject() called with null current object"; @@ -125,7 +125,7 @@ docReaderJson::endObject() } bool -docReaderJson::beginList(const char* i_name) +docReaderJson::beginList(const std::string& i_name) { if (!m_current) { LOG_ERROR << "beginList() called with null current object"; @@ -181,7 +181,7 @@ docReaderJson::endList() } bool -docReaderJson::hasKey(const char* key) +docReaderJson::hasKey(const std::string& key) { nlohmann::json* current = getCurrentObject(); if (!current || !current->is_object()) { diff --git a/renderlib/serialize/docReaderJson.h b/renderlib/serialize/docReaderJson.h index c96f3546a..29540233b 100644 --- a/renderlib/serialize/docReaderJson.h +++ b/renderlib/serialize/docReaderJson.h @@ -19,15 +19,15 @@ class docReaderJson : public docReader virtual void endDocument() override; // Object support - virtual bool beginObject(const char* i_name) override; + virtual bool beginObject(const std::string& i_name) override; virtual void endObject() override; // List/array support - virtual bool beginList(const char* i_name) override; + virtual bool beginList(const std::string& i_name) override; virtual void endList() override; // Key checking - virtual bool hasKey(const char* key) override; + virtual bool hasKey(const std::string& key) override; // Peek operations virtual std::string peekObjectType() override; diff --git a/renderlib/serialize/docReaderYaml.cpp b/renderlib/serialize/docReaderYaml.cpp index afc3534f7..9b7b07847 100644 --- a/renderlib/serialize/docReaderYaml.cpp +++ b/renderlib/serialize/docReaderYaml.cpp @@ -48,7 +48,7 @@ docReaderYaml::endDocument() } bool -docReaderYaml::beginObject(const char* i_name) +docReaderYaml::beginObject(const std::string& i_name) { if (!m_current) { LOG_ERROR << "beginObject() called with null current value"; @@ -105,7 +105,7 @@ docReaderYaml::endObject() } bool -docReaderYaml::beginList(const char* i_name) +docReaderYaml::beginList(const std::string& i_name) { if (!m_current) { LOG_ERROR << "beginList() called with null current value"; @@ -162,7 +162,7 @@ docReaderYaml::endList() } bool -docReaderYaml::hasKey(const char* key) +docReaderYaml::hasKey(const std::string& key) { YamlValue* current = getCurrentValue(); if (!current || !current->isObject()) { diff --git a/renderlib/serialize/docReaderYaml.h b/renderlib/serialize/docReaderYaml.h index 8abb86ea4..ad57b4b53 100644 --- a/renderlib/serialize/docReaderYaml.h +++ b/renderlib/serialize/docReaderYaml.h @@ -20,15 +20,15 @@ class docReaderYaml : public docReader virtual void endDocument() override; // Object support - virtual bool beginObject(const char* i_name) override; + virtual bool beginObject(const std::string& i_name) override; virtual void endObject() override; // List/array support - virtual bool beginList(const char* i_name) override; + virtual bool beginList(const std::string& i_name) override; virtual void endList() override; // Key checking - virtual bool hasKey(const char* key) override; + virtual bool hasKey(const std::string& key) override; // Peek operations virtual std::string peekObjectType() override; From e5890d651af53d69f20434140a6d6273b9728674 Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Fri, 12 Dec 2025 16:42:37 -0800 Subject: [PATCH 46/63] wip toward reading and writing --- agave_app/agaveGui.cpp | 301 ++++++++----------- renderlib/CameraObject.cpp | 44 ++- renderlib/CameraObject.hpp | 4 +- renderlib/core/prty/prtyBoolean.cpp | 2 +- renderlib/core/prty/prtyColor.cpp | 2 +- renderlib/core/prty/prtyFloat.cpp | 2 +- renderlib/core/prty/prtyInt32.cpp | 2 +- renderlib/core/prty/prtyInt8.cpp | 2 +- renderlib/core/prty/prtyRotation.cpp | 2 +- renderlib/core/prty/prtyText.cpp | 2 +- renderlib/core/prty/prtyVector3d.cpp | 2 +- renderlib/serialize/SerializationConstants.h | 1 + renderlib/serialize/docReader.cpp | 8 +- renderlib/serialize/docReader.h | 61 +++- renderlib/serialize/docReaderJson.cpp | 81 +++-- renderlib/serialize/docReaderJson.h | 27 +- renderlib/serialize/docReaderYaml.cpp | 79 +++-- renderlib/serialize/docReaderYaml.h | 27 +- renderlib/serialize/docWriter.h | 2 +- renderlib/serialize/docWriterJson.cpp | 9 +- renderlib/serialize/docWriterJson.h | 2 +- renderlib/serialize/docWriterYaml.cpp | 17 +- renderlib/serialize/docWriterYaml.h | 2 +- test/test_docReader.cpp | 46 +-- test/test_docWriter.cpp | 10 +- 25 files changed, 408 insertions(+), 329 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index baa1d0b0f..29303a864 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -723,214 +723,136 @@ agaveGui::onRenderAction() void agaveGui::writeDocument(std::string filepath) { - docWriterJson writer; - writer.beginDocument(filepath); - writer.beginObject("_AGAVE"); + docWriter* writer = new docWriterJson(); + + writer->beginDocument(filepath); + writer->beginObject("_AGAVE", "AgaveDocument", 1); // write agave version at least - writer.endObject(); - writer.beginList("_camera"); + writer->writeProperty("Version", std::string(AICS_VERSION_STRING)); + writer->endObject(); + + writer->beginList("_camera"); // list of all cameras - writer.endList(); - writer.beginList("_light"); + m_cameraObject->toDocument(writer); + // writer->beginObject("_camera0"); + // writer->writeProperty("_version", (uint32_t)1); // Camera object version + // writer->writeProperties(m_cameraObject.get()); + // writer->endObject(); + writer->endList(); + + writer->beginList("_light"); // list of all lights - writer.endList(); - writer.beginList("_clipPlane"); + writer->beginObject("_skylight0", "SkyLightObject", 1); + writer->writeProperties(m_skyLightObject.get()); + writer->endObject(); + writer->beginObject("_arealight0", "AreaLightObject", 1); + writer->writeProperties(m_areaLightObject.get()); + writer->endObject(); + writer->endList(); + + writer->beginList("_clipPlane"); // list of all clip planes - writer.endList(); - writer.beginList("_renderSettings"); + writer->beginObject("_clipPlane0", "ClipPlaneObject", 1); + // writer.writeProperties(m_clipPlaneObject.get()); + writer->endObject(); + writer->endList(); + + writer->beginList("_renderSettings"); // list of all render settings objects - writer.endList(); - writer.beginList("_captureSettings"); - // list of capture settings objects (only one?) - writer.endList(); + writer->beginObject("_renderSettings0", "RenderSettingsObject", 1); + writer->writeProperties(m_appearanceObject.get()); + writer->endObject(); + writer->endList(); - writer.beginObject("_geometry"); - writer.beginList("_volume"); + writer->beginList("_captureSettings"); + // list of capture settings objects (only one?) + writer->endList(); + writer->beginObject("_geometry", "GeometryObject", 1); + writer->beginList("_volume"); // list of all volumes - writer.endList(); - writer.beginList("_mesh"); + writer->endList(); + + writer->beginList("_mesh"); // list of all meshes - writer.endList(); - writer.endObject(); + writer->endList(); + writer->endObject(); - writer.beginObject("_scene"); + writer->beginObject("_scene", "SceneObject", 1); // one and only active scene. // this includes references to selections of the above objects. // other objects should not be cross referencing each other. // so scene needs to be set up after other objects are defined. - writer.endObject(); - writer.endDocument(); + writer->endObject(); + + writer->endDocument(); + delete writer; } void agaveGui::readDocument(std::string filepath) { - docReaderJson reader; - if (reader.beginDocument(filepath)) { - if (reader.beginObject("_AGAVE")) { + docReader* reader = new docReaderJson(); + if (reader->beginDocument(filepath)) { + if (reader->beginObject("_AGAVE")) { // read agave version at least - reader.endObject(); + reader->endObject(); } - if (reader.beginList("_camera")) { + if (reader->beginList("_camera")) { // list of all cameras // v1, read only the first camera CameraObject* camObj = new CameraObject(); - camObj->fromDocument(&reader); + camObj->fromDocument(reader); + camObj->updateObjectFromProps(); - // install camObj into m_cameraObject + // install camObj into m_cameraObject??? m_cameraObject = std::unique_ptr(camObj); // follow through to other parts of the app that need to know about camera change m_glView->setCameraObject(m_cameraObject.get()); // m_cameradock->setCameraObject(m_cameraObject); - reader.endList(); + reader->endList(); } - if (reader.beginList("_light")) { + if (reader->beginList("_light")) { // list of all lights - reader.endList(); + reader->endList(); } - if (reader.beginList("_clipPlane")) { + if (reader->beginList("_clipPlane")) { // list of all clip planes - reader.endList(); + reader->endList(); } - if (reader.beginList("_renderSettings")) { + if (reader->beginList("_renderSettings")) { // list of all render settings objects - reader.endList(); + reader->endList(); } - if (reader.beginList("_captureSettings")) { + if (reader->beginList("_captureSettings")) { // list of capture settings objects (only one?) - reader.endList(); + reader->endList(); } - if (reader.beginObject("_geometry")) { - if (reader.beginList("_volume")) { + if (reader->beginObject("_geometry")) { + if (reader->beginList("_volume")) { // list of all volumes - reader.endList(); + reader->endList(); } - if (reader.beginList("_mesh")) { + if (reader->beginList("_mesh")) { // list of all meshes - reader.endList(); + reader->endList(); } - reader.endObject(); + reader->endObject(); } - if (reader.beginObject("_scene")) { + if (reader->beginObject("_scene")) { // one and only active scene. - reader.endObject(); - } - - reader.endDocument(); - } -} - -void -agaveGui::saveJson() -{ - QFileDialog::Options options; -#ifdef __linux__ - options |= QFileDialog::DontUseNativeDialog; -#endif - QString file = QFileDialog::getSaveFileName(this, tr("Save JSON"), QString(), tr("JSON (*.json)"), nullptr, options); - if (!file.isEmpty()) { - - QFile saveFile(file); - if (!saveFile.open(QIODevice::WriteOnly)) { - qWarning("Couldn't open save file."); - return; + reader->endObject(); } - // Serialize::ViewerState st = appToViewerState(); - // nlohmann::json doc = st; - // std::string str = doc.dump(); - // saveFile.write(str.c_str()); // QString::fromStdString(str)); - - docWriter* writer = new docWriterJson(); - writer->beginDocument(file.toStdString()); - writer->beginObject("_AGAVE"); - // write agave version at least - writer->writeProperty("_version", std::string(AICS_VERSION_STRING)); - writer->endObject(); - writer->beginList("_camera"); - // list of all cameras - writer->beginObject("_camera0"); - writer->writeProperty("_version", (uint32_t)1); // Camera object version - writer->writeProperties(m_cameraObject.get()); - writer->endObject(); - writer->endList(); - - writer->beginList("_light"); - // list of all lights - writer->beginObject("_skylight0"); - writer->writeProperty("_version", (uint32_t)1); // SkyLight object version - writer->writeProperties(m_skyLightObject.get()); - writer->endObject(); - writer->beginObject("_arealight0"); - writer->writeProperty("_version", (uint32_t)1); // AreaLight object version - writer->writeProperties(m_areaLightObject.get()); - writer->endObject(); - writer->endList(); - - writer->beginList("_clipPlane"); - // list of all clip planes - writer->beginObject("_clipPlane0"); - writer->writeProperty("_version", (uint32_t)1); // ClipPlane object version - // writer.writeProperties(m_clipPlaneObject.get()); - writer->endObject(); - writer->endList(); - - writer->beginList("_renderSettings"); - // list of all render settings objects - writer->beginObject("_renderSettings0"); - writer->writeProperty("_version", (uint32_t)1); // RenderSettings object version - writer->writeProperties(m_appearanceObject.get()); - writer->endObject(); - writer->endList(); - - writer->beginList("_captureSettings"); - // list of capture settings objects (only one?) - writer->endList(); - writer->beginObject("_geometry"); - writer->writeProperty("_version", (uint32_t)1); // Geometry object version - writer->beginList("_volume"); - // list of all volumes - writer->endList(); - writer->beginList("_mesh"); - // list of all meshes - writer->endList(); - writer->endObject(); - - writer->beginObject("_scene"); - writer->writeProperty("_version", (uint32_t)1); // Scene object version - // one and only active scene. - // this includes references to selections of the above objects. - // other objects should not be cross referencing each other. - // so scene needs to be set up after other objects are defined. - - writer->endObject(); - - writer->endDocument(); - delete writer; - } -} -void -agaveGui::loadJson() -{ - QFileDialog::Options options; -#ifdef __linux__ - options |= QFileDialog::DontUseNativeDialog; -#endif - QString file = QFileDialog::getOpenFileName(this, tr("Load JSON"), QString(), tr("JSON (*.json)"), nullptr, options); - if (!file.isEmpty()) { - - QFile loadFile(file); - if (!loadFile.open(QIODevice::ReadOnly)) { - qWarning("Couldn't open load file."); - return; - } + reader->endDocument(); +////////////////////////////////// +#if 0 docReader* reader = new docReaderJson(); if (!reader->beginDocument(file.toStdString())) { qWarning("Failed to parse JSON document."); @@ -941,7 +863,7 @@ agaveGui::loadJson() // Read AGAVE metadata if (reader->beginObject("_AGAVE")) { if (reader->hasKey("_version")) { - std::string agaveVersion = reader->readString(); + std::string agaveVersion = reader->readString("_version"); LOG_INFO << "Loading AGAVE file version: " << agaveVersion; } reader->endObject(); @@ -952,7 +874,7 @@ agaveGui::loadJson() if (reader->beginObject("_camera0")) { // Check version if (reader->hasKey("_version")) { - uint32_t version = reader->readUint32(); + uint32_t version = reader->readUint32("_version"); LOG_INFO << "Camera object version: " << version; if (version != 1) { LOG_WARNING << "Unknown camera version " << version << ", attempting to read anyway"; @@ -972,28 +894,28 @@ agaveGui::loadJson() // Sky light if (reader->beginObject("_skylight0")) { if (reader->hasKey("_version")) { - uint32_t version = reader->readUint32(); + uint32_t version = reader->readUint32("_version"); LOG_INFO << "SkyLight object version: " << version; if (version != 1) { LOG_WARNING << "Unknown skylight version " << version << ", attempting to read anyway"; } } reader->readProperties(m_skyLightObject.get()); - m_skyLightObject->updateObjectFromProps(); + // m_skyLightObject->updateObjectFromProps(); reader->endObject(); } // Area light if (reader->beginObject("_arealight0")) { if (reader->hasKey("_version")) { - uint32_t version = reader->readUint32(); + uint32_t version = reader->readUint32("_version"); LOG_INFO << "AreaLight object version: " << version; if (version != 1) { LOG_WARNING << "Unknown arealight version " << version << ", attempting to read anyway"; } } reader->readProperties(m_areaLightObject.get()); - m_areaLightObject->updateObjectFromProps(); + // m_areaLightObject->updateObjectFromProps(); reader->endObject(); } reader->endList(); @@ -1003,7 +925,7 @@ agaveGui::loadJson() if (reader->beginList("_clipPlane")) { if (reader->beginObject("_clipPlane0")) { if (reader->hasKey("_version")) { - uint32_t version = reader->readUint32(); + uint32_t version = reader->readUint32("_version"); LOG_INFO << "ClipPlane object version: " << version; } // TODO: read clip plane properties when implemented @@ -1016,7 +938,7 @@ agaveGui::loadJson() if (reader->beginList("_renderSettings")) { if (reader->beginObject("_renderSettings0")) { if (reader->hasKey("_version")) { - uint32_t version = reader->readUint32(); + uint32_t version = reader->readUint32("_version"); LOG_INFO << "RenderSettings object version: " << version; if (version != 1) { LOG_WARNING << "Unknown render settings version " << version << ", attempting to read anyway"; @@ -1038,27 +960,27 @@ agaveGui::loadJson() // Read geometry if (reader->beginObject("_geometry")) { if (reader->hasKey("_version")) { - uint32_t version = reader->readUint32(); + uint32_t version = reader->readUint32("_version"); LOG_INFO << "Geometry object version: " << version; } - + if (reader->beginList("_volume")) { // TODO: implement volume loading reader->endList(); } - + if (reader->beginList("_mesh")) { // TODO: implement mesh loading reader->endList(); } - + reader->endObject(); } // Read scene if (reader->beginObject("_scene")) { if (reader->hasKey("_version")) { - uint32_t version = reader->readUint32(); + uint32_t version = reader->readUint32("_version"); LOG_INFO << "Scene object version: " << version; } // TODO: implement scene state loading @@ -1066,7 +988,48 @@ agaveGui::loadJson() } reader->endDocument(); +#endif delete reader; + } +} + +void +agaveGui::saveJson() +{ + QFileDialog::Options options; +#ifdef __linux__ + options |= QFileDialog::DontUseNativeDialog; +#endif + QString file = QFileDialog::getSaveFileName(this, tr("Save JSON"), QString(), tr("JSON (*.json)"), nullptr, options); + if (!file.isEmpty()) { + + QFile saveFile(file); + if (!saveFile.open(QIODevice::WriteOnly)) { + qWarning("Couldn't open save file."); + return; + } + Serialize::ViewerState st = appToViewerState(); + nlohmann::json doc = st; + std::string str = doc.dump(); + saveFile.write(str.c_str()); // QString::fromStdString(str)); + } +} + +void +agaveGui::loadJson() +{ + QFileDialog::Options options; +#ifdef __linux__ + options |= QFileDialog::DontUseNativeDialog; +#endif + QString file = QFileDialog::getOpenFileName(this, tr("Load JSON"), QString(), tr("JSON (*.json)"), nullptr, options); + if (!file.isEmpty()) { + + QFile loadFile(file); + if (!loadFile.open(QIODevice::ReadOnly)) { + qWarning("Couldn't open load file."); + return; + } LOG_INFO << "Successfully loaded JSON file: " << file.toStdString(); } diff --git a/renderlib/CameraObject.cpp b/renderlib/CameraObject.cpp index e23031ce1..4287c99a8 100644 --- a/renderlib/CameraObject.cpp +++ b/renderlib/CameraObject.cpp @@ -265,16 +265,48 @@ CameraObject::TransformationChanged(prtyProperty* i_Property, bool i_bDirty) void CameraObject::fromDocument(docReader* reader) { - reader->beginObject("CameraObject"); - reader->readProperties(this); + reader->beginObject("camera0"); + + // Peek at metadata + uint32_t version = reader->peekVersion(); + std::string type = reader->peekObjectType(); + std::string name = reader->peekObjectName(); + + reader->readPrty(&m_cameraDataObject.Exposure); + reader->readPrty(&m_cameraDataObject.ExposureIterations); + reader->readPrty(&m_cameraDataObject.NoiseReduction); + reader->readPrty(&m_cameraDataObject.ApertureSize); + reader->readPrty(&m_cameraDataObject.FieldOfView); + reader->readPrty(&m_cameraDataObject.FocalDistance); + reader->readPrty(&m_cameraDataObject.Position); + reader->readPrty(&m_cameraDataObject.Target); + reader->readPrty(&m_cameraDataObject.NearPlane); + reader->readPrty(&m_cameraDataObject.FarPlane); + reader->readPrty(&m_cameraDataObject.Roll); + reader->readPrty(&m_cameraDataObject.OrthoScale); + reader->readPrty(&m_cameraDataObject.ProjectionMode); + reader->endObject(); } void CameraObject::toDocument(docWriter* writer) { - writer->beginObject("CameraObject"); - // write version property explicitly? - // ensure that this and most other objects have a (unique) name property? - writer->writeProperties(this); + writer->beginObject("camera0", "CameraObject", CameraObject::CURRENT_VERSION); + + m_cameraDataObject.Exposure.Write(*writer); + m_cameraDataObject.ExposureIterations.Write(*writer); + m_cameraDataObject.NoiseReduction.Write(*writer); + m_cameraDataObject.ApertureSize.Write(*writer); + m_cameraDataObject.FieldOfView.Write(*writer); + m_cameraDataObject.FocalDistance.Write(*writer); + + m_cameraDataObject.Position.Write(*writer); + m_cameraDataObject.Target.Write(*writer); + m_cameraDataObject.NearPlane.Write(*writer); + m_cameraDataObject.FarPlane.Write(*writer); + m_cameraDataObject.Roll.Write(*writer); + m_cameraDataObject.OrthoScale.Write(*writer); + m_cameraDataObject.ProjectionMode.Write(*writer); + writer->endObject(); } \ No newline at end of file diff --git a/renderlib/CameraObject.hpp b/renderlib/CameraObject.hpp index f6f69b8be..78ddc26bf 100644 --- a/renderlib/CameraObject.hpp +++ b/renderlib/CameraObject.hpp @@ -54,8 +54,8 @@ class CameraObject : public prtyObject // Convert UI specific combo box index to a known enum type static uint8_t GetExposureIterationsValue(int i_ComboBoxIndex); - // document reading and writing - static constexpr int CURRENT_VERSION = 1; + // document reading and writing; TODO consider an abstract base class to enforce commonality + static constexpr uint32_t CURRENT_VERSION = 1; void fromDocument(docReader* reader); void toDocument(docWriter* writer); diff --git a/renderlib/core/prty/prtyBoolean.cpp b/renderlib/core/prty/prtyBoolean.cpp index 5d8784e24..9464484ba 100644 --- a/renderlib/core/prty/prtyBoolean.cpp +++ b/renderlib/core/prty/prtyBoolean.cpp @@ -83,7 +83,7 @@ prtyBoolean::operator!=(const bool i_Value) const void prtyBoolean::Read(docReader& io_Reader) { - bool temp = io_Reader.readBool(); + bool temp = io_Reader.readBool(GetPropertyName()); SetValue(temp); } diff --git a/renderlib/core/prty/prtyColor.cpp b/renderlib/core/prty/prtyColor.cpp index afa718cad..d57979197 100644 --- a/renderlib/core/prty/prtyColor.cpp +++ b/renderlib/core/prty/prtyColor.cpp @@ -85,7 +85,7 @@ void prtyColor::Read(docReader& io_Reader) { std::vector temp; - temp = io_Reader.readFloat32Array(); + temp = io_Reader.readFloat32Array(GetPropertyName()); SetValue(glm::vec4(temp[0], temp[1], temp[2], temp[3])); } diff --git a/renderlib/core/prty/prtyFloat.cpp b/renderlib/core/prty/prtyFloat.cpp index 1a30532b7..cc3cf9449 100644 --- a/renderlib/core/prty/prtyFloat.cpp +++ b/renderlib/core/prty/prtyFloat.cpp @@ -148,7 +148,7 @@ void prtyFloat::Read(docReader& io_Reader) { float temp; - temp = io_Reader.readFloat32(); + temp = io_Reader.readFloat32(GetPropertyName()); SetValue(temp); } diff --git a/renderlib/core/prty/prtyInt32.cpp b/renderlib/core/prty/prtyInt32.cpp index 987b33f39..9301a29ed 100644 --- a/renderlib/core/prty/prtyInt32.cpp +++ b/renderlib/core/prty/prtyInt32.cpp @@ -129,7 +129,7 @@ void prtyInt32::Read(docReader& io_Reader) { int32_t temp; - temp = io_Reader.readInt32(); + temp = io_Reader.readInt32(GetPropertyName()); SetValue(temp); } diff --git a/renderlib/core/prty/prtyInt8.cpp b/renderlib/core/prty/prtyInt8.cpp index 0ba1d5fc7..9e8cc2819 100644 --- a/renderlib/core/prty/prtyInt8.cpp +++ b/renderlib/core/prty/prtyInt8.cpp @@ -128,7 +128,7 @@ void prtyInt8::Read(docReader& io_Reader) { int8_t temp; - temp = io_Reader.readInt8(); + temp = io_Reader.readInt8(GetPropertyName()); SetValue(temp); } diff --git a/renderlib/core/prty/prtyRotation.cpp b/renderlib/core/prty/prtyRotation.cpp index 233f509c3..4d0965ef8 100644 --- a/renderlib/core/prty/prtyRotation.cpp +++ b/renderlib/core/prty/prtyRotation.cpp @@ -197,7 +197,7 @@ prtyRotation::Read(docReader& io_Reader) { // // We need to write euler angles, how to handle versions? std::vector temp; - temp = io_Reader.readFloat32Array(); + temp = io_Reader.readFloat32Array(GetPropertyName()); SetQuaternion(glm::quat(temp[3], temp[0], temp[1], temp[2])); } diff --git a/renderlib/core/prty/prtyText.cpp b/renderlib/core/prty/prtyText.cpp index 58256c7ea..c937368ac 100644 --- a/renderlib/core/prty/prtyText.cpp +++ b/renderlib/core/prty/prtyText.cpp @@ -91,7 +91,7 @@ void prtyText::Read(docReader& io_Reader) { std::string temp; - temp = io_Reader.readString(); + temp = io_Reader.readString(GetPropertyName()); SetValue(temp); } diff --git a/renderlib/core/prty/prtyVector3d.cpp b/renderlib/core/prty/prtyVector3d.cpp index 9f8f15823..5ea6f8d7c 100644 --- a/renderlib/core/prty/prtyVector3d.cpp +++ b/renderlib/core/prty/prtyVector3d.cpp @@ -137,7 +137,7 @@ void prtyVector3d::Read(docReader& io_Reader) { std::vector temp; - temp = io_Reader.readFloat32Array(); + temp = io_Reader.readFloat32Array(GetPropertyName()); SetValue(glm::vec3(temp[0], temp[1], temp[2])); } diff --git a/renderlib/serialize/SerializationConstants.h b/renderlib/serialize/SerializationConstants.h index 9a319919b..5622e7148 100644 --- a/renderlib/serialize/SerializationConstants.h +++ b/renderlib/serialize/SerializationConstants.h @@ -5,5 +5,6 @@ namespace SerializationConstants { // Reserved keys for serialization metadata constexpr const char* TYPE_KEY = "_type"; constexpr const char* VERSION_KEY = "_version"; +constexpr const char* NAME_KEY = "_name"; } // namespace SerializationConstants diff --git a/renderlib/serialize/docReader.cpp b/renderlib/serialize/docReader.cpp index 8b17401a4..97ca13c27 100644 --- a/renderlib/serialize/docReader.cpp +++ b/renderlib/serialize/docReader.cpp @@ -3,6 +3,8 @@ #include "core/prty/prtyProperty.hpp" #include "core/prty/prtyObject.hpp" +#include "Logging.h" + // Reads all properties from the current object context // Assumes we're already inside the object (after beginObject was called) void @@ -28,7 +30,11 @@ docReader::readProperties(prtyObject* obj) // Check if the property exists in the document if (hasKey(propName.c_str())) { // Set up the property name for reading and let the property read itself - readPrty(prop); + bool ok = readPrty(prop); + if (!ok) { + // Log error if property read failed + LOG_ERROR << "Failed to read property: " << propName << " even though key exists."; + } } } } diff --git a/renderlib/serialize/docReader.h b/renderlib/serialize/docReader.h index 82fb34c20..8f18bddc1 100644 --- a/renderlib/serialize/docReader.h +++ b/renderlib/serialize/docReader.h @@ -30,20 +30,57 @@ class docReader // Peek at object type and version without consuming virtual std::string peekObjectType() = 0; - virtual int peekVersion() = 0; + virtual uint32_t peekVersion() = 0; + virtual std::string peekObjectName() = 0; // properties will read their name and associated value using the primitive read methods. - virtual void readPrty(prtyProperty* p) = 0; - - virtual bool readBool() = 0; - virtual int8_t readInt8() = 0; - virtual int32_t readInt32() = 0; - virtual uint32_t readUint32() = 0; - virtual float readFloat32() = 0; - virtual std::vector readFloat32Array() = 0; - virtual std::vector readInt32Array() = 0; - virtual std::vector readUint32Array() = 0; - virtual std::string readString() = 0; + virtual bool readPrty(prtyProperty* p) = 0; + + // Templated property reader that maps value types to primitive read methods + template + T readProperty(const std::string& name) + { + if constexpr (std::is_same_v) { + return readBool(name); + } else if constexpr (std::is_same_v) { + return readInt8(name); + } else if constexpr (std::is_same_v) { + return readInt32(name); + } else if constexpr (std::is_same_v) { + return readUint32(name); + } else if constexpr (std::is_same_v) { + return readFloat32(name); + } else if constexpr (std::is_same_v) { + return readString(name); + } else if constexpr (std::is_same_v>) { + return readFloat32Array(name); + } else if constexpr (std::is_same_v>) { + return readInt32Array(name); + } else if constexpr (std::is_same_v>) { + return readUint32Array(name); + } else { + static_assert(sizeof(T) == 0, "Unsupported type for readProperty"); + return T{}; + } + } + + // Overload that takes a pointer and assigns to it + template + void readProperty(const std::string& name, T* value) + { + *value = readProperty(name); + } + + // All primitive read methods now require a name parameter + virtual bool readBool(const std::string& name) = 0; + virtual int8_t readInt8(const std::string& name) = 0; + virtual int32_t readInt32(const std::string& name) = 0; + virtual uint32_t readUint32(const std::string& name) = 0; + virtual float readFloat32(const std::string& name) = 0; + virtual std::vector readFloat32Array(const std::string& name) = 0; + virtual std::vector readInt32Array(const std::string& name) = 0; + virtual std::vector readUint32Array(const std::string& name) = 0; + virtual std::string readString(const std::string& name) = 0; void readProperties(prtyObject* obj); }; diff --git a/renderlib/serialize/docReaderJson.cpp b/renderlib/serialize/docReaderJson.cpp index af90a8674..634bc813c 100644 --- a/renderlib/serialize/docReaderJson.cpp +++ b/renderlib/serialize/docReaderJson.cpp @@ -206,7 +206,7 @@ docReaderJson::peekObjectType() return ""; } -int +uint32_t docReaderJson::peekVersion() { nlohmann::json* current = getCurrentObject(); @@ -217,17 +217,33 @@ docReaderJson::peekVersion() // Look for a "_version" key in the current object if (current->contains(SerializationConstants::VERSION_KEY) && (*current)[SerializationConstants::VERSION_KEY].is_number_integer()) { - return (*current)[SerializationConstants::VERSION_KEY].get(); + return (*current)[SerializationConstants::VERSION_KEY].get(); } return 0; } -void +std::string +docReaderJson::peekObjectName() +{ + nlohmann::json* current = getCurrentObject(); + if (!current || !current->is_object()) { + return ""; + } + + // Look for a "_name" key in the current object + if (current->contains(SerializationConstants::NAME_KEY) && (*current)[SerializationConstants::NAME_KEY].is_string()) { + return (*current)[SerializationConstants::NAME_KEY].get(); + } + + return ""; +} + +bool docReaderJson::readPrty(prtyProperty* p) { if (!p) { - return; + return false; } // Store the property name for the next read operation @@ -237,15 +253,16 @@ docReaderJson::readPrty(prtyProperty* p) nlohmann::json* current = getCurrentObject(); if (!current || !current->contains(m_nextKey)) { LOG_ERROR << "readPrty() - property key not found: " << m_nextKey; - return; + return false; } // Let the property read itself p->Read(*this); + return true; } bool -docReaderJson::readBool() +docReaderJson::readBool(const std::string& name) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -254,8 +271,8 @@ docReaderJson::readBool() if (m_contextStack.empty() || !m_contextStack.top().isArray()) { // Reading from object by key - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_boolean()) { - return (*current)[m_nextKey].get(); + if (current->contains(name) && (*current)[name].is_boolean()) { + return (*current)[name].get(); } } else { // Reading from array by index @@ -271,7 +288,7 @@ docReaderJson::readBool() } int8_t -docReaderJson::readInt8() +docReaderJson::readInt8(const std::string& name) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -280,8 +297,8 @@ docReaderJson::readInt8() if (m_contextStack.empty() || !m_contextStack.top().isArray()) { // Reading from object by key - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_number_integer()) { - return (*current)[m_nextKey].get(); + if (current->contains(name) && (*current)[name].is_number_integer()) { + return (*current)[name].get(); } } else { // Reading from array by index @@ -297,7 +314,7 @@ docReaderJson::readInt8() } int32_t -docReaderJson::readInt32() +docReaderJson::readInt32(const std::string& name) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -306,8 +323,8 @@ docReaderJson::readInt32() if (m_contextStack.empty() || !m_contextStack.top().isArray()) { // Reading from object by key - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_number_integer()) { - return (*current)[m_nextKey].get(); + if (current->contains(name) && (*current)[name].is_number_integer()) { + return (*current)[name].get(); } } else { // Reading from array by index @@ -323,7 +340,7 @@ docReaderJson::readInt32() } uint32_t -docReaderJson::readUint32() +docReaderJson::readUint32(const std::string& name) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -332,8 +349,8 @@ docReaderJson::readUint32() if (m_contextStack.empty() || !m_contextStack.top().isArray()) { // Reading from object by key - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_number_unsigned()) { - return (*current)[m_nextKey].get(); + if (current->contains(name) && (*current)[name].is_number_unsigned()) { + return (*current)[name].get(); } } else { // Reading from array by index @@ -349,7 +366,7 @@ docReaderJson::readUint32() } float -docReaderJson::readFloat32() +docReaderJson::readFloat32(const std::string& name) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -358,8 +375,8 @@ docReaderJson::readFloat32() if (m_contextStack.empty() || !m_contextStack.top().isArray()) { // Reading from object by key - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_number()) { - return (*current)[m_nextKey].get(); + if (current->contains(name) && (*current)[name].is_number()) { + return (*current)[name].get(); } } else { // Reading from array by index @@ -375,7 +392,7 @@ docReaderJson::readFloat32() } std::vector -docReaderJson::readFloat32Array() +docReaderJson::readFloat32Array(const std::string& name) { std::vector result; nlohmann::json* current = getCurrentObject(); @@ -383,8 +400,8 @@ docReaderJson::readFloat32Array() return result; } - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_array()) { - const nlohmann::json& arr = (*current)[m_nextKey]; + if (current->contains(name) && (*current)[name].is_array()) { + const nlohmann::json& arr = (*current)[name]; for (const auto& elem : arr) { if (elem.is_number()) { result.push_back(elem.get()); @@ -396,7 +413,7 @@ docReaderJson::readFloat32Array() } std::vector -docReaderJson::readInt32Array() +docReaderJson::readInt32Array(const std::string& name) { std::vector result; nlohmann::json* current = getCurrentObject(); @@ -404,8 +421,8 @@ docReaderJson::readInt32Array() return result; } - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_array()) { - const nlohmann::json& arr = (*current)[m_nextKey]; + if (current->contains(name) && (*current)[name].is_array()) { + const nlohmann::json& arr = (*current)[name]; for (const auto& elem : arr) { if (elem.is_number_integer()) { result.push_back(elem.get()); @@ -417,7 +434,7 @@ docReaderJson::readInt32Array() } std::vector -docReaderJson::readUint32Array() +docReaderJson::readUint32Array(const std::string& name) { std::vector result; nlohmann::json* current = getCurrentObject(); @@ -425,8 +442,8 @@ docReaderJson::readUint32Array() return result; } - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_array()) { - const nlohmann::json& arr = (*current)[m_nextKey]; + if (current->contains(name) && (*current)[name].is_array()) { + const nlohmann::json& arr = (*current)[name]; for (const auto& elem : arr) { if (elem.is_number_unsigned()) { result.push_back(elem.get()); @@ -438,7 +455,7 @@ docReaderJson::readUint32Array() } std::string -docReaderJson::readString() +docReaderJson::readString(const std::string& name) { nlohmann::json* current = getCurrentObject(); if (!current) { @@ -447,8 +464,8 @@ docReaderJson::readString() if (m_contextStack.empty() || !m_contextStack.top().isArray()) { // Reading from object by key - if (current->contains(m_nextKey) && (*current)[m_nextKey].is_string()) { - return (*current)[m_nextKey].get(); + if (current->contains(name) && (*current)[name].is_string()) { + return (*current)[name].get(); } } else { // Reading from array by index diff --git a/renderlib/serialize/docReaderJson.h b/renderlib/serialize/docReaderJson.h index 29540233b..76fffabd5 100644 --- a/renderlib/serialize/docReaderJson.h +++ b/renderlib/serialize/docReaderJson.h @@ -31,21 +31,22 @@ class docReaderJson : public docReader // Peek operations virtual std::string peekObjectType() override; - virtual int peekVersion() override; + virtual uint32_t peekVersion() override; + virtual std::string peekObjectName() override; // Property reading - virtual void readPrty(prtyProperty* p) override; - - // Primitive type reading - virtual bool readBool() override; - virtual int8_t readInt8() override; - virtual int32_t readInt32() override; - virtual uint32_t readUint32() override; - virtual float readFloat32() override; - virtual std::vector readFloat32Array() override; - virtual std::vector readInt32Array() override; - virtual std::vector readUint32Array() override; - virtual std::string readString() override; + virtual bool readPrty(prtyProperty* p) override; + + // Primitive type reading - all require a name parameter + virtual bool readBool(const std::string& name) override; + virtual int8_t readInt8(const std::string& name) override; + virtual int32_t readInt32(const std::string& name) override; + virtual uint32_t readUint32(const std::string& name) override; + virtual float readFloat32(const std::string& name) override; + virtual std::vector readFloat32Array(const std::string& name) override; + virtual std::vector readInt32Array(const std::string& name) override; + virtual std::vector readUint32Array(const std::string& name) override; + virtual std::string readString(const std::string& name) override; private: enum class ContextType diff --git a/renderlib/serialize/docReaderYaml.cpp b/renderlib/serialize/docReaderYaml.cpp index 7fcac537f..9bf36af8b 100644 --- a/renderlib/serialize/docReaderYaml.cpp +++ b/renderlib/serialize/docReaderYaml.cpp @@ -188,7 +188,7 @@ docReaderYaml::peekObjectType() return ""; } -int +uint32_t docReaderYaml::peekVersion() { YamlValue* current = getCurrentValue(); @@ -205,11 +205,27 @@ docReaderYaml::peekVersion() return 0; } -void +std::string +docReaderYaml::peekObjectName() +{ + YamlValue* current = getCurrentValue(); + if (!current || !current->isObject()) { + return ""; + } + + YamlObject& obj = current->asObject(); + if (obj.find(SerializationConstants::NAME_KEY) != obj.end() && obj[SerializationConstants::NAME_KEY].isString()) { + return obj[SerializationConstants::NAME_KEY].asString(); + } + + return ""; +} + +bool docReaderYaml::readPrty(prtyProperty* p) { if (!p) { - return; + return false; } // Store the property name for the next read operation @@ -218,15 +234,16 @@ docReaderYaml::readPrty(prtyProperty* p) // Check if the key exists if (!hasKey(m_nextKey.c_str())) { LOG_ERROR << "readPrty() - property key not found: " << m_nextKey; - return; + return false; } // Let the property read itself p->Read(*this); + return true; } bool -docReaderYaml::readBool() +docReaderYaml::readBool(const std::string& name) { YamlValue* current = getCurrentValue(); if (!current) { @@ -237,8 +254,8 @@ docReaderYaml::readBool() // Reading from object by key if (current->isObject()) { YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { - return stringToBool(obj[m_nextKey].asString()); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return stringToBool(obj[name].asString()); } } } else { @@ -258,7 +275,7 @@ docReaderYaml::readBool() } int8_t -docReaderYaml::readInt8() +docReaderYaml::readInt8(const std::string& name) { YamlValue* current = getCurrentValue(); if (!current) { @@ -269,8 +286,8 @@ docReaderYaml::readInt8() // Reading from object by key if (current->isObject()) { YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { - return static_cast(stringToInt(obj[m_nextKey].asString())); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return static_cast(stringToInt(obj[name].asString())); } } } else { @@ -290,7 +307,7 @@ docReaderYaml::readInt8() } int32_t -docReaderYaml::readInt32() +docReaderYaml::readInt32(const std::string& name) { YamlValue* current = getCurrentValue(); if (!current) { @@ -301,8 +318,8 @@ docReaderYaml::readInt32() // Reading from object by key if (current->isObject()) { YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { - return stringToInt(obj[m_nextKey].asString()); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return stringToInt(obj[name].asString()); } } } else { @@ -322,7 +339,7 @@ docReaderYaml::readInt32() } uint32_t -docReaderYaml::readUint32() +docReaderYaml::readUint32(const std::string& name) { YamlValue* current = getCurrentValue(); if (!current) { @@ -333,8 +350,8 @@ docReaderYaml::readUint32() // Reading from object by key if (current->isObject()) { YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { - return static_cast(stringToInt(obj[m_nextKey].asString())); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return static_cast(stringToInt(obj[name].asString())); } } } else { @@ -354,7 +371,7 @@ docReaderYaml::readUint32() } float -docReaderYaml::readFloat32() +docReaderYaml::readFloat32(const std::string& name) { YamlValue* current = getCurrentValue(); if (!current) { @@ -365,8 +382,8 @@ docReaderYaml::readFloat32() // Reading from object by key if (current->isObject()) { YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { - return stringToFloat(obj[m_nextKey].asString()); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return stringToFloat(obj[name].asString()); } } } else { @@ -386,7 +403,7 @@ docReaderYaml::readFloat32() } std::vector -docReaderYaml::readFloat32Array() +docReaderYaml::readFloat32Array(const std::string& name) { std::vector result; YamlValue* current = getCurrentValue(); @@ -395,8 +412,8 @@ docReaderYaml::readFloat32Array() } YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isArray()) { - YamlArray& arr = obj[m_nextKey].asArray(); + if (obj.find(name) != obj.end() && obj[name].isArray()) { + YamlArray& arr = obj[name].asArray(); for (const auto& elem : arr) { if (elem.isString()) { result.push_back(stringToFloat(elem.asString())); @@ -408,7 +425,7 @@ docReaderYaml::readFloat32Array() } std::vector -docReaderYaml::readInt32Array() +docReaderYaml::readInt32Array(const std::string& name) { std::vector result; YamlValue* current = getCurrentValue(); @@ -417,8 +434,8 @@ docReaderYaml::readInt32Array() } YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isArray()) { - YamlArray& arr = obj[m_nextKey].asArray(); + if (obj.find(name) != obj.end() && obj[name].isArray()) { + YamlArray& arr = obj[name].asArray(); for (const auto& elem : arr) { if (elem.isString()) { result.push_back(stringToInt(elem.asString())); @@ -430,7 +447,7 @@ docReaderYaml::readInt32Array() } std::vector -docReaderYaml::readUint32Array() +docReaderYaml::readUint32Array(const std::string& name) { std::vector result; YamlValue* current = getCurrentValue(); @@ -439,8 +456,8 @@ docReaderYaml::readUint32Array() } YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isArray()) { - YamlArray& arr = obj[m_nextKey].asArray(); + if (obj.find(name) != obj.end() && obj[name].isArray()) { + YamlArray& arr = obj[name].asArray(); for (const auto& elem : arr) { if (elem.isString()) { result.push_back(static_cast(stringToInt(elem.asString()))); @@ -452,7 +469,7 @@ docReaderYaml::readUint32Array() } std::string -docReaderYaml::readString() +docReaderYaml::readString(const std::string& name) { YamlValue* current = getCurrentValue(); if (!current) { @@ -463,8 +480,8 @@ docReaderYaml::readString() // Reading from object by key if (current->isObject()) { YamlObject& obj = current->asObject(); - if (obj.find(m_nextKey) != obj.end() && obj[m_nextKey].isString()) { - return obj[m_nextKey].asString(); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return obj[name].asString(); } } } else { diff --git a/renderlib/serialize/docReaderYaml.h b/renderlib/serialize/docReaderYaml.h index ad57b4b53..2f7073ae8 100644 --- a/renderlib/serialize/docReaderYaml.h +++ b/renderlib/serialize/docReaderYaml.h @@ -32,21 +32,22 @@ class docReaderYaml : public docReader // Peek operations virtual std::string peekObjectType() override; - virtual int peekVersion() override; + virtual uint32_t peekVersion() override; + virtual std::string peekObjectName() override; // Property reading - virtual void readPrty(prtyProperty* p) override; - - // Primitive type reading - virtual bool readBool() override; - virtual int8_t readInt8() override; - virtual int32_t readInt32() override; - virtual uint32_t readUint32() override; - virtual float readFloat32() override; - virtual std::vector readFloat32Array() override; - virtual std::vector readInt32Array() override; - virtual std::vector readUint32Array() override; - virtual std::string readString() override; + virtual bool readPrty(prtyProperty* p) override; + + // Primitive type reading - all require a name parameter + virtual bool readBool(const std::string& name) override; + virtual int8_t readInt8(const std::string& name) override; + virtual int32_t readInt32(const std::string& name) override; + virtual uint32_t readUint32(const std::string& name) override; + virtual float readFloat32(const std::string& name) override; + virtual std::vector readFloat32Array(const std::string& name) override; + virtual std::vector readInt32Array(const std::string& name) override; + virtual std::vector readUint32Array(const std::string& name) override; + virtual std::string readString(const std::string& name) override; private: // Simple YAML value types diff --git a/renderlib/serialize/docWriter.h b/renderlib/serialize/docWriter.h index 540a4a33f..252125df8 100644 --- a/renderlib/serialize/docWriter.h +++ b/renderlib/serialize/docWriter.h @@ -18,7 +18,7 @@ class docWriter virtual void endDocument() = 0; // objects can contain other objects, lists, and properties. - virtual void beginObject(const std::string& i_name) = 0; + virtual void beginObject(const std::string& i_name, const std::string& i_objectType, uint32_t version) = 0; virtual void endObject() = 0; // lists can contain objects or properties. diff --git a/renderlib/serialize/docWriterJson.cpp b/renderlib/serialize/docWriterJson.cpp index 972753133..bdcbed5e4 100644 --- a/renderlib/serialize/docWriterJson.cpp +++ b/renderlib/serialize/docWriterJson.cpp @@ -1,5 +1,7 @@ #include "docWriterJson.h" +#include "SerializationConstants.h" + #include "core/prty/prtyProperty.hpp" #include "Logging.h" @@ -59,10 +61,15 @@ docWriterJson::endDocument() } void -docWriterJson::beginObject(const std::string& i_name) +docWriterJson::beginObject(const std::string& i_name, const std::string& i_objectType, uint32_t version) { nlohmann::json* newObj = new nlohmann::json(nlohmann::json::object()); + // Add _name, _type and _version metadata + (*newObj)[SerializationConstants::TYPE_KEY] = i_objectType; + (*newObj)[SerializationConstants::VERSION_KEY] = version; + (*newObj)[SerializationConstants::NAME_KEY] = i_name; + if (m_contextStack.empty()) { // Root level object (*m_current)[i_name] = *newObj; diff --git a/renderlib/serialize/docWriterJson.h b/renderlib/serialize/docWriterJson.h index 81b0a192a..998579a48 100644 --- a/renderlib/serialize/docWriterJson.h +++ b/renderlib/serialize/docWriterJson.h @@ -19,7 +19,7 @@ class docWriterJson : public docWriter virtual void endDocument() override; // Object support - virtual void beginObject(const std::string& i_name) override; + virtual void beginObject(const std::string& i_name, const std::string& i_objectType, uint32_t version) override; virtual void endObject() override; // List/array support diff --git a/renderlib/serialize/docWriterYaml.cpp b/renderlib/serialize/docWriterYaml.cpp index 54f4601bd..7a0a17ad7 100644 --- a/renderlib/serialize/docWriterYaml.cpp +++ b/renderlib/serialize/docWriterYaml.cpp @@ -1,5 +1,7 @@ #include "docWriterYaml.h" +#include "SerializationConstants.h" + #include "core/prty/prtyProperty.hpp" #include "Logging.h" @@ -48,7 +50,7 @@ docWriterYaml::endDocument() } void -docWriterYaml::beginObject(const std::string& i_name) +docWriterYaml::beginObject(const std::string& i_name, const std::string& i_objectType, uint32_t version) { if (m_contextStack.empty()) { // Root level object @@ -78,6 +80,19 @@ docWriterYaml::beginObject(const std::string& i_name) } } + // Write _type and _version metadata + writeIndent(); + writeKey(SerializationConstants::TYPE_KEY); + m_output << escapeString(i_objectType) << "\n"; + + writeIndent(); + writeKey(SerializationConstants::VERSION_KEY); + m_output << version << "\n"; + + writeIndent(); + writeKey(SerializationConstants::NAME_KEY); + m_output << escapeString(i_name) << "\n"; + pushContext(i_name, ContextType::Object); } diff --git a/renderlib/serialize/docWriterYaml.h b/renderlib/serialize/docWriterYaml.h index 2545aa8a6..9a3683b4a 100644 --- a/renderlib/serialize/docWriterYaml.h +++ b/renderlib/serialize/docWriterYaml.h @@ -18,7 +18,7 @@ class docWriterYaml : public docWriter virtual void endDocument() override; // Object support - virtual void beginObject(const std::string& i_name) override; + virtual void beginObject(const std::string& i_name, const std::string& i_objectType, uint32_t version) override; virtual void endObject() override; // List/array support diff --git a/test/test_docReader.cpp b/test/test_docReader.cpp index 3c25b0ea1..d151a59b0 100644 --- a/test/test_docReader.cpp +++ b/test/test_docReader.cpp @@ -51,7 +51,7 @@ createTestJsonFile(const std::string& filePath) docWriterJson writer; writer.beginDocument(filePath); - writer.beginObject("testObject"); + writer.beginObject("testObject", "MYTYPE", 1); writer.writeProperties(obj); writer.endObject(); writer.endDocument(); @@ -67,7 +67,7 @@ createTestYamlFile(const std::string& filePath) docWriterYaml writer; writer.beginDocument(filePath); - writer.beginObject("testObject"); + writer.beginObject("testObject", "MYTYPE", 1); writer.writeProperties(obj); writer.endObject(); writer.endDocument(); @@ -93,27 +93,27 @@ TEST_CASE("Read prtyObject from JSON", "[serialize][docReader]") // Read individual properties prtyInt32 testInt("testInt"); reader.readPrty(&testInt); - int32_t intValue = reader.readInt32(); + int32_t intValue = testInt.GetValue(); REQUIRE(intValue == 42); prtyFloat testFloat("testFloat"); reader.readPrty(&testFloat); - float floatValue = reader.readFloat32(); + float floatValue = testFloat.GetValue(); REQUIRE(floatValue == Catch::Approx(3.14f)); prtyText testString("testString"); reader.readPrty(&testString); - std::string stringValue = reader.readString(); + std::string stringValue = testString.GetValue(); REQUIRE(stringValue == "Hello World"); prtyBoolean testBool("testBool"); reader.readPrty(&testBool); - bool boolValue = reader.readBool(); + bool boolValue = testBool.GetValue(); REQUIRE(boolValue == true); prtyInt8 testInt8("testInt8"); reader.readPrty(&testInt8); - int8_t int8Value = reader.readInt8(); + int8_t int8Value = testInt8.GetValue(); REQUIRE(int8Value == 127); reader.endObject(); @@ -141,27 +141,27 @@ TEST_CASE("Read prtyObject from YAML", "[serialize][docReader]") // Read individual properties prtyInt32 testInt("testInt"); reader.readPrty(&testInt); - int32_t intValue = reader.readInt32(); + int32_t intValue = testInt.GetValue(); REQUIRE(intValue == 42); prtyFloat testFloat("testFloat"); reader.readPrty(&testFloat); - float floatValue = reader.readFloat32(); + float floatValue = testFloat.GetValue(); REQUIRE(floatValue == Catch::Approx(3.14f)); prtyText testString("testString"); reader.readPrty(&testString); - std::string stringValue = reader.readString(); + std::string stringValue = testString.GetValue(); REQUIRE(stringValue == "Hello World"); prtyBoolean testBool("testBool"); reader.readPrty(&testBool); - bool boolValue = reader.readBool(); + bool boolValue = testBool.GetValue(); REQUIRE(boolValue == true); prtyInt8 testInt8("testInt8"); reader.readPrty(&testInt8); - int8_t int8Value = reader.readInt8(); + int8_t int8Value = testInt8.GetValue(); REQUIRE(int8Value == 127); reader.endObject(); @@ -179,7 +179,7 @@ TEST_CASE("Read and write roundtrip JSON", "[serialize][docReader]") prtyObject* originalObj = createTestObject(); docWriterJson writer; writer.beginDocument(jsonPath); - writer.beginObject("testObject"); + writer.beginObject("testObject", "MYTYPE", 1); writer.writeProperties(originalObj); writer.endObject(); writer.endDocument(); @@ -206,19 +206,10 @@ TEST_CASE("Read and write roundtrip JSON", "[serialize][docReader]") // Read properties reader.readPrty(intProp->GetProperty(0)); - static_cast(intProp->GetProperty(0))->SetValue(reader.readInt32()); - reader.readPrty(floatProp->GetProperty(0)); - static_cast(floatProp->GetProperty(0))->SetValue(reader.readFloat32()); - reader.readPrty(stringProp->GetProperty(0)); - static_cast(stringProp->GetProperty(0))->SetValue(reader.readString()); - reader.readPrty(boolProp->GetProperty(0)); - static_cast(boolProp->GetProperty(0))->SetValue(reader.readBool()); - reader.readPrty(int8Prop->GetProperty(0)); - static_cast(int8Prop->GetProperty(0))->SetValue(reader.readInt8()); reader.endObject(); reader.endDocument(); @@ -244,7 +235,7 @@ TEST_CASE("Read and write roundtrip YAML", "[serialize][docReader]") prtyObject* originalObj = createTestObject(); docWriterYaml writer; writer.beginDocument(yamlPath); - writer.beginObject("testObject"); + writer.beginObject("testObject", "MYTYPE", 1); writer.writeProperties(originalObj); writer.endObject(); writer.endDocument(); @@ -271,19 +262,10 @@ TEST_CASE("Read and write roundtrip YAML", "[serialize][docReader]") // Read properties reader.readPrty(intProp->GetProperty(0)); - static_cast(intProp->GetProperty(0))->SetValue(reader.readInt32()); - reader.readPrty(floatProp->GetProperty(0)); - static_cast(floatProp->GetProperty(0))->SetValue(reader.readFloat32()); - reader.readPrty(stringProp->GetProperty(0)); - static_cast(stringProp->GetProperty(0))->SetValue(reader.readString()); - reader.readPrty(boolProp->GetProperty(0)); - static_cast(boolProp->GetProperty(0))->SetValue(reader.readBool()); - reader.readPrty(int8Prop->GetProperty(0)); - static_cast(int8Prop->GetProperty(0))->SetValue(reader.readInt8()); reader.endObject(); reader.endDocument(); diff --git a/test/test_docWriter.cpp b/test/test_docWriter.cpp index 2f8bd909f..3a1053023 100644 --- a/test/test_docWriter.cpp +++ b/test/test_docWriter.cpp @@ -27,7 +27,7 @@ writePrtyObject(docWriter& writer, prtyObject* obj, const std::string& name) return; } - writer.beginObject(name.c_str()); + writer.beginObject(name.c_str(), "MYTYPE", 1); writer.writeProperties(obj); @@ -178,7 +178,7 @@ TEST_CASE("Validate nesting protection in docWriter", "[serialize][docWriter]") { docWriterJson writer; writer.beginDocument("test_nesting_error.json"); - writer.beginObject("obj1"); + writer.beginObject("obj1", "MYTYPE", 1); writer.endObject(); writer.endObject(); // Extra end - should log error but not crash writer.endDocument(); @@ -189,7 +189,7 @@ TEST_CASE("Validate nesting protection in docWriter", "[serialize][docWriter]") { docWriterJson writer; writer.beginDocument("test_mismatch_error.json"); - writer.beginObject("obj1"); + writer.beginObject("obj1", "MYTYPE", 1); writer.endList(); // Wrong type - should log error writer.endDocument(); std::filesystem::remove("test_mismatch_error.json"); @@ -199,8 +199,8 @@ TEST_CASE("Validate nesting protection in docWriter", "[serialize][docWriter]") { docWriterJson writer; writer.beginDocument("test_unclosed_error.json"); - writer.beginObject("obj1"); - writer.beginObject("obj2"); + writer.beginObject("obj1", "MYTYPE", 1); + writer.beginObject("obj2", "MYTYPE", 1); // Missing endObject calls - should log error on endDocument writer.endDocument(); std::filesystem::remove("test_unclosed_error.json"); From 95c3a05c5eb4a90546398a8f4a9e15e692e1bed6 Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Fri, 12 Dec 2025 21:17:38 -0800 Subject: [PATCH 47/63] fix tests --- agave_app/agaveGui.cpp | 1 + renderlib/serialize/docReaderYaml.cpp | 38 ++++++++++++++++- test/test_CameraObject.cpp | 61 ++++++++++++++------------- 3 files changed, 69 insertions(+), 31 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 29303a864..9fae1353f 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -726,6 +726,7 @@ agaveGui::writeDocument(std::string filepath) docWriter* writer = new docWriterJson(); writer->beginDocument(filepath); + writer->beginObject("_AGAVE", "AgaveDocument", 1); // write agave version at least writer->writeProperty("Version", std::string(AICS_VERSION_STRING)); diff --git a/renderlib/serialize/docReaderYaml.cpp b/renderlib/serialize/docReaderYaml.cpp index 9bf36af8b..054681972 100644 --- a/renderlib/serialize/docReaderYaml.cpp +++ b/renderlib/serialize/docReaderYaml.cpp @@ -574,9 +574,43 @@ docReaderYaml::parseObject(std::ifstream& file, int& currentIndent, int baseInde } } } else { - // Simple key-value pair + // Simple key-value pair - check if it's an inline array YamlValue yamlValue; - yamlValue.data = value; + + // Check if value is an inline array (starts with [ and ends with ]) + if (!value.empty() && value.front() == '[' && value.back() == ']') { + // Parse inline array + YamlArray arr; + std::string content = value.substr(1, value.length() - 2); // Remove [ and ] + + if (!content.empty()) { + size_t start = 0; + size_t end = 0; + + while ((end = content.find(',', start)) != std::string::npos) { + std::string element = trimString(content.substr(start, end - start)); + if (!element.empty()) { + YamlValue elemValue; + elemValue.data = element; + arr.push_back(elemValue); + } + start = end + 1; + } + + // Add the last element + std::string element = trimString(content.substr(start)); + if (!element.empty()) { + YamlValue elemValue; + elemValue.data = element; + arr.push_back(elemValue); + } + } + + yamlValue.data = arr; + } else { + yamlValue.data = value; + } + obj[key] = yamlValue; } } diff --git a/test/test_CameraObject.cpp b/test/test_CameraObject.cpp index 3d6a0d6fe..69c08acca 100644 --- a/test/test_CameraObject.cpp +++ b/test/test_CameraObject.cpp @@ -52,21 +52,21 @@ verifyTestCameraObject(CameraObject* camera) REQUIRE(camera->getCameraDataObject().FieldOfView.GetValue() == Catch::Approx(45.0f)); REQUIRE(camera->getCameraDataObject().FocalDistance.GetValue() == Catch::Approx(5.5f)); - // auto pos = camera->getCameraDataObject().Position.GetValue(); - // REQUIRE(pos.x == Catch::Approx(1.0f)); - // REQUIRE(pos.y == Catch::Approx(2.0f)); - // REQUIRE(pos.z == Catch::Approx(3.0f)); - - // auto target = camera->getCameraDataObject().Target.GetValue(); - // REQUIRE(target.x == Catch::Approx(0.0f)); - // REQUIRE(target.y == Catch::Approx(0.0f)); - // REQUIRE(target.z == Catch::Approx(0.0f)); - - // REQUIRE(camera->getCameraDataObject().NearPlane.GetValue() == Catch::Approx(0.5f)); - // REQUIRE(camera->getCameraDataObject().FarPlane.GetValue() == Catch::Approx(500.0f)); - // REQUIRE(camera->getCameraDataObject().Roll.GetValue() == Catch::Approx(15.0f)); - // REQUIRE(camera->getCameraDataObject().OrthoScale.GetValue() == Catch::Approx(1.5f)); - // REQUIRE(camera->getCameraDataObject().ProjectionMode.GetValue() == 0); + auto pos = camera->getCameraDataObject().Position.GetValue(); + REQUIRE(pos.x == Catch::Approx(1.0f)); + REQUIRE(pos.y == Catch::Approx(2.0f)); + REQUIRE(pos.z == Catch::Approx(3.0f)); + + auto target = camera->getCameraDataObject().Target.GetValue(); + REQUIRE(target.x == Catch::Approx(0.0f)); + REQUIRE(target.y == Catch::Approx(0.0f)); + REQUIRE(target.z == Catch::Approx(0.0f)); + + REQUIRE(camera->getCameraDataObject().NearPlane.GetValue() == Catch::Approx(0.5f)); + REQUIRE(camera->getCameraDataObject().FarPlane.GetValue() == Catch::Approx(500.0f)); + REQUIRE(camera->getCameraDataObject().Roll.GetValue() == Catch::Approx(15.0f)); + REQUIRE(camera->getCameraDataObject().OrthoScale.GetValue() == Catch::Approx(1.5f)); + REQUIRE(camera->getCameraDataObject().ProjectionMode.GetValue() == 0); } TEST_CASE("CameraObject JSON roundtrip serialization", "[CameraObject][serialize]") @@ -151,9 +151,10 @@ TEST_CASE("CameraObject version 1 JSON format compatibility", "[CameraObject][se { std::ofstream file(jsonPath); file << R"({ - "CameraObject": { + "camera0": { "_type": "CameraObject", "_version": 1, + "_name": "camera0", "Exposure": 0.85, "ExposureIterations": 2, "NoiseReduction": true, @@ -179,8 +180,9 @@ TEST_CASE("CameraObject version 1 JSON format compatibility", "[CameraObject][se docReaderJson reader; reader.beginDocument(jsonPath); + loadedCamera->fromDocument(&reader); // Enter the CameraObject before checking version - reader.beginObject("CameraObject"); + reader.beginObject("camera0"); // Check version before reading properties std::string objectType = reader.peekObjectType(); @@ -189,8 +191,6 @@ TEST_CASE("CameraObject version 1 JSON format compatibility", "[CameraObject][se int version = reader.peekVersion(); REQUIRE(version == 1); - // Read properties (fromDocument would normally call beginObject, but we already did) - reader.readProperties(loadedCamera); reader.endObject(); reader.endDocument(); @@ -211,9 +211,10 @@ TEST_CASE("CameraObject version 1 YAML format compatibility", "[CameraObject][se // Manually create a version 1 YAML file to simulate old format { std::ofstream file(yamlPath); - file << R"(CameraObject: + file << R"(camera0: _type: CameraObject _version: 1 + _name: camera0 Exposure: 0.85 ExposureIterations: 2 NoiseReduction: true @@ -238,8 +239,10 @@ TEST_CASE("CameraObject version 1 YAML format compatibility", "[CameraObject][se docReaderYaml reader; reader.beginDocument(yamlPath); + loadedCamera->fromDocument(&reader); + // Enter the CameraObject before checking version - reader.beginObject("CameraObject"); + reader.beginObject("camera0"); // Check version before reading properties std::string objectType = reader.peekObjectType(); @@ -248,8 +251,6 @@ TEST_CASE("CameraObject version 1 YAML format compatibility", "[CameraObject][se int version = reader.peekVersion(); REQUIRE(version == 1); - // Read properties (fromDocument would normally call beginObject, but we already did) - reader.readProperties(loadedCamera); reader.endObject(); reader.endDocument(); @@ -317,9 +318,10 @@ TEST_CASE("CameraObject partial data loading", "[CameraObject][serialize]") { std::ofstream file(jsonPath); file << R"({ - "CameraObject": { + "camera0": { "_type": "CameraObject", "_version": 1, + "_name": "camera0", "Exposure": 0.5, "FieldOfView": 60.0 } @@ -363,9 +365,10 @@ TEST_CASE("CameraObject invalid version handling", "[CameraObject][serialize][ve { std::ofstream file(jsonPath); file << R"({ - "CameraObject": { + "camera0": { "_type": "CameraObject", "_version": 999, + "_name": "camera0", "Exposure": 0.85, "FieldOfView": 45.0 } @@ -380,14 +383,14 @@ TEST_CASE("CameraObject invalid version handling", "[CameraObject][serialize][ve docReaderJson reader; reader.beginDocument(jsonPath); + loadedCamera->fromDocument(&reader); + // Enter the CameraObject before checking version - reader.beginObject("CameraObject"); + reader.beginObject("camera0"); - int version = reader.peekVersion(); + uint32_t version = reader.peekVersion(); REQUIRE(version == 999); - // Read properties (fromDocument would normally call beginObject, but we already did) - reader.readProperties(loadedCamera); reader.endObject(); reader.endDocument(); From 585e592c3e676ac6bdc69a5f8f4c4b71d94bf229 Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Sat, 13 Dec 2025 18:25:53 -0800 Subject: [PATCH 48/63] fromDocument does not call beginObject/endObject but toDocument does --- agave_app/agaveGui.cpp | 94 ++++++++++++++++++++++++++++++++++---- renderlib/CameraObject.cpp | 4 -- test/test_CameraObject.cpp | 25 +++++++--- 3 files changed, 104 insertions(+), 19 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 9fae1353f..4e4776a55 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -803,39 +803,117 @@ agaveGui::readDocument(std::string filepath) if (reader->beginList("_camera")) { // list of all cameras // v1, read only the first camera + // Iterate through objects in the list + while (reader->beginObject("")) { - CameraObject* camObj = new CameraObject(); - camObj->fromDocument(reader); - camObj->updateObjectFromProps(); + CameraObject* camObj = new CameraObject(); + camObj->fromDocument(reader); + reader->endObject(); + + camObj->updateObjectFromProps(); - // install camObj into m_cameraObject??? - m_cameraObject = std::unique_ptr(camObj); - // follow through to other parts of the app that need to know about camera change - m_glView->setCameraObject(m_cameraObject.get()); - // m_cameradock->setCameraObject(m_cameraObject); + // install camObj into m_cameraObject??? + m_cameraObject = std::unique_ptr(camObj); + // follow through to other parts of the app that need to know about camera change + m_glView->setCameraObject(m_cameraObject.get()); + // m_cameradock->setCameraObject(m_cameraObject); + + break; // only read first camera for now + } reader->endList(); } if (reader->beginList("_light")) { // list of all lights + while (reader->beginObject("")) { + std::string objType = reader->peekObjectType(); + if (objType == "SkyLightObject") { + SkyLightObject* skyLightObj = new SkyLightObject(); + skyLightObj->fromDocument(reader); + reader->endObject(); + // install skyLightObj into m_skyLightObject??? + m_skyLightObject = std::unique_ptr(skyLightObj); + // follow through to other parts of the app that need to know about skylight change + m_appScene.initLights(m_skyLightObject->getSceneLight(), m_areaLightObject->getSceneLight()); + // m_skylightDock->setSkyLightObject(m_skyLightObject); + } else if (objType == "AreaLightObject") { + AreaLightObject* areaLightObj = new AreaLightObject(); + areaLightObj->fromDocument(reader); + reader->endObject(); + // install areaLightObj into m_areaLightObject??? + m_areaLightObject = std::unique_ptr(areaLightObj); + // follow through to other parts of the app that need to know about area light change + m_appScene.initLights(m_skyLightObject->getSceneLight(), m_areaLightObject->getSceneLight()); + // m_areaLightDock->setAreaLightObject(m_areaLightObject); + } else { + LOG_WARNING << "Unknown light object type: " << objType; + reader->endObject(); + } + } reader->endList(); } if (reader->beginList("_clipPlane")) { // list of all clip planes + while (reader->beginObject("")) { + std::string objType = reader->peekObjectType(); + if (objType == "ClipPlaneObject") { + // TODO implement clip plane loading + reader->endObject(); + } else { + LOG_WARNING << "Unknown clip plane object type: " << objType; + reader->endObject(); + } + } reader->endList(); } if (reader->beginList("_renderSettings")) { // list of all render settings objects + while (reader->beginObject("")) { + std::string objType = reader->peekObjectType(); + if (objType == "RenderSettingsObject") { + AppearanceObject* appObj = new AppearanceObject(); + appObj->fromDocument(reader); + reader->endObject(); + // install appObj into m_appearanceObject??? + m_appearanceObject = std::unique_ptr(appObj); + // follow through to other parts of the app that need to know about appearance change + m_appearanceObject->updateObjectFromProps(); + // m_appearanceDockWidget->setAppearanceObject(m_appearanceObject); + } else { + LOG_WARNING << "Unknown render settings object type: " << objType; + reader->endObject(); + } + } reader->endList(); } if (reader->beginList("_captureSettings")) { // list of capture settings objects (only one?) + while (reader->beginObject("")) { + std::string objType = reader->peekObjectType(); + if (objType == "CaptureSettingsObject") { + // TODO implement capture settings loading + reader->endObject(); + } else { + LOG_WARNING << "Unknown capture settings object type: " << objType; + reader->endObject(); + } + } reader->endList(); } if (reader->beginObject("_geometry")) { if (reader->beginList("_volume")) { // list of all volumes + while (reader->beginObject("")) { + std::string objType = reader->peekObjectType(); + if (objType == "VolumeObject") { + // TODO implement volume loading + reader->endObject(); + } else { + LOG_WARNING << "Unknown volume object type: " << objType; + reader->endObject(); + } + } reader->endList(); } if (reader->beginList("_mesh")) { diff --git a/renderlib/CameraObject.cpp b/renderlib/CameraObject.cpp index 4287c99a8..fb636b4a9 100644 --- a/renderlib/CameraObject.cpp +++ b/renderlib/CameraObject.cpp @@ -265,8 +265,6 @@ CameraObject::TransformationChanged(prtyProperty* i_Property, bool i_bDirty) void CameraObject::fromDocument(docReader* reader) { - reader->beginObject("camera0"); - // Peek at metadata uint32_t version = reader->peekVersion(); std::string type = reader->peekObjectType(); @@ -285,8 +283,6 @@ CameraObject::fromDocument(docReader* reader) reader->readPrty(&m_cameraDataObject.Roll); reader->readPrty(&m_cameraDataObject.OrthoScale); reader->readPrty(&m_cameraDataObject.ProjectionMode); - - reader->endObject(); } void CameraObject::toDocument(docWriter* writer) diff --git a/test/test_CameraObject.cpp b/test/test_CameraObject.cpp index 69c08acca..ef89b7e7a 100644 --- a/test/test_CameraObject.cpp +++ b/test/test_CameraObject.cpp @@ -94,7 +94,9 @@ TEST_CASE("CameraObject JSON roundtrip serialization", "[CameraObject][serialize docReaderJson reader; reader.beginDocument(jsonPath); + reader.beginObject("camera0"); loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); verifyTestCameraObject(loadedCamera); @@ -131,7 +133,9 @@ TEST_CASE("CameraObject YAML roundtrip serialization", "[CameraObject][serialize docReaderYaml reader; reader.beginDocument(yamlPath); + reader.beginObject("camera0"); loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); verifyTestCameraObject(loadedCamera); @@ -180,8 +184,6 @@ TEST_CASE("CameraObject version 1 JSON format compatibility", "[CameraObject][se docReaderJson reader; reader.beginDocument(jsonPath); - loadedCamera->fromDocument(&reader); - // Enter the CameraObject before checking version reader.beginObject("camera0"); // Check version before reading properties @@ -191,6 +193,8 @@ TEST_CASE("CameraObject version 1 JSON format compatibility", "[CameraObject][se int version = reader.peekVersion(); REQUIRE(version == 1); + loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); @@ -239,9 +243,6 @@ TEST_CASE("CameraObject version 1 YAML format compatibility", "[CameraObject][se docReaderYaml reader; reader.beginDocument(yamlPath); - loadedCamera->fromDocument(&reader); - - // Enter the CameraObject before checking version reader.beginObject("camera0"); // Check version before reading properties @@ -251,6 +252,8 @@ TEST_CASE("CameraObject version 1 YAML format compatibility", "[CameraObject][se int version = reader.peekVersion(); REQUIRE(version == 1); + loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); @@ -287,7 +290,9 @@ TEST_CASE("CameraObject default values serialization", "[CameraObject][serialize docReaderJson reader; reader.beginDocument(jsonPath); + reader.beginObject("camera0"); loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); // Verify all values match defaults @@ -336,7 +341,9 @@ TEST_CASE("CameraObject partial data loading", "[CameraObject][serialize]") docReaderJson reader; reader.beginDocument(jsonPath); + reader.beginObject("camera0"); loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); // Specified properties should be loaded @@ -383,14 +390,14 @@ TEST_CASE("CameraObject invalid version handling", "[CameraObject][serialize][ve docReaderJson reader; reader.beginDocument(jsonPath); - loadedCamera->fromDocument(&reader); - // Enter the CameraObject before checking version reader.beginObject("camera0"); uint32_t version = reader.peekVersion(); REQUIRE(version == 999); + loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); @@ -430,7 +437,9 @@ TEST_CASE("CameraObject enum property serialization", "[CameraObject][serialize] docReaderJson reader; reader.beginDocument(jsonPath); + reader.beginObject("camera0"); loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); REQUIRE(loadedCamera->getCameraDataObject().ExposureIterations.GetValue() == i); @@ -460,7 +469,9 @@ TEST_CASE("CameraObject enum property serialization", "[CameraObject][serialize] docReaderJson reader; reader.beginDocument(jsonPath); + reader.beginObject("camera0"); loadedCamera->fromDocument(&reader); + reader.endObject(); reader.endDocument(); REQUIRE(loadedCamera->getCameraDataObject().ProjectionMode.GetValue() == i); From 42953759ac261654a65391704bb99bb3d3139154 Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Sat, 13 Dec 2025 19:30:09 -0800 Subject: [PATCH 49/63] fix compile error --- agave_app/agaveGui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 4e4776a55..d81936daf 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -829,7 +829,7 @@ agaveGui::readDocument(std::string filepath) std::string objType = reader->peekObjectType(); if (objType == "SkyLightObject") { SkyLightObject* skyLightObj = new SkyLightObject(); - skyLightObj->fromDocument(reader); + // skyLightObj->fromDocument(reader); reader->endObject(); // install skyLightObj into m_skyLightObject??? m_skyLightObject = std::unique_ptr(skyLightObj); @@ -838,7 +838,7 @@ agaveGui::readDocument(std::string filepath) // m_skylightDock->setSkyLightObject(m_skyLightObject); } else if (objType == "AreaLightObject") { AreaLightObject* areaLightObj = new AreaLightObject(); - areaLightObj->fromDocument(reader); + // areaLightObj->fromDocument(reader); reader->endObject(); // install areaLightObj into m_areaLightObject??? m_areaLightObject = std::unique_ptr(areaLightObj); @@ -872,7 +872,7 @@ agaveGui::readDocument(std::string filepath) std::string objType = reader->peekObjectType(); if (objType == "RenderSettingsObject") { AppearanceObject* appObj = new AppearanceObject(); - appObj->fromDocument(reader); + // appObj->fromDocument(reader); reader->endObject(); // install appObj into m_appearanceObject??? m_appearanceObject = std::unique_ptr(appObj); From 498ae2e256819eecf403d6d90720387c3d9eae90 Mon Sep 17 00:00:00 2001 From: Daniel Toloudis Date: Sun, 14 Dec 2025 10:32:07 -0800 Subject: [PATCH 50/63] add light tests, one bug still --- renderlib/AreaLightObject.cpp | 34 +++ renderlib/AreaLightObject.hpp | 9 + renderlib/CameraObject.cpp | 2 +- renderlib/SkyLightObject.cpp | 34 +++ renderlib/SkyLightObject.hpp | 9 + test/CMakeLists.txt | 1 + test/test_LightObjects.cpp | 421 ++++++++++++++++++++++++++++++++++ 7 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 test/test_LightObjects.cpp diff --git a/renderlib/AreaLightObject.cpp b/renderlib/AreaLightObject.cpp index 434037b5d..03a53f23b 100644 --- a/renderlib/AreaLightObject.cpp +++ b/renderlib/AreaLightObject.cpp @@ -4,6 +4,9 @@ #include "MathUtil.h" #include "Logging.h" +#include "serialize/docReader.h" +#include "serialize/docWriter.h" + AreaLightObject::AreaLightObject() : prtyObject() { @@ -156,3 +159,34 @@ AreaLightObject::ColorChanged(prtyProperty* i_Property, bool i_bDirty) { updateSceneLightFromProps(); } + +void +AreaLightObject::fromDocument(docReader* reader) +{ + // Peek at metadata + uint32_t version = reader->peekVersion(); + std::string type = reader->peekObjectType(); + std::string name = reader->peekObjectName(); + + reader->readPrty(&m_arealightDataObject.Theta); + reader->readPrty(&m_arealightDataObject.Phi); + reader->readPrty(&m_arealightDataObject.Size); + reader->readPrty(&m_arealightDataObject.Distance); + reader->readPrty(&m_arealightDataObject.Intensity); + reader->readPrty(&m_arealightDataObject.Color); +} + +void +AreaLightObject::toDocument(docWriter* writer) +{ + writer->beginObject("areaLight0", "AreaLightObject", AreaLightObject::CURRENT_VERSION); + + m_arealightDataObject.Theta.Write(*writer); + m_arealightDataObject.Phi.Write(*writer); + m_arealightDataObject.Size.Write(*writer); + m_arealightDataObject.Distance.Write(*writer); + m_arealightDataObject.Intensity.Write(*writer); + m_arealightDataObject.Color.Write(*writer); + + writer->endObject(); +} diff --git a/renderlib/AreaLightObject.hpp b/renderlib/AreaLightObject.hpp index 036f1025b..90790e627 100644 --- a/renderlib/AreaLightObject.hpp +++ b/renderlib/AreaLightObject.hpp @@ -37,6 +37,10 @@ class AreaLightObject : public prtyObject // Update scene light instance from properties (called automatically via callbacks) void updateSceneLightFromProps(); + // Getter for data object + ArealightDataObject& getDataObject() { return m_arealightDataObject; } + const ArealightDataObject& getDataObject() const { return m_arealightDataObject; } + // Property change callbacks void ThetaChanged(prtyProperty* i_Property, bool i_bDirty); void PhiChanged(prtyProperty* i_Property, bool i_bDirty); @@ -47,6 +51,11 @@ class AreaLightObject : public prtyObject void setDirtyCallback(std::function callback) { m_dirtyCallback = callback; } + // document reading and writing; TODO consider an abstract base class to enforce commonality + static constexpr uint32_t CURRENT_VERSION = 1; + void fromDocument(docReader* reader); + void toDocument(docWriter* writer); + private: ArealightDataObject m_arealightDataObject; diff --git a/renderlib/CameraObject.cpp b/renderlib/CameraObject.cpp index fb636b4a9..62163086a 100644 --- a/renderlib/CameraObject.cpp +++ b/renderlib/CameraObject.cpp @@ -305,4 +305,4 @@ CameraObject::toDocument(docWriter* writer) m_cameraDataObject.ProjectionMode.Write(*writer); writer->endObject(); -} \ No newline at end of file +} diff --git a/renderlib/SkyLightObject.cpp b/renderlib/SkyLightObject.cpp index 9f169a1e3..919f84555 100644 --- a/renderlib/SkyLightObject.cpp +++ b/renderlib/SkyLightObject.cpp @@ -3,6 +3,9 @@ #include "SceneLight.h" #include "Logging.h" +#include "serialize/docReader.h" +#include "serialize/docWriter.h" + SkyLightObject::SkyLightObject() : prtyObject() { @@ -153,3 +156,34 @@ SkyLightObject::BottomColorChanged(prtyProperty* i_Property, bool i_bDirty) { updateSceneLightFromProps(); } + +void +SkyLightObject::fromDocument(docReader* reader) +{ + // Peek at metadata + uint32_t version = reader->peekVersion(); + std::string type = reader->peekObjectType(); + std::string name = reader->peekObjectName(); + + reader->readPrty(&m_skylightDataObject.TopIntensity); + reader->readPrty(&m_skylightDataObject.TopColor); + reader->readPrty(&m_skylightDataObject.MiddleIntensity); + reader->readPrty(&m_skylightDataObject.MiddleColor); + reader->readPrty(&m_skylightDataObject.BottomIntensity); + reader->readPrty(&m_skylightDataObject.BottomColor); +} + +void +SkyLightObject::toDocument(docWriter* writer) +{ + writer->beginObject("skyLight0", "SkyLightObject", SkyLightObject::CURRENT_VERSION); + + m_skylightDataObject.TopIntensity.Write(*writer); + m_skylightDataObject.TopColor.Write(*writer); + m_skylightDataObject.MiddleIntensity.Write(*writer); + m_skylightDataObject.MiddleColor.Write(*writer); + m_skylightDataObject.BottomIntensity.Write(*writer); + m_skylightDataObject.BottomColor.Write(*writer); + + writer->endObject(); +} diff --git a/renderlib/SkyLightObject.hpp b/renderlib/SkyLightObject.hpp index bce75a844..0737e6b5b 100644 --- a/renderlib/SkyLightObject.hpp +++ b/renderlib/SkyLightObject.hpp @@ -37,6 +37,10 @@ class SkyLightObject : public prtyObject // Update scene light instance from properties (called automatically via callbacks) void updateSceneLightFromProps(); + // Getter for data object + SkylightDataObject& getDataObject() { return m_skylightDataObject; } + const SkylightDataObject& getDataObject() const { return m_skylightDataObject; } + // Property change callbacks void TopIntensityChanged(prtyProperty* i_Property, bool i_bDirty); void TopColorChanged(prtyProperty* i_Property, bool i_bDirty); @@ -47,6 +51,11 @@ class SkyLightObject : public prtyObject void setDirtyCallback(std::function callback) { m_dirtyCallback = callback; } + // document reading and writing; TODO consider an abstract base class to enforce commonality + static constexpr uint32_t CURRENT_VERSION = 1; + void fromDocument(docReader* reader); + void toDocument(docWriter* writer); + private: SkylightDataObject m_skylightDataObject; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 50ff94a5a..a076fa18e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(agave_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/test_docWriter.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_docReader.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_histogram.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/test_LightObjects.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_main.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_mathUtil.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_prty.cpp" diff --git a/test/test_LightObjects.cpp b/test/test_LightObjects.cpp new file mode 100644 index 000000000..c712ed41b --- /dev/null +++ b/test/test_LightObjects.cpp @@ -0,0 +1,421 @@ +#include +#include + +#include "renderlib/AreaLightObject.hpp" +#include "renderlib/SkyLightObject.hpp" +#include "renderlib/serialize/docReader.h" +#include "renderlib/serialize/docReaderJson.h" +#include "renderlib/serialize/docReaderYaml.h" +#include "renderlib/serialize/docWriter.h" +#include "renderlib/serialize/docWriterJson.h" +#include "renderlib/serialize/docWriterYaml.h" +#include "renderlib/Logging.h" + +#include +#include + +// ============================================================================ +// AreaLightObject Tests +// ============================================================================ + +// Helper function to create an AreaLightObject with known test values +AreaLightObject* +createTestAreaLightObject() +{ + AreaLightObject* light = new AreaLightObject(); + + // Set specific test values for all properties + light->getDataObject().Theta.SetValue(45.0f); + light->getDataObject().Phi.SetValue(90.0f); + light->getDataObject().Size.SetValue(2.5f); + light->getDataObject().Distance.SetValue(15.0f); + light->getDataObject().Intensity.SetValue(250.0f); + light->getDataObject().Color.SetValue(glm::vec4(0.8f, 0.9f, 1.0f, 1.0f)); + + return light; +} + +// Helper function to verify AreaLightObject values match expected test values +void +verifyTestAreaLightObject(AreaLightObject* light) +{ + REQUIRE(light != nullptr); + + const ArealightDataObject& data = light->getDataObject(); + REQUIRE(data.Theta.GetValue() == Catch::Approx(45.0f)); + REQUIRE(data.Phi.GetValue() == Catch::Approx(90.0f)); + REQUIRE(data.Size.GetValue() == Catch::Approx(2.5f)); + REQUIRE(data.Distance.GetValue() == Catch::Approx(15.0f)); + REQUIRE(data.Intensity.GetValue() == Catch::Approx(250.0f)); + + glm::vec4 color = data.Color.GetValue(); + REQUIRE(color.x == Catch::Approx(0.8f)); + REQUIRE(color.y == Catch::Approx(0.9f)); + REQUIRE(color.z == Catch::Approx(1.0f)); + REQUIRE(color.w == Catch::Approx(1.0f)); +} + +TEST_CASE("AreaLightObject creation and initialization", "[AreaLightObject]") +{ + AreaLightObject* light = new AreaLightObject(); + + REQUIRE(light != nullptr); + REQUIRE(light->getSceneLight() != nullptr); + + // Verify default values + const ArealightDataObject& data = light->getDataObject(); + REQUIRE(data.Theta.GetValue() == Catch::Approx(0.0f)); + REQUIRE(data.Phi.GetValue() == Catch::Approx(0.0f)); + REQUIRE(data.Size.GetValue() == Catch::Approx(1.0f)); + REQUIRE(data.Distance.GetValue() == Catch::Approx(10.0f)); + REQUIRE(data.Intensity.GetValue() == Catch::Approx(100.0f)); + + delete light; +} + +TEST_CASE("AreaLightObject property setting", "[AreaLightObject]") +{ + AreaLightObject* light = new AreaLightObject(); + + // Test setting properties + light->getDataObject().Theta.SetValue(30.0f); + light->getDataObject().Phi.SetValue(60.0f); + light->getDataObject().Size.SetValue(5.0f); + light->getDataObject().Distance.SetValue(20.0f); + light->getDataObject().Intensity.SetValue(150.0f); + light->getDataObject().Color.SetValue(glm::vec4(1.0f, 0.5f, 0.25f, 1.0f)); + + REQUIRE(light->getDataObject().Theta.GetValue() == Catch::Approx(30.0f)); + REQUIRE(light->getDataObject().Phi.GetValue() == Catch::Approx(60.0f)); + REQUIRE(light->getDataObject().Size.GetValue() == Catch::Approx(5.0f)); + REQUIRE(light->getDataObject().Distance.GetValue() == Catch::Approx(20.0f)); + REQUIRE(light->getDataObject().Intensity.GetValue() == Catch::Approx(150.0f)); + + glm::vec4 color = light->getDataObject().Color.GetValue(); + REQUIRE(color.x == Catch::Approx(1.0f)); + REQUIRE(color.y == Catch::Approx(0.5f)); + REQUIRE(color.z == Catch::Approx(0.25f)); + + delete light; +} + +TEST_CASE("AreaLightObject JSON roundtrip serialization", "[AreaLightObject][serialize]") +{ + std::string jsonPath = "test_arealight.json"; + + // Create and save light + { + AreaLightObject* light = createTestAreaLightObject(); + + docWriterJson writer; + writer.beginDocument(jsonPath); + light->toDocument(&writer); + writer.endDocument(); + + delete light; + } + + // Verify file exists + REQUIRE(std::filesystem::exists(jsonPath)); + + // Load and verify light + { + AreaLightObject* loadedLight = new AreaLightObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + reader.beginObject("areaLight0"); + loadedLight->fromDocument(&reader); + reader.endObject(); + reader.endDocument(); + + verifyTestAreaLightObject(loadedLight); + + delete loadedLight; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("AreaLightObject YAML roundtrip serialization", "[AreaLightObject][serialize]") +{ + std::string yamlPath = "test_arealight.yaml"; + + // Create and save light + { + AreaLightObject* light = createTestAreaLightObject(); + + docWriterYaml writer; + writer.beginDocument(yamlPath); + light->toDocument(&writer); + writer.endDocument(); + + delete light; + } + + // Verify file exists + REQUIRE(std::filesystem::exists(yamlPath)); + + // Load and verify light + { + AreaLightObject* loadedLight = new AreaLightObject(); + + docReaderYaml reader; + reader.beginDocument(yamlPath); + reader.beginObject("areaLight0"); + loadedLight->fromDocument(&reader); + reader.endObject(); + reader.endDocument(); + + verifyTestAreaLightObject(loadedLight); + + delete loadedLight; + } + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("AreaLightObject dirty callback", "[AreaLightObject]") +{ + AreaLightObject* light = new AreaLightObject(); + bool callbackCalled = false; + + light->setDirtyCallback([&callbackCalled]() { callbackCalled = true; }); + + // The callback should be invoked when properties change + light->ThetaChanged(nullptr, false); + REQUIRE(callbackCalled); + + callbackCalled = false; + light->PhiChanged(nullptr, false); + REQUIRE(callbackCalled); + + callbackCalled = false; + light->IntensityChanged(nullptr, false); + REQUIRE(callbackCalled); + + delete light; +} + +// ============================================================================ +// SkyLightObject Tests +// ============================================================================ + +// Helper function to create a SkyLightObject with known test values +SkyLightObject* +createTestSkyLightObject() +{ + SkyLightObject* light = new SkyLightObject(); + + // Set specific test values for all properties + light->getDataObject().TopIntensity.SetValue(2.5f); + light->getDataObject().TopColor.SetValue(glm::vec4(0.9f, 0.95f, 1.0f, 1.0f)); + light->getDataObject().MiddleIntensity.SetValue(1.5f); + light->getDataObject().MiddleColor.SetValue(glm::vec4(0.6f, 0.6f, 0.6f, 1.0f)); + light->getDataObject().BottomIntensity.SetValue(0.8f); + light->getDataObject().BottomColor.SetValue(glm::vec4(0.3f, 0.25f, 0.2f, 1.0f)); + + return light; +} + +// Helper function to verify SkyLightObject values match expected test values +void +verifyTestSkyLightObject(SkyLightObject* light) +{ + REQUIRE(light != nullptr); + + const SkylightDataObject& data = light->getDataObject(); + REQUIRE(data.TopIntensity.GetValue() == Catch::Approx(2.5f)); + REQUIRE(data.MiddleIntensity.GetValue() == Catch::Approx(1.5f)); + REQUIRE(data.BottomIntensity.GetValue() == Catch::Approx(0.8f)); + + glm::vec4 topColor = data.TopColor.GetValue(); + REQUIRE(topColor.x == Catch::Approx(0.9f)); + REQUIRE(topColor.y == Catch::Approx(0.95f)); + REQUIRE(topColor.z == Catch::Approx(1.0f)); + + glm::vec4 middleColor = data.MiddleColor.GetValue(); + REQUIRE(middleColor.x == Catch::Approx(0.6f)); + REQUIRE(middleColor.y == Catch::Approx(0.6f)); + REQUIRE(middleColor.z == Catch::Approx(0.6f)); + + glm::vec4 bottomColor = data.BottomColor.GetValue(); + REQUIRE(bottomColor.x == Catch::Approx(0.3f)); + REQUIRE(bottomColor.y == Catch::Approx(0.25f)); + REQUIRE(bottomColor.z == Catch::Approx(0.2f)); +} + +TEST_CASE("SkyLightObject creation and initialization", "[SkyLightObject]") +{ + SkyLightObject* light = new SkyLightObject(); + + REQUIRE(light != nullptr); + REQUIRE(light->getSceneLight() != nullptr); + + // Verify default values + const SkylightDataObject& data = light->getDataObject(); + REQUIRE(data.TopIntensity.GetValue() == Catch::Approx(1.0f)); + REQUIRE(data.MiddleIntensity.GetValue() == Catch::Approx(1.0f)); + REQUIRE(data.BottomIntensity.GetValue() == Catch::Approx(1.0f)); + + glm::vec4 topColor = data.TopColor.GetValue(); + REQUIRE(topColor.x == Catch::Approx(1.0f)); + REQUIRE(topColor.y == Catch::Approx(1.0f)); + REQUIRE(topColor.z == Catch::Approx(1.0f)); + + glm::vec4 bottomColor = data.BottomColor.GetValue(); + REQUIRE(bottomColor.x == Catch::Approx(0.2f)); + REQUIRE(bottomColor.y == Catch::Approx(0.2f)); + REQUIRE(bottomColor.z == Catch::Approx(0.2f)); + + delete light; +} + +TEST_CASE("SkyLightObject property setting", "[SkyLightObject]") +{ + SkyLightObject* light = new SkyLightObject(); + + // Test setting properties + light->getDataObject().TopIntensity.SetValue(3.0f); + light->getDataObject().TopColor.SetValue(glm::vec4(1.0f, 1.0f, 0.8f, 1.0f)); + light->getDataObject().MiddleIntensity.SetValue(2.0f); + light->getDataObject().MiddleColor.SetValue(glm::vec4(0.7f, 0.7f, 0.7f, 1.0f)); + light->getDataObject().BottomIntensity.SetValue(1.0f); + light->getDataObject().BottomColor.SetValue(glm::vec4(0.4f, 0.3f, 0.2f, 1.0f)); + + REQUIRE(light->getDataObject().TopIntensity.GetValue() == Catch::Approx(3.0f)); + REQUIRE(light->getDataObject().MiddleIntensity.GetValue() == Catch::Approx(2.0f)); + REQUIRE(light->getDataObject().BottomIntensity.GetValue() == Catch::Approx(1.0f)); + + glm::vec4 topColor = light->getDataObject().TopColor.GetValue(); + REQUIRE(topColor.x == Catch::Approx(1.0f)); + REQUIRE(topColor.y == Catch::Approx(1.0f)); + REQUIRE(topColor.z == Catch::Approx(0.8f)); + + delete light; +} + +TEST_CASE("SkyLightObject JSON roundtrip serialization", "[SkyLightObject][serialize]") +{ + std::string jsonPath = "test_skylight.json"; + + // Create and save light + { + SkyLightObject* light = createTestSkyLightObject(); + + docWriterJson writer; + writer.beginDocument(jsonPath); + light->toDocument(&writer); + writer.endDocument(); + + delete light; + } + + // Verify file exists + REQUIRE(std::filesystem::exists(jsonPath)); + + // Load and verify light + { + SkyLightObject* loadedLight = new SkyLightObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + reader.beginObject("skyLight0"); + loadedLight->fromDocument(&reader); + reader.endObject(); + reader.endDocument(); + + verifyTestSkyLightObject(loadedLight); + + delete loadedLight; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("SkyLightObject YAML roundtrip serialization", "[SkyLightObject][serialize]") +{ + std::string yamlPath = "test_skylight.yaml"; + + // Create and save light + { + SkyLightObject* light = createTestSkyLightObject(); + + docWriterYaml writer; + writer.beginDocument(yamlPath); + light->toDocument(&writer); + writer.endDocument(); + + delete light; + } + + // Verify file exists + REQUIRE(std::filesystem::exists(yamlPath)); + + // Load and verify light + { + SkyLightObject* loadedLight = new SkyLightObject(); + + docReaderYaml reader; + reader.beginDocument(yamlPath); + reader.beginObject("skyLight0"); + loadedLight->fromDocument(&reader); + reader.endObject(); + reader.endDocument(); + + verifyTestSkyLightObject(loadedLight); + + delete loadedLight; + } + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("SkyLightObject dirty callback", "[SkyLightObject]") +{ + SkyLightObject* light = new SkyLightObject(); + bool callbackCalled = false; + + light->setDirtyCallback([&callbackCalled]() { callbackCalled = true; }); + + // The callback should be invoked when properties change + light->TopIntensityChanged(nullptr, false); + REQUIRE(callbackCalled); + + callbackCalled = false; + light->TopColorChanged(nullptr, false); + REQUIRE(callbackCalled); + + callbackCalled = false; + light->MiddleIntensityChanged(nullptr, false); + REQUIRE(callbackCalled); + + callbackCalled = false; + light->BottomColorChanged(nullptr, false); + REQUIRE(callbackCalled); + + delete light; +} + +TEST_CASE("SkyLightObject and AreaLightObject independent instances", "[LightObjects]") +{ + AreaLightObject* areaLight = new AreaLightObject(); + SkyLightObject* skyLight = new SkyLightObject(); + + // Verify they have different scene lights + REQUIRE(areaLight->getSceneLight() != skyLight->getSceneLight()); + + // Modify one and verify the other is unaffected + areaLight->getDataObject().Intensity.SetValue(500.0f); + REQUIRE(skyLight->getDataObject().TopIntensity.GetValue() == Catch::Approx(1.0f)); + + skyLight->getDataObject().TopIntensity.SetValue(5.0f); + REQUIRE(areaLight->getDataObject().Intensity.GetValue() == Catch::Approx(500.0f)); + + delete areaLight; + delete skyLight; +} From 48c7585650533c6d3cb8eea3584d78cf2d590eed Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 14 Dec 2025 18:21:49 -0800 Subject: [PATCH 51/63] fix arealight default phi --- renderlib/AreaLightObject.hpp | 2 +- test/test_LightObjects.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/renderlib/AreaLightObject.hpp b/renderlib/AreaLightObject.hpp index 90790e627..ec216421d 100644 --- a/renderlib/AreaLightObject.hpp +++ b/renderlib/AreaLightObject.hpp @@ -17,7 +17,7 @@ class ArealightDataObject ArealightDataObject() = default; prtyFloat Theta{ "Theta", 0.0f }; - prtyFloat Phi{ "Phi", 1.5708f }; // PI/2 + prtyFloat Phi{ "Phi", 90.0f }; // PI/2 prtyFloat Size{ "Size", 1.0f }; prtyFloat Distance{ "Distance", 10.0f }; prtyFloat Intensity{ "Intensity", 100.0f }; diff --git a/test/test_LightObjects.cpp b/test/test_LightObjects.cpp index c712ed41b..bec1c5b18 100644 --- a/test/test_LightObjects.cpp +++ b/test/test_LightObjects.cpp @@ -65,7 +65,7 @@ TEST_CASE("AreaLightObject creation and initialization", "[AreaLightObject]") // Verify default values const ArealightDataObject& data = light->getDataObject(); REQUIRE(data.Theta.GetValue() == Catch::Approx(0.0f)); - REQUIRE(data.Phi.GetValue() == Catch::Approx(0.0f)); + REQUIRE(data.Phi.GetValue() == Catch::Approx(90.0f)); REQUIRE(data.Size.GetValue() == Catch::Approx(1.0f)); REQUIRE(data.Distance.GetValue() == Catch::Approx(10.0f)); REQUIRE(data.Intensity.GetValue() == Catch::Approx(100.0f)); From 06fbb88cec2c3ad6f10d9bc80244286212c0456e Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 14 Dec 2025 20:33:04 -0800 Subject: [PATCH 52/63] serialize appearanceObject and remove the methods to serialize prtyObjects --- renderlib/AppearanceObject.cpp | 43 ++++ renderlib/AppearanceObject.hpp | 8 + renderlib/serialize/docReader.cpp | 35 --- renderlib/serialize/docReader.h | 2 - renderlib/serialize/docWriter.cpp | 27 --- renderlib/serialize/docWriter.h | 2 - test/CMakeLists.txt | 1 + test/test_AppearanceObject.cpp | 358 ++++++++++++++++++++++++++++++ test/test_docReader.cpp | 52 ++++- test/test_docWriter.cpp | 11 +- 10 files changed, 468 insertions(+), 71 deletions(-) create mode 100644 test/test_AppearanceObject.cpp diff --git a/renderlib/AppearanceObject.cpp b/renderlib/AppearanceObject.cpp index a641cb328..102dcd30a 100644 --- a/renderlib/AppearanceObject.cpp +++ b/renderlib/AppearanceObject.cpp @@ -1,6 +1,8 @@ #include "AppearanceObject.hpp" #include "Logging.h" +#include "serialize/docReader.h" +#include "serialize/docWriter.h" // ComboBoxUiInfo AppearanceUiDescription::m_rendererType("Renderer Type", // "Select volume rendering type", // "Select volume rendering type", @@ -284,3 +286,44 @@ AppearanceObject::ShowScaleBarChanged(prtyProperty* i_Property, bool i_bDirty) m_renderSettings->m_DirtyFlags.SetFlag(EnvironmentDirty); } } + +void +AppearanceObject::fromDocument(docReader* reader) +{ + // Peek at metadata + uint32_t version = reader->peekVersion(); + std::string type = reader->peekObjectType(); + std::string name = reader->peekObjectName(); + + reader->readPrty(&m_appearanceDataObject.RendererType); + reader->readPrty(&m_appearanceDataObject.ShadingType); + reader->readPrty(&m_appearanceDataObject.DensityScale); + reader->readPrty(&m_appearanceDataObject.GradientFactor); + reader->readPrty(&m_appearanceDataObject.StepSizePrimaryRay); + reader->readPrty(&m_appearanceDataObject.StepSizeSecondaryRay); + reader->readPrty(&m_appearanceDataObject.Interpolate); + reader->readPrty(&m_appearanceDataObject.BackgroundColor); + reader->readPrty(&m_appearanceDataObject.ShowBoundingBox); + reader->readPrty(&m_appearanceDataObject.BoundingBoxColor); + reader->readPrty(&m_appearanceDataObject.ShowScaleBar); +} + +void +AppearanceObject::toDocument(docWriter* writer) +{ + writer->beginObject("appearance0", "AppearanceObject", AppearanceObject::CURRENT_VERSION); + + m_appearanceDataObject.RendererType.Write(*writer); + m_appearanceDataObject.ShadingType.Write(*writer); + m_appearanceDataObject.DensityScale.Write(*writer); + m_appearanceDataObject.GradientFactor.Write(*writer); + m_appearanceDataObject.StepSizePrimaryRay.Write(*writer); + m_appearanceDataObject.StepSizeSecondaryRay.Write(*writer); + m_appearanceDataObject.Interpolate.Write(*writer); + m_appearanceDataObject.BackgroundColor.Write(*writer); + m_appearanceDataObject.ShowBoundingBox.Write(*writer); + m_appearanceDataObject.BoundingBoxColor.Write(*writer); + m_appearanceDataObject.ShowScaleBar.Write(*writer); + + writer->endObject(); +} diff --git a/renderlib/AppearanceObject.hpp b/renderlib/AppearanceObject.hpp index 367748468..5c34f2079 100644 --- a/renderlib/AppearanceObject.hpp +++ b/renderlib/AppearanceObject.hpp @@ -6,6 +6,9 @@ #include "AppScene.h" #include "uiInfo.hpp" +class docReader; +class docWriter; + struct AppearanceUiDescription { static ComboBoxUiInfo m_rendererType; @@ -49,6 +52,11 @@ class AppearanceObject : public prtyObject // Getter for the rendersettings std::shared_ptr getRenderSettings() const { return m_renderSettings; } + // document reading and writing; TODO consider an abstract base class to enforce commonality + static constexpr uint32_t CURRENT_VERSION = 1; + void fromDocument(docReader* reader); + void toDocument(docWriter* writer); + private: // the properties AppearanceDataObject m_appearanceDataObject; diff --git a/renderlib/serialize/docReader.cpp b/renderlib/serialize/docReader.cpp index 97ca13c27..e017761ff 100644 --- a/renderlib/serialize/docReader.cpp +++ b/renderlib/serialize/docReader.cpp @@ -4,38 +4,3 @@ #include "core/prty/prtyObject.hpp" #include "Logging.h" - -// Reads all properties from the current object context -// Assumes we're already inside the object (after beginObject was called) -void -docReader::readProperties(prtyObject* obj) -{ - if (!obj) { - return; - } - - const PropertyUIIList& propList = obj->GetList(); - - for (const auto& propUIInfo : propList) { - int numProps = propUIInfo->GetNumberOfProperties(); - - for (int i = 0; i < numProps; ++i) { - prtyProperty* prop = propUIInfo->GetProperty(i); - if (!prop) { - continue; - } - - std::string propName = prop->GetPropertyName(); - - // Check if the property exists in the document - if (hasKey(propName.c_str())) { - // Set up the property name for reading and let the property read itself - bool ok = readPrty(prop); - if (!ok) { - // Log error if property read failed - LOG_ERROR << "Failed to read property: " << propName << " even though key exists."; - } - } - } - } -} diff --git a/renderlib/serialize/docReader.h b/renderlib/serialize/docReader.h index 8f18bddc1..d3e6bd234 100644 --- a/renderlib/serialize/docReader.h +++ b/renderlib/serialize/docReader.h @@ -81,6 +81,4 @@ class docReader virtual std::vector readInt32Array(const std::string& name) = 0; virtual std::vector readUint32Array(const std::string& name) = 0; virtual std::string readString(const std::string& name) = 0; - - void readProperties(prtyObject* obj); }; diff --git a/renderlib/serialize/docWriter.cpp b/renderlib/serialize/docWriter.cpp index 4cd8c958e..991891179 100644 --- a/renderlib/serialize/docWriter.cpp +++ b/renderlib/serialize/docWriter.cpp @@ -2,30 +2,3 @@ #include "core/prty/prtyProperty.hpp" #include "core/prty/prtyObject.hpp" - -// assumes a current named object context (i.e. beginObject was already called). -// if objects had ONLY properties, and no children, -// then this could create the object context itself using beginObject/endObject. -void -docWriter::writeProperties(prtyObject* obj) -{ - if (!obj) { - return; - } - - const PropertyUIIList& propList = obj->GetList(); - - for (const auto& propUIInfo : propList) { - int numProps = propUIInfo->GetNumberOfProperties(); - - for (int i = 0; i < numProps; ++i) { - prtyProperty* prop = propUIInfo->GetProperty(i); - if (!prop) { - // std::cout << "Property is null!" << std::endl; - continue; - } - - writePrty(prop); - } - } -} diff --git a/renderlib/serialize/docWriter.h b/renderlib/serialize/docWriter.h index 252125df8..67cb65be5 100644 --- a/renderlib/serialize/docWriter.h +++ b/renderlib/serialize/docWriter.h @@ -67,6 +67,4 @@ class docWriter virtual size_t writeInt32Array(const std::string& name, const std::vector& value) = 0; virtual size_t writeUint32Array(const std::string& name, const std::vector& value) = 0; virtual size_t writeString(const std::string& name, const std::string& value) = 0; - - void writeProperties(prtyObject* obj); }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a076fa18e..8c92e41e2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ target_include_directories(agave_test PUBLIC "${glm_SOURCE_DIR}" ) target_sources(agave_test PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/test_AppearanceObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_CameraObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_commands.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/test_docWriter.cpp" diff --git a/test/test_AppearanceObject.cpp b/test/test_AppearanceObject.cpp new file mode 100644 index 000000000..689926074 --- /dev/null +++ b/test/test_AppearanceObject.cpp @@ -0,0 +1,358 @@ +#include +#include + +#include "renderlib/AppearanceObject.hpp" +#include "renderlib/serialize/docReader.h" +#include "renderlib/serialize/docReaderJson.h" +#include "renderlib/serialize/docReaderYaml.h" +#include "renderlib/serialize/docWriter.h" +#include "renderlib/serialize/docWriterJson.h" +#include "renderlib/serialize/docWriterYaml.h" +#include "renderlib/serialize/SerializationConstants.h" +#include "renderlib/Logging.h" + +#include +#include +#include + +// Helper function to create an AppearanceObject with known test values +AppearanceObject* +createTestAppearanceObject() +{ + AppearanceObject* appearance = new AppearanceObject(); + + // Set specific test values for all properties + appearance->appearanceDataObject().RendererType.SetValue(1); + appearance->appearanceDataObject().ShadingType.SetValue(1); + appearance->appearanceDataObject().DensityScale.SetValue(2.5f); + appearance->appearanceDataObject().GradientFactor.SetValue(0.75f); + appearance->appearanceDataObject().StepSizePrimaryRay.SetValue(1.5f); + appearance->appearanceDataObject().StepSizeSecondaryRay.SetValue(2.0f); + appearance->appearanceDataObject().Interpolate.SetValue(true); + appearance->appearanceDataObject().BackgroundColor.SetValue(glm::vec4(0.2f, 0.3f, 0.4f, 1.0f)); + appearance->appearanceDataObject().ShowBoundingBox.SetValue(true); + appearance->appearanceDataObject().BoundingBoxColor.SetValue(glm::vec4(1.0f, 0.0f, 0.0f, 1.0f)); + appearance->appearanceDataObject().ShowScaleBar.SetValue(true); + + return appearance; +} + +// Helper function to verify AppearanceObject values match expected test values +void +verifyTestAppearanceObject(AppearanceObject* appearance) +{ + REQUIRE(appearance != nullptr); + + REQUIRE(appearance->appearanceDataObject().RendererType.GetValue() == 1); + REQUIRE(appearance->appearanceDataObject().ShadingType.GetValue() == 1); + REQUIRE(appearance->appearanceDataObject().DensityScale.GetValue() == Catch::Approx(2.5f)); + REQUIRE(appearance->appearanceDataObject().GradientFactor.GetValue() == Catch::Approx(0.75f)); + REQUIRE(appearance->appearanceDataObject().StepSizePrimaryRay.GetValue() == Catch::Approx(1.5f)); + REQUIRE(appearance->appearanceDataObject().StepSizeSecondaryRay.GetValue() == Catch::Approx(2.0f)); + REQUIRE(appearance->appearanceDataObject().Interpolate.GetValue() == true); + + auto bgColor = appearance->appearanceDataObject().BackgroundColor.GetValue(); + REQUIRE(bgColor.x == Catch::Approx(0.2f)); + REQUIRE(bgColor.y == Catch::Approx(0.3f)); + REQUIRE(bgColor.z == Catch::Approx(0.4f)); + REQUIRE(bgColor.w == Catch::Approx(1.0f)); + + REQUIRE(appearance->appearanceDataObject().ShowBoundingBox.GetValue() == true); + + auto bbColor = appearance->appearanceDataObject().BoundingBoxColor.GetValue(); + REQUIRE(bbColor.x == Catch::Approx(1.0f)); + REQUIRE(bbColor.y == Catch::Approx(0.0f)); + REQUIRE(bbColor.z == Catch::Approx(0.0f)); + REQUIRE(bbColor.w == Catch::Approx(1.0f)); + + REQUIRE(appearance->appearanceDataObject().ShowScaleBar.GetValue() == true); +} + +TEST_CASE("AppearanceObject JSON roundtrip serialization", "[AppearanceObject][serialize]") +{ + std::string jsonPath = "test_appearance.json"; + + // Create and save appearance + { + AppearanceObject* appearance = createTestAppearanceObject(); + + docWriterJson writer; + writer.beginDocument(jsonPath); + appearance->toDocument(&writer); + writer.endDocument(); + + delete appearance; + } + + // Verify file exists + REQUIRE(std::filesystem::exists(jsonPath)); + + // Load and verify appearance + { + AppearanceObject* loadedAppearance = new AppearanceObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + reader.beginObject("appearance0"); + loadedAppearance->fromDocument(&reader); + reader.endObject(); + reader.endDocument(); + + verifyTestAppearanceObject(loadedAppearance); + + delete loadedAppearance; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("AppearanceObject YAML roundtrip serialization", "[AppearanceObject][serialize]") +{ + std::string yamlPath = "test_appearance.yaml"; + + // Create and save appearance + { + AppearanceObject* appearance = createTestAppearanceObject(); + + docWriterYaml writer; + writer.beginDocument(yamlPath); + appearance->toDocument(&writer); + writer.endDocument(); + + delete appearance; + } + + // Verify file exists + REQUIRE(std::filesystem::exists(yamlPath)); + + // Load and verify appearance + { + AppearanceObject* loadedAppearance = new AppearanceObject(); + + docReaderYaml reader; + reader.beginDocument(yamlPath); + reader.beginObject("appearance0"); + loadedAppearance->fromDocument(&reader); + reader.endObject(); + reader.endDocument(); + + verifyTestAppearanceObject(loadedAppearance); + + delete loadedAppearance; + } + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("AppearanceObject version 1 JSON format compatibility", "[AppearanceObject][serialize][version]") +{ + std::string jsonPath = "test_appearance_v1.json"; + + // Manually create a version 1 JSON file to simulate old format + { + std::ofstream file(jsonPath); + file << R"({ + "appearance0": { + "_type": "AppearanceObject", + "_version": 1, + "_name": "appearance0", + "RendererType": 1, + "ShadingType": 1, + "DensityScale": 2.5, + "GradientFactor": 0.75, + "StepSizePrimaryRay": 1.5, + "StepSizeSecondaryRay": 2.0, + "Interpolate": true, + "BackgroundColor": [0.2, 0.3, 0.4, 1.0], + "ShowBoundingBox": true, + "BoundingBoxColor": [1.0, 0.0, 0.0, 1.0], + "ShowScaleBar": true + } +})"; + file.close(); + } + + // Load and verify the version 1 format is correctly read + { + AppearanceObject* loadedAppearance = new AppearanceObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + + reader.beginObject("appearance0"); + + // Check version before reading properties + std::string objectType = reader.peekObjectType(); + REQUIRE(objectType == "AppearanceObject"); + + uint32_t version = reader.peekVersion(); + REQUIRE(version == 1); + + loadedAppearance->fromDocument(&reader); + + reader.endObject(); + + reader.endDocument(); + + verifyTestAppearanceObject(loadedAppearance); + + delete loadedAppearance; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("AppearanceObject version 1 YAML format compatibility", "[AppearanceObject][serialize][version]") +{ + std::string yamlPath = "test_appearance_v1.yaml"; + + // Manually create a version 1 YAML file to simulate old format + { + std::ofstream file(yamlPath); + file << R"(appearance0: + _type: AppearanceObject + _version: 1 + _name: appearance0 + RendererType: 1 + ShadingType: 1 + DensityScale: 2.5 + GradientFactor: 0.75 + StepSizePrimaryRay: 1.5 + StepSizeSecondaryRay: 2.0 + Interpolate: true + BackgroundColor: [0.2, 0.3, 0.4, 1.0] + ShowBoundingBox: true + BoundingBoxColor: [1.0, 0.0, 0.0, 1.0] + ShowScaleBar: true +)"; + file.close(); + } + + // Load and verify the version 1 format is correctly read + { + AppearanceObject* loadedAppearance = new AppearanceObject(); + + docReaderYaml reader; + reader.beginDocument(yamlPath); + + reader.beginObject("appearance0"); + + // Check version before reading properties + std::string objectType = reader.peekObjectType(); + REQUIRE(objectType == "AppearanceObject"); + + uint32_t version = reader.peekVersion(); + REQUIRE(version == 1); + + loadedAppearance->fromDocument(&reader); + + reader.endObject(); + + reader.endDocument(); + + verifyTestAppearanceObject(loadedAppearance); + + delete loadedAppearance; + } + + // Cleanup + std::filesystem::remove(yamlPath); +} + +TEST_CASE("AppearanceObject default values serialization", "[AppearanceObject][serialize]") +{ + std::string jsonPath = "test_appearance_defaults.json"; + + // Create appearance with default values (no modifications) + { + AppearanceObject* appearance = new AppearanceObject(); + + docWriterJson writer; + writer.beginDocument(jsonPath); + appearance->toDocument(&writer); + writer.endDocument(); + + delete appearance; + } + + // Load and verify defaults are preserved + { + AppearanceObject* loadedAppearance = new AppearanceObject(); + AppearanceObject* defaultAppearance = new AppearanceObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + reader.beginObject("appearance0"); + loadedAppearance->fromDocument(&reader); + reader.endObject(); + reader.endDocument(); + + // Verify all values match defaults + REQUIRE(loadedAppearance->appearanceDataObject().RendererType.GetValue() == + defaultAppearance->appearanceDataObject().RendererType.GetValue()); + REQUIRE(loadedAppearance->appearanceDataObject().ShadingType.GetValue() == + defaultAppearance->appearanceDataObject().ShadingType.GetValue()); + REQUIRE(loadedAppearance->appearanceDataObject().DensityScale.GetValue() == + Catch::Approx(defaultAppearance->appearanceDataObject().DensityScale.GetValue())); + REQUIRE(loadedAppearance->appearanceDataObject().GradientFactor.GetValue() == + Catch::Approx(defaultAppearance->appearanceDataObject().GradientFactor.GetValue())); + + delete loadedAppearance; + delete defaultAppearance; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} + +TEST_CASE("AppearanceObject partial data loading", "[AppearanceObject][serialize]") +{ + std::string jsonPath = "test_appearance_partial.json"; + + // Create a JSON file with only some properties + { + std::ofstream file(jsonPath); + file << R"({ + "appearance0": { + "_type": "AppearanceObject", + "_version": 1, + "_name": "appearance0", + "DensityScale": 3.0, + "Interpolate": true + } +})"; + file.close(); + } + + // Load and verify that specified properties are loaded, others remain default + { + AppearanceObject* loadedAppearance = new AppearanceObject(); + AppearanceObject* defaultAppearance = new AppearanceObject(); + + docReaderJson reader; + reader.beginDocument(jsonPath); + reader.beginObject("appearance0"); + loadedAppearance->fromDocument(&reader); + reader.endObject(); + reader.endDocument(); + + // Specified properties should be loaded + REQUIRE(loadedAppearance->appearanceDataObject().DensityScale.GetValue() == Catch::Approx(3.0f)); + REQUIRE(loadedAppearance->appearanceDataObject().Interpolate.GetValue() == true); + + // Other properties should remain at default + REQUIRE(loadedAppearance->appearanceDataObject().RendererType.GetValue() == + defaultAppearance->appearanceDataObject().RendererType.GetValue()); + REQUIRE(loadedAppearance->appearanceDataObject().GradientFactor.GetValue() == + Catch::Approx(defaultAppearance->appearanceDataObject().GradientFactor.GetValue())); + + delete loadedAppearance; + delete defaultAppearance; + } + + // Cleanup + std::filesystem::remove(jsonPath); +} diff --git a/test/test_docReader.cpp b/test/test_docReader.cpp index d151a59b0..c96214290 100644 --- a/test/test_docReader.cpp +++ b/test/test_docReader.cpp @@ -52,7 +52,18 @@ createTestJsonFile(const std::string& filePath) docWriterJson writer; writer.beginDocument(filePath); writer.beginObject("testObject", "MYTYPE", 1); - writer.writeProperties(obj); + + // Write properties directly + for (const auto& propUIInfo : obj->GetList()) { + int numProps = propUIInfo->GetNumberOfProperties(); + for (int i = 0; i < numProps; ++i) { + prtyProperty* prop = propUIInfo->GetProperty(i); + if (prop) { + prop->Write(writer); + } + } + } + writer.endObject(); writer.endDocument(); @@ -68,7 +79,18 @@ createTestYamlFile(const std::string& filePath) docWriterYaml writer; writer.beginDocument(filePath); writer.beginObject("testObject", "MYTYPE", 1); - writer.writeProperties(obj); + + // Write properties directly + for (const auto& propUIInfo : obj->GetList()) { + int numProps = propUIInfo->GetNumberOfProperties(); + for (int i = 0; i < numProps; ++i) { + prtyProperty* prop = propUIInfo->GetProperty(i); + if (prop) { + prop->Write(writer); + } + } + } + writer.endObject(); writer.endDocument(); @@ -180,7 +202,18 @@ TEST_CASE("Read and write roundtrip JSON", "[serialize][docReader]") docWriterJson writer; writer.beginDocument(jsonPath); writer.beginObject("testObject", "MYTYPE", 1); - writer.writeProperties(originalObj); + + // Write properties directly + for (const auto& propUIInfo : originalObj->GetList()) { + int numProps = propUIInfo->GetNumberOfProperties(); + for (int i = 0; i < numProps; ++i) { + prtyProperty* prop = propUIInfo->GetProperty(i); + if (prop) { + prop->Write(writer); + } + } + } + writer.endObject(); writer.endDocument(); @@ -236,7 +269,18 @@ TEST_CASE("Read and write roundtrip YAML", "[serialize][docReader]") docWriterYaml writer; writer.beginDocument(yamlPath); writer.beginObject("testObject", "MYTYPE", 1); - writer.writeProperties(originalObj); + + // Write properties directly + for (const auto& propUIInfo : originalObj->GetList()) { + int numProps = propUIInfo->GetNumberOfProperties(); + for (int i = 0; i < numProps; ++i) { + prtyProperty* prop = propUIInfo->GetProperty(i); + if (prop) { + prop->Write(writer); + } + } + } + writer.endObject(); writer.endDocument(); diff --git a/test/test_docWriter.cpp b/test/test_docWriter.cpp index 3a1053023..c8fe1f6a3 100644 --- a/test/test_docWriter.cpp +++ b/test/test_docWriter.cpp @@ -29,7 +29,16 @@ writePrtyObject(docWriter& writer, prtyObject* obj, const std::string& name) writer.beginObject(name.c_str(), "MYTYPE", 1); - writer.writeProperties(obj); + // Write properties directly + for (const auto& propUIInfo : obj->GetList()) { + int numProps = propUIInfo->GetNumberOfProperties(); + for (int i = 0; i < numProps; ++i) { + prtyProperty* prop = propUIInfo->GetProperty(i); + if (prop) { + prop->Write(writer); + } + } + } writer.endObject(); } From 49020e8587304762513d738c1f7db2d9aec959d1 Mon Sep 17 00:00:00 2001 From: dmt Date: Sun, 14 Dec 2025 20:33:14 -0800 Subject: [PATCH 53/63] cleanup --- agave_app/agaveGui.cpp | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index d81936daf..350b3a8fe 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -735,34 +735,22 @@ agaveGui::writeDocument(std::string filepath) writer->beginList("_camera"); // list of all cameras m_cameraObject->toDocument(writer); - // writer->beginObject("_camera0"); - // writer->writeProperty("_version", (uint32_t)1); // Camera object version - // writer->writeProperties(m_cameraObject.get()); - // writer->endObject(); writer->endList(); writer->beginList("_light"); // list of all lights - writer->beginObject("_skylight0", "SkyLightObject", 1); - writer->writeProperties(m_skyLightObject.get()); - writer->endObject(); - writer->beginObject("_arealight0", "AreaLightObject", 1); - writer->writeProperties(m_areaLightObject.get()); - writer->endObject(); + m_skyLightObject->toDocument(writer); + m_areaLightObject->toDocument(writer); writer->endList(); writer->beginList("_clipPlane"); // list of all clip planes - writer->beginObject("_clipPlane0", "ClipPlaneObject", 1); - // writer.writeProperties(m_clipPlaneObject.get()); - writer->endObject(); + // m_clipPlaneObject->toDocument(writer); writer->endList(); writer->beginList("_renderSettings"); // list of all render settings objects - writer->beginObject("_renderSettings0", "RenderSettingsObject", 1); - writer->writeProperties(m_appearanceObject.get()); - writer->endObject(); + m_appearanceObject->toDocument(writer); writer->endList(); writer->beginList("_captureSettings"); @@ -872,7 +860,7 @@ agaveGui::readDocument(std::string filepath) std::string objType = reader->peekObjectType(); if (objType == "RenderSettingsObject") { AppearanceObject* appObj = new AppearanceObject(); - // appObj->fromDocument(reader); + appObj->fromDocument(reader); reader->endObject(); // install appObj into m_appearanceObject??? m_appearanceObject = std::unique_ptr(appObj); From b3655ae86141632efb56025ae41ae9d998d11ef0 Mon Sep 17 00:00:00 2001 From: Dan Toloudis Date: Mon, 15 Dec 2025 17:19:05 -0800 Subject: [PATCH 54/63] fix enum values --- renderlib/AppearanceDataObject.cpp | 2 -- renderlib/AppearanceDataObject.hpp | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/renderlib/AppearanceDataObject.cpp b/renderlib/AppearanceDataObject.cpp index 82055d9a1..b79c2d4e6 100644 --- a/renderlib/AppearanceDataObject.cpp +++ b/renderlib/AppearanceDataObject.cpp @@ -2,5 +2,3 @@ #include "Enumerations.h" #include "Logging.h" - -AppearanceDataObject::AppearanceDataObject() {} diff --git a/renderlib/AppearanceDataObject.hpp b/renderlib/AppearanceDataObject.hpp index 35774b7fc..05a710a67 100644 --- a/renderlib/AppearanceDataObject.hpp +++ b/renderlib/AppearanceDataObject.hpp @@ -1,6 +1,7 @@ #pragma once #include "core/prty/prtyInt8.hpp" +#include "core/prty/prtyEnum.hpp" #include "core/prty/prtyFloat.hpp" #include "core/prty/prtyBoolean.hpp" #include "core/prty/prtyVector3d.hpp" @@ -10,10 +11,18 @@ class AppearanceDataObject { public: - AppearanceDataObject(); + AppearanceDataObject() + { + RendererType.SetEnumTag(0, "Ray march blending"); + RendererType.SetEnumTag(1, "Path traced"); - prtyInt8 RendererType{ "RendererType", 0 }; - prtyInt8 ShadingType{ "ShadingType", 0 }; + ShadingType.SetEnumTag(0, "BRDF Only"); + ShadingType.SetEnumTag(1, "Phase Function Only"); + ShadingType.SetEnumTag(2, "Mixed"); + } + + prtyEnum RendererType{ "RendererType", 0 }; + prtyEnum ShadingType{ "ShadingType", 0 }; prtyFloat DensityScale{ "DensityScale", 1.0f }; prtyFloat GradientFactor{ "GradientFactor", 0.5f }; prtyFloat StepSizePrimaryRay{ "StepSizePrimaryRay", 1.0f }; From 285ce423c3efd75b4eb6f2736c18e2ce6ac8bf74 Mon Sep 17 00:00:00 2001 From: dmt Date: Wed, 31 Dec 2025 16:25:35 -0800 Subject: [PATCH 55/63] add clip plane as property object --- agave_app/agaveGui.cpp | 30 ++++++--- agave_app/agaveGui.h | 2 + renderlib/CMakeLists.txt | 2 + renderlib/ClipPlaneObject.cpp | 123 ++++++++++++++++++++++++++++++++++ renderlib/ClipPlaneObject.hpp | 67 ++++++++++++++++++ renderlib/ScenePlane.h | 1 + 6 files changed, 215 insertions(+), 10 deletions(-) create mode 100644 renderlib/ClipPlaneObject.cpp create mode 100644 renderlib/ClipPlaneObject.hpp diff --git a/agave_app/agaveGui.cpp b/agave_app/agaveGui.cpp index 350b3a8fe..5ceef7e94 100644 --- a/agave_app/agaveGui.cpp +++ b/agave_app/agaveGui.cpp @@ -15,6 +15,7 @@ #include "renderlib/io/FileReader.h" #include "renderlib/version.hpp" #include "renderlib/CameraObject.hpp" +#include "renderlib/ClipPlaneObject.hpp" #include "renderlib/AppearanceObject.hpp" #include "renderlib/serialize/docWriterJson.h" #include "renderlib/serialize/docReaderJson.h" @@ -88,7 +89,7 @@ QMenu#quickViewsMenu {border-radius: 2px;} agaveGui::agaveGui(QWidget* parent) : QMainWindow(parent) { - // create our two document objects + // create our document objects m_cameraObject = std::make_unique(); m_appearanceObject = std::make_unique(); m_areaLightObject = std::make_unique(); @@ -99,6 +100,9 @@ agaveGui::agaveGui(QWidget* parent) m_skyLightObject->getSceneLight()->m_light = Scene::defaultSkyLight(); m_skyLightObject->setDirtyCallback( [this]() { m_appearanceObject->getRenderSettings()->m_DirtyFlags.SetFlag(LightsDirty); }); + m_clipPlaneObject = std::make_unique(); + m_clipPlaneObject->setDirtyCallback( + [this]() { m_appearanceObject->getRenderSettings()->m_DirtyFlags.SetFlag(RoiDirty); }); m_ui.setupUi(this); @@ -745,7 +749,7 @@ agaveGui::writeDocument(std::string filepath) writer->beginList("_clipPlane"); // list of all clip planes - // m_clipPlaneObject->toDocument(writer); + m_clipPlaneObject->toDocument(writer); writer->endList(); writer->beginList("_renderSettings"); @@ -843,14 +847,20 @@ agaveGui::readDocument(std::string filepath) if (reader->beginList("_clipPlane")) { // list of all clip planes while (reader->beginObject("")) { - std::string objType = reader->peekObjectType(); - if (objType == "ClipPlaneObject") { - // TODO implement clip plane loading - reader->endObject(); - } else { - LOG_WARNING << "Unknown clip plane object type: " << objType; - reader->endObject(); - } + ClipPlaneObject* clipPlaneObj = new ClipPlaneObject(); + clipPlaneObj->fromDocument(reader); + reader->endObject(); + + clipPlaneObj->updateObjectFromProps(); + + // std::string objType = reader->peekObjectType(); + // if (objType == "ClipPlaneObject") { + // // TODO implement clip plane loading + // reader->endObject(); + // } else { + // LOG_WARNING << "Unknown clip plane object type: " << objType; + // reader->endObject(); + // } } reader->endList(); } diff --git a/agave_app/agaveGui.h b/agave_app/agaveGui.h index 32e5a35d6..b29e8d835 100644 --- a/agave_app/agaveGui.h +++ b/agave_app/agaveGui.h @@ -23,6 +23,7 @@ class QTimelineDockWidget; class AreaLightObject; class CameraObject; +class ClipPlaneObject; class SkyLightObject; class IFileReader; class ViewToolbar; @@ -202,6 +203,7 @@ private slots: std::unique_ptr m_cameraObject; std::unique_ptr m_areaLightObject; std::unique_ptr m_skyLightObject; + std::unique_ptr m_clipPlaneObject; std::string m_currentFilePath; // TODO remove the above m_currentFilePath and use this instead diff --git a/renderlib/CMakeLists.txt b/renderlib/CMakeLists.txt index bc770b2f7..07e3f1aad 100644 --- a/renderlib/CMakeLists.txt +++ b/renderlib/CMakeLists.txt @@ -36,6 +36,8 @@ target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/CameraDataObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneObject.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.h" "${CMAKE_CURRENT_SOURCE_DIR}/Colormap.cpp" diff --git a/renderlib/ClipPlaneObject.cpp b/renderlib/ClipPlaneObject.cpp new file mode 100644 index 000000000..e1b80c6db --- /dev/null +++ b/renderlib/ClipPlaneObject.cpp @@ -0,0 +1,123 @@ +#include "ClipPlaneObject.hpp" + +#include "ScenePlane.h" +#include "MathUtil.h" +#include "Logging.h" + +#include "serialize/docReader.h" +#include "serialize/docWriter.h" + +ClipPlaneObject::ClipPlaneObject() + : prtyObject() +{ + m_scenePlane = std::make_shared(glm::vec3(0.0f, 0.0f, 0.0f)); + + m_enabledUIInfo = new CheckBoxUiInfo(&m_clipPlaneDataObject.Enabled, "General", "Enabled"); + m_enabledUIInfo->SetToolTip("Enable or disable the clip plane"); + m_enabledUIInfo->SetStatusTip("Enable or disable the clip plane in the scene"); + AddProperty(m_enabledUIInfo); + m_showHelperUIInfo = new CheckBoxUiInfo(&m_clipPlaneDataObject.ShowHelper, "General", "Show Helper"); + m_showHelperUIInfo->SetToolTip("Show or hide the clip plane helper"); + m_showHelperUIInfo->SetStatusTip("Show or hide the clip plane helper in the scene"); + AddProperty(m_showHelperUIInfo); + + // Add callbacks for property changes + m_clipPlaneDataObject.Enabled.AddCallback( + new prtyCallbackWrapper(this, &ClipPlaneObject::EnabledChanged)); + m_clipPlaneDataObject.ShowHelper.AddCallback( + new prtyCallbackWrapper(this, &ClipPlaneObject::ShowHelperChanged)); + m_clipPlaneDataObject.Position.AddCallback( + new prtyCallbackWrapper(this, &ClipPlaneObject::PositionChanged)); + m_clipPlaneDataObject.Rotation.AddCallback( + new prtyCallbackWrapper(this, &ClipPlaneObject::RotationChanged)); +} + +void +ClipPlaneObject::updatePropsFromObject() +{ + if (!m_scenePlane) + return; + + const Plane& plane = m_scenePlane->m_plane; + + m_clipPlaneDataObject.Enabled.SetValue(m_scenePlane->m_enabled); + m_clipPlaneDataObject.ShowHelper.SetValue(m_scenePlane->getVisible()); + m_clipPlaneDataObject.Position.SetValue(m_scenePlane->m_transform.m_center); + m_clipPlaneDataObject.Rotation.SetValue(m_scenePlane->m_transform.m_rotation); +} + +void +ClipPlaneObject::updateObjectFromProps() +{ + if (!m_scenePlane) + return; + + Plane& plane = m_scenePlane->m_plane; + + // update plane + m_scenePlane->m_enabled = m_clipPlaneDataObject.Enabled.GetValue(); + m_scenePlane->setVisible(m_clipPlaneDataObject.ShowHelper.GetValue()); + m_scenePlane->m_transform.m_center = m_clipPlaneDataObject.Position.GetValue(); + m_scenePlane->m_transform.m_rotation = m_clipPlaneDataObject.Rotation.GetValue(); + m_scenePlane->updateTransform(); + + // Notify observers + for (auto& observer : m_scenePlane->m_observers) { + observer(plane); + } + + // Call dirty callback if set + if (m_dirtyCallback) { + m_dirtyCallback(); + } +} + +void +ClipPlaneObject::EnabledChanged(prtyProperty* i_Property, bool i_bDirty) +{ + updateObjectFromProps(); +} + +void +ClipPlaneObject::ShowHelperChanged(prtyProperty* i_Property, bool i_bDirty) +{ + updateObjectFromProps(); +} + +void +ClipPlaneObject::PositionChanged(prtyProperty* i_Property, bool i_bDirty) +{ + updateObjectFromProps(); +} + +void +ClipPlaneObject::RotationChanged(prtyProperty* i_Property, bool i_bDirty) +{ + updateObjectFromProps(); +} + +void +ClipPlaneObject::fromDocument(docReader* reader) +{ + // Peek at metadata + uint32_t version = reader->peekVersion(); + std::string type = reader->peekObjectType(); + std::string name = reader->peekObjectName(); + + reader->readPrty(&m_clipPlaneDataObject.Enabled); + reader->readPrty(&m_clipPlaneDataObject.ShowHelper); + reader->readPrty(&m_clipPlaneDataObject.Position); + reader->readPrty(&m_clipPlaneDataObject.Rotation); +} + +void +ClipPlaneObject::toDocument(docWriter* writer) +{ + writer->beginObject("clipPlane0", "ClipPlaneObject", ClipPlaneObject::CURRENT_VERSION); + + m_clipPlaneDataObject.Enabled.Write(*writer); + m_clipPlaneDataObject.ShowHelper.Write(*writer); + m_clipPlaneDataObject.Position.Write(*writer); + m_clipPlaneDataObject.Rotation.Write(*writer); + writer->endObject(); +} diff --git a/renderlib/ClipPlaneObject.hpp b/renderlib/ClipPlaneObject.hpp new file mode 100644 index 000000000..4b13cc147 --- /dev/null +++ b/renderlib/ClipPlaneObject.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "core/prty/prtyObject.hpp" +#include "core/prty/prtyBoolean.hpp" +#include "core/prty/prtyRotation.hpp" +#include "core/prty/prtyVector3d.hpp" +#include "uiInfo.hpp" + +#include +#include + +class ScenePlane; + +// Data object for arealight properties +class ClipPlaneDataObject +{ +public: + ClipPlaneDataObject() = default; + + prtyBoolean Enabled{ "Enabled", true }; + prtyBoolean ShowHelper{ "ShowHelper", true }; + prtyVector3d Position{ "Position", glm::vec3(0.0f, 0.0f, 0.0f) }; + prtyRotation Rotation{ "Rotation", glm::vec3(0.0f, 0.0f, 0.0f) }; +}; + +class ClipPlaneObject : public prtyObject +{ +public: + ClipPlaneObject(); + + std::shared_ptr getScenePlane() const { return m_scenePlane; } + + // Update properties from scene plane instance + void updatePropsFromObject(); + + // Update scene plane instance from properties (called automatically via callbacks) + void updateObjectFromProps(); + + // Getter for data object + ClipPlaneDataObject& getDataObject() { return m_clipPlaneDataObject; } + const ClipPlaneDataObject& getDataObject() const { return m_clipPlaneDataObject; } + + // Property change callbacks + void EnabledChanged(prtyProperty* i_Property, bool i_bDirty); + void ShowHelperChanged(prtyProperty* i_Property, bool i_bDirty); + void PositionChanged(prtyProperty* i_Property, bool i_bDirty); + void RotationChanged(prtyProperty* i_Property, bool i_bDirty); + + void setDirtyCallback(std::function callback) { m_dirtyCallback = callback; } + + // document reading and writing; TODO consider an abstract base class to enforce commonality + static constexpr uint32_t CURRENT_VERSION = 1; + void fromDocument(docReader* reader); + void toDocument(docWriter* writer); + +private: + ClipPlaneDataObject m_clipPlaneDataObject; + + // the actual object + std::shared_ptr m_scenePlane; + + std::function m_dirtyCallback; + + // UI Info objects + CheckBoxUiInfo* m_enabledUIInfo; + CheckBoxUiInfo* m_showHelperUIInfo; +}; diff --git a/renderlib/ScenePlane.h b/renderlib/ScenePlane.h index 7fabbf6cb..edf83076d 100644 --- a/renderlib/ScenePlane.h +++ b/renderlib/ScenePlane.h @@ -29,4 +29,5 @@ class ScenePlane : public SceneObject // should the tool be showing? void setVisible(bool v); + bool getVisible() const { return m_tool->m_visible; } }; From 6a130cad88c8d27972eaaf347171167389d021ab Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 2 Jan 2026 11:58:33 -0800 Subject: [PATCH 56/63] wip, broken, start to move channel settings into prty's --- renderlib/CMakeLists.txt | 2 + renderlib/CameraDataObject.cpp | 2 - renderlib/ChannelSettingsDataObject.cpp | 1 + renderlib/ChannelSettingsDataObject.hpp | 68 +++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 renderlib/ChannelSettingsDataObject.cpp create mode 100644 renderlib/ChannelSettingsDataObject.hpp diff --git a/renderlib/CMakeLists.txt b/renderlib/CMakeLists.txt index 07e3f1aad..b1ac2a237 100644 --- a/renderlib/CMakeLists.txt +++ b/renderlib/CMakeLists.txt @@ -36,6 +36,8 @@ target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/CameraDataObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.cpp" diff --git a/renderlib/CameraDataObject.cpp b/renderlib/CameraDataObject.cpp index 461b8a678..d81aa4932 100644 --- a/renderlib/CameraDataObject.cpp +++ b/renderlib/CameraDataObject.cpp @@ -1,3 +1 @@ #include "CameraDataObject.hpp" - -#include "Logging.h" diff --git a/renderlib/ChannelSettingsDataObject.cpp b/renderlib/ChannelSettingsDataObject.cpp new file mode 100644 index 000000000..0fde0daf4 --- /dev/null +++ b/renderlib/ChannelSettingsDataObject.cpp @@ -0,0 +1 @@ +#include "ChannelSettingsDataObject.hpp" diff --git a/renderlib/ChannelSettingsDataObject.hpp b/renderlib/ChannelSettingsDataObject.hpp new file mode 100644 index 000000000..68ceaa9fa --- /dev/null +++ b/renderlib/ChannelSettingsDataObject.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "core/prty/prtyFloat.hpp" +#include "core/prty/prtyEnum.hpp" +#include "core/prty/prtyBoolean.hpp" +#include "core/prty/prtyVector3d.hpp" + +class GradientDataObject +{ +public: + GradientDataObject() = default; + + // Gradient related properties can be added here + prtyEnum ActiveMode; + + prtyFloat Window{ "Window", 0.25f }; + prtyFloat Level{ "Level", 0.5f }; + prtyFloat Isovalue{ "Isovalue", 0.5f }; + prtyFloat Isorange{ "Isorange", 0.1f }; + prtyFloat PctLow{ "PctLow", 0.5f }; + prtyFloat PctHigh{ "PctHigh", 0.98f }; + prtyUint16 MaxU16{ "MaxU16", 65535 }; + prtyUint16 MinU16{ "MinU16", 0 }; + prty std::vector m_customControlPoints = { { 0.0f, 0.0f }, { 1.0f, 1.0f } }; +}; + +class ChannelSettingsDataObject +{ +public: + ChannelSettingsDataObject() + { + ExposureIterations.SetEnumTag(0, "1"); + ExposureIterations.SetEnumTag(1, "2"); + ExposureIterations.SetEnumTag(2, "4"); + ExposureIterations.SetEnumTag(3, "8"); + + ProjectionMode.SetEnumTag(0, "Perspective"); + ProjectionMode.SetEnumTag(1, "Orthographic"); + } + + prtyFloat Exposure{ "Exposure", 0.75f }; + prtyEnum ExposureIterations{ "ExposureIterations", 0 }; + prtyBoolean NoiseReduction{ "NoiseReduction", false }; + prtyFloat ApertureSize{ "ApertureSize", 0.0f }; + prtyFloat FieldOfView{ "FieldOfView", 30.0f }; // degrees + prtyFloat FocalDistance{ "FocalDistance", 0.0f }; + + prtyVector3d Position{ "Position", glm::vec3(0.0f, 0.0f, 0.0f) }; + prtyVector3d Target{ "Target", glm::vec3(0.0f, 0.0f, -1.0f) }; + prtyFloat NearPlane{ "NearPlane", 0.1f }; + prtyFloat FarPlane{ "FarPlane", 1000.0f }; + prtyFloat Roll{ "Roll", 0.0f }; // tilt angle in degrees + + prtyFloat OrthoScale{ "OrthoScale", 1.0f }; // orthographic scale for orthographic projection + prtyEnum ProjectionMode{ "ProjectionMode", 0 }; // 0 = perspective, 1 = orthographic + + prtyColor DiffuseColor{ "DiffuseColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; + prtyColor SpecularColor{ "SpecularColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; + prtyColor EmissiveColor{ "EmissiveColor", glm::vec4(0.0f, 0.0f, 0.0f, 1.0f) }; + prtyFloat Roughness{ "Roughness", 0.5f }; + prtyFloat Opacity{ "Opacity", 1.0f }; + prtyBoolean Enabled{ "Enabled", true }; + prtyFloat Labels{ "Labels", 0.0f }; + + prtyColorMap Colormap{ "Colormap", ColorRamp() }; + + GradientData m_gradientData[MAX_CPU_CHANNELS]; +}; From 31ef6888c3c961358e345e566c5fd33c50887e0a Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 2 Jan 2026 12:31:45 -0800 Subject: [PATCH 57/63] templatize the int prty types --- agave_app/qtControls/controlFactory.h | 3 +- renderlib/AppearanceDataObject.hpp | 2 +- renderlib/CMakeLists.txt | 4 +- renderlib/core/prty/CMakeLists.txt | 9 +- renderlib/core/prty/prtyEnum.hpp | 2 +- renderlib/core/prty/prtyInt32.cpp | 5 +- renderlib/core/prty/prtyInt8.cpp | 5 +- renderlib/core/prty/prtyIntegerTemplate.hpp | 148 ++++++++++++++++ renderlib/serialize/docReader.h | 41 +++++ renderlib/serialize/docReaderJson.cpp | 130 ++++++++++++++ renderlib/serialize/docReaderJson.h | 5 + renderlib/serialize/docReaderYaml.cpp | 180 ++++++++++++++++++++ renderlib/serialize/docReaderYaml.h | 7 + renderlib/serialize/docWriter.h | 41 +++++ renderlib/serialize/docWriterJson.cpp | 85 +++++++++ renderlib/serialize/docWriterJson.h | 5 + renderlib/serialize/docWriterYaml.cpp | 115 +++++++++++++ renderlib/serialize/docWriterYaml.h | 5 + test/test_docReader.cpp | 3 +- test/test_docWriter.cpp | 3 +- test/test_prty.cpp | 2 +- 21 files changed, 779 insertions(+), 21 deletions(-) create mode 100644 renderlib/core/prty/prtyIntegerTemplate.hpp diff --git a/agave_app/qtControls/controlFactory.h b/agave_app/qtControls/controlFactory.h index b512bc3b2..19a713e45 100644 --- a/agave_app/qtControls/controlFactory.h +++ b/agave_app/qtControls/controlFactory.h @@ -5,8 +5,7 @@ #include "renderlib/core/prty/prtyBoolean.hpp" #include "renderlib/core/prty/prtyEnum.hpp" #include "renderlib/core/prty/prtyFloat.hpp" -#include "renderlib/core/prty/prtyInt32.hpp" -#include "renderlib/core/prty/prtyInt8.hpp" +#include "renderlib/core/prty/prtyIntegerTemplate.hpp" #include "renderlib/core/prty/prtyObject.hpp" #include "renderlib/glm.h" diff --git a/renderlib/AppearanceDataObject.hpp b/renderlib/AppearanceDataObject.hpp index 05a710a67..543ebc4f9 100644 --- a/renderlib/AppearanceDataObject.hpp +++ b/renderlib/AppearanceDataObject.hpp @@ -1,6 +1,6 @@ #pragma once -#include "core/prty/prtyInt8.hpp" +#include "core/prty/prtyIntegerTemplate.hpp" #include "core/prty/prtyEnum.hpp" #include "core/prty/prtyFloat.hpp" #include "core/prty/prtyBoolean.hpp" diff --git a/renderlib/CMakeLists.txt b/renderlib/CMakeLists.txt index b1ac2a237..a1a2c7101 100644 --- a/renderlib/CMakeLists.txt +++ b/renderlib/CMakeLists.txt @@ -36,8 +36,8 @@ target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/CameraDataObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.hpp" + # "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.cpp" + # "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.cpp" diff --git a/renderlib/core/prty/CMakeLists.txt b/renderlib/core/prty/CMakeLists.txt index 0447f8f85..99954360f 100644 --- a/renderlib/core/prty/CMakeLists.txt +++ b/renderlib/core/prty/CMakeLists.txt @@ -13,10 +13,11 @@ target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/prtyEnum.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyFloat.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyFloat.hpp" -"${CMAKE_CURRENT_SOURCE_DIR}/prtyInt32.cpp" -"${CMAKE_CURRENT_SOURCE_DIR}/prtyInt32.hpp" -"${CMAKE_CURRENT_SOURCE_DIR}/prtyInt8.cpp" -"${CMAKE_CURRENT_SOURCE_DIR}/prtyInt8.hpp" +# "${CMAKE_CURRENT_SOURCE_DIR}/prtyInt32.cpp" +# "${CMAKE_CURRENT_SOURCE_DIR}/prtyInt32.hpp" +# "${CMAKE_CURRENT_SOURCE_DIR}/prtyInt8.cpp" +# "${CMAKE_CURRENT_SOURCE_DIR}/prtyInt8.hpp" +"${CMAKE_CURRENT_SOURCE_DIR}/prtyIntegerTemplate.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyInterest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyInterest.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyInterestUtil.cpp" diff --git a/renderlib/core/prty/prtyEnum.hpp b/renderlib/core/prty/prtyEnum.hpp index 48a821e5e..efae24180 100644 --- a/renderlib/core/prty/prtyEnum.hpp +++ b/renderlib/core/prty/prtyEnum.hpp @@ -1,6 +1,6 @@ #pragma once -#include "core/prty/prtyInt8.hpp" +#include "core/prty/prtyIntegerTemplate.hpp" #include diff --git a/renderlib/core/prty/prtyInt32.cpp b/renderlib/core/prty/prtyInt32.cpp index 9301a29ed..d4cd61a6a 100644 --- a/renderlib/core/prty/prtyInt32.cpp +++ b/renderlib/core/prty/prtyInt32.cpp @@ -128,8 +128,7 @@ prtyInt32::operator<=(const prtyInt32& i_Value) const void prtyInt32::Read(docReader& io_Reader) { - int32_t temp; - temp = io_Reader.readInt32(GetPropertyName()); + int32_t temp = io_Reader.readInt(GetPropertyName()); SetValue(temp); } @@ -139,5 +138,5 @@ prtyInt32::Read(docReader& io_Reader) void prtyInt32::Write(docWriter& io_Writer) const { - io_Writer.writeInt32(GetPropertyName(), GetValue()); + io_Writer.writeInt(GetPropertyName(), GetValue()); } diff --git a/renderlib/core/prty/prtyInt8.cpp b/renderlib/core/prty/prtyInt8.cpp index 9e8cc2819..2b7f54667 100644 --- a/renderlib/core/prty/prtyInt8.cpp +++ b/renderlib/core/prty/prtyInt8.cpp @@ -127,8 +127,7 @@ prtyInt8::operator<=(const prtyInt8& i_Value) const void prtyInt8::Read(docReader& io_Reader) { - int8_t temp; - temp = io_Reader.readInt8(GetPropertyName()); + int8_t temp = io_Reader.readInt(GetPropertyName()); SetValue(temp); } @@ -138,5 +137,5 @@ prtyInt8::Read(docReader& io_Reader) void prtyInt8::Write(docWriter& io_Writer) const { - io_Writer.writeInt8(GetPropertyName(), GetValue()); + io_Writer.writeInt(GetPropertyName(), GetValue()); } diff --git a/renderlib/core/prty/prtyIntegerTemplate.hpp b/renderlib/core/prty/prtyIntegerTemplate.hpp new file mode 100644 index 000000000..df69356d3 --- /dev/null +++ b/renderlib/core/prty/prtyIntegerTemplate.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include "core/prty/prtyPropertyTemplate.hpp" +#include "serialize/docReader.h" +#include "serialize/docWriter.h" +#include + +//============================================================================ +// Template class for integer properties (signed and unsigned) +// This eliminates the need for separate prtyInt8, prtyInt16, prtyInt32, etc. +//============================================================================ +template +class prtyIntegerTemplate : public prtyPropertyTemplate +{ + static_assert(std::is_integral_v, "prtyIntegerTemplate only works with integral types"); + +public: + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + prtyIntegerTemplate() + : prtyPropertyTemplate(GetTypeName(), T{}) + { + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + prtyIntegerTemplate(const std::string& i_Name) + : prtyPropertyTemplate(i_Name, T{}) + { + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + prtyIntegerTemplate(const std::string& i_Name, T i_InitialValue) + : prtyPropertyTemplate(i_Name, i_InitialValue) + { + } + + //-------------------------------------------------------------------- + // The type of property it is + //-------------------------------------------------------------------- + virtual const char* GetType() override { return GetTypeName(); } + + //-------------------------------------------------------------------- + // operators + //-------------------------------------------------------------------- + prtyIntegerTemplate& operator=(const prtyIntegerTemplate& i_Property) + { + // copy base data + prtyProperty::operator=(i_Property); + this->SetValue(i_Property.GetValue()); + return *this; + } + + prtyIntegerTemplate& operator=(T i_Value) + { + this->SetValue(i_Value); + return *this; + } + + //-------------------------------------------------------------------- + // comparison operators + //-------------------------------------------------------------------- + bool operator==(const prtyIntegerTemplate& i_Property) const { return (this->m_Value == i_Property.GetValue()); } + + bool operator!=(const prtyIntegerTemplate& i_Property) const { return (this->m_Value != i_Property.GetValue()); } + + bool operator==(T i_Value) const { return (this->m_Value == i_Value); } + + bool operator!=(T i_Value) const { return (this->m_Value != i_Value); } + + //-------------------------------------------------------------------- + // comparison operators + //-------------------------------------------------------------------- + bool operator>(T i_Value) const { return (this->m_Value > i_Value); } + + bool operator>=(T i_Value) const { return (this->m_Value >= i_Value); } + + bool operator<(T i_Value) const { return (this->m_Value < i_Value); } + + bool operator<=(T i_Value) const { return (this->m_Value <= i_Value); } + + bool operator>(const prtyIntegerTemplate& i_Value) const { return (this->m_Value > i_Value.GetValue()); } + + bool operator>=(const prtyIntegerTemplate& i_Value) const { return (this->m_Value >= i_Value.GetValue()); } + + bool operator<(const prtyIntegerTemplate& i_Value) const { return (this->m_Value < i_Value.GetValue()); } + + bool operator<=(const prtyIntegerTemplate& i_Value) const { return (this->m_Value <= i_Value.GetValue()); } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + virtual void Read(docReader& io_Reader) override + { + T temp; + if constexpr (std::is_signed_v) { + temp = io_Reader.readInt(this->GetPropertyName()); + } else { + temp = io_Reader.readUint(this->GetPropertyName()); + } + this->SetValue(temp); + } + + //-------------------------------------------------------------------- + //-------------------------------------------------------------------- + virtual void Write(docWriter& io_Writer) const override + { + if constexpr (std::is_signed_v) { + io_Writer.writeInt(this->GetPropertyName(), this->GetValue()); + } else { + io_Writer.writeUint(this->GetPropertyName(), this->GetValue()); + } + } + +private: + static constexpr const char* GetTypeName() + { + if constexpr (std::is_same_v) { + return "Int8"; + } else if constexpr (std::is_same_v) { + return "Int16"; + } else if constexpr (std::is_same_v || std::is_same_v) { + return "Int32"; + } else if constexpr (std::is_same_v) { + return "Int64"; + } else if constexpr (std::is_same_v) { + return "Uint8"; + } else if constexpr (std::is_same_v) { + return "Uint16"; + } else if constexpr (std::is_same_v) { + return "Uint32"; + } else if constexpr (std::is_same_v) { + return "Uint64"; + } else { + return "Integer"; + } + } +}; + +// Convenient type aliases for common integer types +using prtyInt8 = prtyIntegerTemplate; +using prtyInt16 = prtyIntegerTemplate; +using prtyInt32 = prtyIntegerTemplate; +using prtyInt64 = prtyIntegerTemplate; +using prtyUint8 = prtyIntegerTemplate; +using prtyUint16 = prtyIntegerTemplate; +using prtyUint32 = prtyIntegerTemplate; +using prtyUint64 = prtyIntegerTemplate; diff --git a/renderlib/serialize/docReader.h b/renderlib/serialize/docReader.h index d3e6bd234..68645349a 100644 --- a/renderlib/serialize/docReader.h +++ b/renderlib/serialize/docReader.h @@ -71,11 +71,52 @@ class docReader *value = readProperty(name); } + // Templated integer read method + template + T readInt(const std::string& name) + { + if constexpr (std::is_same_v) { + return readInt8(name); + } else if constexpr (std::is_same_v) { + return readInt16(name); + } else if constexpr (std::is_same_v || std::is_same_v) { + return readInt32(name); + } else if constexpr (std::is_same_v) { + return readInt64(name); + } else { + static_assert(sizeof(T) == 0, "Unsupported signed integer type for readInt"); + return T{}; + } + } + + // Templated unsigned integer read method + template + T readUint(const std::string& name) + { + if constexpr (std::is_same_v) { + return readUint8(name); + } else if constexpr (std::is_same_v) { + return readUint16(name); + } else if constexpr (std::is_same_v) { + return readUint32(name); + } else if constexpr (std::is_same_v) { + return readUint64(name); + } else { + static_assert(sizeof(T) == 0, "Unsupported unsigned integer type for readUint"); + return T{}; + } + } + // All primitive read methods now require a name parameter virtual bool readBool(const std::string& name) = 0; virtual int8_t readInt8(const std::string& name) = 0; + virtual int16_t readInt16(const std::string& name) = 0; virtual int32_t readInt32(const std::string& name) = 0; + virtual int64_t readInt64(const std::string& name) = 0; + virtual uint8_t readUint8(const std::string& name) = 0; + virtual uint16_t readUint16(const std::string& name) = 0; virtual uint32_t readUint32(const std::string& name) = 0; + virtual uint64_t readUint64(const std::string& name) = 0; virtual float readFloat32(const std::string& name) = 0; virtual std::vector readFloat32Array(const std::string& name) = 0; virtual std::vector readInt32Array(const std::string& name) = 0; diff --git a/renderlib/serialize/docReaderJson.cpp b/renderlib/serialize/docReaderJson.cpp index 634bc813c..7f24d023d 100644 --- a/renderlib/serialize/docReaderJson.cpp +++ b/renderlib/serialize/docReaderJson.cpp @@ -313,6 +313,32 @@ docReaderJson::readInt8(const std::string& name) return 0; } +int16_t +docReaderJson::readInt16(const std::string& name) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(name) && (*current)[name].is_number_integer()) { + return (*current)[name].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { + int16_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; +} + int32_t docReaderJson::readInt32(const std::string& name) { @@ -339,6 +365,84 @@ docReaderJson::readInt32(const std::string& name) return 0; } +int64_t +docReaderJson::readInt64(const std::string& name) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(name) && (*current)[name].is_number_integer()) { + return (*current)[name].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { + int64_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; +} + +uint8_t +docReaderJson::readUint8(const std::string& name) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(name) && (*current)[name].is_number_unsigned()) { + return (*current)[name].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { + uint8_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; +} + +uint16_t +docReaderJson::readUint16(const std::string& name) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(name) && (*current)[name].is_number_unsigned()) { + return (*current)[name].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { + uint16_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; +} + uint32_t docReaderJson::readUint32(const std::string& name) { @@ -365,6 +469,32 @@ docReaderJson::readUint32(const std::string& name) return 0; } +uint64_t +docReaderJson::readUint64(const std::string& name) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(name) && (*current)[name].is_number_unsigned()) { + return (*current)[name].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { + uint64_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; +} + float docReaderJson::readFloat32(const std::string& name) { diff --git a/renderlib/serialize/docReaderJson.h b/renderlib/serialize/docReaderJson.h index 76fffabd5..9539ce348 100644 --- a/renderlib/serialize/docReaderJson.h +++ b/renderlib/serialize/docReaderJson.h @@ -40,8 +40,13 @@ class docReaderJson : public docReader // Primitive type reading - all require a name parameter virtual bool readBool(const std::string& name) override; virtual int8_t readInt8(const std::string& name) override; + virtual int16_t readInt16(const std::string& name) override; virtual int32_t readInt32(const std::string& name) override; + virtual int64_t readInt64(const std::string& name) override; + virtual uint8_t readUint8(const std::string& name) override; + virtual uint16_t readUint16(const std::string& name) override; virtual uint32_t readUint32(const std::string& name) override; + virtual uint64_t readUint64(const std::string& name) override; virtual float readFloat32(const std::string& name) override; virtual std::vector readFloat32Array(const std::string& name) override; virtual std::vector readInt32Array(const std::string& name) override; diff --git a/renderlib/serialize/docReaderYaml.cpp b/renderlib/serialize/docReaderYaml.cpp index 054681972..44ba77eef 100644 --- a/renderlib/serialize/docReaderYaml.cpp +++ b/renderlib/serialize/docReaderYaml.cpp @@ -306,6 +306,38 @@ docReaderYaml::readInt8(const std::string& name) return 0; } +int16_t +docReaderYaml::readInt16(const std::string& name) +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return static_cast(stringToInt(obj[name].asString())); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + int16_t value = static_cast(stringToInt(arr[ctx.arrayIndex].asString())); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; +} + int32_t docReaderYaml::readInt32(const std::string& name) { @@ -338,6 +370,102 @@ docReaderYaml::readInt32(const std::string& name) return 0; } +int64_t +docReaderYaml::readInt64(const std::string& name) +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return stringToInt64(obj[name].asString()); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + int64_t value = stringToInt64(arr[ctx.arrayIndex].asString()); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; +} + +uint8_t +docReaderYaml::readUint8(const std::string& name) +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return static_cast(stringToUint(obj[name].asString())); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + uint8_t value = static_cast(stringToUint(arr[ctx.arrayIndex].asString())); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; +} + +uint16_t +docReaderYaml::readUint16(const std::string& name) +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return static_cast(stringToUint(obj[name].asString())); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + uint16_t value = static_cast(stringToUint(arr[ctx.arrayIndex].asString())); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; +} + uint32_t docReaderYaml::readUint32(const std::string& name) { @@ -370,6 +498,38 @@ docReaderYaml::readUint32(const std::string& name) return 0; } +uint64_t +docReaderYaml::readUint64(const std::string& name) +{ + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return static_cast(stringToUint(obj[name].asString())); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + uint64_t value = static_cast(stringToUint(arr[ctx.arrayIndex].asString())); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; +} + float docReaderYaml::readFloat32(const std::string& name) { @@ -735,6 +895,26 @@ docReaderYaml::stringToInt(const std::string& str) } } +int64_t +docReaderYaml::stringToInt64(const std::string& str) +{ + try { + return std::stoll(str); + } catch (...) { + return 0; + } +} + +uint32_t +docReaderYaml::stringToUint(const std::string& str) +{ + try { + return static_cast(std::stoull(str)); + } catch (...) { + return 0; + } +} + float docReaderYaml::stringToFloat(const std::string& str) { diff --git a/renderlib/serialize/docReaderYaml.h b/renderlib/serialize/docReaderYaml.h index 2f7073ae8..33e40f539 100644 --- a/renderlib/serialize/docReaderYaml.h +++ b/renderlib/serialize/docReaderYaml.h @@ -41,8 +41,13 @@ class docReaderYaml : public docReader // Primitive type reading - all require a name parameter virtual bool readBool(const std::string& name) override; virtual int8_t readInt8(const std::string& name) override; + virtual int16_t readInt16(const std::string& name) override; virtual int32_t readInt32(const std::string& name) override; + virtual int64_t readInt64(const std::string& name) override; + virtual uint8_t readUint8(const std::string& name) override; + virtual uint16_t readUint16(const std::string& name) override; virtual uint32_t readUint32(const std::string& name) override; + virtual uint64_t readUint64(const std::string& name) override; virtual float readFloat32(const std::string& name) override; virtual std::vector readFloat32Array(const std::string& name) override; virtual std::vector readInt32Array(const std::string& name) override; @@ -109,6 +114,8 @@ class docReaderYaml : public docReader // Type conversion helpers bool stringToBool(const std::string& str); int stringToInt(const std::string& str); + int64_t stringToInt64(const std::string& str); + uint32_t stringToUint(const std::string& str); float stringToFloat(const std::string& str); void pushContext(YamlValue* val, const std::string& name, ContextType type); diff --git a/renderlib/serialize/docWriter.h b/renderlib/serialize/docWriter.h index 67cb65be5..404dfcf61 100644 --- a/renderlib/serialize/docWriter.h +++ b/renderlib/serialize/docWriter.h @@ -56,11 +56,52 @@ class docWriter } } + // Templated integer write method + template + size_t writeInt(const std::string& name, T value) + { + if constexpr (std::is_same_v) { + return writeInt8(name, value); + } else if constexpr (std::is_same_v) { + return writeInt16(name, value); + } else if constexpr (std::is_same_v || std::is_same_v) { + return writeInt32(name, value); + } else if constexpr (std::is_same_v) { + return writeInt64(name, value); + } else { + static_assert(sizeof(T) == 0, "Unsupported signed integer type for writeInt"); + return 0; + } + } + + // Templated unsigned integer write method + template + size_t writeUint(const std::string& name, T value) + { + if constexpr (std::is_same_v) { + return writeUint8(name, value); + } else if constexpr (std::is_same_v) { + return writeUint16(name, value); + } else if constexpr (std::is_same_v) { + return writeUint32(name, value); + } else if constexpr (std::is_same_v) { + return writeUint64(name, value); + } else { + static_assert(sizeof(T) == 0, "Unsupported unsigned integer type for writeUint"); + return 0; + } + } + // All primitive write methods now require a name parameter virtual size_t writeBool(const std::string& name, bool value) = 0; virtual size_t writeInt8(const std::string& name, int8_t value) = 0; + virtual size_t writeInt16(const std::string& name, int16_t value) = 0; virtual size_t writeInt32(const std::string& name, int32_t value) = 0; + virtual size_t writeInt64(const std::string& name, int64_t value) = 0; + virtual size_t writeUint8(const std::string& name, uint8_t value) = 0; + virtual size_t writeUint16(const std::string& name, uint16_t value) = 0; virtual size_t writeUint32(const std::string& name, uint32_t value) = 0; + virtual size_t writeUint64(const std::string& name, uint64_t value) = 0; virtual size_t writeFloat32(const std::string& name, float value) = 0; virtual size_t writeFloat32Array(const std::string& name, const std::vector& value) = 0; virtual size_t writeFloat32Array(const std::string& name, size_t count, const float* values) = 0; diff --git a/renderlib/serialize/docWriterJson.cpp b/renderlib/serialize/docWriterJson.cpp index bdcbed5e4..5b6bac479 100644 --- a/renderlib/serialize/docWriterJson.cpp +++ b/renderlib/serialize/docWriterJson.cpp @@ -185,6 +185,23 @@ docWriterJson::writeInt8(const std::string& name, int8_t value) return sizeof(int8_t); } +size_t +docWriterJson::writeInt16(const std::string& name, int16_t value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[name] = value; + } else { + current->push_back(value); + } + + return sizeof(int16_t); +} + size_t docWriterJson::writeInt32(const std::string& name, int32_t value) { @@ -202,6 +219,57 @@ docWriterJson::writeInt32(const std::string& name, int32_t value) return sizeof(int32_t); } +size_t +docWriterJson::writeInt64(const std::string& name, int64_t value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[name] = value; + } else { + current->push_back(value); + } + + return sizeof(int64_t); +} + +size_t +docWriterJson::writeUint8(const std::string& name, uint8_t value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[name] = value; + } else { + current->push_back(value); + } + + return sizeof(uint8_t); +} + +size_t +docWriterJson::writeUint16(const std::string& name, uint16_t value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[name] = value; + } else { + current->push_back(value); + } + + return sizeof(uint16_t); +} + size_t docWriterJson::writeUint32(const std::string& name, uint32_t value) { @@ -219,6 +287,23 @@ docWriterJson::writeUint32(const std::string& name, uint32_t value) return sizeof(uint32_t); } +size_t +docWriterJson::writeUint64(const std::string& name, uint64_t value) +{ + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[name] = value; + } else { + current->push_back(value); + } + + return sizeof(uint64_t); +} + size_t docWriterJson::writeFloat32(const std::string& name, float value) { diff --git a/renderlib/serialize/docWriterJson.h b/renderlib/serialize/docWriterJson.h index 998579a48..3234b886e 100644 --- a/renderlib/serialize/docWriterJson.h +++ b/renderlib/serialize/docWriterJson.h @@ -32,8 +32,13 @@ class docWriterJson : public docWriter // Primitive type writing - all require a name parameter virtual size_t writeBool(const std::string& name, bool value) override; virtual size_t writeInt8(const std::string& name, int8_t value) override; + virtual size_t writeInt16(const std::string& name, int16_t value) override; virtual size_t writeInt32(const std::string& name, int32_t value) override; + virtual size_t writeInt64(const std::string& name, int64_t value) override; + virtual size_t writeUint8(const std::string& name, uint8_t value) override; + virtual size_t writeUint16(const std::string& name, uint16_t value) override; virtual size_t writeUint32(const std::string& name, uint32_t value) override; + virtual size_t writeUint64(const std::string& name, uint64_t value) override; virtual size_t writeFloat32(const std::string& name, float value) override; virtual size_t writeFloat32Array(const std::string& name, const std::vector& value) override; virtual size_t writeFloat32Array(const std::string& name, size_t count, const float* values) override; diff --git a/renderlib/serialize/docWriterYaml.cpp b/renderlib/serialize/docWriterYaml.cpp index 7a0a17ad7..22c473f43 100644 --- a/renderlib/serialize/docWriterYaml.cpp +++ b/renderlib/serialize/docWriterYaml.cpp @@ -216,6 +216,29 @@ docWriterYaml::writeInt8(const std::string& name, int8_t value) return sizeof(int8_t); } +size_t +docWriterYaml::writeInt16(const std::string& name, int16_t value) +{ + if (m_contextStack.empty()) { + writeKey(name); + m_output << value << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << value << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(name); + m_output << value << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(int16_t); +} + size_t docWriterYaml::writeInt32(const std::string& name, int32_t value) { @@ -239,6 +262,75 @@ docWriterYaml::writeInt32(const std::string& name, int32_t value) return sizeof(int32_t); } +size_t +docWriterYaml::writeInt64(const std::string& name, int64_t value) +{ + if (m_contextStack.empty()) { + writeKey(name); + m_output << value << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << value << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(name); + m_output << value << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(int64_t); +} + +size_t +docWriterYaml::writeUint8(const std::string& name, uint8_t value) +{ + if (m_contextStack.empty()) { + writeKey(name); + m_output << static_cast(value) << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << static_cast(value) << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(name); + m_output << static_cast(value) << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(uint8_t); +} + +size_t +docWriterYaml::writeUint16(const std::string& name, uint16_t value) +{ + if (m_contextStack.empty()) { + writeKey(name); + m_output << value << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << value << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(name); + m_output << value << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(uint16_t); +} + size_t docWriterYaml::writeUint32(const std::string& name, uint32_t value) { @@ -262,6 +354,29 @@ docWriterYaml::writeUint32(const std::string& name, uint32_t value) return sizeof(uint32_t); } +size_t +docWriterYaml::writeUint64(const std::string& name, uint64_t value) +{ + if (m_contextStack.empty()) { + writeKey(name); + m_output << value << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << value << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(name); + m_output << value << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(uint64_t); +} + size_t docWriterYaml::writeFloat32(const std::string& name, float value) { diff --git a/renderlib/serialize/docWriterYaml.h b/renderlib/serialize/docWriterYaml.h index 9a3683b4a..f46ecef8c 100644 --- a/renderlib/serialize/docWriterYaml.h +++ b/renderlib/serialize/docWriterYaml.h @@ -31,8 +31,13 @@ class docWriterYaml : public docWriter // Primitive type writing - all require a name parameter virtual size_t writeBool(const std::string& name, bool value) override; virtual size_t writeInt8(const std::string& name, int8_t value) override; + virtual size_t writeInt16(const std::string& name, int16_t value) override; virtual size_t writeInt32(const std::string& name, int32_t value) override; + virtual size_t writeInt64(const std::string& name, int64_t value) override; + virtual size_t writeUint8(const std::string& name, uint8_t value) override; + virtual size_t writeUint16(const std::string& name, uint16_t value) override; virtual size_t writeUint32(const std::string& name, uint32_t value) override; + virtual size_t writeUint64(const std::string& name, uint64_t value) override; virtual size_t writeFloat32(const std::string& name, float value) override; virtual size_t writeFloat32Array(const std::string& name, const std::vector& value) override; virtual size_t writeFloat32Array(const std::string& name, size_t count, const float* values) override; diff --git a/test/test_docReader.cpp b/test/test_docReader.cpp index c96214290..6eb2ff9a5 100644 --- a/test/test_docReader.cpp +++ b/test/test_docReader.cpp @@ -11,8 +11,7 @@ #include "renderlib/core/prty/prtyObject.hpp" #include "renderlib/core/prty/prtyPropertyUIInfo.hpp" #include "renderlib/core/prty/prtyProperty.hpp" -#include "renderlib/core/prty/prtyInt8.hpp" -#include "renderlib/core/prty/prtyInt32.hpp" +#include "renderlib/core/prty/prtyIntegerTemplate.hpp" #include "renderlib/core/prty/prtyFloat.hpp" #include "renderlib/core/prty/prtyText.hpp" #include "renderlib/core/prty/prtyBoolean.hpp" diff --git a/test/test_docWriter.cpp b/test/test_docWriter.cpp index c8fe1f6a3..0bbab6f07 100644 --- a/test/test_docWriter.cpp +++ b/test/test_docWriter.cpp @@ -7,8 +7,7 @@ #include "renderlib/core/prty/prtyObject.hpp" #include "renderlib/core/prty/prtyPropertyUIInfo.hpp" #include "renderlib/core/prty/prtyProperty.hpp" -#include "renderlib/core/prty/prtyInt8.hpp" -#include "renderlib/core/prty/prtyInt32.hpp" +#include "renderlib/core/prty/prtyIntegerTemplate.hpp" #include "renderlib/core/prty/prtyFloat.hpp" #include "renderlib/core/prty/prtyText.hpp" #include "renderlib/core/prty/prtyBoolean.hpp" diff --git a/test/test_prty.cpp b/test/test_prty.cpp index 3f65cac92..b53f95d5d 100644 --- a/test/test_prty.cpp +++ b/test/test_prty.cpp @@ -3,7 +3,7 @@ #include #include "core/prty/prtyProperty.hpp" -#include "core/prty/prtyInt8.hpp" +#include "core/prty/prtyIntegerTemplate.hpp" #include "core/prty/prtyText.hpp" #include "core/prty/prtyPropertyTemplate.hpp" From ccb9e1f9db7297966cdc61fb9ebd458d1c561ccb Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 2 Jan 2026 12:31:57 -0800 Subject: [PATCH 58/63] DRY refactor --- renderlib/serialize/docReaderJson.cpp | 176 +------------------- renderlib/serialize/docReaderJson.h | 53 ++++++ renderlib/serialize/docReaderYaml.cpp | 224 +------------------------- renderlib/serialize/docReaderYaml.h | 33 ++++ renderlib/serialize/docWriterJson.cpp | 104 +----------- renderlib/serialize/docWriterJson.h | 18 +++ renderlib/serialize/docWriterYaml.cpp | 152 +---------------- renderlib/serialize/docWriterYaml.h | 24 +++ 8 files changed, 160 insertions(+), 624 deletions(-) diff --git a/renderlib/serialize/docReaderJson.cpp b/renderlib/serialize/docReaderJson.cpp index 7f24d023d..c926ba57d 100644 --- a/renderlib/serialize/docReaderJson.cpp +++ b/renderlib/serialize/docReaderJson.cpp @@ -290,209 +290,49 @@ docReaderJson::readBool(const std::string& name) int8_t docReaderJson::readInt8(const std::string& name) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->contains(name) && (*current)[name].is_number_integer()) { - return (*current)[name].get(); - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { - int8_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); - ctx.arrayIndex++; - return value; - } - } - - return 0; + return readSignedIntegerValue(name); } int16_t docReaderJson::readInt16(const std::string& name) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->contains(name) && (*current)[name].is_number_integer()) { - return (*current)[name].get(); - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { - int16_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); - ctx.arrayIndex++; - return value; - } - } - - return 0; + return readSignedIntegerValue(name); } int32_t docReaderJson::readInt32(const std::string& name) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->contains(name) && (*current)[name].is_number_integer()) { - return (*current)[name].get(); - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { - int32_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); - ctx.arrayIndex++; - return value; - } - } - - return 0; + return readSignedIntegerValue(name); } int64_t docReaderJson::readInt64(const std::string& name) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->contains(name) && (*current)[name].is_number_integer()) { - return (*current)[name].get(); - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { - int64_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); - ctx.arrayIndex++; - return value; - } - } - - return 0; + return readSignedIntegerValue(name); } uint8_t docReaderJson::readUint8(const std::string& name) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->contains(name) && (*current)[name].is_number_unsigned()) { - return (*current)[name].get(); - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { - uint8_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); - ctx.arrayIndex++; - return value; - } - } - - return 0; + return readUnsignedIntegerValue(name); } uint16_t docReaderJson::readUint16(const std::string& name) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->contains(name) && (*current)[name].is_number_unsigned()) { - return (*current)[name].get(); - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { - uint16_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); - ctx.arrayIndex++; - return value; - } - } - - return 0; + return readUnsignedIntegerValue(name); } uint32_t docReaderJson::readUint32(const std::string& name) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->contains(name) && (*current)[name].is_number_unsigned()) { - return (*current)[name].get(); - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { - uint32_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); - ctx.arrayIndex++; - return value; - } - } - - return 0; + return readUnsignedIntegerValue(name); } uint64_t docReaderJson::readUint64(const std::string& name) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->contains(name) && (*current)[name].is_number_unsigned()) { - return (*current)[name].get(); - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { - uint64_t value = (*ctx.jsonObj)[ctx.arrayIndex].get(); - ctx.arrayIndex++; - return value; - } - } - - return 0; + return readUnsignedIntegerValue(name); } float diff --git a/renderlib/serialize/docReaderJson.h b/renderlib/serialize/docReaderJson.h index 9539ce348..8356c36b3 100644 --- a/renderlib/serialize/docReaderJson.h +++ b/renderlib/serialize/docReaderJson.h @@ -83,6 +83,59 @@ class docReaderJson : public docReader bool popContext(ContextType expectedType); nlohmann::json* getCurrentObject(); + // Template helpers to reduce duplication in integer reading + template + T readSignedIntegerValue(const std::string& name) + { + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(name) && (*current)[name].is_number_integer()) { + return (*current)[name].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_integer()) { + T value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; + } + + template + T readUnsignedIntegerValue(const std::string& name) + { + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->contains(name) && (*current)[name].is_number_unsigned()) { + return (*current)[name].get(); + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.arrayIndex < ctx.jsonObj->size() && (*ctx.jsonObj)[ctx.arrayIndex].is_number_unsigned()) { + T value = (*ctx.jsonObj)[ctx.arrayIndex].get(); + ctx.arrayIndex++; + return value; + } + } + + return 0; + } + nlohmann::json* m_root; nlohmann::json* m_current; std::stack m_contextStack; diff --git a/renderlib/serialize/docReaderYaml.cpp b/renderlib/serialize/docReaderYaml.cpp index 44ba77eef..89923d42e 100644 --- a/renderlib/serialize/docReaderYaml.cpp +++ b/renderlib/serialize/docReaderYaml.cpp @@ -277,257 +277,49 @@ docReaderYaml::readBool(const std::string& name) int8_t docReaderYaml::readInt8(const std::string& name) { - YamlValue* current = getCurrentValue(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->isObject()) { - YamlObject& obj = current->asObject(); - if (obj.find(name) != obj.end() && obj[name].isString()) { - return static_cast(stringToInt(obj[name].asString())); - } - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.value->isArray()) { - YamlArray& arr = ctx.value->asArray(); - if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { - int8_t value = static_cast(stringToInt(arr[ctx.arrayIndex].asString())); - ctx.arrayIndex++; - return value; - } - } - } - - return 0; + return readIntegerValue(name, [this](const std::string& s) { return stringToInt(s); }); } int16_t docReaderYaml::readInt16(const std::string& name) { - YamlValue* current = getCurrentValue(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->isObject()) { - YamlObject& obj = current->asObject(); - if (obj.find(name) != obj.end() && obj[name].isString()) { - return static_cast(stringToInt(obj[name].asString())); - } - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.value->isArray()) { - YamlArray& arr = ctx.value->asArray(); - if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { - int16_t value = static_cast(stringToInt(arr[ctx.arrayIndex].asString())); - ctx.arrayIndex++; - return value; - } - } - } - - return 0; + return readIntegerValue(name, [this](const std::string& s) { return stringToInt(s); }); } int32_t docReaderYaml::readInt32(const std::string& name) { - YamlValue* current = getCurrentValue(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->isObject()) { - YamlObject& obj = current->asObject(); - if (obj.find(name) != obj.end() && obj[name].isString()) { - return stringToInt(obj[name].asString()); - } - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.value->isArray()) { - YamlArray& arr = ctx.value->asArray(); - if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { - int32_t value = stringToInt(arr[ctx.arrayIndex].asString()); - ctx.arrayIndex++; - return value; - } - } - } - - return 0; + return readIntegerValue(name, [this](const std::string& s) { return stringToInt(s); }); } int64_t docReaderYaml::readInt64(const std::string& name) { - YamlValue* current = getCurrentValue(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->isObject()) { - YamlObject& obj = current->asObject(); - if (obj.find(name) != obj.end() && obj[name].isString()) { - return stringToInt64(obj[name].asString()); - } - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.value->isArray()) { - YamlArray& arr = ctx.value->asArray(); - if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { - int64_t value = stringToInt64(arr[ctx.arrayIndex].asString()); - ctx.arrayIndex++; - return value; - } - } - } - - return 0; + return readIntegerValue(name, [this](const std::string& s) { return stringToInt64(s); }); } uint8_t docReaderYaml::readUint8(const std::string& name) { - YamlValue* current = getCurrentValue(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->isObject()) { - YamlObject& obj = current->asObject(); - if (obj.find(name) != obj.end() && obj[name].isString()) { - return static_cast(stringToUint(obj[name].asString())); - } - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.value->isArray()) { - YamlArray& arr = ctx.value->asArray(); - if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { - uint8_t value = static_cast(stringToUint(arr[ctx.arrayIndex].asString())); - ctx.arrayIndex++; - return value; - } - } - } - - return 0; + return readIntegerValue(name, [this](const std::string& s) { return stringToUint(s); }); } uint16_t docReaderYaml::readUint16(const std::string& name) { - YamlValue* current = getCurrentValue(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->isObject()) { - YamlObject& obj = current->asObject(); - if (obj.find(name) != obj.end() && obj[name].isString()) { - return static_cast(stringToUint(obj[name].asString())); - } - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.value->isArray()) { - YamlArray& arr = ctx.value->asArray(); - if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { - uint16_t value = static_cast(stringToUint(arr[ctx.arrayIndex].asString())); - ctx.arrayIndex++; - return value; - } - } - } - - return 0; + return readIntegerValue(name, [this](const std::string& s) { return stringToUint(s); }); } uint32_t docReaderYaml::readUint32(const std::string& name) { - YamlValue* current = getCurrentValue(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->isObject()) { - YamlObject& obj = current->asObject(); - if (obj.find(name) != obj.end() && obj[name].isString()) { - return static_cast(stringToInt(obj[name].asString())); - } - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.value->isArray()) { - YamlArray& arr = ctx.value->asArray(); - if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { - uint32_t value = static_cast(stringToInt(arr[ctx.arrayIndex].asString())); - ctx.arrayIndex++; - return value; - } - } - } - - return 0; + return readIntegerValue(name, [this](const std::string& s) { return stringToUint(s); }); } uint64_t docReaderYaml::readUint64(const std::string& name) { - YamlValue* current = getCurrentValue(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - // Reading from object by key - if (current->isObject()) { - YamlObject& obj = current->asObject(); - if (obj.find(name) != obj.end() && obj[name].isString()) { - return static_cast(stringToUint(obj[name].asString())); - } - } - } else { - // Reading from array by index - Context& ctx = m_contextStack.top(); - if (ctx.value->isArray()) { - YamlArray& arr = ctx.value->asArray(); - if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { - uint64_t value = static_cast(stringToUint(arr[ctx.arrayIndex].asString())); - ctx.arrayIndex++; - return value; - } - } - } - - return 0; + return readIntegerValue(name, [this](const std::string& s) { return stringToUint(s); }); } float diff --git a/renderlib/serialize/docReaderYaml.h b/renderlib/serialize/docReaderYaml.h index 33e40f539..6ab2dd86b 100644 --- a/renderlib/serialize/docReaderYaml.h +++ b/renderlib/serialize/docReaderYaml.h @@ -122,6 +122,39 @@ class docReaderYaml : public docReader bool popContext(ContextType expectedType); YamlValue* getCurrentValue(); + // Template helper to reduce duplication in integer reading + template + T readIntegerValue(const std::string& name, ConvFunc convFunc) + { + YamlValue* current = getCurrentValue(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + // Reading from object by key + if (current->isObject()) { + YamlObject& obj = current->asObject(); + if (obj.find(name) != obj.end() && obj[name].isString()) { + return static_cast(convFunc(obj[name].asString())); + } + } + } else { + // Reading from array by index + Context& ctx = m_contextStack.top(); + if (ctx.value->isArray()) { + YamlArray& arr = ctx.value->asArray(); + if (ctx.arrayIndex < arr.size() && arr[ctx.arrayIndex].isString()) { + T value = static_cast(convFunc(arr[ctx.arrayIndex].asString())); + ctx.arrayIndex++; + return value; + } + } + } + + return 0; + } + YamlValue m_root; YamlValue* m_current; std::stack m_contextStack; diff --git a/renderlib/serialize/docWriterJson.cpp b/renderlib/serialize/docWriterJson.cpp index 5b6bac479..8a7658ef2 100644 --- a/renderlib/serialize/docWriterJson.cpp +++ b/renderlib/serialize/docWriterJson.cpp @@ -171,137 +171,49 @@ docWriterJson::writeBool(const std::string& name, bool value) size_t docWriterJson::writeInt8(const std::string& name, int8_t value) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[name] = value; - } else { - current->push_back(value); - } - - return sizeof(int8_t); + return writeIntegerValue(name, value); } size_t docWriterJson::writeInt16(const std::string& name, int16_t value) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[name] = value; - } else { - current->push_back(value); - } - - return sizeof(int16_t); + return writeIntegerValue(name, value); } size_t docWriterJson::writeInt32(const std::string& name, int32_t value) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[name] = value; - } else { - current->push_back(value); - } - - return sizeof(int32_t); + return writeIntegerValue(name, value); } size_t docWriterJson::writeInt64(const std::string& name, int64_t value) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[name] = value; - } else { - current->push_back(value); - } - - return sizeof(int64_t); + return writeIntegerValue(name, value); } size_t docWriterJson::writeUint8(const std::string& name, uint8_t value) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[name] = value; - } else { - current->push_back(value); - } - - return sizeof(uint8_t); + return writeIntegerValue(name, value); } size_t docWriterJson::writeUint16(const std::string& name, uint16_t value) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[name] = value; - } else { - current->push_back(value); - } - - return sizeof(uint16_t); + return writeIntegerValue(name, value); } size_t docWriterJson::writeUint32(const std::string& name, uint32_t value) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[name] = value; - } else { - current->push_back(value); - } - - return sizeof(uint32_t); + return writeIntegerValue(name, value); } size_t docWriterJson::writeUint64(const std::string& name, uint64_t value) { - nlohmann::json* current = getCurrentObject(); - if (!current) { - return 0; - } - - if (m_contextStack.empty() || !m_contextStack.top().isArray()) { - (*current)[name] = value; - } else { - current->push_back(value); - } - - return sizeof(uint64_t); + return writeIntegerValue(name, value); } size_t diff --git a/renderlib/serialize/docWriterJson.h b/renderlib/serialize/docWriterJson.h index 3234b886e..a1ba98dde 100644 --- a/renderlib/serialize/docWriterJson.h +++ b/renderlib/serialize/docWriterJson.h @@ -79,4 +79,22 @@ class docWriterJson : public docWriter bool popContext(ContextType expectedType); nlohmann::json* getCurrentObject(); void logError(const std::string& message); + + // Template helper to reduce duplication in integer writing + template + size_t writeIntegerValue(const std::string& name, T value) + { + nlohmann::json* current = getCurrentObject(); + if (!current) { + return 0; + } + + if (m_contextStack.empty() || !m_contextStack.top().isArray()) { + (*current)[name] = value; + } else { + current->push_back(value); + } + + return sizeof(T); + } }; diff --git a/renderlib/serialize/docWriterYaml.cpp b/renderlib/serialize/docWriterYaml.cpp index 22c473f43..a171be3e7 100644 --- a/renderlib/serialize/docWriterYaml.cpp +++ b/renderlib/serialize/docWriterYaml.cpp @@ -196,185 +196,49 @@ docWriterYaml::writeBool(const std::string& name, bool value) size_t docWriterYaml::writeInt8(const std::string& name, int8_t value) { - if (m_contextStack.empty()) { - writeKey(name); - m_output << static_cast(value) << "\n"; - } else { - Context& ctx = m_contextStack.top(); - if (ctx.isArray()) { - writeIndent(); - m_output << "- " << static_cast(value) << "\n"; - ctx.firstItem = false; - } else { - writeIndent(); - writeKey(name); - m_output << static_cast(value) << "\n"; - ctx.firstItem = false; - } - } - - return sizeof(int8_t); + return writeIntegerValue(name, static_cast(value)); } size_t docWriterYaml::writeInt16(const std::string& name, int16_t value) { - if (m_contextStack.empty()) { - writeKey(name); - m_output << value << "\n"; - } else { - Context& ctx = m_contextStack.top(); - if (ctx.isArray()) { - writeIndent(); - m_output << "- " << value << "\n"; - ctx.firstItem = false; - } else { - writeIndent(); - writeKey(name); - m_output << value << "\n"; - ctx.firstItem = false; - } - } - - return sizeof(int16_t); + return writeIntegerValue(name, value); } size_t docWriterYaml::writeInt32(const std::string& name, int32_t value) { - if (m_contextStack.empty()) { - writeKey(name); - m_output << value << "\n"; - } else { - Context& ctx = m_contextStack.top(); - if (ctx.isArray()) { - writeIndent(); - m_output << "- " << value << "\n"; - ctx.firstItem = false; - } else { - writeIndent(); - writeKey(name); - m_output << value << "\n"; - ctx.firstItem = false; - } - } - - return sizeof(int32_t); + return writeIntegerValue(name, value); } size_t docWriterYaml::writeInt64(const std::string& name, int64_t value) { - if (m_contextStack.empty()) { - writeKey(name); - m_output << value << "\n"; - } else { - Context& ctx = m_contextStack.top(); - if (ctx.isArray()) { - writeIndent(); - m_output << "- " << value << "\n"; - ctx.firstItem = false; - } else { - writeIndent(); - writeKey(name); - m_output << value << "\n"; - ctx.firstItem = false; - } - } - - return sizeof(int64_t); + return writeIntegerValue(name, value); } size_t docWriterYaml::writeUint8(const std::string& name, uint8_t value) { - if (m_contextStack.empty()) { - writeKey(name); - m_output << static_cast(value) << "\n"; - } else { - Context& ctx = m_contextStack.top(); - if (ctx.isArray()) { - writeIndent(); - m_output << "- " << static_cast(value) << "\n"; - ctx.firstItem = false; - } else { - writeIndent(); - writeKey(name); - m_output << static_cast(value) << "\n"; - ctx.firstItem = false; - } - } - - return sizeof(uint8_t); + return writeIntegerValue(name, static_cast(value)); } size_t docWriterYaml::writeUint16(const std::string& name, uint16_t value) { - if (m_contextStack.empty()) { - writeKey(name); - m_output << value << "\n"; - } else { - Context& ctx = m_contextStack.top(); - if (ctx.isArray()) { - writeIndent(); - m_output << "- " << value << "\n"; - ctx.firstItem = false; - } else { - writeIndent(); - writeKey(name); - m_output << value << "\n"; - ctx.firstItem = false; - } - } - - return sizeof(uint16_t); + return writeIntegerValue(name, value); } size_t docWriterYaml::writeUint32(const std::string& name, uint32_t value) { - if (m_contextStack.empty()) { - writeKey(name); - m_output << value << "\n"; - } else { - Context& ctx = m_contextStack.top(); - if (ctx.isArray()) { - writeIndent(); - m_output << "- " << value << "\n"; - ctx.firstItem = false; - } else { - writeIndent(); - writeKey(name); - m_output << value << "\n"; - ctx.firstItem = false; - } - } - - return sizeof(uint32_t); + return writeIntegerValue(name, value); } size_t docWriterYaml::writeUint64(const std::string& name, uint64_t value) { - if (m_contextStack.empty()) { - writeKey(name); - m_output << value << "\n"; - } else { - Context& ctx = m_contextStack.top(); - if (ctx.isArray()) { - writeIndent(); - m_output << "- " << value << "\n"; - ctx.firstItem = false; - } else { - writeIndent(); - writeKey(name); - m_output << value << "\n"; - ctx.firstItem = false; - } - } - - return sizeof(uint64_t); + return writeIntegerValue(name, value); } size_t diff --git a/renderlib/serialize/docWriterYaml.h b/renderlib/serialize/docWriterYaml.h index f46ecef8c..b105257c5 100644 --- a/renderlib/serialize/docWriterYaml.h +++ b/renderlib/serialize/docWriterYaml.h @@ -80,4 +80,28 @@ class docWriterYaml : public docWriter void writeKey(const std::string& key); std::string escapeString(const std::string& str); void logError(const std::string& message); + + // Template helper to reduce duplication in integer writing + template + size_t writeIntegerValue(const std::string& name, T value) + { + if (m_contextStack.empty()) { + writeKey(name); + m_output << value << "\n"; + } else { + Context& ctx = m_contextStack.top(); + if (ctx.isArray()) { + writeIndent(); + m_output << "- " << value << "\n"; + ctx.firstItem = false; + } else { + writeIndent(); + writeKey(name); + m_output << value << "\n"; + ctx.firstItem = false; + } + } + + return sizeof(T); + } }; From 26e381ac7a0d7ca9c8aa6e2c2f3cd7a27d1de4dc Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 2 Jan 2026 12:32:55 -0800 Subject: [PATCH 59/63] cleanup --- renderlib/core/prty/CMakeLists.txt | 4 - renderlib/core/prty/prtyInt32.cpp | 142 ----------------------------- renderlib/core/prty/prtyInt32.hpp | 60 ------------ renderlib/core/prty/prtyInt8.cpp | 141 ---------------------------- renderlib/core/prty/prtyInt8.hpp | 60 ------------ 5 files changed, 407 deletions(-) delete mode 100644 renderlib/core/prty/prtyInt32.cpp delete mode 100644 renderlib/core/prty/prtyInt32.hpp delete mode 100644 renderlib/core/prty/prtyInt8.cpp delete mode 100644 renderlib/core/prty/prtyInt8.hpp diff --git a/renderlib/core/prty/CMakeLists.txt b/renderlib/core/prty/CMakeLists.txt index 99954360f..694649eca 100644 --- a/renderlib/core/prty/CMakeLists.txt +++ b/renderlib/core/prty/CMakeLists.txt @@ -13,10 +13,6 @@ target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/prtyEnum.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyFloat.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyFloat.hpp" -# "${CMAKE_CURRENT_SOURCE_DIR}/prtyInt32.cpp" -# "${CMAKE_CURRENT_SOURCE_DIR}/prtyInt32.hpp" -# "${CMAKE_CURRENT_SOURCE_DIR}/prtyInt8.cpp" -# "${CMAKE_CURRENT_SOURCE_DIR}/prtyInt8.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyIntegerTemplate.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyInterest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/prtyInterest.hpp" diff --git a/renderlib/core/prty/prtyInt32.cpp b/renderlib/core/prty/prtyInt32.cpp deleted file mode 100644 index d4cd61a6a..000000000 --- a/renderlib/core/prty/prtyInt32.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "core/prty/prtyInt32.hpp" - -// #include "core/ch/chChunkParserUtil.hpp" -// #include "core/ch/chReader.hpp" -#include "serialize/docReader.h" -#include "serialize/docWriter.h" - -//---------------------------------------------------------------------------- -//---------------------------------------------------------------------------- -prtyInt32::prtyInt32() - : prtyPropertyTemplate("Int32", 0) -{ -} - -//---------------------------------------------------------------------------- -//---------------------------------------------------------------------------- -prtyInt32::prtyInt32(const std::string& i_Name) - : prtyPropertyTemplate(i_Name, 0) -{ -} - -//---------------------------------------------------------------------------- -//---------------------------------------------------------------------------- -prtyInt32::prtyInt32(const std::string& i_Name, const int& i_InitialValue) - : prtyPropertyTemplate(i_Name, i_InitialValue) -{ -} - -//-------------------------------------------------------------------- -// The type of property it is -//-------------------------------------------------------------------- -const char* -prtyInt32::GetType() -{ - return "Int32"; -} - -//-------------------------------------------------------------------- -// operators -//-------------------------------------------------------------------- -prtyInt32& -prtyInt32::operator=(const prtyInt32& i_Property) -{ - // copy base data - prtyProperty::operator=(i_Property); - - SetValue(i_Property.GetValue()); - return *this; -} -prtyInt32& -prtyInt32::operator=(const int i_Value) -{ - SetValue(i_Value); - return *this; -} - -//-------------------------------------------------------------------- -// comparison operators -//-------------------------------------------------------------------- -bool -prtyInt32::operator==(const prtyInt32& i_Property) const -{ - return (m_Value == i_Property.GetValue()); -} -bool -prtyInt32::operator!=(const prtyInt32& i_Property) const -{ - return (m_Value != i_Property.GetValue()); -} -bool -prtyInt32::operator==(const int i_Value) const -{ - return (m_Value == i_Value); -} -bool -prtyInt32::operator!=(const int i_Value) const -{ - return (m_Value != i_Value); -} - -//-------------------------------------------------------------------- -// comparison operators -//-------------------------------------------------------------------- -bool -prtyInt32::operator>(const int i_Value) const -{ - return (m_Value > i_Value); -} -bool -prtyInt32::operator>=(const int i_Value) const -{ - return (m_Value >= i_Value); -} -bool -prtyInt32::operator<(const int i_Value) const -{ - return (m_Value < i_Value); -} -bool -prtyInt32::operator<=(const int i_Value) const -{ - return (m_Value <= i_Value); -} -bool -prtyInt32::operator>(const prtyInt32& i_Value) const -{ - return (m_Value > i_Value.GetValue()); -} -bool -prtyInt32::operator>=(const prtyInt32& i_Value) const -{ - return (m_Value >= i_Value.GetValue()); -} -bool -prtyInt32::operator<(const prtyInt32& i_Value) const -{ - return (m_Value < i_Value.GetValue()); -} -bool -prtyInt32::operator<=(const prtyInt32& i_Value) const -{ - return (m_Value <= i_Value.GetValue()); -} - -//-------------------------------------------------------------------- -//-------------------------------------------------------------------- -// virtual -void -prtyInt32::Read(docReader& io_Reader) -{ - int32_t temp = io_Reader.readInt(GetPropertyName()); - SetValue(temp); -} - -//-------------------------------------------------------------------- -//-------------------------------------------------------------------- -// virtual -void -prtyInt32::Write(docWriter& io_Writer) const -{ - io_Writer.writeInt(GetPropertyName(), GetValue()); -} diff --git a/renderlib/core/prty/prtyInt32.hpp b/renderlib/core/prty/prtyInt32.hpp deleted file mode 100644 index f6af709f1..000000000 --- a/renderlib/core/prty/prtyInt32.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "core/prty/prtyPropertyTemplate.hpp" - -//============================================================================ -//============================================================================ -class prtyInt32 : public prtyPropertyTemplate -{ -public: - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - prtyInt32(); - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - prtyInt32(const std::string& i_Name); - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - prtyInt32(const std::string& i_Name, const int& i_InitialValue); - - //-------------------------------------------------------------------- - // The type of property it is - //-------------------------------------------------------------------- - virtual const char* GetType(); - - //-------------------------------------------------------------------- - // operators - //-------------------------------------------------------------------- - prtyInt32& operator=(const prtyInt32& i_Property); - prtyInt32& operator=(const int i_Value); - - //-------------------------------------------------------------------- - // comparison operators - //-------------------------------------------------------------------- - bool operator==(const prtyInt32& i_Property) const; - bool operator!=(const prtyInt32& i_Property) const; - bool operator==(const int i_Value) const; - bool operator!=(const int i_Value) const; - - //-------------------------------------------------------------------- - // comparison operators - //-------------------------------------------------------------------- - bool operator>(const int i_Value) const; - bool operator>=(const int i_Value) const; - bool operator<(const int i_Value) const; - bool operator<=(const int i_Value) const; - bool operator>(const prtyInt32& i_Value) const; - bool operator>=(const prtyInt32& i_Value) const; - bool operator<(const prtyInt32& i_Value) const; - bool operator<=(const prtyInt32& i_Value) const; - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - virtual void Read(docReader& io_Reader); - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - virtual void Write(docWriter& io_Writer) const; -}; diff --git a/renderlib/core/prty/prtyInt8.cpp b/renderlib/core/prty/prtyInt8.cpp deleted file mode 100644 index 2b7f54667..000000000 --- a/renderlib/core/prty/prtyInt8.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "core/prty/prtyInt8.hpp" - -// #include "core/ch/chReader.hpp" -#include "serialize/docReader.h" -#include "serialize/docWriter.h" - -//---------------------------------------------------------------------------- -//---------------------------------------------------------------------------- -prtyInt8::prtyInt8() - : prtyPropertyTemplate("Int8", 0) -{ -} - -//---------------------------------------------------------------------------- -//---------------------------------------------------------------------------- -prtyInt8::prtyInt8(const std::string& i_Name) - : prtyPropertyTemplate(i_Name, 0) -{ -} - -//---------------------------------------------------------------------------- -//---------------------------------------------------------------------------- -prtyInt8::prtyInt8(const std::string& i_Name, int8_t i_InitialValue) - : prtyPropertyTemplate(i_Name, i_InitialValue) -{ -} - -//-------------------------------------------------------------------- -// The type of property it is -//-------------------------------------------------------------------- -const char* -prtyInt8::GetType() -{ - return "Int8"; -} - -//-------------------------------------------------------------------- -// operators -//-------------------------------------------------------------------- -prtyInt8& -prtyInt8::operator=(const prtyInt8& i_Property) -{ - // copy base data - prtyProperty::operator=(i_Property); - - SetValue(i_Property.GetValue()); - return *this; -} -prtyInt8& -prtyInt8::operator=(const int8_t i_Value) -{ - SetValue(i_Value); - return *this; -} - -//-------------------------------------------------------------------- -// comparison operators -//-------------------------------------------------------------------- -bool -prtyInt8::operator==(const prtyInt8& i_Property) const -{ - return (m_Value == i_Property.GetValue()); -} -bool -prtyInt8::operator!=(const prtyInt8& i_Property) const -{ - return (m_Value != i_Property.GetValue()); -} -bool -prtyInt8::operator==(const int8_t i_Value) const -{ - return (m_Value == i_Value); -} -bool -prtyInt8::operator!=(const int8_t i_Value) const -{ - return (m_Value != i_Value); -} - -//-------------------------------------------------------------------- -// comparison operators -//-------------------------------------------------------------------- -bool -prtyInt8::operator>(const int8_t i_Value) const -{ - return (m_Value > i_Value); -} -bool -prtyInt8::operator>=(const int8_t i_Value) const -{ - return (m_Value >= i_Value); -} -bool -prtyInt8::operator<(const int8_t i_Value) const -{ - return (m_Value < i_Value); -} -bool -prtyInt8::operator<=(const int8_t i_Value) const -{ - return (m_Value <= i_Value); -} -bool -prtyInt8::operator>(const prtyInt8& i_Value) const -{ - return (m_Value > i_Value.GetValue()); -} -bool -prtyInt8::operator>=(const prtyInt8& i_Value) const -{ - return (m_Value >= i_Value.GetValue()); -} -bool -prtyInt8::operator<(const prtyInt8& i_Value) const -{ - return (m_Value < i_Value.GetValue()); -} -bool -prtyInt8::operator<=(const prtyInt8& i_Value) const -{ - return (m_Value <= i_Value.GetValue()); -} - -//-------------------------------------------------------------------- -//-------------------------------------------------------------------- -// virtual -void -prtyInt8::Read(docReader& io_Reader) -{ - int8_t temp = io_Reader.readInt(GetPropertyName()); - SetValue(temp); -} - -//-------------------------------------------------------------------- -//-------------------------------------------------------------------- -// virtual -void -prtyInt8::Write(docWriter& io_Writer) const -{ - io_Writer.writeInt(GetPropertyName(), GetValue()); -} diff --git a/renderlib/core/prty/prtyInt8.hpp b/renderlib/core/prty/prtyInt8.hpp deleted file mode 100644 index 6501d0810..000000000 --- a/renderlib/core/prty/prtyInt8.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "core/prty/prtyPropertyTemplate.hpp" - -//============================================================================ -//============================================================================ -class prtyInt8 : public prtyPropertyTemplate -{ -public: - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - prtyInt8(); - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - prtyInt8(const std::string& i_Name); - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - prtyInt8(const std::string& i_Name, int8_t i_InitialValue); - - //-------------------------------------------------------------------- - // The type of property it is - //-------------------------------------------------------------------- - virtual const char* GetType(); - - //-------------------------------------------------------------------- - // operators - //-------------------------------------------------------------------- - prtyInt8& operator=(const prtyInt8& i_Property); - prtyInt8& operator=(const int8_t i_Value); - - //-------------------------------------------------------------------- - // comparison operators - //-------------------------------------------------------------------- - bool operator==(const prtyInt8& i_Property) const; - bool operator!=(const prtyInt8& i_Property) const; - bool operator==(const int8_t i_Value) const; - bool operator!=(const int8_t i_Value) const; - - //-------------------------------------------------------------------- - // comparison operators - //-------------------------------------------------------------------- - bool operator>(const int8_t i_Value) const; - bool operator>=(const int8_t i_Value) const; - bool operator<(const int8_t i_Value) const; - bool operator<=(const int8_t i_Value) const; - bool operator>(const prtyInt8& i_Value) const; - bool operator>=(const prtyInt8& i_Value) const; - bool operator<(const prtyInt8& i_Value) const; - bool operator<=(const prtyInt8& i_Value) const; - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - virtual void Read(docReader& io_Reader); - - //-------------------------------------------------------------------- - //-------------------------------------------------------------------- - virtual void Write(docWriter& io_Writer) const; -}; From 333d9d4447fa750923475063af05d0bbcbb7bbdc Mon Sep 17 00:00:00 2001 From: dmt Date: Fri, 2 Jan 2026 18:57:16 -0800 Subject: [PATCH 60/63] add prty for vector of LutControlPoint --- renderlib/CMakeLists.txt | 4 +- renderlib/ChannelSettingsDataObject.hpp | 60 +++++++++++++------------ renderlib/GradientData.h | 13 ++++++ 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/renderlib/CMakeLists.txt b/renderlib/CMakeLists.txt index a1a2c7101..b1ac2a237 100644 --- a/renderlib/CMakeLists.txt +++ b/renderlib/CMakeLists.txt @@ -36,8 +36,8 @@ target_sources(renderlib PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/CameraDataObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CameraObject.hpp" - # "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.cpp" - # "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ChannelSettingsDataObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneObject.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneObject.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/ClipPlaneTool.cpp" diff --git a/renderlib/ChannelSettingsDataObject.hpp b/renderlib/ChannelSettingsDataObject.hpp index 68ceaa9fa..680766d17 100644 --- a/renderlib/ChannelSettingsDataObject.hpp +++ b/renderlib/ChannelSettingsDataObject.hpp @@ -1,10 +1,38 @@ #pragma once +#include "GradientData.h" +#include "core/prty/prtyColor.hpp" #include "core/prty/prtyFloat.hpp" #include "core/prty/prtyEnum.hpp" #include "core/prty/prtyBoolean.hpp" #include "core/prty/prtyVector3d.hpp" +class prtyControlPointVector + : public prtyPropertyTemplate, const std::vector&> +{ +public: + prtyControlPointVector(const std::string& i_Name) + : prtyPropertyTemplate, const std::vector&>( + i_Name, + std::vector{ { 0.0f, 0.0f }, { 1.0f, 1.0f } }) + { + } + prtyControlPointVector(const std::string& i_Name, const std::vector& i_InitialValue) + : prtyPropertyTemplate, const std::vector&>(i_Name, i_InitialValue) + { + } + + virtual const char* GetType() override { return "ControlPointVector"; } + virtual void Read(docReader& io_Reader) override + { + // Implement reading from a reader if needed + } + virtual void Write(docWriter& io_Writer) const override + { + // Implement writing to a writer if needed + } +}; + class GradientDataObject { public: @@ -21,38 +49,14 @@ class GradientDataObject prtyFloat PctHigh{ "PctHigh", 0.98f }; prtyUint16 MaxU16{ "MaxU16", 65535 }; prtyUint16 MinU16{ "MinU16", 0 }; - prty std::vector m_customControlPoints = { { 0.0f, 0.0f }, { 1.0f, 1.0f } }; + prtyControlPointVector CustomControlPoints{ "CustomControlPoints", + std::vector{ { 0.0f, 0.0f }, { 1.0f, 1.0f } } }; }; class ChannelSettingsDataObject { public: - ChannelSettingsDataObject() - { - ExposureIterations.SetEnumTag(0, "1"); - ExposureIterations.SetEnumTag(1, "2"); - ExposureIterations.SetEnumTag(2, "4"); - ExposureIterations.SetEnumTag(3, "8"); - - ProjectionMode.SetEnumTag(0, "Perspective"); - ProjectionMode.SetEnumTag(1, "Orthographic"); - } - - prtyFloat Exposure{ "Exposure", 0.75f }; - prtyEnum ExposureIterations{ "ExposureIterations", 0 }; - prtyBoolean NoiseReduction{ "NoiseReduction", false }; - prtyFloat ApertureSize{ "ApertureSize", 0.0f }; - prtyFloat FieldOfView{ "FieldOfView", 30.0f }; // degrees - prtyFloat FocalDistance{ "FocalDistance", 0.0f }; - - prtyVector3d Position{ "Position", glm::vec3(0.0f, 0.0f, 0.0f) }; - prtyVector3d Target{ "Target", glm::vec3(0.0f, 0.0f, -1.0f) }; - prtyFloat NearPlane{ "NearPlane", 0.1f }; - prtyFloat FarPlane{ "FarPlane", 1000.0f }; - prtyFloat Roll{ "Roll", 0.0f }; // tilt angle in degrees - - prtyFloat OrthoScale{ "OrthoScale", 1.0f }; // orthographic scale for orthographic projection - prtyEnum ProjectionMode{ "ProjectionMode", 0 }; // 0 = perspective, 1 = orthographic + ChannelSettingsDataObject() {} prtyColor DiffuseColor{ "DiffuseColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; prtyColor SpecularColor{ "SpecularColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f) }; @@ -64,5 +68,5 @@ class ChannelSettingsDataObject prtyColorMap Colormap{ "Colormap", ColorRamp() }; - GradientData m_gradientData[MAX_CPU_CHANNELS]; + GradientDataObject GradientData; }; diff --git a/renderlib/GradientData.h b/renderlib/GradientData.h index f249a8e3c..77ea288ba 100644 --- a/renderlib/GradientData.h +++ b/renderlib/GradientData.h @@ -1,12 +1,25 @@ #pragma once #include +#include #include struct Histogram; using LutControlPoint = std::pair; +// Serialization operator for LutControlPoint vectors +inline std::ostream& +operator<<(std::ostream& os, const std::vector& f) +{ + os << "[ "; + for (const auto& point : f) { + os << "(" << point.first << ", " << point.second << ") "; + } + os << " ]"; + return os; +} + enum class GradientEditMode { WINDOW_LEVEL, From cc1a7f8de202c7be03e89042426633ac787794c1 Mon Sep 17 00:00:00 2001 From: dmt Date: Sat, 3 Jan 2026 21:20:49 -0800 Subject: [PATCH 61/63] wip on colormap prty --- renderlib/ChannelSettingsDataObject.hpp | 39 ++++++++++++++++++++++++- renderlib/Colormap.h | 12 ++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/renderlib/ChannelSettingsDataObject.hpp b/renderlib/ChannelSettingsDataObject.hpp index 680766d17..48403608a 100644 --- a/renderlib/ChannelSettingsDataObject.hpp +++ b/renderlib/ChannelSettingsDataObject.hpp @@ -1,11 +1,13 @@ #pragma once +#include "Colormap.h" #include "GradientData.h" #include "core/prty/prtyColor.hpp" #include "core/prty/prtyFloat.hpp" #include "core/prty/prtyEnum.hpp" #include "core/prty/prtyBoolean.hpp" #include "core/prty/prtyVector3d.hpp" +#include "core/prty/prtyText.hpp" class prtyControlPointVector : public prtyPropertyTemplate, const std::vector&> @@ -33,6 +35,33 @@ class prtyControlPointVector } }; +class prtyColorControlPointVector + : public prtyPropertyTemplate, const std::vector&> +{ +public: + prtyColorControlPointVector(const std::string& i_Name) + : prtyPropertyTemplate, const std::vector&>( + i_Name, + std::vector{ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f } }) + { + } + prtyColorControlPointVector(const std::string& i_Name, const std::vector& i_InitialValue) + : prtyPropertyTemplate, const std::vector&>(i_Name, + i_InitialValue) + { + } + + virtual const char* GetType() override { return "ColorControlPointVector"; } + virtual void Read(docReader& io_Reader) override + { + // Implement reading from a reader if needed + } + virtual void Write(docWriter& io_Writer) const override + { + // Implement writing to a writer if needed + } +}; + class GradientDataObject { public: @@ -66,7 +95,15 @@ class ChannelSettingsDataObject prtyBoolean Enabled{ "Enabled", true }; prtyFloat Labels{ "Labels", 0.0f }; - prtyColorMap Colormap{ "Colormap", ColorRamp() }; + // a colormap has a name and a set of color control points + // std::string name = "none"; + // std::vector stops; + // where a ControlPointSettings_V1 is std::pair> + + prtyText ColormapName{ "ColormapName", ColorRamp::NO_COLORMAP_NAME }; + prtyColorControlPointVector Colormap{ "Colormap", + std::vector{ { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, + { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f } } }; GradientDataObject GradientData; }; diff --git a/renderlib/Colormap.h b/renderlib/Colormap.h index 828365412..5db24d73e 100644 --- a/renderlib/Colormap.h +++ b/renderlib/Colormap.h @@ -60,6 +60,18 @@ struct ColorControlPoint } }; +// Serialization operator for ColorControlPoint vectors +inline std::ostream& +operator<<(std::ostream& os, const std::vector& f) +{ + os << "[ "; + for (const auto& point : f) { + os << "(" << point.first << ", [" << point.r << ", " << point.g << ", " << point.b << ", " << point.a << "]) "; + } + os << " ]"; + return os; +} + class ColorRamp { public: From 4b14cee1bc758ea150b29c3ac406a724f6b33768 Mon Sep 17 00:00:00 2001 From: toloudis Date: Sun, 4 Jan 2026 07:42:46 -0800 Subject: [PATCH 62/63] fix compile error --- renderlib/Colormap.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/renderlib/Colormap.h b/renderlib/Colormap.h index 5db24d73e..31419d054 100644 --- a/renderlib/Colormap.h +++ b/renderlib/Colormap.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -58,6 +59,13 @@ struct ColorControlPoint g = (rgb >> 8) & 0xff; b = (rgb >> 0) & 0xff; } + + bool operator==(const ColorControlPoint& other) const + { + return first == other.first && r == other.r && g == other.g && b == other.b && a == other.a; + } + + bool operator!=(const ColorControlPoint& other) const { return !(*this == other); } }; // Serialization operator for ColorControlPoint vectors From 679e1208957d35df0db3d318c7414ebb67641ad8 Mon Sep 17 00:00:00 2001 From: toloudis Date: Tue, 6 Jan 2026 14:05:02 -0800 Subject: [PATCH 63/63] cleanup --- renderlib/CameraObject.cpp | 44 -------------------------------------- renderlib/CameraObject.hpp | 20 ----------------- 2 files changed, 64 deletions(-) diff --git a/renderlib/CameraObject.cpp b/renderlib/CameraObject.cpp index 62163086a..1705ca2d2 100644 --- a/renderlib/CameraObject.cpp +++ b/renderlib/CameraObject.cpp @@ -5,50 +5,6 @@ #include "serialize/docWriter.h" #include "glm.h" -// FloatSliderSpinnerUiInfo CameraUiDescription::m_exposure("Exposure", -// "Set Exposure", -// "Set camera exposure", -// 0.0f, -// 1.0f, -// 2, // decimals -// 0.01, // singleStep -// 0 // numTickMarks -// ); -// ComboBoxUiInfo CameraUiDescription::m_exposureIterations("Exposure Time", -// "Set Exposure Time", -// "Set number of samples to accumulate per viewport update", -// { "1", "2", "4", "8" }); -// CheckBoxUiInfo CameraUiDescription::m_noiseReduction("Noise Reduction", -// "Enable denoising pass", -// "Enable denoising pass"); -// FloatSliderSpinnerUiInfo CameraUiDescription::m_apertureSize("Aperture Size", -// "Set camera aperture size", -// "Set camera aperture size", -// 0.0f, -// 0.1f, -// 2, // decimals -// 0.01, // singleStep -// 0, // numTickMarks -// " mm"); -// FloatSliderSpinnerUiInfo CameraUiDescription::m_fieldOfView("Field of view", -// "Set camera field of view angle", -// "Set camera field of view angle", -// 10.0f, -// 150.0f, -// 2, // decimals -// 0.01, // singleStep -// 0, // numTickMarks -// " deg"); -// FloatSliderSpinnerUiInfo CameraUiDescription::m_focalDistance("Focal distance", -// "Set focal distance", -// "Set focal distance", -// 0.0f, -// 15.0f, -// 2, // decimals -// 0.01, // singleStep -// 0, // numTickMarks -// " m"); - CameraObject::CameraObject() : prtyObject() { diff --git a/renderlib/CameraObject.hpp b/renderlib/CameraObject.hpp index 78ddc26bf..91cec842b 100644 --- a/renderlib/CameraObject.hpp +++ b/renderlib/CameraObject.hpp @@ -8,26 +8,6 @@ class docReader; class docWriter; -struct CameraUiDescription -{ - static FloatSliderSpinnerUiInfo m_exposure; - static ComboBoxUiInfo m_exposureIterations; - static CheckBoxUiInfo m_noiseReduction; - static FloatSliderSpinnerUiInfo m_apertureSize; - static FloatSliderSpinnerUiInfo m_fieldOfView; - static FloatSliderSpinnerUiInfo m_focalDistance; -}; - -// class IDocumentObject -// { -// public: -// virtual void fromDocument(docReader* reader) = 0; -// virtual void toDocument(docWriter* writer) = 0; - -// // necessary for doc reading and writing? -// prtyName m_Name; -// } - class CameraObject : public prtyObject { public: