Przeglądaj źródła

Merge branch 'ctkCoordinatesWidget-Normalized'

* ctkCoordinatesWidget-Normalized:
  Add ctkCoordinatesWidget::normalized
Julien Finet 12 lat temu
rodzic
commit
cf750a5061

+ 3 - 0
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -21,6 +21,7 @@ set(TEST_SOURCES
   ctkComboBoxTest1.cpp
   ctkCompleterTest1.cpp
   ctkConsoleTest1.cpp
+  ctkCoordinatesWidgetTest.cpp
   ctkCoordinatesWidgetTest1.cpp
   ctkCrosshairLabelTest1.cpp
   ctkDirectoryButtonTest1.cpp
@@ -172,6 +173,7 @@ endif()
 set(Tests_MOC_CPP)
 QT4_WRAP_CPP(Tests_MOC_CPP ${Tests_MOC_SRCS})
 QT4_GENERATE_MOCS(
+  ctkCoordinatesWidgetTest.cpp
   ctkFlatProxyModelTest.cpp
   ctkFontButtonTest.cpp
   ctkMessageBoxDontShowAgainTest.cpp
@@ -214,6 +216,7 @@ SIMPLE_TEST( ctkColorPickerButtonTest1 )
 SIMPLE_TEST( ctkComboBoxTest1 )
 SIMPLE_TEST( ctkCompleterTest1 )
 SIMPLE_TEST( ctkConsoleTest1 )
+SIMPLE_TEST( ctkCoordinatesWidgetTest )
 SIMPLE_TEST( ctkCoordinatesWidgetTest1 )
 SIMPLE_TEST( ctkCrosshairLabelTest1 )
 SIMPLE_TEST( ctkDateRangeWidgetTest1 )

+ 100 - 0
Libs/Widgets/Testing/Cpp/ctkCoordinatesWidgetTest.cpp

@@ -0,0 +1,100 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QApplication>
+#include <QString>
+#include <QStyle>
+#include <QStyleOptionSlider>
+#include <QTimer>
+#include <QVector3D>
+
+// CTK includes
+#include "ctkCoordinatesWidget.h"
+#include "ctkTest.h"
+
+// ----------------------------------------------------------------------------
+class ctkCoordinatesWidgetTester: public QObject
+{
+  Q_OBJECT
+private slots:
+  void testDefaults();
+
+  void testNormalized();
+  void testNormalized_data();
+};
+
+// ----------------------------------------------------------------------------
+void ctkCoordinatesWidgetTester::testDefaults()
+{
+  ctkCoordinatesWidget coordinatesWidget;
+  QCOMPARE(coordinatesWidget.dimension(), 3);
+  QCOMPARE(coordinatesWidget.decimals(), 3);
+  QCOMPARE(coordinatesWidget.singleStep(), 1.);
+  QCOMPARE(coordinatesWidget.minimum(), -100000.);
+  QCOMPARE(coordinatesWidget.maximum(), 100000.);
+  QCOMPARE(coordinatesWidget.isNormalized(), false);
+  QCOMPARE(coordinatesWidget.coordinatesAsString(), QString("0,0,0"));
+  QCOMPARE(QVector3D(coordinatesWidget.coordinates()[0],
+                      coordinatesWidget.coordinates()[1],
+                      coordinatesWidget.coordinates()[2]),
+            QVector3D(0., 0., 0.));
+}
+
+// ----------------------------------------------------------------------------
+void ctkCoordinatesWidgetTester::testNormalized()
+{
+  ctkCoordinatesWidget coordinatesWidget;
+  QFETCH(QVector3D, coordinates);
+  coordinatesWidget.setCoordinates(coordinates.x(), coordinates.y(), coordinates.z());
+
+  coordinatesWidget.setNormalized(true);
+  QVector3D after(coordinatesWidget.coordinates()[0],
+                  coordinatesWidget.coordinates()[1],
+                  coordinatesWidget.coordinates()[2]);
+  QVector3D normalized = coordinates.normalized();
+  // CoodinatesWidget has only 3 significant decimals (so just test the 2 first
+  // decimals because the last one might off of 1).
+  QCOMPARE(static_cast<int>(normalized.x() * 100), static_cast<int>(after.x() * 100));
+  QCOMPARE(static_cast<int>(normalized.y() * 100), static_cast<int>(after.y() * 100));
+  QCOMPARE(static_cast<int>(normalized.z() * 100), static_cast<int>(after.z() * 100));
+  QCOMPARE(coordinatesWidget.minimum(), -1.);
+  QCOMPARE(coordinatesWidget.maximum(), 1.);
+}
+
+// ----------------------------------------------------------------------------
+void ctkCoordinatesWidgetTester::testNormalized_data()
+{
+  QTest::addColumn<QVector3D>("coordinates");
+
+  QTest::newRow("0,0,0") << QVector3D();
+  QTest::newRow("1,0,0") << QVector3D(1., 0., 0.);
+  QTest::newRow("0,1,0") << QVector3D(0., 1., 0.);
+  QTest::newRow("0,0,-1") << QVector3D(0., 0., -1.);
+  QTest::newRow("2,0,0") << QVector3D(2., 0., 0.);
+  QTest::newRow("2,2,0") << QVector3D(2., 2., 0.);
+  QTest::newRow("0,-2,2") << QVector3D(0., -2., 2.);
+  QTest::newRow("1,2,3") << QVector3D(1., 2., 3.);
+  QTest::newRow("-1,-2,-3") << QVector3D(-1., -2., -3.);
+}
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkCoordinatesWidgetTest)
+#include "moc_ctkCoordinatesWidgetTest.cpp"

+ 147 - 9
Libs/Widgets/ctkCoordinatesWidget.cpp

@@ -25,6 +25,9 @@
 // CTK includes
 #include "ctkCoordinatesWidget.h"
 
+// STD includes
+#include <cmath>
+
 //------------------------------------------------------------------------------
 ctkCoordinatesWidget::ctkCoordinatesWidget(QWidget* _parent) :QWidget(_parent)
 {
@@ -32,9 +35,10 @@ ctkCoordinatesWidget::ctkCoordinatesWidget(QWidget* _parent) :QWidget(_parent)
   this->SingleStep = 1.;
   this->Minimum = -100000.;
   this->Maximum = 100000.;
+  this->Normalized = false;
   this->Dimension = 3;
   this->Coordinates = new double [this->Dimension];
-  
+
   QHBoxLayout* hboxLayout = new QHBoxLayout(this);
   this->setLayout(hboxLayout);
   for (int i = 0; i < this->Dimension; ++i)
@@ -59,7 +63,7 @@ void ctkCoordinatesWidget::addSpinBox()
   spinBox->setSingleStep(this->SingleStep);
   spinBox->setMinimum(this->Minimum);
   spinBox->setMaximum(this->Maximum);
-  connect( spinBox, SIGNAL(valueChanged(double)), 
+  connect( spinBox, SIGNAL(valueChanged(double)),
            this, SLOT(updateCoordinate(double)));
   this->layout()->addWidget(spinBox);
 }
@@ -152,6 +156,29 @@ double ctkCoordinatesWidget::maximum() const
 }
 
 //------------------------------------------------------------------------------
+void ctkCoordinatesWidget::setNormalized(bool normalized)
+{
+  this->Normalized = normalized;
+  if (this->Normalized)
+    {
+    double normalizedCoordinates[this->Dimension];
+    memcpy(normalizedCoordinates, this->Coordinates, sizeof(double)*this->Dimension);
+    ctkCoordinatesWidget::normalize(normalizedCoordinates, this->Dimension);
+
+    this->setMinimum(-1.);
+    this->setMaximum(1.);
+
+    this->setCoordinates(normalizedCoordinates);
+    }
+}
+
+//------------------------------------------------------------------------------
+bool ctkCoordinatesWidget::isNormalized() const
+{
+  return this->Normalized;
+}
+
+//------------------------------------------------------------------------------
 void ctkCoordinatesWidget::setDecimals(int newDecimals)
 {
   this->Decimals = newDecimals;
@@ -228,17 +255,21 @@ QString ctkCoordinatesWidget::coordinatesAsString()const
 }
 
 //------------------------------------------------------------------------------
-void ctkCoordinatesWidget::setCoordinates(double* _pos)
+void ctkCoordinatesWidget::setCoordinates(double* coordinates)
 {
   for (int i = 0; i < this->Dimension; ++i)
     {
-    this->Coordinates[i] = _pos[i];
+    this->Coordinates[i] = coordinates[i];
+    }
+  if (this->Normalized)
+    {
+    this->normalize(this->Coordinates, this->Dimension);
     }
   bool blocked = this->blockSignals(true);
   for (int i = 0; i < this->Dimension; ++i)
     {
     QLayoutItem* item = this->layout()->itemAt(i);
-    QDoubleSpinBox* spinBox = 
+    QDoubleSpinBox* spinBox =
       item ? dynamic_cast<QDoubleSpinBox*>(item->widget()) : 0;
     if (spinBox)
       {
@@ -250,6 +281,33 @@ void ctkCoordinatesWidget::setCoordinates(double* _pos)
 }
 
 //------------------------------------------------------------------------------
+void ctkCoordinatesWidget::setCoordinates(double x, double y, double z, double w)
+{
+  double coordinates[this->Dimension];
+  if (this->Dimension >= 1)
+    {
+    coordinates[0] = x;
+    }
+  if (this->Dimension >= 2)
+    {
+    coordinates[1] = y;
+    }
+  if (this->Dimension >= 3)
+    {
+    coordinates[2] = z;
+    }
+  if (this->Dimension >= 4)
+    {
+    coordinates[3] = w;
+    }
+  for (int i = 4; i < this->Dimension; ++i)
+    {
+    coordinates[i] = this->Coordinates[i];
+    }
+  this->setCoordinates(coordinates);
+}
+
+//------------------------------------------------------------------------------
 double const * ctkCoordinatesWidget::coordinates()const
 {
   return this->Coordinates;
@@ -258,18 +316,63 @@ double const * ctkCoordinatesWidget::coordinates()const
 //------------------------------------------------------------------------------
 void ctkCoordinatesWidget::updateCoordinate(double coordinate)
 {
+  double den = 0.;
+  int element = -1;
   for (int i = 0; i < this->Dimension; ++i)
     {
     QLayoutItem* item = this->layout()->itemAt(i);
-    QDoubleSpinBox* spinBox = 
+    QDoubleSpinBox* spinBox =
       item ? qobject_cast<QDoubleSpinBox*>(item->widget()) : 0;
     if ( spinBox && spinBox == this->sender())
       {
       this->Coordinates[i] = coordinate;
-      break;
+      element = i;
+      }
+    else
+      {
+      den += this->Coordinates[i]*this->Coordinates[i];
       }
     }
-  emit coordinatesChanged(this->Coordinates);
+  Q_ASSERT(element != -1);
+  if (this->isNormalized())
+    {
+    // Old Values xx + yy + zz = 1
+    // New values: x'x' + y'y' + z'z' = 1
+    // Say we are changing y into y':
+    // x'x' + z'z' = 1 - y'y'
+    // Let's pose a the coef to multiply x into x' that keeps the norm to 1:
+    // axax + azaz = 1 - y'y'
+    // aa(xx + zz) = 1 - y'y'
+    // a = sqrt( (1 - y'y') / (xx + zz) )
+    bool mult = true;
+    if (den != 0.0)
+      {
+      mult = true;
+      den = sqrt( (1. - coordinate * coordinate) / den);
+      }
+    else if (this->Dimension > 1)
+      {
+      mult = false;
+      den = sqrt((1. - coordinate*coordinate) / (this->Dimension - 1));
+      }
+    double normalizedCoordinates[this->Dimension];
+    for (int i = 0; i < this->Dimension; ++i)
+      {
+      if (i != element)
+        {
+        normalizedCoordinates[i] = mult ? this->Coordinates[i] * den : den;
+        }
+      else
+        {
+        normalizedCoordinates[i] = this->Coordinates[i];
+        }
+      }
+    this->setCoordinates(normalizedCoordinates);
+    }
+  else
+    {
+    emit coordinatesChanged(this->Coordinates);
+    }
 }
 
 //------------------------------------------------------------------------------
@@ -278,7 +381,7 @@ void ctkCoordinatesWidget::updateCoordinates()
   for (int i = 0; i < this->Dimension; ++i)
     {
     QLayoutItem* item = this->layout()->itemAt(i);
-    QDoubleSpinBox* spinBox = 
+    QDoubleSpinBox* spinBox =
       item ? qobject_cast<QDoubleSpinBox*>(item->widget()) : 0;
     if ( spinBox)
       {
@@ -287,3 +390,38 @@ void ctkCoordinatesWidget::updateCoordinates()
     }
   emit coordinatesChanged(this->Coordinates);
 }
+
+//------------------------------------------------------------------------------
+void ctkCoordinatesWidget::normalize()
+{
+  double normalizedCoordinates[this->Dimension];
+  memcpy(normalizedCoordinates, this->Coordinates,
+         sizeof(double) * this->Dimension);
+  ctkCoordinatesWidget::normalize(normalizedCoordinates, this->Dimension);
+  this->setCoordinates(normalizedCoordinates);
+}
+
+//------------------------------------------------------------------------------
+double ctkCoordinatesWidget::normalize(double* coordinates, int dimension)
+{
+  double den = ctkCoordinatesWidget::norm( coordinates, dimension );
+  if ( den != 0.0 )
+    {
+    for (int i = 0; i < dimension; ++i)
+      {
+      coordinates[i] /= den;
+      }
+    }
+  return den;
+}
+
+//------------------------------------------------------------------------------
+double ctkCoordinatesWidget::norm(double* coordinates, int dimension)
+{
+  double sum = 0.;
+  for (int i = 0; i < dimension; ++i)
+    {
+    sum += coordinates[i] * coordinates[i];
+    }
+  return sqrt(sum);
+}

+ 38 - 18
Libs/Widgets/ctkCoordinatesWidget.h

@@ -35,10 +35,16 @@
 /// TODO: use pimpl
 class CTK_WIDGETS_EXPORT ctkCoordinatesWidget : public QWidget
 {
-  Q_OBJECT 
+  Q_OBJECT
+
+  Q_PROPERTY(int dimension READ dimension WRITE setDimension)
+  /// This property controls whether the coordinates must be normalized.
+  /// If true, the norm of the coordinates is enforced to be 1.
+  /// False by default.
+  Q_PROPERTY(bool normalized READ isNormalized WRITE setNormalized)
+
   Q_PROPERTY(int decimals READ decimals WRITE setDecimals)
   Q_PROPERTY(double singleStep  READ singleStep WRITE setSingleStep STORED false)
-  Q_PROPERTY(int dimension READ dimension WRITE setDimension)
   Q_PROPERTY(double minimum READ minimum WRITE setMinimum)
   Q_PROPERTY(double maximum READ maximum WRITE setMaximum)
 
@@ -48,49 +54,56 @@ public:
   explicit ctkCoordinatesWidget(QWidget* parent = 0);
   virtual ~ctkCoordinatesWidget();
 
-  /// 
   /// Set/Get the dimension of the point
   /// The default dimension is 3
   void setDimension(int dim);
   int dimension() const;
-  
-  /// 
-  /// Set/Get the number of decimals of each coordinate QDoubleSpinBoxes 
-  /// The default single step is 3
+
+  /// Set/Get the number of decimals of each coordinate QDoubleSpinBoxes
+  /// The default number of decimals is 3.
   void setDecimals(int decimals);
   int decimals() const;
-  
 
-  /// 
-  /// Set/Get the single step of each coordinate QDoubleSpinBoxes 
+  /// Set/Get the single step of each coordinate QDoubleSpinBoxes
   /// The default single step is 1.
   void setSingleStep(double step);
   double singleStep() const;
 
-  /// 
-  /// Set/Get the minimum value of each coordinate QDoubleSpinBoxes 
+  /// Set/Get the minimum value of each coordinate QDoubleSpinBoxes
   /// The default minimum is -100000.
   void setMinimum(double minimum);
   double minimum() const;
 
-  /// 
-  /// Set/Get the maximum value of each coordinate QDoubleSpinBoxes 
+  /// Set/Get the maximum value of each coordinate QDoubleSpinBoxes
   /// The default maximum is 100000.
   void setMaximum(double minimum);
   double maximum() const;
 
-  /// 
-  /// Set/Get the coordinates. Use commas between numbers
-  /// i.e. "0,0.0,0." 
+  /// Change the normalized property. If \a normalize is true, it normalizes
+  /// the current coordinates, the range of possible values is reset to [-1, 1].
+  /// \sa isNormalized
+  void setNormalized(bool normalize);
+  bool isNormalized()const;
+
+  /// Return the norm of the coordinates.
+  double norm()const;
+
+  /// Set/Get the coordinates. Use commas to separate elements, spaces are
+  /// allowed: e.g. "0,0.0, 0."
   void setCoordinatesAsString(QString pos);
   QString coordinatesAsString()const;
 
-  /// 
   /// Set/Get the coordinates
   /// The default values are 0.
   void setCoordinates(double* pos);
   double const * coordinates()const;
 
+  /// Convenient function that sets up to 4 elements of the coordinates.
+  void setCoordinates(double x, double y = 0., double z = 0., double w = 0.);
+
+public Q_SLOTS:
+  void normalize();
+
 Q_SIGNALS:
   ///
   /// valueChanged is fired anytime a coordinate is modified, the returned
@@ -105,10 +118,17 @@ protected Q_SLOTS:
 protected:
   void addSpinBox();
 
+  /// Normalize coordinates vector and return the previous norm.
+  static double normalize(double* coordinates, int dimension);
+
+  /// Compute the norm of a coordinates \a dimension vector
+  static double norm(double* coordinates, int dimension);
+
   int     Decimals;
   double  SingleStep;
   double  Minimum;
   double  Maximum;
+  bool    Normalized;
   int     Dimension;
   double* Coordinates;
 };