Skip to content

Commit acc6afc

Browse files
authored
make scalar minmax limits persistent (#250)
1 parent f8e3509 commit acc6afc

File tree

5 files changed

+94
-28
lines changed

5 files changed

+94
-28
lines changed

include/polyscope/persistent_value.h

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,13 @@ class PersistentValue {
4646
PersistentValue(const std::string& name_, T value_) : name(name_), value(value_) {
4747
if (detail::getPersistentCacheRef<T>().cache.find(name) != detail::getPersistentCacheRef<T>().cache.end()) {
4848
value = detail::getPersistentCacheRef<T>().cache[name];
49-
holdsDefaultValue = false;
49+
holdsDefaultValue_ = false;
5050
} else {
5151
// Update cache value
5252
detail::getPersistentCacheRef<T>().cache[name] = value;
5353
}
5454
}
5555

56-
// Ensure in cache on deletion (see not above reference conversion)
5756
~PersistentValue() {}
5857

5958
// Don't want copy or move constructors, only operators
@@ -85,32 +84,48 @@ class PersistentValue {
8584
// NOTE if you write via this reference, the value will not _actually_ be cached until
8685
// manuallyChanged() is called, rather than immediately (ugly, but seems necessary to use with imgui)
8786
T& get() { return value; }
87+
88+
// Mark that a value has been directly written via the get() reference, and should be cached
8889
void manuallyChanged() { set(value); }
8990

91+
// clears any cached value, but does not change the current value of the variable
92+
void clearCache() {
93+
detail::getPersistentCacheRef<T>().cache.erase(name);
94+
holdsDefaultValue_ = true;
95+
}
96+
9097
// Explicit setter, which takes care of storing in cache
9198
void set(T value_) {
9299
value = value_;
93100
detail::getPersistentCacheRef<T>().cache[name] = value;
94-
holdsDefaultValue = false;
101+
holdsDefaultValue_ = false;
95102
}
96103

97104
// Passive setter, will change value without marking in cache; does nothing if some value has already been directly
98105
// set (equivalent to constructing with a different value).
99106
void setPassive(T value_) {
100-
if (holdsDefaultValue) {
107+
if (holdsDefaultValue_) {
101108
value = value_;
102109
detail::getPersistentCacheRef<T>().cache[name] = value;
103110
}
104111
}
105112

113+
bool holdsDefaultValue() const { return holdsDefaultValue_; }
114+
106115
// Make all template variants friends, so conversion can access private members
107116
template <typename>
108117
friend class PersistentValue;
109118

119+
protected:
120+
// the name of the value
110121
const std::string name;
122+
123+
// the value
111124
T value;
112-
bool holdsDefaultValue = true; // True if the value was set on construction and never changed. False if it was pulled
113-
// from cache or has ever been explicitly set
125+
126+
// True if the value was set on construction or passively and never changed. False if
127+
// it was pulled from cache or has ever been explicitly set
128+
bool holdsDefaultValue_ = true;
114129
};
115130

116131
// clang-format off

include/polyscope/scalar_quantity.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ class ScalarQuantity {
6767
// === Visualization parameters
6868

6969
// Affine data maps and limits
70-
std::pair<float, float> vizRange; // TODO make these persistent
7170
std::pair<double, double> dataRange;
71+
PersistentValue<float> vizRangeMin;
72+
PersistentValue<float> vizRangeMax;
7273
Histogram hist;
7374

7475
// Parameters

include/polyscope/scalar_quantity.ipp

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ template <typename QuantityT>
88
ScalarQuantity<QuantityT>::ScalarQuantity(QuantityT& quantity_, const std::vector<float>& values_, DataType dataType_)
99
: quantity(quantity_), values(&quantity, quantity.uniquePrefix() + "values", valuesData), valuesData(values_),
1010
dataType(dataType_), dataRange(robustMinMax(values.data, 1e-5)),
11+
vizRangeMin(quantity.uniquePrefix() + "vizRangeMin", -777.), // set later,
12+
vizRangeMax(quantity.uniquePrefix() + "vizRangeMax", -777.), // including clearing cache
1113
cMap(quantity.uniquePrefix() + "cmap", defaultColorMap(dataType)),
1214
isolinesEnabled(quantity.uniquePrefix() + "isolinesEnabled", false),
1315
isolineWidth(quantity.uniquePrefix() + "isolineWidth",
@@ -17,7 +19,12 @@ ScalarQuantity<QuantityT>::ScalarQuantity(QuantityT& quantity_, const std::vecto
1719
{
1820
hist.updateColormap(cMap.get());
1921
hist.buildHistogram(values.data);
20-
resetMapRange();
22+
23+
if (vizRangeMin.holdsDefaultValue()) { // min and max should always have same cache state
24+
// dynamically compute a viz range from the data min/max
25+
// note that this also clears the persistent value's cahce, so it's like it was never set
26+
resetMapRange();
27+
}
2128
}
2229

2330
template <typename QuantityT>
@@ -62,7 +69,7 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {
6269

6370

6471
// Draw the histogram of values
65-
hist.colormapRange = vizRange;
72+
hist.colormapRange = std::pair<float, float>(vizRangeMin.get(), vizRangeMax.get());
6673
float windowWidth = ImGui::GetWindowWidth();
6774
float histWidth = 0.75 * windowWidth;
6875
hist.buildUI(histWidth);
@@ -77,36 +84,47 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {
7784
float imPad = ImGui::GetStyle().ItemSpacing.x;
7885
ImGui::PushItemWidth((histWidth - imPad) / 2);
7986
float speed = (dataRange.second - dataRange.first) / 100.;
87+
bool changed = false;
8088

8189
switch (dataType) {
8290
case DataType::STANDARD: {
8391

84-
ImGui::DragFloat("##min", &vizRange.first, speed, dataRange.first, vizRange.second, "%.5g",
85-
ImGuiSliderFlags_NoRoundToFormat);
92+
changed = changed || ImGui::DragFloat("##min", &vizRangeMin.get(), speed, dataRange.first, vizRangeMax.get(),
93+
"%.5g", ImGuiSliderFlags_NoRoundToFormat);
8694
ImGui::SameLine();
87-
ImGui::DragFloat("##max", &vizRange.second, speed, vizRange.first, dataRange.second, "%.5g",
88-
ImGuiSliderFlags_NoRoundToFormat);
95+
changed = changed || ImGui::DragFloat("##max", &vizRangeMax.get(), speed, vizRangeMin.get(), dataRange.second,
96+
"%.5g", ImGuiSliderFlags_NoRoundToFormat);
8997

9098
} break;
9199
case DataType::SYMMETRIC: {
92100
float absRange = std::max(std::abs(dataRange.first), std::abs(dataRange.second));
93101

94-
if (ImGui::DragFloat("##min", &vizRange.first, speed, -absRange, 0.f, "%.5g", ImGuiSliderFlags_NoRoundToFormat)) {
95-
vizRange.second = -vizRange.first;
102+
if (ImGui::DragFloat("##min", &vizRangeMin.get(), speed, -absRange, 0.f, "%.5g",
103+
ImGuiSliderFlags_NoRoundToFormat)) {
104+
vizRangeMax.get() = -vizRangeMin.get();
105+
changed = true;
96106
}
97107
ImGui::SameLine();
98-
if (ImGui::DragFloat("##max", &vizRange.second, speed, 0.f, absRange, "%.5g", ImGuiSliderFlags_NoRoundToFormat)) {
99-
vizRange.first = -vizRange.second;
108+
if (ImGui::DragFloat("##max", &vizRangeMax.get(), speed, 0.f, absRange, "%.5g",
109+
ImGuiSliderFlags_NoRoundToFormat)) {
110+
vizRangeMin.get() = -vizRangeMax.get();
111+
changed = true;
100112
}
101113

102114
} break;
103115
case DataType::MAGNITUDE: {
104-
ImGui::DragFloat("##max", &vizRange.second, speed, 0.f, dataRange.second, "%.5g",
105-
ImGuiSliderFlags_NoRoundToFormat);
116+
changed = changed || ImGui::DragFloat("##max", &vizRangeMax.get(), speed, 0.f, dataRange.second, "%.5g",
117+
ImGuiSliderFlags_NoRoundToFormat);
106118

107119
} break;
108120
}
109121

122+
if (changed) {
123+
vizRangeMin.manuallyChanged();
124+
vizRangeMax.manuallyChanged();
125+
requestRedraw();
126+
}
127+
110128
ImGui::PopItemWidth();
111129
}
112130

@@ -162,8 +180,8 @@ std::vector<std::string> ScalarQuantity<QuantityT>::addScalarRules(std::vector<s
162180

163181
template <typename QuantityT>
164182
void ScalarQuantity<QuantityT>::setScalarUniforms(render::ShaderProgram& p) {
165-
p.setUniform("u_rangeLow", vizRange.first);
166-
p.setUniform("u_rangeHigh", vizRange.second);
183+
p.setUniform("u_rangeLow", vizRangeMin.get());
184+
p.setUniform("u_rangeHigh", vizRangeMax.get());
167185

168186
if (isolinesEnabled.get()) {
169187
p.setUniform("u_modLen", getIsolineWidth());
@@ -175,17 +193,23 @@ template <typename QuantityT>
175193
QuantityT* ScalarQuantity<QuantityT>::resetMapRange() {
176194
switch (dataType) {
177195
case DataType::STANDARD:
178-
vizRange = dataRange;
196+
vizRangeMin = dataRange.first;
197+
vizRangeMax = dataRange.second;
179198
break;
180199
case DataType::SYMMETRIC: {
181200
double absRange = std::max(std::abs(dataRange.first), std::abs(dataRange.second));
182-
vizRange = std::make_pair(-absRange, absRange);
201+
vizRangeMin = -absRange;
202+
vizRangeMax = absRange;
183203
} break;
184204
case DataType::MAGNITUDE:
185-
vizRange = std::make_pair(0., dataRange.second);
205+
vizRangeMin = 0.;
206+
vizRangeMax = dataRange.second;
186207
break;
187208
}
188209

210+
vizRangeMin.clearCache();
211+
vizRangeMax.clearCache();
212+
189213
requestRedraw();
190214
return &quantity;
191215
}
@@ -214,13 +238,14 @@ std::string ScalarQuantity<QuantityT>::getColorMap() {
214238

215239
template <typename QuantityT>
216240
QuantityT* ScalarQuantity<QuantityT>::setMapRange(std::pair<double, double> val) {
217-
vizRange = val;
241+
vizRangeMin = val.first;
242+
vizRangeMax = val.second;
218243
requestRedraw();
219244
return &quantity;
220245
}
221246
template <typename QuantityT>
222247
std::pair<double, double> ScalarQuantity<QuantityT>::getMapRange() {
223-
return vizRange;
248+
return std::pair<float, float>(vizRangeMin.get(), vizRangeMax.get());
224249
}
225250
template <typename QuantityT>
226251
std::pair<double, double> ScalarQuantity<QuantityT>::getDataRange() {

src/volume_grid_scalar_quantity.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ void VolumeGridNodeScalarQuantity::buildCustomUI() {
7171

7272
// Set isovalue
7373
ImGui::PushItemWidth(120);
74-
if (ImGui::SliderFloat("##Radius", &isosurfaceLevel.get(), vizRange.first, vizRange.second, "%.4e")) {
74+
if (ImGui::SliderFloat("##Radius", &isosurfaceLevel.get(), vizRangeMin.get(), vizRangeMax.get(), "%.4e")) {
7575
// Note: we intentionally do this rather than calling setIsosurfaceLevel(), because that function immediately
76-
// recomputes the level set mesh, which is too expensive during user interaction
76+
// recomputes the levelset mesh, which is too expensive during user interaction
7777
isosurfaceLevel.manuallyChanged();
7878
}
7979
ImGui::PopItemWidth();

test/src/misc_test.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@
22

33
#include "polyscope_test.h"
44

5+
// ============================================================
6+
// =============== Scalar Quantity Tests
7+
// ============================================================
8+
9+
// We test these on a point cloud because it is convenient, but really we are testing the scalar quantity
10+
11+
TEST_F(PolyscopeTest, TestScalarQuantity) {
12+
auto psPoints = registerPointCloud();
13+
14+
std::vector<double> vScalar(psPoints->nPoints(), 7.);
15+
auto q1 = psPoints->addScalarQuantity("vScalar", vScalar);
16+
q1->setEnabled(true);
17+
polyscope::show(3);
18+
19+
// get map range
20+
std::pair<double, double> newRange = {-1., 1.};
21+
q1->setMapRange(newRange);
22+
EXPECT_EQ(newRange, q1->getMapRange());
23+
24+
25+
polyscope::show(3);
26+
27+
polyscope::removeAllStructures();
28+
}
29+
530
// ============================================================
631
// =============== Materials tests
732
// ============================================================

0 commit comments

Comments
 (0)