├── LICENSE ├── README.md ├── imcontrol.cpp ├── imcontrol.h ├── imcontrol_demo.cpp ├── imcontrol_example_widgets.cpp ├── imcontrol_example_widgets.h └── imcontrol_internal.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 James Griffin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImControl 2 | 3 | ImControl is an immediate mode variable manipulation library for [Dear ImGui](https://github.com/ocornut/imgui). It's API mimics the style of Dear ImGui, it's fast like Dear ImGui and it's easy to use, like Dear ImGui. 4 | If you use variables to control, position and orient your objects in 1D, 2D or 3D and wish you could edit them by dragging the objects on screen then ImControl is for you. You just state how to position some control points with your variables and let it do the rest. 5 | 6 | What ImControl provides: 7 | 8 | * a transformation stack to describe how you manipulate your objects with your variables 9 | * a simple quadratic solver that feeds mouse-based interaction back to changes to your variables 10 | * reparametrisation of variables (you may not realise it yet, but you want this...) 11 | * a bunch of demos which would be a headache to implement without ImControl! 12 | * example implementations of widgets, 3D gui elements, etc. built on top of ImControl. 13 | 14 | What ImControl does not provide: 15 | 16 | * a stable library of widgets, you can easily write your own though! 17 | * rendering of anything (unless you ask it to), it is expected that you render your own objects, widgets etc. 18 | * a physics simulation 19 | * resolution of mathematical [singularities](https://en.wikipedia.org/wiki/Singularity_(mathematics)), you need to stay the right side of the [Inverse Function Theorem](https://en.wikipedia.org/wiki/Inverse_function_theorem) 20 | 21 | ![arm demo](https://user-images.githubusercontent.com/2971239/103889425-51301700-50de-11eb-8e90-e20fde6e0885.gif) 22 | 23 | Read the source files for documentation. To run the demos start with your chosen Dear ImGui implementation example, add a call to ImControl::NewFrame() after ImGui::NewFrame(), then call ImControl::ShowDemoWindow as you would ImGui's. 24 | 25 | If you have any questions about the code then please contact me directly or raise an issue. Furthermore if you have any suggestions for transforms / features you want included then again raise an issue. Contributions to the code are of course welcome. 26 | 27 | You may notice some strange behaviour when dragging certain control points around. This may be an unavoidable result of your underlying geometry, the simplest way to mitigate such issues is to restrict the domain of the parameters, see the examples. 28 | 29 | ### Tests and Benchmarks 30 | 31 | There are some simple tests I used when developing the transforms, they perform a numerical check of the implemented formal derivatives. They are part of the demo. There are also some benchmarks which compare the performance of the various types of transformations. 32 | -------------------------------------------------------------------------------- /imcontrol.cpp: -------------------------------------------------------------------------------- 1 | #include "imcontrol.h" 2 | 3 | #include "imcontrol_internal.h" 4 | 5 | 6 | namespace ImControl { 7 | 8 | namespace Transformations { 9 | /* 10 | Implementation of the transformation stack 11 | 12 | There are three separate stacks of matrices, one for the matrix transformation, 13 | one for the first derivatives and one for the second derivatives. 14 | 15 | The variables which can be differentiated against can vary but only when the stacks are empty. 16 | 17 | By default pushing a transformation will calculate a new matrix in the stack, however the 18 | transformation is applied in place if using the composite transformation system. This means 19 | that transformations cannot be popped during a composite transformation. 20 | */ 21 | 22 | const ImMat4& TransformationStack::getDerivativeMatrix(param_t param) const 23 | { 24 | auto p = m_derivative_parameters.find(param); 25 | 26 | // By convention if a parameter isn't found we just return 0 27 | if (p == m_derivative_parameters.end()) 28 | return m_zero_matrix; 29 | 30 | return beginDerivatives()[p - m_derivative_parameters.begin()]; 31 | } 32 | 33 | const ImMat4& TransformationStack::getSecondDerivativeMatrix(param_t p1, param_t p2) const 34 | { 35 | // NB, we might want our own find here as we only need to look as far as m_num_second_derivs 36 | // However this is only relevant if computing a large number of first derivatives 37 | size_t ix1 = m_derivative_parameters.find(p1) - m_derivative_parameters.begin(); 38 | 39 | if (ix1 >= m_num_second_derivs) 40 | return m_zero_matrix; 41 | 42 | if (p1 == p2) // Shortcut in this common case 43 | return beginSecondDerivatives()[symmetric_index_ordered(ix1, ix1)]; 44 | 45 | size_t ix2 = m_derivative_parameters.find(p2) - m_derivative_parameters.begin(); 46 | 47 | if (ix2 >= m_num_second_derivs) 48 | return m_zero_matrix; 49 | 50 | return beginSecondDerivatives()[symmetric_index(ix1, ix2)]; 51 | } 52 | 53 | // This must only be called when the stack is empty 54 | void TransformationStack::setDerivativeParameters(const ImVector& d_params, int n_second_derivatives) 55 | { 56 | IM_ASSERT(isEmpty()); 57 | IM_ASSERT(n_second_derivatives <= d_params.size()); 58 | 59 | m_derivative_parameters = d_params; 60 | m_num_second_derivs = n_second_derivatives; 61 | 62 | // This is the only function where the above two function parameters should be changed and so the only place where m_width is recalculated 63 | m_width = 1 + (size_t)m_derivative_parameters.size() + symmetric_size(m_num_second_derivs); 64 | } 65 | 66 | void TransformationStack::copyWithinStack(size_t source_ix, size_t target_ix) 67 | { 68 | IM_ASSERT((source_ix < m_depth) && (target_ix < m_depth)); 69 | 70 | if (source_ix == target_ix) 71 | return; 72 | 73 | memcpy(m_data.Data + target_ix * m_width, m_data.Data + source_ix * m_width, m_width * sizeof(ImMat4)); 74 | } 75 | 76 | void TransformationStack::pushIdentity() 77 | { 78 | resize(m_depth + 1); // NB increments m_depth 79 | 80 | if (m_depth > 1) { 81 | copyWithinStack(m_depth - 2, m_depth - 1); 82 | return; 83 | } 84 | 85 | // Deal with case when stack was empty 86 | m_data[0] = m_identity; 87 | 88 | // Set all derivative matrices to 0 89 | if (m_width > 1) 90 | memset(m_data.Data + 1, 0, ((size_t)m_width - 1) * sizeof(ImMat4)); 91 | } 92 | 93 | template 94 | void TransformationStack::pushConstantTransformation(const T_t& T) 95 | { 96 | if (m_composite_level == 0) 97 | pushIdentity(); 98 | 99 | T.applyTransformationOnRightInPlace(beginLevel(), endLevel()); 100 | } 101 | 102 | template 103 | void TransformationStack::pushTransformation(const T_t& T, param_t param) 104 | { 105 | pushTransformation(T, Parameters::Parameter{ param }); 106 | } 107 | 108 | template 109 | void TransformationStack::pushTransformation(const T_t& T, const Parameters::Parameter& param) 110 | { 111 | auto p = m_derivative_parameters.find(param.p); 112 | 113 | // If parameter is not being tracked then transformation may as well be constant 114 | if (p == m_derivative_parameters.end()) 115 | return pushConstantTransformation(T); 116 | 117 | size_t param_ix = p - m_derivative_parameters.begin(); 118 | 119 | if (m_composite_level == 0) 120 | pushIdentity(); 121 | 122 | // We apply the transformation in place (we only have the previous 123 | // values available if m_composite_level is zero). Since the calculation 124 | // of the derivatives requires the previous transformation matrix and 125 | // the calculation of the second derivatives requires both the previous 126 | // transformation matrix and the previous derivative matrices, we will 127 | // first calculate the second derivatives, then the first derivatives 128 | // and only then the new transformation matrix. 129 | 130 | const ImMat4& old_M = getMatrix(); // A const reference so we cannot apply anything in-place by accident 131 | 132 | ImMat4 const* const old_D1 = beginDerivatives(); // Again this is for looking up derivative values without changing them 133 | 134 | // First just apply transformation matrix to all second derivatives 135 | T.applyTransformationOnRightInPlace(beginSecondDerivatives(), endSecondDerivatives()); 136 | 137 | if (param_ix < m_num_second_derivs) 138 | { 139 | // D2 will increment through the second derivatives involving param_ix, starting with (0, param_ix) 140 | ImMat4* D2 = beginSecondDerivatives() + symmetric_index_ordered(0, param_ix); 141 | 142 | for (size_t ix = 0; ix < param_ix; ++ix) { 143 | *D2 += param.d * T.applyDerivativeOnRightInPlace(ImMat4{ old_D1[ix] }); // D2 = beginSecondDerivatives() + ix + (param_ix * (param_ix + 1)) / 2 144 | ++D2; 145 | } 146 | 147 | // ix = param_ix case, D2 = beginSecondDerivatives() + param_ix + (param_ix * (param_ix + 1)) / 2 148 | *D2 += (2.0f * param.d) * T.applyDerivativeOnRightInPlace(ImMat4{ old_D1[param_ix] }); 149 | *D2 += (param.d * param.d) * T.apply2ndDerivOnRightInPlace(ImMat4{ old_M }); 150 | *D2 += param.d2 * T.applyDerivativeOnRightInPlace(ImMat4{ old_M }); 151 | 152 | // ix > param_ix case 153 | for (size_t ix = param_ix + 1; ix < m_num_second_derivs; ++ix) { 154 | D2 += ix; // Larger increment needed now ix > param_ix 155 | *D2 += param.d * T.applyDerivativeOnRightInPlace(ImMat4{ old_D1[ix] }); // D2 = beginSecondDerivatives() + param_ix + (ix * (ix + 1)) / 2 156 | } 157 | IM_ASSERT(D2 == beginSecondDerivatives() + symmetric_index_ordered(param_ix, m_num_second_derivs - 1)); 158 | } 159 | 160 | // Apply to first derivatives 161 | T.applyTransformationOnRightInPlace(beginDerivatives(), endDerivatives()); 162 | beginDerivatives()[param_ix] += param.d * T.applyDerivativeOnRightInPlace(ImMat4{ old_M }); 163 | 164 | // Apply to transformation matrix 165 | T.applyTransformationOnRightInPlace(beginLevel(), beginLevel() + 1); 166 | } 167 | 168 | void TransformationStack::reset() 169 | { 170 | clear(); 171 | 172 | m_derivative_parameters.clear(); 173 | m_num_second_derivs = 0; 174 | } 175 | 176 | ImJet1 apply_stack_to_1jet(const TransformationStack& transform, const ImJet1& jet) 177 | { 178 | ImJet1 output{ jet.parameters }; 179 | 180 | output.value() = transform.apply(jet.value()); 181 | 182 | for (int i = 0; i < jet.size(); ++i) 183 | output.derivative(i) = transform.applyDerivative(jet.value(), jet.parameters[i]); 184 | 185 | return output; 186 | } 187 | 188 | ImJet2 apply_stack_to_2jet(const TransformationStack& transform, const ImJet2& jet) 189 | { 190 | ImJet2 output{ jet.parameters }; 191 | 192 | output.value() = transform.apply(jet.value()); 193 | 194 | for (int i = 0; i < jet.size(); ++i) { 195 | output.derivative(i) = transform.applyDerivative(jet.value(), jet.parameters[i]); 196 | for (int j = 0; j <= i; ++j) 197 | output.second_derivative(j, i) = transform.applySecondDerivative(jet.value(), jet.parameters[i], jet.parameters[j]); 198 | } 199 | return output; 200 | } 201 | 202 | ImJet1 project_1jet(const ImJet1& jet) 203 | { 204 | const ImVec4& p = jet.value(); // Call p the position 205 | const float& w = p.w; // Call w the w-coord of p 206 | 207 | // Calculate the output 1-jet 208 | ImJet1 output{ jet.parameters }; 209 | output.value() = p / w; 210 | 211 | // Now fill in the second derivatives 212 | for (int i = 0; i < jet.size(); ++i) 213 | { 214 | const ImVec4& dpdu = jet.derivative(i); // u is a name for the ith parameter 215 | const float& dwdu = dpdu.w; 216 | 217 | // Quotient rule 218 | output.derivative(i) = (dpdu * w - p * dwdu) / (w * w); 219 | } 220 | return output; 221 | } 222 | 223 | ImJet2 project_2jet(const ImJet2& jet) 224 | { 225 | const ImVec4& p = jet.value(); // Call p the position 226 | const float& w = p.w; // Call w the w-coord of p 227 | 228 | // Calculate the output 1-jet 229 | ImJet2 output{ jet.parameters }; 230 | output.value() = p / w; 231 | 232 | // Now fill in the second derivatives 233 | for (int i = 0; i < jet.size(); ++i) 234 | { 235 | const ImVec4& dpdu = jet.derivative(i); // u is a name for the ith parameter 236 | const float& dwdu = dpdu.w; 237 | 238 | // Quotient rule 239 | output.derivative(i) = (dpdu * w - p * dwdu) / (w * w); 240 | 241 | for (int j = 0; j <= i; ++j) 242 | { 243 | const ImVec4& dpdv = jet.derivative(j); // v is a name for the jth parameter 244 | const float& dwdv = dpdv.w; 245 | 246 | const ImVec4& d2pdudv = jet.second_derivative(j, i); 247 | const float& d2wdudv = d2pdudv.w; 248 | 249 | // This is a formula for the second derivative of p / w with respect to u and v 250 | output.second_derivative(j, i) = (d2pdudv * w * w - dpdu * dwdv * w - dpdv * dwdu * w - p * d2wdudv * w + p * (2 * dwdu * dwdv)) / (w * w * w); 251 | } 252 | } 253 | return output; 254 | } 255 | 256 | ImJet2 distance_squared(const ImJet2& jet, const ImVec4& target) 257 | { 258 | ImJet2 output{ jet.parameters }; 259 | ImVec4 diff = jet.value() - target; 260 | output.value() = dot(diff, diff) / 2; 261 | 262 | for (int i = 0; i < jet.size(); ++i) { 263 | output.derivative(i) = dot(diff, jet.derivative(i)); 264 | for (int j = 0; j < jet.size(); ++j) 265 | output.second_derivative(i, j) = dot(jet.derivative(i), jet.derivative(j)) + dot(diff, jet.second_derivative(i, j)); 266 | } 267 | return output; 268 | } 269 | 270 | static inline void apply_second_derivative(const ImJet2& jet, ImVector& out, ImVector const& x, float kappa = 0) { 271 | IM_ASSERT((jet.size() == out.size()) && (jet.size() == x.size())); 272 | // out <== out + f second_derivative * x + kappa * x 273 | for (int i = 0; i < jet.size(); ++i) { 274 | for (int j = 0; j < jet.size(); ++j) { 275 | out[i] += jet.second_derivative(i, j) * x[j]; 276 | } 277 | out[i] += kappa * x[i]; 278 | } 279 | } 280 | 281 | ImVector apply_conjugate_gradient_method(const ImJet2& jet, float kappa) 282 | { 283 | // Compute first_derivative / (second_derivative + kappa * I) using the conjugate gradient method. 284 | 285 | constexpr float error_tolerance{ 0.01f }; 286 | const int D = jet.size(); 287 | 288 | // Since we start with x = 0, the initialisation may look slightly different to standard 289 | ImVector x{}; 290 | x.resize(D, 0); // Start with x = 0 291 | 292 | ImVector r{}; 293 | r.resize(D); // Start with r = first_derivative 294 | for (int i = 0; i < D; ++i) 295 | r[i] = jet.derivative(i); 296 | 297 | ImVector d{ r }; 298 | 299 | float delta_new = 0; 300 | for (auto const& v : r) 301 | delta_new += v * v; 302 | 303 | float const delta_0 = delta_new > 0.001f ? delta_new : 0.001f; 304 | 305 | ImVector q{}; 306 | q.resize(D); 307 | 308 | for (int i = 0; (i < D) && (delta_new > error_tolerance * error_tolerance * delta_0); ++i) 309 | { 310 | for (auto& v : q) 311 | v = 0.0f; // Zero q 312 | apply_second_derivative(jet, q, d, kappa); 313 | 314 | float alpha{}; 315 | for (int j = 0; j < D; ++j) 316 | alpha += d[j] * q[j]; 317 | alpha = delta_new / alpha; 318 | 319 | for (int j = 0; j < D; ++j) { 320 | x[j] += alpha * d[j]; 321 | r[j] -= alpha * q[j]; 322 | } 323 | 324 | float delta_old{ delta_new }; 325 | 326 | delta_new = {}; 327 | for (auto const& v : r) 328 | delta_new += v * v; 329 | 330 | float beta = delta_new / delta_old; 331 | 332 | for (int j = 0; j < D; ++j) 333 | d[j] = r[j] + beta * d[j]; 334 | } 335 | return x; 336 | } 337 | 338 | inline ImVec4 calculate_tangent_after_projection(const TransformationStack& transform, const ImVec4& pos, param_t param) 339 | { 340 | ImVector params{}; 341 | params.resize(1, param); 342 | ImJet1 jet{ params }; 343 | jet.value() = transform.apply(pos); 344 | jet.derivative(0) = transform.applyDerivative(pos, param); 345 | 346 | ImJet1 output_jet = project_1jet(jet); 347 | return output_jet.derivative(0); 348 | } 349 | 350 | ImVector bring_together(const TransformationStack& transform, const ImVec4& pos, const ImVec4& target, const ImVector& free_parameters, float kappa) 351 | { 352 | // Minimises Q(a) = |p - Mv|^2 over a where Mv is the transformed vector, and 353 | // M(a) is the parametrised transformation with a the free parameter. 354 | // When p is closest to Mv, the derivative with respect to a will be 355 | // zero, so we use the Newton-Raphson method on the derivative. 356 | 357 | // A jet with given parameters, position and 0 first and second derivatives 358 | ImJet2 jet{ free_parameters }; 359 | jet.value() = pos; 360 | 361 | // We need to project from 4 coords to 3 coords via p --> p / p.w 362 | ImJet2 output_jet = project_2jet(apply_stack_to_2jet(transform, jet)); 363 | 364 | // Compute the distance squared 365 | ImJet2 Qderivatives = distance_squared(output_jet, target); 366 | 367 | // Now solve 368 | ImVector steps = apply_conjugate_gradient_method(Qderivatives, kappa); 369 | 370 | return steps; 371 | } 372 | 373 | } // namespace Transformations 374 | 375 | void ImControlContext::newFrame(Transformations::TransformationStack& transform) 376 | { 377 | IM_ASSERT(transform.getStackSize() == 0); 378 | 379 | ImVector params{}; 380 | 381 | // This is inefficient but the number of parameters is small 382 | for (auto p : m_registered_2nd_der_params) 383 | if (params.find(p) == params.end()) 384 | params.push_back(p); 385 | 386 | int n_second_derivatives = params.size(); 387 | 388 | for (auto p : m_registered_der_params) 389 | if (params.find(p) == params.end()) 390 | params.push_back(p); 391 | 392 | // Pass the parameters for the duration of the following frame to the transform stack, second derivatives are computed for an initial segment of the vector 393 | transform.setDerivativeParameters(params, n_second_derivatives); 394 | 395 | m_registered_der_params.clear(); 396 | m_registered_2nd_der_params.clear(); 397 | } 398 | 399 | void ImControlContext::endFrame() 400 | { 401 | // If there is a change in the deferral stack then apply it 402 | bool parameters_updated = m_deferral_stack.applyParameterChanges(); 403 | 404 | // Reset deferral stack state 405 | m_deferral_stack.reset(); 406 | 407 | if (m_activated_point_id) { 408 | ImGui::SetActiveID(m_activated_point_id, (ImGuiWindow*)m_activated_point_window); 409 | for(auto param : m_activated_point_parameters) 410 | registerFreeParameterSecondDerForNextFrame(param); 411 | } 412 | 413 | if (parameters_updated) 414 | ImGui::MarkItemEdited(ImGui::GetActiveID()); 415 | 416 | // Reset the saved control point, NB z-position should not be reset 417 | m_activated_point_id = 0; 418 | } 419 | 420 | bool ImControlContext::beginView(const char* name, ImVec2 size, ImVec4 const& border_col, bool defer_changes) 421 | { 422 | IM_ASSERT(m_view_active == false); // We cannot start two views at once 423 | 424 | ImGuiWindow* window = ImGui::GetCurrentWindow(); 425 | if (window->SkipItems) 426 | return false; 427 | 428 | ImGuiContext& g = *GImGui; 429 | ImGui::PushID(name); 430 | m_view_id = window->GetID(0); 431 | 432 | // Handle automatic sizing - taking off space for border 433 | if (size.x <= 0) 434 | size.x = ImGui::GetContentRegionAvail().x - ((border_col.w > 0) ? 2.0f : 0.0f); 435 | if (size.y <= 0) 436 | size.y = ImGui::GetContentRegionAvail().y - ((border_col.w > 0) ? 2.0f : 0.0f); 437 | 438 | m_view_marker_size_factor = sqrtf(size.x * size.y); 439 | 440 | // Bounding box used for clipping 3D view and optional border 441 | m_cursor_pos_before_view = ImGui::GetCursorScreenPos(); 442 | m_view_bb = ImRect{ m_cursor_pos_before_view, m_cursor_pos_before_view + size }; 443 | 444 | if (border_col.w > 0.0f) 445 | m_view_bb.Max += ImVec2(2, 2); 446 | 447 | ImGui::ItemSize(m_view_bb); 448 | if (!ImGui::ItemAdd(m_view_bb, m_view_id)) 449 | { 450 | ImGui::PopID(); 451 | return false; 452 | } 453 | 454 | m_view_active = true; 455 | m_view_size = size; 456 | m_view_defers_changes = defer_changes; 457 | 458 | if (border_col.w > 0.0f) 459 | { 460 | window->DrawList->AddRect(m_view_bb.Min, m_view_bb.Max, ImGui::GetColorU32(border_col), 0.0f); 461 | } 462 | ImGui::PushClipRect(m_view_bb.Min, m_view_bb.Max, true); 463 | 464 | if (!m_view_defers_changes) 465 | pushDeferralSlot(); 466 | 467 | return true; 468 | } 469 | 470 | bool ImControlContext::endView(bool include_button, ImGuiButtonFlags flags) 471 | { 472 | if (!m_view_active) 473 | return false; 474 | 475 | // Set the Dear ImGui state to where it should be after the view widget 476 | ImGui::PopID(); 477 | ImGui::PopClipRect(); 478 | 479 | if (!m_view_defers_changes) 480 | popDeferralSlot(); 481 | 482 | bool ret_value = true; 483 | if (include_button) { 484 | bool hovered, held; 485 | ret_value = ImGui::ButtonBehavior(m_view_bb, m_view_id, &hovered, &held, flags); 486 | } 487 | 488 | ImGui::SetCursorScreenPos(m_cursor_position_after_view); 489 | 490 | m_view_id = 0; 491 | m_view_active = false; 492 | 493 | return ret_value; 494 | } 495 | 496 | void ImControlContext::pushDeferralSlot() 497 | { 498 | m_deferral_stack.pushDeferralSlot(); 499 | } 500 | 501 | bool ImControlContext::popDeferralSlot() 502 | { 503 | return m_deferral_stack.popDeferralSlot(); 504 | } 505 | 506 | ImVec2 ImControlContext::getViewBoundsMin() const 507 | { 508 | return m_cursor_pos_before_view; 509 | } 510 | 511 | ImVec2 ImControlContext::getViewBoundsMax() const 512 | { 513 | return m_cursor_pos_before_view + m_view_size; 514 | } 515 | 516 | const ImVec2& ImControlContext::getViewSize() const 517 | { 518 | return m_view_size; 519 | } 520 | 521 | 522 | void ImControlContext::drawDerivative(ImVec2 const& pos, ImVec2 const& d, ImVec4 const& col, float thickness) const 523 | { 524 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 525 | ImVec2 start_vector = viewCoordsToScreenCoords(pos); 526 | ImVec2 end_vector = viewCoordsToScreenCoords(pos + d); 527 | draw_list->AddLine(start_vector, end_vector, ImGui::GetColorU32(col), thickness); 528 | } 529 | 530 | ImVec2 ImControlContext::getMousePositionInViewCoords() const 531 | { 532 | ImVec2 mouse_pos = ImGui::GetMousePos(); 533 | return screenCoordsToViewCoords(mouse_pos); 534 | } 535 | 536 | ImVec2 ImControlContext::viewCoordsToScreenCoords(ImVec2 const& v) const 537 | { 538 | ImVec2 result{ v }; 539 | result += ImVec2(1, 1); 540 | result *= m_view_size / 2; 541 | result += m_cursor_pos_before_view; 542 | return result; 543 | } 544 | 545 | ImVec2 ImControlContext::screenCoordsToViewCoords(ImVec2 const& p) const 546 | { 547 | ImVec2 result{ p }; 548 | result -= m_cursor_pos_before_view; 549 | result /= m_view_size / 2; 550 | result -= ImVec2(1, 1); 551 | return result; 552 | } 553 | 554 | bool ImControlContext::createPoint(const char* str_id, ImVec4 const& transformed_pos, ImControlPointFlags const& flags, ImGuiButtonFlags const& button_flags, float marker_radius, ImVec4 marker_col) 555 | { 556 | float w = transformed_pos.w; 557 | float z_order = transformed_pos.z / w; 558 | ImVec2 marker_pos = viewCoordsToScreenCoords({ transformed_pos.x / w, transformed_pos.y / w }); 559 | 560 | // Calculate marker size in pixels 561 | if (!(flags & ImControlPointFlags_FixedSize)) 562 | marker_radius /= w; 563 | if (!(flags & ImControlPointFlags_SizeInPixels)) 564 | marker_radius *= m_view_marker_size_factor; 565 | 566 | // An Invisible button cannot have a size of 0, so limit radius to 1.0f 567 | marker_radius = (marker_radius < 1.0f) ? 1.0f : marker_radius; 568 | 569 | ImRect marker_bb(marker_pos - ImVec2{marker_radius, marker_radius}, marker_pos + ImVec2{ marker_radius, marker_radius }); 570 | 571 | m_last_control_point_position = marker_pos; 572 | m_last_control_point_radius = marker_radius; 573 | m_last_step = {}; 574 | 575 | if (z_order < -1.0f) // || (z_order > 1.0f), try this change to limit control points to viewing frustrum 576 | { 577 | // The control point is behind us so create a dummy icon we cannot interact with 578 | ImGui::Dummy({ 0, 0 }); 579 | return false; 580 | } 581 | 582 | m_last_point_z_order = z_order; 583 | 584 | bool cp_activated = false; 585 | bool result{}; 586 | 587 | { // Create invisible button at marker and return cursor to its original position 588 | ImVec2 saved_cursor_screen_pos = ImGui::GetCursorScreenPos(); 589 | ImGui::SetCursorScreenPos(marker_bb.Min); 590 | 591 | // Use an invisible button to handle hovering / clicking / dragging 592 | result = ImGui::InvisibleButton(str_id, ImVec2{ 2 * marker_radius, 2 * marker_radius }, button_flags); 593 | 594 | ImGui::SetItemAllowOverlap(); 595 | 596 | if (ImGui::IsItemActivated()) // Occurs only when first activated 597 | { 598 | cp_activated = true; 599 | ImVec2 vect_to_center = ImGui::GetMousePos() - (marker_bb.Min + marker_bb.Max) * 0.5f; 600 | vect_to_center /= marker_radius; // Convert to coords local to marker 601 | float r2 = vect_to_center.x * vect_to_center.x + vect_to_center.y * vect_to_center.y; 602 | 603 | if (flags & ImControlPointFlags_ChooseClosestWhenOverlapping) 604 | m_last_point_z_order = r2; 605 | 606 | if ((flags & ImControlPointFlags_Circular) && (r2 > 1.0f)) { 607 | // Mouse is outside circle inscribed in bbox 608 | cp_activated = false; 609 | ImGui::ClearActiveID(); 610 | } 611 | } 612 | 613 | ImGui::SetCursorScreenPos(saved_cursor_screen_pos); 614 | } 615 | 616 | // Draw the marker 617 | if ((flags & ImControlPointFlags_DrawControlPointMarkers) && marker_col.w > 0.0f) 618 | { 619 | if (flags & ImControlPointFlags_Circular) 620 | { 621 | if (ImGui::IsItemActive()) 622 | ImGui::GetWindowDrawList()->AddCircleFilled((marker_bb.Min + marker_bb.Max) / 2, marker_radius, ImGui::GetColorU32(marker_col)); 623 | else 624 | ImGui::GetWindowDrawList()->AddCircle((marker_bb.Min + marker_bb.Max) / 2, marker_radius, ImGui::GetColorU32(marker_col)); 625 | } 626 | else 627 | { 628 | if (ImGui::IsItemActive()) 629 | ImGui::GetWindowDrawList()->AddRectFilled(marker_bb.Min, marker_bb.Max, ImGui::GetColorU32(marker_col)); 630 | else 631 | ImGui::GetWindowDrawList()->AddRect(marker_bb.Min, marker_bb.Max, ImGui::GetColorU32(marker_col), 0.0f); 632 | } 633 | } 634 | return result; 635 | } 636 | 637 | bool ImControlContext::saveActivatedPoint(const ImVector& params) 638 | { 639 | // If there is already a control point infront of this one then return 640 | if (m_activated_point_id && (m_last_point_z_order > m_activated_point_z_order)) 641 | return false; 642 | 643 | m_activated_point_id = ImGui::GetItemID(); 644 | m_activated_point_window = ImGui::GetCurrentWindow(); 645 | m_activated_point_z_order = m_last_point_z_order; 646 | m_activated_point_parameters = params; 647 | return true; 648 | } 649 | 650 | bool ImControlContext::updateParameters(ImVector const& changes, ImControlPointFlags const& flags) 651 | { 652 | m_last_step = changes; 653 | 654 | if (flags & ImControlPointFlags_DoNotChangeParams) 655 | return false; 656 | 657 | m_deferral_stack.addParameterChange(changes); 658 | 659 | if (flags & ImControlPointFlags_ApplyParamChangesImmediately) 660 | { 661 | bool change_applied = m_deferral_stack.applyParameterChanges(); 662 | if (change_applied) 663 | { 664 | IM_ASSERT(ImGui::IsItemActive()); // If we are making parameter changes immediately the previous item must be active and marking it as edited makes sense 665 | ImGui::MarkItemEdited(ImGui::GetItemID()); 666 | } 667 | return change_applied; 668 | } 669 | return false; 670 | } 671 | 672 | void ImControlContext::registerFreeParameterDerivativeForNextFrame(param_t param) 673 | { 674 | m_registered_der_params.push_back(param); 675 | } 676 | 677 | void ImControlContext::registerFreeParameterSecondDerForNextFrame(param_t p) 678 | { 679 | m_registered_2nd_der_params.push_back(p); 680 | } 681 | 682 | ImVec2 ImControlContext::pointInScreenCoords(const Transformations::TransformationStack& transform, const ImVec4& pos) 683 | { 684 | ImVec4 transformed_pos = transform.apply(pos); 685 | ImVec2 view_coords{ transformed_pos.x / transformed_pos.w, transformed_pos.y / transformed_pos.w }; 686 | return viewCoordsToScreenCoords(view_coords); 687 | } 688 | 689 | bool ImControlContext::staticControlPoint(const char* str_id, const Transformations::TransformationStack& transform, const ImVec4& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col) 690 | { 691 | ImVec4 transformed_pos = transform.apply(pos); 692 | bool result = createPoint(str_id, transformed_pos, flags, button_flags, marker_radius, marker_col); 693 | if (ImGui::IsItemActivated()) 694 | saveActivatedPoint(); 695 | return result; 696 | } 697 | 698 | bool ImControlContext::controlPoint(const char* str_id, const Transformations::TransformationStack& transform, const ImVec4& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col) 699 | { 700 | IM_ASSERT(m_free_parameters.size() > 0); // Else nothing to change, may relax this in future 701 | 702 | ImVec4 transformed_pos = transform.apply(pos); 703 | 704 | bool result = createPoint(str_id, transformed_pos, flags, button_flags, marker_radius, marker_col); 705 | 706 | if (ImGui::IsItemActive() && !ImGui::IsItemActivated()) 707 | { 708 | // Derivatives with respect to the parameters are required to move the control point in the next frame 709 | for (auto param : m_free_parameters) { 710 | registerFreeParameterDerivativeForNextFrame(param); 711 | registerFreeParameterSecondDerForNextFrame(param); 712 | } 713 | 714 | ImVec2 mp = getMousePositionInViewCoords(); 715 | ImVec4 mouse_in_view_coords{ mp[0], mp[1], transformed_pos.z / transformed_pos.w, 1.0f }; 716 | 717 | // Could add this alternative behaviour behind a flag 718 | //ImVec4 mouse_in_view_coords{ mp[0], mp[1], m_activated_point_z_order, 1.0f }; 719 | 720 | // Used to change the parameters 721 | ImVector step = bring_together(transform, pos, mouse_in_view_coords, m_free_parameters, m_regularisation_constant); 722 | 723 | constexpr float softening_param = 1.0f; 724 | ImVector changes{}; 725 | for (int i = 0; i < m_free_parameters.size(); ++i) // Zip the free parameters and their changes 726 | changes.push_back({ m_free_parameters[i], -softening_param * step[i] }); 727 | 728 | updateParameters(changes, flags); 729 | 730 | if (flags & ImControlPointFlags_DrawParamDerivatives) 731 | { 732 | // Draw tangent vector(s) 733 | for (auto p : m_free_parameters) { 734 | ImVec4 tangent = calculate_tangent_after_projection(transform, pos, p); 735 | transformed_pos /= transformed_pos.w; 736 | tangent -= transformed_pos * tangent.w; // Required in unusual case when w-coord is non-zero 737 | drawDerivative({ transformed_pos.x, transformed_pos.y }, { tangent.x, tangent.y }, marker_col); 738 | } 739 | } 740 | } 741 | else if (ImGui::IsItemActivated()) 742 | { 743 | saveActivatedPoint(m_free_parameters); 744 | } 745 | return result; 746 | } 747 | 748 | ImControlContext g_current_context{}; 749 | Transformations::TransformationStack g_current_transform{}; 750 | 751 | void NewFrame() { 752 | g_current_context.endFrame(); 753 | g_current_context.newFrame(g_current_transform); 754 | } 755 | 756 | bool BeginView(const char* name, ImVec2 const& size, ImVec4 const& border_color, bool defer_changes) 757 | { 758 | return g_current_context.beginView(name, size, border_color, defer_changes); 759 | } 760 | 761 | void EndView() 762 | { 763 | g_current_context.endView(false); 764 | } 765 | 766 | bool EndViewAsButton(ImGuiWindowFlags flags) 767 | { 768 | return g_current_context.endView(true, flags); 769 | } 770 | 771 | void PushDeferralSlot() 772 | { 773 | g_current_context.pushDeferralSlot(); 774 | } 775 | 776 | void PopDeferralSlot() 777 | { 778 | g_current_context.popDeferralSlot(); 779 | } 780 | 781 | void BeginCompositeTransformation() 782 | { 783 | g_current_transform.pushCompositeLevel(); 784 | } 785 | 786 | void EndCompositeTransformation() 787 | { 788 | g_current_transform.popCompositeLevel(); 789 | } 790 | 791 | ImVec2 GetViewBoundsMin() 792 | { 793 | return g_current_context.getViewBoundsMin(); 794 | } 795 | 796 | ImVec2 GetViewBoundsMax() 797 | { 798 | return g_current_context.getViewBoundsMax(); 799 | } 800 | 801 | ImVec2 GetViewSize() 802 | { 803 | return g_current_context.getViewSize(); 804 | } 805 | 806 | float GetViewAspectRatio() 807 | { 808 | auto& size = g_current_context.getViewSize(); 809 | return size.x / size.y; 810 | } 811 | 812 | ImVec2 GetLastControlPointPosition() { 813 | return g_current_context.getLastControlPointPosition(); 814 | } 815 | 816 | float GetLastControlPointRadius() { 817 | return g_current_context.getLastControlPointRadius(); 818 | } 819 | 820 | void SetRegularisationParameter(float kappa) 821 | { 822 | g_current_context.setRegularisationParam(kappa); 823 | } 824 | 825 | ImVec4 ApplyTransformation(ImVec4 const& pos) 826 | { 827 | return g_current_transform.apply(pos); 828 | } 829 | 830 | ImVec2 ApplyTransformation(ImVec2 const& pos) 831 | { 832 | ImVec4 embedded_pos{ pos.x, pos.y, 0.0f, 1.0f }; 833 | ImVec4 result = ApplyTransformation(embedded_pos); 834 | return ImVec2{ result.x / result.w, result.y / result.w }; 835 | } 836 | 837 | ImVec2 ApplyTransformationScreenCoords(ImVec2 const& pos) 838 | { 839 | return g_current_context.pointInScreenCoords(g_current_transform, { pos.x, pos.y, 0.0f, 1.0f }); 840 | } 841 | 842 | ImVec2 ApplyTransformationScreenCoords(ImVec4 const& pos) 843 | { 844 | return g_current_context.pointInScreenCoords(g_current_transform, pos); 845 | } 846 | 847 | void PushFreeParameter(float* param) 848 | { 849 | g_current_context.pushFreeParameter(param); 850 | } 851 | 852 | void PopFreeParameter() 853 | { 854 | g_current_context.popFreeParameter(); 855 | } 856 | 857 | void RestrictParameter(float* param) 858 | { 859 | g_current_context.restrictParameter(param); 860 | } 861 | 862 | void ClearFreeParameters() 863 | { 864 | g_current_context.clearParameters(); 865 | } 866 | 867 | bool Button(const char* str_id, ImVec2 const& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col, float z_order) 868 | { 869 | return g_current_context.staticControlPoint(str_id, g_current_transform, { pos.x, pos.y, z_order, 1 }, flags, button_flags, marker_radius, marker_col); 870 | } 871 | 872 | bool Button(const char* str_id, ImVec4 const& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col) 873 | { 874 | return g_current_context.staticControlPoint(str_id, g_current_transform, pos, flags, button_flags, marker_radius, marker_col); 875 | } 876 | 877 | bool Point(const char* str_id, ImVec2 const& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col, float z_order) 878 | { 879 | return g_current_context.controlPoint(str_id, g_current_transform, { pos.x, pos.y, z_order, 1 }, flags, button_flags, marker_radius, marker_col); 880 | } 881 | 882 | bool Point(const char* str_id, ImVec4 const& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col) 883 | { 884 | return g_current_context.controlPoint(str_id, g_current_transform, pos, flags, button_flags, marker_radius, marker_col); 885 | } 886 | 887 | void PushConstantMatrix(ImMat4 const& M) { 888 | g_current_transform.pushConstantTransformation(Transformations::ConstantMatrix{ M }); 889 | } 890 | 891 | void PushConstantPerspectiveMatrix(float fov, float aspect_ratio, float z_near, float z_far) { 892 | float tan_half_fov = tanf(fov / 2); 893 | PushConstantMatrix(ImMat4{ 894 | ImVec4{1 / (aspect_ratio * tan_half_fov), 0, 0, 0}, 895 | ImVec4{0, 1 / tan_half_fov, 0, 0}, 896 | ImVec4{0, 0, -(z_near + z_far) / (z_far - z_near), -1}, 897 | ImVec4{0, 0, -2 * z_far * z_near / (z_far - z_near), 0} 898 | }); 899 | } 900 | 901 | void PushConstantLookAtMatrix(ImVec4 const& eye, ImVec4 const& center, ImVec4 const& up) { 902 | IM_ASSERT(center.w != 0.0f); IM_ASSERT(eye.w != 0.0f); IM_ASSERT(up.w == 0.0f); 903 | ImVec4 const f{ normalise(center / center.w - eye / eye.w) }; 904 | ImVec4 const s{ normalise(cross(f, up)) }; 905 | ImVec4 const u{ cross(s, f) }; 906 | PushConstantMatrix(ImMat4{ 907 | ImVec4{ s.x, u.x, -f.x, 0 }, 908 | ImVec4{ s.y, u.y, -f.y, 0 }, 909 | ImVec4{ s.z, u.z, -f.z, 0 }, 910 | ImVec4{-dot(s, eye), -dot(u, eye), dot(f, eye), 1.0f } 911 | }); 912 | } 913 | 914 | void PushRotationAboutAxis(float* parameter, ImVec4 const& axis) { g_current_transform.pushTransformation(Transformations::RotationAroundAxis{ axis, *parameter }, parameter); } 915 | void PushConstantRotationAboutAxis(float angle, ImVec4 const& axis) { 916 | Transformations::RotationAroundAxis T{ axis, angle }; 917 | g_current_transform.pushConstantTransformation(T); 918 | } 919 | void PushRotationAboutX(float* parameter) { g_current_transform.pushTransformation(Transformations::RotationX{ *parameter }, parameter); } 920 | void PushRotationAboutY(float* parameter) { g_current_transform.pushTransformation(Transformations::RotationY{ *parameter }, parameter); } 921 | void PushRotationAboutZ(float* parameter) { g_current_transform.pushTransformation(Transformations::RotationZ{ *parameter }, parameter); } 922 | void PushRotationAboutX(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::RotationX{ param.value }, param); } 923 | void PushRotationAboutY(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::RotationY{ param.value }, param); } 924 | void PushRotationAboutZ(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::RotationZ{ param.value }, param); } 925 | void PushConstantRotationAboutX(float angle) { g_current_transform.pushConstantTransformation(Transformations::RotationX{ angle }); } 926 | void PushConstantRotationAboutY(float angle) { g_current_transform.pushConstantTransformation(Transformations::RotationY{ angle }); } 927 | void PushConstantRotationAboutZ(float angle) { g_current_transform.pushConstantTransformation(Transformations::RotationZ{ angle }); } 928 | 929 | void PushRotation(float* parameter) { PushRotationAboutZ(parameter); } 930 | void PushRotation(const Parameters::Parameter& param) { PushRotationAboutZ(param); } 931 | void PushConstantRotation(float angle) { PushConstantRotationAboutZ(angle); } 932 | 933 | void PushScale(float* parameter) { g_current_transform.pushTransformation(Transformations::Scale{ *parameter }, parameter); } 934 | void PushScaleX(float* parameter) { g_current_transform.pushTransformation(Transformations::ScaleCoordinate<0>{ *parameter }, parameter); } 935 | void PushScaleY(float* parameter) { g_current_transform.pushTransformation(Transformations::ScaleCoordinate<1>{ *parameter }, parameter); } 936 | void PushScaleZ(float* parameter) { g_current_transform.pushTransformation(Transformations::ScaleCoordinate<2>{ *parameter }, parameter); } 937 | void PushScaleAlongAxis(float* parameter, const ImVec4& axis) { g_current_transform.pushTransformation(Transformations::ScaleInDirection{ *parameter, axis }, parameter); } 938 | void PushScaleFromAxis(float* parameter, const ImVec4& axis) { g_current_transform.pushTransformation(Transformations::ScaleAboutAxis{ *parameter, axis }, parameter); } 939 | void PushScale(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::Scale{ param.value }, param); } 940 | void PushScaleX(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::ScaleCoordinate<0>{ param.value }, param); } 941 | void PushScaleY(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::ScaleCoordinate<1>{ param.value }, param); } 942 | void PushScaleZ(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::ScaleCoordinate<2>{ param.value }, param); } 943 | void PushScaleAlongAxis(const Parameters::Parameter& param, const ImVec4& axis) { g_current_transform.pushTransformation(Transformations::ScaleInDirection{ param.value, axis }, param); } 944 | void PushScaleFromAxis(const Parameters::Parameter& param, const ImVec4& axis) { g_current_transform.pushTransformation(Transformations::ScaleAboutAxis{ param.value, axis }, param); } 945 | void PushConstantScale(float factor) { g_current_transform.pushConstantTransformation(Transformations::Scale{ factor }); } 946 | void PushConstantScaleX(float factor) { g_current_transform.pushConstantTransformation(Transformations::ScaleCoordinate<0>{ factor }); } 947 | void PushConstantScaleY(float factor) { g_current_transform.pushConstantTransformation(Transformations::ScaleCoordinate<1>{ factor }); } 948 | void PushConstantScaleZ(float factor) { g_current_transform.pushConstantTransformation(Transformations::ScaleCoordinate<2>{ factor }); } 949 | void PushConstantScaleAlongAxis(float factor, const ImVec4& axis) { g_current_transform.pushConstantTransformation(Transformations::ScaleInDirection{ factor, axis }); } 950 | void PushConstantScaleFromAxis(float factor, const ImVec4& axis) { g_current_transform.pushConstantTransformation(Transformations::ScaleAboutAxis{ factor, axis }); } 951 | void PushConstantReflection(const ImVec4& normal) { PushConstantScaleAlongAxis(-1.0f, normal); } 952 | 953 | void PushTranslation(float* parameter, ImVec4 const& v) { g_current_transform.pushTransformation(Transformations::Translation{ v, *parameter }, parameter); } 954 | void PushTranslation(float* parameter, ImVec2 const& v) { g_current_transform.pushTransformation(Transformations::Translation{ { v.x, v.y, 0.0f, 0.0f }, *parameter }, parameter); } 955 | void PushTranslation(const Parameters::Parameter& param, ImVec4 const& v) { g_current_transform.pushTransformation(Transformations::Translation{ v, param.value }, param); } 956 | void PushTranslation(const Parameters::Parameter& param, ImVec2 const& v) { g_current_transform.pushTransformation(Transformations::Translation{ { v.x, v.y, 0.0f, 0.0f }, param.value }, param); } 957 | void PushConstantTranslation(float value, ImVec4 const& v) { g_current_transform.pushConstantTransformation(Transformations::Translation{ v, value }); } 958 | void PushConstantTranslation(float value, ImVec2 const& v) { g_current_transform.pushConstantTransformation(Transformations::Translation{ { v.x, v.y, 0.0f, 0.0f }, value }); } 959 | 960 | void PushTranslationAlongX(float* parameter) { g_current_transform.pushTransformation(Transformations::TranslationOfCoordinate<0>{ *parameter }, parameter); } 961 | void PushTranslationAlongY(float* parameter) { g_current_transform.pushTransformation(Transformations::TranslationOfCoordinate<1>{ *parameter }, parameter); } 962 | void PushTranslationAlongZ(float* parameter) { g_current_transform.pushTransformation(Transformations::TranslationOfCoordinate<2>{ *parameter }, parameter); } 963 | void PushTranslationAlongX(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::TranslationOfCoordinate<0>{ param.value }, param); } 964 | void PushTranslationAlongY(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::TranslationOfCoordinate<1>{ param.value }, param); } 965 | void PushTranslationAlongZ(const Parameters::Parameter& param) { g_current_transform.pushTransformation(Transformations::TranslationOfCoordinate<2>{ param.value }, param); } 966 | void PushConstantTranslationAlongX(float value) { g_current_transform.pushConstantTransformation(Transformations::TranslationOfCoordinate<0>{ value }); } 967 | void PushConstantTranslationAlongY(float value) { g_current_transform.pushConstantTransformation(Transformations::TranslationOfCoordinate<1>{ value }); } 968 | void PushConstantTranslationAlongZ(float value) { g_current_transform.pushConstantTransformation(Transformations::TranslationOfCoordinate<2>{ value }); } 969 | 970 | void PopTransformation() { g_current_transform.popTransformation(); } 971 | void ClearTransformations() { g_current_transform.clear(); } 972 | 973 | // Composite transformations 974 | void PushTranslation(float* x, float* y) 975 | { 976 | BeginCompositeTransformation(); 977 | PushTranslationAlongX(x); 978 | PushTranslationAlongY(y); 979 | EndCompositeTransformation(); 980 | } 981 | 982 | void PushTranslation(float* x, float* y, float* z) { 983 | BeginCompositeTransformation(); 984 | PushTranslationAlongX(x); 985 | PushTranslationAlongY(y); 986 | PushTranslationAlongZ(z); 987 | EndCompositeTransformation(); 988 | } 989 | 990 | void PushRotationAboutPoint(float* angle, const ImVec2& p) 991 | { 992 | BeginCompositeTransformation(); 993 | PushConstantTranslation(-1.0f, p); 994 | PushRotation(angle); 995 | PushConstantTranslation(1.0f, p); 996 | EndCompositeTransformation(); 997 | } 998 | 999 | void PushScaleXY(float* factor) 1000 | { 1001 | BeginCompositeTransformation(); 1002 | PushScaleX(factor); 1003 | PushScaleY(factor); 1004 | EndCompositeTransformation(); 1005 | } 1006 | 1007 | ImMat4 GetTransformationMatrix() 1008 | { 1009 | return g_current_transform.getMatrix(); 1010 | } 1011 | 1012 | int GetTransformationStackWidth() 1013 | { 1014 | return static_cast(g_current_transform.getStackWidth()); 1015 | } 1016 | 1017 | ImVec4 GetDerivativeAt(ImVec4 const& v, float* parameter) 1018 | { 1019 | g_current_context.registerFreeParameterDerivativeForNextFrame(parameter); 1020 | return g_current_transform.applyDerivative(v, parameter); 1021 | } 1022 | 1023 | ImVec4 GetSecondDerivativeAt(ImVec4 const& v, float* p1, float* p2) 1024 | { 1025 | g_current_context.registerFreeParameterSecondDerForNextFrame(p1); 1026 | g_current_context.registerFreeParameterSecondDerForNextFrame(p2); 1027 | return g_current_transform.applySecondDerivative(v, p1, p2); 1028 | } 1029 | 1030 | void ParameterChangeDeferralStack::addParameterChange(ImVector const& changes) 1031 | { 1032 | IM_ASSERT(m_deferral_stack_size > 0); // There should be a slot in which to place our deferred changes 1033 | m_deferred_changes = changes; 1034 | m_deferred_change_position = m_deferral_stack_size - 1; // Step is at the end of the stack 1035 | } 1036 | 1037 | void ParameterChangeDeferralStack::reset() 1038 | { 1039 | m_deferral_stack_size = 1; 1040 | m_deferred_change_position = -1; 1041 | m_deferred_changes = {}; 1042 | } 1043 | 1044 | bool ParameterChangeDeferralStack::applyParameterChanges() 1045 | { 1046 | bool has_changed = false; 1047 | 1048 | for (auto const& change : m_deferred_changes) { 1049 | if (change.change && change.parameter) 1050 | { 1051 | *(change.parameter) += change.change; 1052 | has_changed = true; 1053 | } 1054 | } 1055 | m_deferred_change_position = -1; 1056 | m_deferred_changes = {}; 1057 | return has_changed; 1058 | } 1059 | 1060 | void ParameterChangeDeferralStack::pushDeferralSlot() 1061 | { 1062 | ++m_deferral_stack_size; 1063 | } 1064 | 1065 | bool ParameterChangeDeferralStack::popDeferralSlot() 1066 | { 1067 | IM_ASSERT(m_deferral_stack_size > 0); // We shouldn't pop an empty stack 1068 | 1069 | bool change_applied = false; 1070 | 1071 | // If there is a change at the top of the stack then apply it 1072 | if (m_deferred_change_position + 1 == m_deferral_stack_size) 1073 | change_applied = applyParameterChanges(); 1074 | 1075 | // Reduce the size 1076 | --m_deferral_stack_size; 1077 | return change_applied; 1078 | } 1079 | } // end namespace ImControl -------------------------------------------------------------------------------- /imcontrol.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Include(s) 4 | #include "math.h" 5 | #include "imgui.h" 6 | 7 | // Version 8 | #define IMCONTROL_VERSION "0.1" 9 | 10 | // Matrix classes - required operations defined in imcontrol_internal.h 11 | struct ImMat4 { 12 | ImVec4 col[4]; 13 | inline ImVec4& operator[](unsigned int i) { return col[i]; } 14 | inline const ImVec4& operator[](unsigned int i) const { return col[i]; } 15 | }; 16 | 17 | struct ImMat2 { 18 | ImVec2 col[2]; 19 | }; 20 | 21 | typedef int ImControlPointFlags; 22 | 23 | enum ImControlPointFlags_ 24 | { 25 | ImControlPointFlags_DrawControlPointMarkers = 1 << 0, 26 | ImControlPointFlags_DrawParamDerivatives = 1 << 1, 27 | ImControlPointFlags_DoNotChangeParams = 1 << 2, 28 | ImControlPointFlags_ApplyParamChangesImmediately = 1 << 3, 29 | ImControlPointFlags_Circular = 1 << 4, 30 | ImControlPointFlags_FixedSize = 1 << 5, 31 | ImControlPointFlags_SizeInPixels = 1 << 6, 32 | ImControlPointFlags_ChooseClosestWhenOverlapping = 1 << 7, 33 | }; 34 | 35 | 36 | namespace ImControl { 37 | 38 | namespace Parameters { 39 | // For reparametrisation of transformations we require more than the 40 | // value of the reparametrisation, we also need the first and second 41 | // derivatives, this class holds exactly the information we require. 42 | // The usual arithmetic operations apply with multiplication given by 43 | // the product rule. 44 | class Parameter { 45 | public: 46 | explicit Parameter(float* param) : p{ param }, value{ *param }, d{ 1.0f }, d2{ 0.0f } {} 47 | Parameter(float* param, float val, float derivative, float second_derivative) : p{ param }, value{ val }, d{ derivative }, d2{ second_derivative } {} 48 | 49 | Parameter operator+(float b) const { return { p, value + b, d, d2 }; } 50 | Parameter operator*(float a) const { return { p, value * a, d * a, d2 * a }; } 51 | Parameter operator/(float a) const { return { p, value / a, d / a, d2 / a }; } 52 | Parameter operator+(const Parameters::Parameter& g) const { IM_ASSERT(p == g.p); return { p, value + g.value, d + g.d, d2 + g.d2 }; } 53 | Parameter operator-(const Parameters::Parameter& g) const { IM_ASSERT(p == g.p); return { p, value - g.value, d - g.d, d2 - g.d2 }; } 54 | Parameter operator*(const Parameters::Parameter& g) const { IM_ASSERT(p == g.p); return { p, value * g.value, d * g.value + value * g.d, d2 * g.value + 2 * d * g.d + value * g.d2 }; } // Product rule 55 | Parameter reciprocal() const { return { p, 1 / value, -d / (value * value), (-d2 * value + 2 * d * d) / (value * value * value) }; } 56 | Parameter operator/(const Parameters::Parameter& g) const { IM_ASSERT(p == g.p); return *this * g.reciprocal(); } 57 | 58 | float value; // value 59 | 60 | // These member variables may change to allow for multi-variate parameters - beware 61 | float* p; 62 | float d; // derivative 63 | float d2; // second derivative 64 | }; 65 | 66 | inline Parameter Exponential(const Parameters::Parameter& f) { float ef = expf(f.value); return { f.p, ef, f.d * ef, ef * (f.d2 + f.d * f.d) }; } // x --> exp(x) 67 | inline Parameter Logarithm(const Parameters::Parameter& f) { float q = f.d / f.value; return { f.p, logf(f.value), q, -q * q + f.d2 / f.value }; } // x --> log(x) 68 | inline Parameter Power(float alpha, const Parameters::Parameter& f) { float pf = powf(f.value, alpha); return { f.p, pf, alpha * f.d * pf / f.value, alpha * f.d2 * pf / f.value + alpha * (alpha - 1) * pf / (f.value * f.value) }; } // x --> x^alpha 69 | inline Parameter Sqrt(const Parameters::Parameter& f) { float rf = sqrtf(f.value); return { f.p, rf, f.d / (2 * rf), (4 * f.d2 * f.value - f.d * f.d) / (8 * f.value * rf) }; } // x --> sqrt(x) 70 | inline Parameter Reciprocal(const Parameters::Parameter& f) { return f.reciprocal(); } // x --> 1 / x 71 | inline Parameter Linear(float a, float b, const Parameters::Parameter& f) { return f * a + b; } // x --> ax + b 72 | inline Parameter HypSine(const Parameters::Parameter& f) { return (Exponential(f) - Exponential(f * (-1))) / 2; } // x --> sinh(x) 73 | inline Parameter HypCosine(const Parameters::Parameter& f) { return (Exponential(f) + Exponential(f * (-1))) / 2; } // x --> cosh(x) 74 | inline Parameter Sine(const Parameters::Parameter& f) { float cf = cosf(f.value), sf = sinf(f.value); return { f.p, sf, cf * f.d, -sf * f.d * f.d + cf * f.d2 }; } // x --> sin(x) 75 | inline Parameter Cosine(const Parameters::Parameter& f) { float cf = cosf(f.value), sf = sinf(f.value); return { f.p, cf, -sf * f.d, -cf * f.d * f.d - sf * f.d2 }; } // x --> cos(x) 76 | inline Parameter Tangent(const Parameters::Parameter& f) { return Sine(f) / Cosine(f); } // x --> tan(x) 77 | } 78 | 79 | // Should be called at the beginning of every frame 80 | void NewFrame(); 81 | 82 | // Creates a bounding box in which all control points are clipped. This has 83 | // its own coordinate system with x-coords and y-coords lying between -1 and 84 | // 1. It cannot currently be restarted, unlike an ImGui Window. 85 | bool BeginView(const char* name, ImVec2 const& size, ImVec4 const& border_color, bool deferchanges = false); 86 | 87 | // Ends the view, applying any parameter changes, unless deferchanges is true 88 | void EndView(); 89 | // Also creates a button in the view's place, which handles inputs that miss a control point. 90 | bool EndViewAsButton(ImGuiWindowFlags flags = 0); 91 | 92 | ImVec2 GetViewBoundsMin(); // In screen coords 93 | ImVec2 GetViewBoundsMax(); // In screen coords 94 | ImVec2 GetViewSize(); // In screen coords 95 | float GetViewAspectRatio(); 96 | 97 | // Typically we want to apply changes at the end of the view, however if we 98 | // wish to bring some changes forward we can use a deferral slot, changes 99 | // will be applied when the slot is popped. 100 | void PushDeferralSlot(); 101 | void PopDeferralSlot(); 102 | 103 | // A composite transformation is represented by a single level on the stack, 104 | // new transformations are applied in place. This has a minor performance 105 | // benefit and also means that the whole composite can be popped back using 106 | // a single call of PopTransformation. Composite transformations can be 107 | // nested. No PopTransformation can occur within a composite transformation. 108 | void BeginCompositeTransformation(); 109 | void EndCompositeTransformation(); 110 | 111 | ImVec2 GetLastControlPointPosition(); // In screen coords 112 | float GetLastControlPointRadius(); // In screen coords 113 | 114 | // The regularisation parameter controls how aggressively parameters are 115 | // changed. Will change this to use a stack of values. 116 | void SetRegularisationParameter(float kappa); 117 | 118 | // Applies the transformation in the stack to the given coordinate 119 | ImVec2 ApplyTransformation(ImVec2 const& pos); // in view coords 120 | ImVec4 ApplyTransformation(ImVec4 const& pos); // in view coords 121 | ImVec2 ApplyTransformationScreenCoords(ImVec2 const& pos); // in screen coords 122 | ImVec2 ApplyTransformationScreenCoords(ImVec4 const& pos); // in screen coords 123 | 124 | // Free parameters can be changed by a control point 125 | void PushFreeParameter(float* param); 126 | void PopFreeParameter(); 127 | void RestrictParameter(float* param); // Removes a given parameter from the stack, retaining the order 128 | void ClearFreeParameters(); 129 | 130 | // Behaves like an invisible button at the transformed position 131 | bool Button(const char* str_id, ImVec2 const& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col, float z_order); 132 | bool Button(const char* str_id, ImVec4 const& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col); 133 | 134 | // A control point which can be clicked and dragged, changing the free parameters 135 | bool Point(const char* str_id, ImVec2 const& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col, float z_order); 136 | bool Point(const char* str_id, ImVec4 const& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col); 137 | 138 | // Transformations 139 | // 140 | // Parameters are passed either by value, reference or by pointer. If passed 141 | // by value or reference they are considered **constant**, they will not be 142 | // considered when changing parameters. If passed by a pointer then the 143 | // parameter can be changed. To avoid mistakenly passing one type instead of 144 | // the other, all functions taking only values or references will have 145 | // "Constant" in their names. Note that some functions may have a mixture 146 | // of both. 147 | 148 | void PushConstantMatrix(ImMat4 const& M); 149 | void PushConstantPerspectiveMatrix(float fov, float aspect_ratio, float z_near, float z_far); // All parameters are constants 150 | void PushConstantLookAtMatrix(ImVec4 const& eye, ImVec4 const& center, ImVec4 const& up); // All parameters are constants 151 | 152 | void PushRotationAboutAxis(float* parameter, ImVec4 const& axis); 153 | void PushConstantRotationAboutAxis(float angle, ImVec4 const& axis); 154 | 155 | void PushRotationAboutX(float* parameter); 156 | void PushRotationAboutY(float* parameter); 157 | void PushRotationAboutZ(float* parameter); 158 | void PushRotationAboutX(const Parameters::Parameter& param); 159 | void PushRotationAboutY(const Parameters::Parameter& param); 160 | void PushRotationAboutZ(const Parameters::Parameter& param); 161 | void PushConstantRotationAboutX(float angle); 162 | void PushConstantRotationAboutY(float angle); 163 | void PushConstantRotationAboutZ(float angle); 164 | 165 | void PushRotation(float* parameter); // 2d cases 166 | void PushRotation(const Parameters::Parameter& param); 167 | void PushConstantRotation(float angle); 168 | 169 | void PushScale(float* parameter); 170 | void PushScaleX(float* parameter); 171 | void PushScaleY(float* parameter); 172 | void PushScaleZ(float* parameter); 173 | void PushScaleAlongAxis(float* parameter, const ImVec4& axis); 174 | void PushScaleFromAxis(float* parameter, const ImVec4& axis); 175 | void PushScale(const Parameters::Parameter& param); 176 | void PushScaleX(const Parameters::Parameter& param); 177 | void PushScaleY(const Parameters::Parameter& param); 178 | void PushScaleZ(const Parameters::Parameter& param); 179 | void PushScaleAlongAxis(const Parameters::Parameter& param, const ImVec4& axis); 180 | void PushScaleFromAxis(const Parameters::Parameter& param, const ImVec4& axis); 181 | void PushConstantScale(float factor); 182 | void PushConstantScaleX(float factor); 183 | void PushConstantScaleY(float factor); 184 | void PushConstantScaleZ(float factor); 185 | void PushConstantScaleAlongAxis(float factor, const ImVec4& axis); 186 | void PushConstantScaleFromAxis(float factor, const ImVec4& axis); 187 | void PushConstantReflection(const ImVec4& normal); 188 | 189 | void PushTranslation(float* parameter, ImVec4 const& v); 190 | void PushTranslation(float* parameter, ImVec2 const& v); 191 | void PushTranslation(const Parameters::Parameter& param, ImVec4 const& v); 192 | void PushTranslation(const Parameters::Parameter& param, ImVec2 const& v); 193 | void PushTranslationAlongX(float* parameter); 194 | void PushTranslationAlongY(float* parameter); 195 | void PushTranslationAlongZ(float* parameter); 196 | void PushTranslationAlongX(const Parameters::Parameter& param); 197 | void PushTranslationAlongY(const Parameters::Parameter& param); 198 | void PushTranslationAlongZ(const Parameters::Parameter& param); 199 | void PushConstantTranslation(float value, ImVec4 const& v); 200 | void PushConstantTranslation(float value, ImVec2 const& v); 201 | void PushConstantTranslationAlongX(float value); 202 | void PushConstantTranslationAlongY(float value); 203 | void PushConstantTranslationAlongZ(float value); 204 | 205 | void PopTransformation(); 206 | void ClearTransformations(); 207 | 208 | // Composite transformations 209 | void PushRotationAboutPoint(float* angle, const ImVec2& p); 210 | void PushScaleXY(float* factor); 211 | void PushTranslation(float* x, float* y); 212 | void PushTranslation(float* x, float* y, float* z); 213 | 214 | // Note that this only retrieves the derivative if also called in the previous frame, it may return 0 otherwise 215 | ImVec4 GetDerivativeAt(ImVec4 const& v, float* parameter); 216 | ImVec4 GetSecondDerivativeAt(ImVec4 const& v, float* p1, float* p2); 217 | 218 | ImMat4 GetTransformationMatrix(); 219 | 220 | int GetTransformationStackWidth(); 221 | 222 | void ShowDemoWindow(bool*); 223 | void ShowTestsWindow(bool*); 224 | void ShowBenchmarkWindow(bool*); 225 | 226 | } -------------------------------------------------------------------------------- /imcontrol_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "imgui.h" 4 | #include "imcontrol.h" 5 | #include "imcontrol_example_widgets.h" 6 | 7 | namespace ImControl { 8 | 9 | // Individual demo windows 10 | void ShowTreeDemo(bool*); 11 | void ShowCameraDemo(bool*); 12 | void ShowSpiralDemo(bool*); 13 | void ShowArmDemo(bool*); 14 | void ShowDraggableShapesDemo(bool*); 15 | void ShowGaussianDemo(bool*); 16 | void ShowKnotDemo(bool*); 17 | void ShowWidgetDemo(bool*); 18 | 19 | static void HelpMarker(const char* desc) 20 | { 21 | ImGui::TextDisabled("(?)"); 22 | if (ImGui::IsItemHovered()) 23 | { 24 | ImGui::BeginTooltip(); 25 | ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); 26 | ImGui::TextUnformatted(desc); 27 | ImGui::PopTextWrapPos(); 28 | ImGui::EndTooltip(); 29 | } 30 | } 31 | 32 | void ShowDemoWindow(bool* p_open) 33 | { 34 | static bool show_arm_demo = false; 35 | static bool show_draggable_shapes_demo = false; 36 | static bool show_camera_demo = false; 37 | static bool show_gaussian_demo = false; 38 | static bool show_knot_demo = false; 39 | static bool show_spiral_demo = false; 40 | static bool show_tree_demo = false; 41 | static bool show_widget_demo = false; 42 | 43 | static bool show_tests = false; 44 | static bool show_benchmarks = false; 45 | 46 | 47 | ImGui::Begin("Control Point Demos", p_open); 48 | 49 | ImGui::Checkbox("show arm demo window", &show_arm_demo); 50 | ImGui::Checkbox("show shapes demo window", &show_draggable_shapes_demo); 51 | ImGui::Checkbox("show knot demo window", &show_knot_demo); 52 | ImGui::Checkbox("show Gaussian demo window", &show_gaussian_demo); 53 | //ImGui::Checkbox("show spiral demo window", &show_spiral_demo); 54 | ImGui::Checkbox("show tree demo window", &show_tree_demo); 55 | //ImGui::Checkbox("show camera demo window", &show_camera_demo); 56 | ImGui::Checkbox("show widget window", &show_widget_demo); 57 | 58 | ImGui::Separator(); 59 | ImGui::Checkbox("show tests window", &show_tests); 60 | ImGui::Checkbox("show benchmarks window", &show_benchmarks); 61 | 62 | ImGui::Separator(); 63 | ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); 64 | 65 | ImGui::Separator(); 66 | ImGui::Text("Parameters"); 67 | 68 | static float kappa = 0.5f; 69 | ImGui::SliderFloat("regularisation", &kappa, 0.0001f, 2.0f); 70 | ImControl::SetRegularisationParameter(kappa); 71 | 72 | ImGui::End(); 73 | 74 | if (show_spiral_demo) 75 | ShowSpiralDemo(&show_spiral_demo); 76 | 77 | if (show_tree_demo) 78 | ShowTreeDemo(&show_tree_demo); 79 | 80 | if (show_gaussian_demo) 81 | ShowGaussianDemo(&show_gaussian_demo); 82 | 83 | if (show_knot_demo) 84 | ShowKnotDemo(&show_knot_demo); 85 | 86 | if (show_camera_demo) 87 | ShowCameraDemo(&show_camera_demo); 88 | 89 | if (show_arm_demo) 90 | ShowArmDemo(&show_arm_demo); 91 | 92 | if (show_draggable_shapes_demo) 93 | ShowDraggableShapesDemo(&show_draggable_shapes_demo); 94 | 95 | if (show_widget_demo) 96 | ShowWidgetDemo(&show_widget_demo); 97 | 98 | if (show_tests) 99 | ShowTestsWindow(&show_tests); 100 | 101 | if (show_benchmarks) 102 | ShowBenchmarkWindow(&show_benchmarks); 103 | } 104 | 105 | inline float min(float a, float b) { return a < b ? a : b; } 106 | inline float max(float a, float b) { return a > b ? a : b; } 107 | inline float clamp(float x, float a, float b) { return min(max(a, x), b); } 108 | 109 | struct TreeParameters { 110 | float main_length; 111 | float left_angle; 112 | float left_log_scale_factor; 113 | float right_angle; 114 | float right_log_scale_factor; 115 | }; 116 | 117 | using namespace ImControl::Parameters; 118 | 119 | // A custom transformation made up of a composite of basic transformations 120 | void RotateAndExpScale(float* angle, float* log_scaling_factor) 121 | { 122 | ImControl::BeginCompositeTransformation(); 123 | ImControl::PushRotation(angle); 124 | ImControl::PushScaleX(Exponential(Parameter{ log_scaling_factor })); 125 | ImControl::PushScaleY(Exponential(Parameter{ log_scaling_factor })); 126 | ImControl::EndCompositeTransformation(); 127 | } 128 | 129 | void create_tree(TreeParameters* params, int depth, int LR, ImControlPointFlags flags) 130 | { 131 | if (depth <= 0) 132 | return; 133 | 134 | // Current branch 135 | ImVec2 start_point = ImControl::ApplyTransformationScreenCoords({ 0.0f, 0.0f }); 136 | ImControl::PushTranslationAlongY(&(params->main_length)); 137 | if (LR == 0) { 138 | //ImControl::PushFreeParameter(¶ms->main_length); 139 | ImControl::Point("", { 0.0f, 0.0f }, flags, 0, 0.025f, { 0, 0, 1, 1 }, 1.0f / depth); 140 | //ImControl::PopFreeParameter(); 141 | } 142 | else if (LR == 1) { // Right hand branch 143 | //ImControl::PushFreeParameter(¶ms->right_log_scale_factor); 144 | //ImControl::PushFreeParameter(¶ms->right_angle); 145 | ImControl::Point("", { 0.0f, 0.0f }, flags, 0, 0.025f, { 0, 1, 0, 1 }, 1.0f / depth); 146 | //ImControl::PopFreeParameter(); 147 | //ImControl::PopFreeParameter(); 148 | } 149 | else if (LR == -1) { // Left hand branch 150 | //ImControl::PushFreeParameter(¶ms->left_log_scale_factor); 151 | //ImControl::PushFreeParameter(¶ms->left_angle); 152 | ImControl::Point("", { 0.0f, 0.0f }, flags, 0, 0.025f, { 1, 0, 0, 1 }, 1.0f / depth); 153 | //ImControl::PopFreeParameter(); 154 | //ImControl::PopFreeParameter(); 155 | } 156 | ImVec2 end_point = ImControl::ApplyTransformationScreenCoords({ 0.0f, 0.0f }); 157 | 158 | // Draw the branch 159 | ImGui::GetWindowDrawList()->AddLine(start_point, end_point, ImGui::GetColorU32({ 1.0, 1.0, 0.0, 1.0 }), 1.0f); 160 | 161 | // Left branch 162 | RotateAndExpScale(&(params->left_angle), &(params->left_log_scale_factor)); 163 | ImGui::PushID("L"); 164 | create_tree(params, depth - 1, -1, flags); 165 | ImGui::PopID(); 166 | ImControl::PopTransformation(); 167 | 168 | // Right branch 169 | RotateAndExpScale(&(params->right_angle), &(params->right_log_scale_factor)); 170 | ImGui::PushID("R"); 171 | create_tree(params, depth - 1, 1, flags); 172 | ImGui::PopID(); 173 | ImControl::PopTransformation(); 174 | 175 | ImControl::PopTransformation(); // Translation along Y 176 | } 177 | 178 | // An example showing the use of the 2D transformation context, using an static instance of the Draggable2DView class 179 | void ShowTreeDemo(bool* p_open) 180 | { 181 | ImGui::Begin("Fractal Tree - using 2D transforms", p_open, ImGuiWindowFlags_NoScrollbar); 182 | 183 | static int depth = 7; 184 | static TreeParameters parameters{ 0.5f, -1.18f, -0.5f, 0.25f, -0.3f }; 185 | static bool draw_markers = false; 186 | static bool draw_derivatives = false; 187 | 188 | static bool left_angle_free = true; 189 | static bool right_angle_free = true; 190 | static bool left_scale_free = true; 191 | static bool right_scale_free = true; 192 | static bool total_scale_free = false; 193 | 194 | float log_factor_lower_bound = logf(0.2f); 195 | float log_factor_upper_bound = 0; 196 | float angle_lower_bound = -3.1416f / 2; 197 | float angle_upper_bound = 3.1416f / 2; 198 | 199 | ImGui::Checkbox("##leftfactor", &left_scale_free); ImGui::SameLine(); 200 | ImGui::SliderFloat("left branch log factor", ¶meters.left_log_scale_factor, log_factor_lower_bound, log_factor_upper_bound); 201 | 202 | ImGui::Checkbox("##leftangle", &left_angle_free); ImGui::SameLine(); 203 | ImGui::SliderFloat("left branch angle", ¶meters.left_angle, angle_lower_bound, angle_upper_bound); 204 | 205 | ImGui::Checkbox("##rightscale", &right_scale_free); ImGui::SameLine(); 206 | ImGui::SliderFloat("right branch log factor", ¶meters.right_log_scale_factor, log_factor_lower_bound, log_factor_upper_bound); 207 | 208 | ImGui::Checkbox("##rightangle", &right_angle_free); ImGui::SameLine(); 209 | ImGui::SliderFloat("right branch angle", ¶meters.right_angle, angle_lower_bound, angle_upper_bound); 210 | 211 | ImGui::Checkbox("##totalscale", &total_scale_free); ImGui::SameLine(); 212 | ImGui::SliderFloat("stem length", ¶meters.main_length, 0.1f, 1.5f); 213 | 214 | bool dummybool{ false }; ImGui::Checkbox("##dummyfortree", &dummybool); ImGui::SameLine(); // for alignment only 215 | ImGui::SliderInt("tree depth", &depth, 1, 12); 216 | 217 | ImGui::Checkbox("draw control points", &draw_markers); 218 | ImGui::Checkbox("draw derivatives", &draw_derivatives); 219 | 220 | if (ImControl::BeginView("tree_view", ImVec2{ -1, -1 }, ImVec4(1, 1, 1, 1))) 221 | { 222 | ImControlPointFlags flags = 0; 223 | if (draw_markers) 224 | flags |= ImControlPointFlags_DrawControlPointMarkers; 225 | if (draw_derivatives) 226 | flags |= ImControlPointFlags_DrawParamDerivatives; 227 | 228 | ImControl::PushConstantScaleX(-1.0f / ImControl::GetViewAspectRatio()); 229 | ImControl::PushConstantScaleY(-1.0f); 230 | ImControl::PushConstantTranslationAlongY(-0.8f); 231 | 232 | if(total_scale_free) 233 | ImControl::PushFreeParameter(¶meters.main_length); 234 | if(left_angle_free) 235 | ImControl::PushFreeParameter(¶meters.left_angle); 236 | if(left_scale_free) 237 | ImControl::PushFreeParameter(¶meters.left_log_scale_factor); 238 | if(right_angle_free) 239 | ImControl::PushFreeParameter(¶meters.right_angle); 240 | if(right_scale_free) 241 | ImControl::PushFreeParameter(¶meters.right_log_scale_factor); 242 | create_tree(¶meters, depth, 0, flags); 243 | ImControl::ClearFreeParameters(); 244 | 245 | ImControl::ClearTransformations(); 246 | 247 | ImControl::EndView(); // Any changes to parameters happen in this call 248 | 249 | // Enforce bounds on parameters should happen after parameters have been changed 250 | parameters.left_log_scale_factor = clamp(parameters.left_log_scale_factor, log_factor_lower_bound, log_factor_upper_bound); 251 | parameters.right_log_scale_factor = clamp(parameters.right_log_scale_factor, log_factor_lower_bound, log_factor_upper_bound); 252 | parameters.left_angle = clamp(parameters.left_angle, angle_lower_bound, angle_upper_bound); 253 | parameters.right_angle = clamp(parameters.right_angle, angle_lower_bound, angle_upper_bound); 254 | } 255 | ImGui::End(); 256 | } 257 | 258 | 259 | void ShowSpiralDemo(bool* p_open) 260 | { 261 | ImGui::Begin("Spiral Demo", p_open); 262 | 263 | static float spiral_rotation = 2.05f; 264 | static float spiral_height = 0.025f; 265 | static float spiral_scale = 0.9f; 266 | static int spiral_length = 20; 267 | static bool draw_control_points = false; 268 | static bool draw_derivatives = false; 269 | 270 | ImGui::SliderFloat("spiral rotation", &spiral_rotation, -3.1416f, 3.1416f); 271 | ImGui::SliderFloat("spiral height", &spiral_height, -0.05f, 0.05f); 272 | ImGui::SliderFloat("spiral scale", &spiral_scale, 0.7f, 1.1f); 273 | ImGui::SliderInt("number of points", &spiral_length, 2, 100); 274 | ImGui::Checkbox("draw control points", &draw_control_points); 275 | ImGui::Checkbox("draw derivatives", &draw_derivatives); 276 | 277 | if (ImControl::BeginView("some_label_for_id", ImVec2(300, 300), ImVec4(0, 1, 1, 1))) 278 | { 279 | ImControl::BeginCompositeTransformation(); 280 | ImControl::PushConstantPerspectiveMatrix(45.0f / 180.f * 3.14159f, 1.0f, 0.01f, 10.0f); 281 | ImControl::PushConstantLookAtMatrix( 282 | { 2.5f, -0.8f, 1.0f, 1.0f }, { 0.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, -1.0f, 0.0f } 283 | ); 284 | 285 | auto draw_list = ImGui::GetWindowDrawList(); 286 | ImControl::Button("start", { 1.0, 0.0, 0.0, 1.0 }, 0, 0, 0.05f, ImVec4{ 0, 1, 1, 1 }); 287 | ImVec2 prev_point = ImControl::GetLastControlPointPosition(); 288 | 289 | ImControlPointFlags flags = 0; 290 | if (draw_control_points) 291 | flags |= ImControlPointFlags_DrawControlPointMarkers; 292 | if (draw_derivatives) 293 | flags |= ImControlPointFlags_DrawParamDerivatives; 294 | 295 | ImControl::PushFreeParameter(&spiral_rotation); 296 | ImControl::PushFreeParameter(&spiral_scale); 297 | for (int i = 0; i < spiral_length; ++i) 298 | { 299 | ImControl::PushRotationAboutZ(&spiral_rotation); 300 | ImControl::PushTranslationAlongZ(&spiral_height); 301 | ImControl::PushScaleX(&spiral_scale); 302 | ImControl::PushScaleY(&spiral_scale); 303 | ImGui::PushID(i); 304 | ImControl::Point("", { 1.0, 0.0, 0.0, 1.0 }, flags, 0, 0.05f, ImVec4(1, 0, 0, 1)); 305 | ImGui::PopID(); 306 | ImVec2 current_point = ImControl::GetLastControlPointPosition(); 307 | draw_list->PathLineTo(prev_point); 308 | draw_list->PathLineTo(current_point); 309 | draw_list->PathStroke(ImGui::GetColorU32({ 1.0, 0.0, 0.0, 1.0 }), false, 2.0f); 310 | prev_point = current_point; 311 | } 312 | ImControl::ClearFreeParameters(); 313 | 314 | draw_list->PathStroke(ImGui::GetColorU32({ 1.0, 0.0, 0.0, 1.0 }), false); 315 | ImControl::PushConstantTranslationAlongZ(0.1f); 316 | ImControl::PushConstantScaleX(0.0f); 317 | ImControl::PushConstantScaleY(0.0f); 318 | ImControl::EndCompositeTransformation(); 319 | 320 | flags |= ImControlPointFlags_DrawControlPointMarkers; 321 | ImControl::PushFreeParameter(&spiral_height); 322 | ImControl::Point("peak", { 1.0, 0.0, 0.0, 1.0 }, flags, 0, 0.05f, ImVec4(0, 1, 0, 1)); 323 | ImControl::PopFreeParameter(); 324 | ImControl::PopTransformation(); 325 | ImControl::EndView(); 326 | 327 | // Clamping of parameter changes should occur only after the incremental changes have been made, usually after EndView() 328 | spiral_scale = clamp(spiral_scale, 0.7f, 1.1f); 329 | spiral_height = clamp(spiral_height, -0.05f, 0.05f); 330 | } 331 | 332 | ImGui::End(); 333 | } 334 | 335 | // These are defined in imcontrol_internal.h, but we don't want everything from there 336 | ImVec4 operator+ (const ImVec4& v, const ImVec4& w) { return ImVec4{ v.x + w.x, v.y + w.y, v.z + w.z, v.w + w.w }; } 337 | ImVec4 operator* (const ImVec4& v, float s) { return ImVec4{ v.x * s, v.y * s, v.z * s, v.w * s }; } 338 | ImVec4 operator* (const ImMat4& M, const ImVec4& v) { return M.col[0] * v.x + M.col[1] * v.y + M.col[2] * v.z + M.col[3] * v.w; } 339 | 340 | void draw_knot(int p, int q, ImMat4 P, int n_segments=300) 341 | { 342 | auto m = ImControl::GetViewBoundsMin(); 343 | auto M = ImControl::GetViewBoundsMax(); 344 | auto view_to_screen_coords = [m, M](const ImVec2& p) { return ImVec2{ (M.x - m.x) * (p.x + 1) / 2 + m.x, (M.y - m.y) * (p.y + 1) / 2 + m.y }; }; 345 | 346 | auto draw_list = ImGui::GetWindowDrawList(); 347 | 348 | for (int i = 0; i < n_segments; ++i) { 349 | float phi = (i * 2 * 3.14159f) / n_segments; 350 | float r = cosf(q * phi) + 2; 351 | ImVec4 pos = P * ImVec4{ r * cosf(p * phi), r * sinf(p * phi), -sinf(q * phi), 1.0f }; 352 | draw_list->PathLineTo(view_to_screen_coords({ pos.x / pos.w, pos.y / pos.w })); 353 | } 354 | draw_list->PathStroke(ImGui::GetColorU32({ 1, 1, 0, 1 }), true, 2.0f); 355 | } 356 | 357 | void ShowKnotDemo(bool* p_open) 358 | { 359 | ImGui::Begin("Knot Demo", p_open, ImGuiWindowFlags_NoScrollbar); 360 | ImGui::Text("Move a point on a (p, q)-torus knot"); 361 | static int pq[2]{ 2, 3 }; 362 | static bool draw_derivatives = false; 363 | static float bead_pos{}; 364 | static float camera_angle{}; 365 | static bool fixed_angle{}; 366 | 367 | ImGui::SliderInt2("(p, q)", pq, 2, 10); 368 | ImGui::SliderFloat("view angle", &camera_angle, 0, 3.14159f / 2); 369 | ImGui::Checkbox("view fixed", &fixed_angle); 370 | ImGui::Checkbox("draw derivatives", &draw_derivatives); 371 | 372 | if (ImControl::BeginView("some_label_for_id", ImVec2(-1, -1), ImVec4(0, 1, 1, 1))) 373 | { 374 | ImControl::PushConstantPerspectiveMatrix(45.0f / 180.f * 3.14159f, ImControl::GetViewAspectRatio(), 0.01f, 10.0f); 375 | ImControl::PushConstantLookAtMatrix( 376 | { 0.0f, 0.0f, 9.0f, 1.0f }, { 0.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 1.0f, 0.0f, 0.0f } 377 | ); 378 | ImControl::PushRotationAboutX(&camera_angle); 379 | 380 | draw_knot(pq[0], pq[1], ImControl::GetTransformationMatrix()); 381 | 382 | auto pphi = Linear((float)pq[0], 0, Parameter{ &bead_pos }); 383 | auto qphi = Linear((float)pq[1], 0, Parameter{ &bead_pos }); 384 | auto r = Cosine(qphi) + 2; 385 | ImControl::BeginCompositeTransformation(); 386 | ImControl::PushTranslationAlongX(r * Cosine(pphi)); 387 | ImControl::PushTranslationAlongY(r * Sine(pphi)); 388 | ImControl::PushTranslationAlongZ(Sine(qphi) * -1); 389 | ImControl::EndCompositeTransformation(); 390 | 391 | ImControlPointFlags flags = ImControlPointFlags_DrawControlPointMarkers | ImControlPointFlags_Circular; 392 | if (draw_derivatives) 393 | flags |= ImControlPointFlags_DrawParamDerivatives; 394 | 395 | ImControl::PushFreeParameter(&bead_pos); 396 | if (!fixed_angle) 397 | ImControl::PushFreeParameter(&camera_angle); 398 | 399 | ImControl::Point("bead", { 0, 0, 0, 1 }, flags, 0, 0.25f, { 1, 1, 1, 1 }); 400 | 401 | ImControl::ClearFreeParameters(); 402 | 403 | ImControl::ClearTransformations(); 404 | ImControl::EndView(); 405 | } 406 | 407 | ImGui::End(); 408 | } 409 | 410 | void ShowArmDemo(bool* p_open) 411 | { 412 | ImGui::Begin("Arm Demo", p_open, ImGuiWindowFlags_NoScrollbar); 413 | 414 | static float base_rotation = 0.0f; 415 | static float base_angle = 0.1f; 416 | static float elbow_angle = 0.2f; 417 | constexpr bool include_reflection = true; 418 | float point_alpha = 1.0f; 419 | 420 | ImControlPointFlags flags = ImControlPointFlags_DrawControlPointMarkers | ImControlPointFlags_DrawParamDerivatives; 421 | 422 | // Begin the viewport, filling available space 423 | ImControl::BeginView("arm", { -1, -1 }, { 1, 1, 1, 1 }); 424 | 425 | 426 | // Push projection and camera matrices 427 | ImControl::PushConstantPerspectiveMatrix(45.0f / 180.0f * 3.14159f, ImControl::GetViewAspectRatio(), 0.01f, 10.0f); 428 | ImControl::PushConstantLookAtMatrix({ 0.0f, 0.9f, -1.5f, 1.0f }, { 0, 0.3f, 0.15f, 1 }, { 0, -1, 0, 0 }); 429 | 430 | for (int i = 0; i < 2; ++i) 431 | { 432 | ImGui::PushID(i); 433 | ImControl::BeginCompositeTransformation(); 434 | ImControl::PushRotationAboutY(&base_rotation); // Rotate arm 435 | 436 | ImControl::PushFreeParameter(&base_rotation); 437 | ImControl::Point("rotation", { 0.2f, 0, 0, 1.0f }, flags, 0, 0.0375f, { 1, 0, 1, point_alpha }); // Controls Rotation 438 | ImControl::PopFreeParameter(); 439 | 440 | ImControl::Button("base", { 0.0f, 0, 0, 1.0f }, flags, 0, 0.025f, { 1, 1, 1, point_alpha }); // Demarks base of arm 441 | 442 | ImControl::PushRotationAboutZ(&base_angle); // Rotate arm up and down 443 | ImControl::PushConstantTranslationAlongY(0.5f); // Move up to next joint 444 | 445 | ImControl::PushFreeParameter(&base_angle); 446 | ImControl::Point("arm1", { 0.0f, -0.1f, 0, 1.0f }, flags, 0, 0.025f, { 1, 1, 1, point_alpha }); // Control points along arm 447 | ImControl::Point("arm2", { 0.0f, -0.2f, 0, 1.0f }, flags, 0, 0.025f, { 1, 1, 1, point_alpha }); 448 | ImControl::Point("arm3", { 0.0f, -0.3f, 0, 1.0f }, flags, 0, 0.025f, { 1, 1, 1, point_alpha }); 449 | ImControl::Point("arm4", { 0.0f, -0.4f, 0, 1.0f }, flags, 0, 0.025f, { 1, 1, 1, point_alpha }); 450 | ImControl::Point("elbow", { 0.0f, 0, 0, 1.0f }, flags, 0, 0.0375f, { 1, 1, 1, point_alpha }); // Control point at joint 451 | ImControl::PopFreeParameter(); 452 | 453 | ImControl::PushRotationAboutZ(&elbow_angle); // Bend the elbow 454 | ImControl::PushConstantTranslationAlongY(0.4f); // Move to end of arm 455 | ImControl::EndCompositeTransformation(); // Done transforming 456 | 457 | ImControl::PushFreeParameter(&elbow_angle); 458 | ImControl::Point("forearm1", { 0.0f, -0.1f, 0, 1.0f }, flags, 0, 0.025f, { 1, 1, 0, point_alpha }); // Control points along forearm 459 | ImControl::Point("forearm2", { 0.0f, -0.2f, 0, 1.0f }, flags, 0, 0.025f, { 1, 1, 0, point_alpha }); 460 | ImControl::Point("forearm3", { 0.0f, -0.3f, 0, 1.0f }, flags, 0, 0.025f, { 1, 1, 0, point_alpha }); 461 | ImControl::PushFreeParameter(&base_angle); 462 | ImControl::Point("hand", { 0.0f, 0, 0, 1.0f }, flags, 0, 0.0375f, { 1, 0, 0, point_alpha }); // Hand controls two angles 463 | ImControl::ClearFreeParameters(); 464 | 465 | ImControl::PopTransformation(); 466 | ImGui::PopID(); 467 | 468 | if (!include_reflection) 469 | continue; 470 | 471 | if (i == 0) { 472 | ImControl::PushConstantReflection({ -0.3f, 0, 1, 0 }); 473 | ImControl::PushConstantTranslationAlongZ(-1.0f); // Together these are a reflection in the plane z = 0.5f 474 | point_alpha = 0.8f; 475 | } 476 | } 477 | 478 | ImControl::ClearTransformations(); 479 | ImControl::EndView(); // Parameter changes are made here 480 | 481 | elbow_angle = clamp(elbow_angle, 0.3f, 2.8f); // Clamp angles after view ends 482 | base_angle = clamp(base_angle, 0.1f, 1.4f); 483 | 484 | ImGui::End(); 485 | } 486 | 487 | void draw_circle(ImMat4 P, float r = 1.0f, int n_segments = 40) { 488 | auto m = ImControl::GetViewBoundsMin(); 489 | auto M = ImControl::GetViewBoundsMax(); 490 | auto view_to_screen_coords = [m, M](const ImVec2& p) { return ImVec2{ (M.x - m.x) * (p.x + 1) / 2 + m.x, (M.y - m.y) * (p.y + 1) / 2 + m.y }; }; 491 | auto world_to_view_coords = [P](const ImVec2& p) { auto q = P * ImVec4{ p.x, p.y, 0.0f, 1.0f }; return ImVec2{ q.x / q.w, q.y / q.w }; }; 492 | 493 | auto draw_list = ImGui::GetWindowDrawList(); 494 | 495 | for (int i = 0; i < n_segments; ++i) { 496 | float angle = (2 * 3.14159f * (i + 0.5f)) / n_segments; 497 | draw_list->PathLineTo(view_to_screen_coords(world_to_view_coords({ r * cosf(angle), r * sinf(angle) }))); 498 | } 499 | draw_list->PathStroke(ImGui::GetColorU32({ 1, 1, 0, 1 }), true, 2.0f); 500 | } 501 | 502 | void draw_square(ImMat4 P) { 503 | draw_circle(P, sqrtf(2.0f), 4); 504 | } 505 | 506 | void ShowDraggableShapesDemo(bool* p_open) 507 | { 508 | ImGui::Begin("Draggable Shapes Demo", p_open, ImGuiWindowFlags_NoScrollbar); 509 | 510 | ImGui::Text("Click and drag the control points."); ImGui::Separator(); 511 | ImGui::TextWrapped("Note that the circle is quick to rotate, the ellipse and rectangle less so, and the square reluctant. This is controlled by reparametrisations of the rotation variables, and this technique can give you more control of the behaviour of control points. You may also wish to use alter the global regularisation parameter to see how this affects the dragging behaviour (see the slider in the demo window)."); 512 | 513 | // Variables defining the state of the shapes 514 | static float circle_rotation{}; 515 | static ImVec2 circle_center{ 1.1f, -1.1f }; 516 | 517 | static float square_rotation{}; 518 | static ImVec2 square_center{ 1.1f, 1.5f }; 519 | 520 | static float rect_rotation{}; 521 | static ImVec2 rect_center{ -1.5f, 1.2f }; 522 | static ImVec2 rect_scale{ 1.1f, 0.9f }; 523 | 524 | static float ellipse_rotation{}; 525 | static ImVec2 ellipse_center{ -1.1f, -0.1f }; 526 | 527 | ImControlPointFlags flags = ImControlPointFlags_DrawControlPointMarkers | ImControlPointFlags_DrawParamDerivatives; 528 | 529 | // Begin the viewport, filling available space 530 | ImControl::BeginView("arm", { -1, -1 }, { 1, 1, 1, 1 }); 531 | 532 | // Push projection and camera matrices 533 | ImControl::PushConstantPerspectiveMatrix(45.0f / 180.0f * 3.14159f, ImControl::GetViewAspectRatio(), 0.01f, 10.0f); 534 | ImControl::PushConstantLookAtMatrix({ 0.0f, 4.0f, 4.0f, 1.0f }, { 0, 0, 0, 1 }, { 0, 0, -1, 0 }); 535 | 536 | // The circle 537 | ImControl::PushTranslation(&circle_center[0], &circle_center[1]); 538 | ImControl::PushRotation(Linear(5, 0, Parameter{ &circle_rotation })); // Reparametrisation makes the circle easier to turn 539 | draw_circle(ImControl::GetTransformationMatrix()); 540 | 541 | ImControl::PushFreeParameter(&circle_rotation); 542 | ImControl::PushFreeParameter(&circle_center[0]); 543 | ImControl::PushFreeParameter(&circle_center[1]); 544 | constexpr int n_points = 40; 545 | for (int i = 0; i < n_points; ++i) { 546 | float angle = i * (2 * 3.14159f / n_points); 547 | ImGui::PushID(i); 548 | ImControl::Point("circle", { cosf(angle), sinf(angle), 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 549 | ImGui::PopID(); 550 | } 551 | ImControl::ClearFreeParameters(); 552 | 553 | ImControl::PopTransformation(); 554 | ImControl::PopTransformation(); 555 | 556 | 557 | // The square 558 | ImControl::PushTranslation(&square_center[0], &square_center[1]); 559 | ImControl::PushRotation(Linear(0.5f, 0, Parameter{ &square_rotation })); // Reparametrisation makes the square harder to turn 560 | draw_square(ImControl::GetTransformationMatrix()); 561 | 562 | ImControl::PushFreeParameter(&square_rotation); 563 | ImControl::PushFreeParameter(&square_center[0]); 564 | ImControl::PushFreeParameter(&square_center[1]); 565 | ImControl::Point("square1", { 1, 1, 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 566 | ImControl::Point("square2", { 1, -1, 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 567 | ImControl::Point("square3", { -1, -1, 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 568 | ImControl::Point("square4", { -1, 1, 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 569 | ImControl::ClearFreeParameters(); 570 | ImControl::PopTransformation(); 571 | ImControl::PopTransformation(); 572 | 573 | 574 | // The rectangle 575 | ImControl::BeginCompositeTransformation(); 576 | ImControl::PushTranslation(&rect_center[0], &rect_center[1]); 577 | ImControl::PushRotation(&rect_rotation); 578 | ImControl::PushScaleX(&rect_scale[0]); 579 | ImControl::PushScaleY(&rect_scale[1]); 580 | ImControl::EndCompositeTransformation(); 581 | 582 | draw_square(ImControl::GetTransformationMatrix()); 583 | 584 | ImControl::PushFreeParameter(&rect_scale[0]); 585 | ImControl::PushFreeParameter(&rect_scale[1]); 586 | ImControl::Point("rect1", { 1, 1, 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 587 | ImControl::Point("rect2", { 1, -1, 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 588 | ImControl::Point("rect3", { -1, -1, 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 589 | ImControl::Point("rect4", { -1, 1, 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 590 | ImControl::ClearFreeParameters(); 591 | 592 | ImControl::PushFreeParameter(&rect_rotation); 593 | ImControl::PushFreeParameter(&rect_scale[0]); 594 | ImControl::Point("side1", { 1, 0, 0, 1 }, flags, 0, 0.1f, { 1, 0, 1, 1 }); 595 | ImControl::Point("side2", { -1, 0, 0, 1 }, flags, 0, 0.1f, { 1, 0, 1, 1 }); 596 | ImControl::PopFreeParameter(); 597 | ImControl::PushFreeParameter(&rect_scale[1]); 598 | ImControl::Point("side3", { 0, 1, 0, 1 }, flags, 0, 0.1f, { 0, 1, 1, 1 }); 599 | ImControl::Point("side4", { 0, -1, 0, 1 }, flags, 0, 0.1f, { 0, 1, 1, 1 }); 600 | ImControl::ClearFreeParameters(); 601 | 602 | ImControl::PushFreeParameter(&rect_center[0]); 603 | ImControl::PushFreeParameter(&rect_center[1]); 604 | ImControl::Point("rect_center", { 0, 0, 0, 1 }, flags, 0, 0.1f, { 1, 1, 0, 1 }); 605 | ImControl::ClearFreeParameters(); 606 | 607 | ImControl::PopTransformation(); // Only one pop as we use a composite above 608 | 609 | 610 | // The ellipse 611 | ImControl::BeginCompositeTransformation(); 612 | ImControl::PushTranslation(&ellipse_center[0], &ellipse_center[1]); 613 | ImControl::PushRotation(&ellipse_rotation); 614 | ImControl::PushConstantScaleX(0.6f); 615 | ImControl::PushConstantScaleY(1 / 0.6f); 616 | ImControl::EndCompositeTransformation(); 617 | draw_circle(ImControl::GetTransformationMatrix()); 618 | 619 | ImControl::PushFreeParameter(&ellipse_rotation); 620 | ImControl::PushFreeParameter(&ellipse_center[0]); 621 | ImControl::PushFreeParameter(&ellipse_center[1]); 622 | 623 | for (int i = 0; i < n_points; ++i) { 624 | float angle = i * (2 * 3.14159f / n_points); 625 | ImGui::PushID(i); 626 | ImControl::Point("ellipse", { cosf(angle), sinf(angle), 0, 1 }, flags, 0, 0.1f, { 1, 1, 1, 1 }); 627 | ImGui::PopID(); 628 | } 629 | ImControl::ClearFreeParameters(); 630 | 631 | ImControl::PopTransformation(); 632 | 633 | ImControl::ClearTransformations(); 634 | ImControl::EndView(); // Parameter changes are made here 635 | 636 | ImGui::End(); 637 | } 638 | 639 | static inline float unit_gaussian(float x) { return 1.0f / (sqrtf(2 * 3.14159f)) * expf(-0.5f * x * x); } 640 | 641 | void draw_gaussian(float amp, float mean, float stddev, const ImVec2& scale, const ImVec2& offset, int n_segments = 200) 642 | { 643 | auto m = ImControl::GetViewBoundsMin(); 644 | auto M = ImControl::GetViewBoundsMax(); 645 | auto view_to_screen_coords = [m, M](const ImVec2& p) { return ImVec2{ (M.x - m.x) * (p.x + 1) / 2 + m.x, (M.y - m.y) * (p.y + 1) / 2 + m.y }; }; 646 | 647 | auto cartesian_to_view_coords = [scale, offset](const ImVec2& p) { return ImVec2{ (p.x - offset.x) / scale.x, (p.y - offset.y) / scale.y }; }; 648 | auto view_to_cartesian_coords = [scale, offset](const ImVec2& p) { return ImVec2{ scale.x * p.x + offset.x, scale.y * p.y + offset.y }; }; 649 | 650 | auto f = [amp, mean, stddev](float x) { return amp / stddev * unit_gaussian((x - mean) / stddev); }; 651 | 652 | auto draw_list = ImGui::GetWindowDrawList(); 653 | 654 | // Draw axes 655 | auto view_axis_center = cartesian_to_view_coords({ 0, 0 }); 656 | draw_list->AddLine(view_to_screen_coords({ -1, view_axis_center.y }), view_to_screen_coords({ 1, view_axis_center.y }), ImGui::GetColorU32({ 1, 1, 1, .8f })); 657 | draw_list->AddLine(view_to_screen_coords({ view_axis_center.x, view_axis_center.y }), view_to_screen_coords({ view_axis_center.x, -1 }), ImGui::GetColorU32({ 1, 1, 1, .5f })); 658 | 659 | // Draw Gaussian curve 660 | //draw_list->PathLineTo(view_to_screen_coords({ -1.0f, f(-1.0f) })); 661 | for (int i = 0; i <= n_segments; ++i) { 662 | float view_x = (2.0f * i) / n_segments - 1.0f; 663 | float x = view_to_cartesian_coords({ view_x, 0 }).x; 664 | draw_list->PathLineTo(view_to_screen_coords(cartesian_to_view_coords({ x, f(x) }))); 665 | } 666 | draw_list->PathStroke(ImGui::GetColorU32({ 1, 1, 0, 1 }), false); 667 | } 668 | 669 | 670 | void ShowGaussianDemo(bool* p_open) 671 | { 672 | static float peak = 1.2f; 673 | static float mu = 0.25f; 674 | static float sigma = 0.5f; 675 | 676 | static bool show_debug_line = false; 677 | static bool show_markers = false; 678 | static bool show_derivatives = false; 679 | static bool alternative_method = false; 680 | 681 | ImGui::Begin("Gaussian chooser widget", p_open); 682 | ImGui::TextWrapped("Click and drag the curve to change its parameters."); 683 | 684 | float ampl = peak * sqrtf(2 * 3.14159f) * sigma; 685 | if (ImGui::SliderFloat("amplitude", &l, 0.0f, 5.0f)) 686 | peak = ampl / (sqrtf(2 * 3.14159f) * sigma); 687 | 688 | ImGui::SliderFloat("mean", &mu, -2.0f, 2.0f); 689 | if(ImGui::SliderFloat("std dev.", &sigma, 0.01f, 2.0f, "%.3f", ImGuiSliderFlags_Logarithmic)) 690 | peak = ampl / (sqrtf(2 * 3.14159f) * sigma); 691 | 692 | ImGui::SliderFloat("peak height", &peak, 0.1f, 4.0f); 693 | ImGui::SameLine(); HelpMarker("When dragging the curve it is the mean, std. dev. and peak height which are edited, the amplitude is then calculated from these. Changing the amplitude directly is problematic as there is a singularity when the standard deviative is small. One could hide this slider and the user would not need to consider this."); 694 | 695 | ImGui::Checkbox("show debug line", &show_debug_line); ImGui::SameLine(); HelpMarker("See the code that generates this line for a helpful debug code-snippet"); 696 | ImGui::Checkbox("draw markers", &show_markers); 697 | ImGui::Checkbox("draw derivatives", &show_derivatives); 698 | ImGui::Checkbox("alternative method", &alternative_method); ImGui::SameLine(); HelpMarker("Drag the peak to change the mean and amplitude, drag the sides where they are steepest to change the standard deviation and amplitude."); 699 | 700 | ImControlPointFlags flags = ImControlPointFlags_Circular | ImControlPointFlags_ChooseClosestWhenOverlapping | ImControlPointFlags_SizeInPixels; 701 | if (show_markers) 702 | flags |= ImControlPointFlags_DrawControlPointMarkers; 703 | if (show_derivatives) 704 | flags |= ImControlPointFlags_DrawParamDerivatives; 705 | 706 | ImControl::BeginView("Gaussian function", { -1, 200 }, { 1, 1, 1, 1 }); 707 | auto view_size = ImControl::GetViewSize(); 708 | float aspect_ratio = view_size.x / view_size.y; 709 | 710 | draw_gaussian(ampl, mu, sigma, { aspect_ratio, -1.0 }, { 0.0f, 0.75f }); 711 | 712 | ImControl::BeginCompositeTransformation(); 713 | ImControl::PushConstantTranslationAlongY(0.75f); 714 | ImControl::PushConstantScaleX(1.0f / aspect_ratio); 715 | ImControl::PushConstantScaleY(-1.0f); 716 | ImControl::PushScaleY(&peak); 717 | ImControl::PushTranslationAlongX(&mu); 718 | ImControl::PushScaleX(&sigma); 719 | ImControl::EndCompositeTransformation(); 720 | 721 | ImControl::PushFreeParameter(&peak); 722 | ImControl::PushFreeParameter(&sigma); 723 | if (!alternative_method) // Control all three at once, unless alternative_method 724 | ImControl::PushFreeParameter(&mu); 725 | 726 | constexpr float marker_size = 7.0f; 727 | for (int i = -35; i <= 35; ++i) { 728 | float x = 0.05f * i; 729 | if (alternative_method && (i == -9)) { 730 | ImControl::PopFreeParameter(); // Change parameter over to mu 731 | ImControl::PushFreeParameter(&mu); 732 | } 733 | else if (alternative_method && (i == 10)) { 734 | ImControl::PopFreeParameter(); // Change parameter back to sigma again 735 | ImControl::PushFreeParameter(&sigma); 736 | } 737 | ImGui::PushID(i); 738 | ImControl::Point("curve", ImVec2{ x, expf(-0.5f * x * x) }, flags, 0, marker_size, { 1, 0, 0, 1 }, 0); 739 | ImGui::PopID(); 740 | } 741 | ImControl::ClearFreeParameters(); 742 | 743 | if (show_debug_line) { // This code snippet is very useful for tracking down out-of-bound control points 744 | ImVec2 pos = ImControl::GetLastControlPointPosition(); 745 | ImGui::GetForegroundDrawList()->AddLine(pos, { 100, 100 }, ImGui::GetColorU32({ 1, 0, 0, 1 })); 746 | } 747 | 748 | ImControl::PopTransformation(); 749 | ImControl::EndView(); 750 | ImGui::End(); 751 | 752 | // Bounding variables should occur after the parameter has changed, in this case after EndView 753 | peak = clamp(peak, 0.0f, 20.0f); 754 | sigma = clamp(sigma, 0.001f, 4.0f); 755 | mu = clamp(mu, -4, 4); 756 | } 757 | 758 | // Our prototype translation widget, the transform stack / control point syntax can be used to create more complicated control widgets 759 | void ShowWidgetDemo(bool* p_open) 760 | { 761 | static bool rotate_widget = false; 762 | static ImMat4 model_matrix{ 763 | ImVec4{1, 0, 0, 0}, 764 | ImVec4{0, 1, 0, 0}, 765 | ImVec4{0, 0, 1, 0}, 766 | ImVec4{0, 0, 0, 1} 767 | }; 768 | 769 | ImGui::Begin("Translation/Rotation Widget", p_open, ImGuiWindowFlags_NoScrollbar); 770 | ImGui::TextWrapped("Click in the view to switch between rotation and translation."); 771 | 772 | static bool use_circular_markers{ true }; 773 | ImGui::Checkbox("circular markers", &use_circular_markers); 774 | 775 | auto size = ImGui::GetContentRegionAvail().x; 776 | 777 | if (ImControl::BeginView("widget", { size, size }, { 1, 1, 1, 1 })) 778 | { 779 | ImControl::PushConstantPerspectiveMatrix(45.0f / 180.0f * 3.14159f, 1.0f, 0.01f, 10.0f); 780 | ImControl::PushConstantLookAtMatrix({ 0.0f, 0.25f, -1.0f, 1.0f }, { 0, 0, 0, 1 }, { 0, -1, 0, 0 }); 781 | 782 | // Draw our control points 783 | ImControlPointFlags flags = ImControlPointFlags_DrawControlPointMarkers; 784 | if (use_circular_markers) 785 | flags |= ImControlPointFlags_Circular; 786 | 787 | ImControl::Button("center", model_matrix[3], flags, 0, 0.03f, { 1, 1, 1, 1 }); 788 | if(rotate_widget) 789 | CustomWidgets::RotationWidget(&model_matrix, 0.1f, flags); 790 | else 791 | CustomWidgets::TranslationWidget(&model_matrix, 0.1f, flags, false); 792 | 793 | ImControl::PopTransformation(); 794 | ImControl::PopTransformation(); 795 | if (ImControl::EndViewAsButton()) 796 | rotate_widget = !rotate_widget; 797 | } 798 | if (ImGui::Button("Reset Position")) 799 | model_matrix = { ImVec4{1, 0, 0, 0}, ImVec4{0, 1, 0, 0}, ImVec4{0, 0, 1, 0}, ImVec4{ 0, 0, 0, 1 } }; 800 | 801 | ImGui::End(); 802 | } 803 | 804 | void ShowCameraDemo(bool* p_open) 805 | { 806 | ImGui::Begin("Camera Demo", p_open, ImGuiWindowFlags_NoScrollbar); 807 | 808 | ImGui::Text("Work in progress"); 809 | 810 | static float cube_rotation = 0.0f; 811 | static float camera_vertical_angle = 0.0f; 812 | static float camera_distance = 3.0f; 813 | static bool show_parameter_derivatives = false; 814 | 815 | ImGui::SliderFloat("cube rotation", &cube_rotation, -10.0f, 10.0f); 816 | ImGui::SliderFloat("vertical angle", &camera_vertical_angle, -3.1416f / 4, 3.1416f / 4); 817 | ImGui::SliderFloat("camera displacement", &camera_distance, -5.0f, 5.0f); 818 | ImGui::Checkbox("show parameter derivatives", &show_parameter_derivatives); 819 | 820 | ImControlPointFlags flags = ImControlPointFlags_DrawControlPointMarkers; 821 | if (show_parameter_derivatives) 822 | flags |= ImControlPointFlags_DrawParamDerivatives; 823 | 824 | ImControl::BeginView("camera", ImVec2(-1, -1), ImVec4(0, 1, 1, 1)); 825 | 826 | // Setup transformations 827 | ImControl::BeginCompositeTransformation(); 828 | ImControl::PushConstantPerspectiveMatrix(45.0f * 3.14159f / 180.f, ImControl::GetViewAspectRatio(), 0.01f, 10.0f); // projection matrix 829 | ImControl::PushTranslationAlongZ(Linear(-1.0f, 0.0f, Parameter{ &camera_distance })); // move camera back 830 | 831 | 832 | ImControl::PushRotationAboutX(&camera_vertical_angle); 833 | ImControl::PushRotationAboutY(&cube_rotation); // Rotation of model along long axis (y is up) 834 | ImControl::PushConstantLookAtMatrix( // Model matrix, centering cube at origin and standing it on its diagonal axis 835 | { 0.5f, 0.5f, 0.5f, 1.0f }, { 4.5f, -3.0f, 0.0f, 1.0f }, { -1.0f, -1.0f, -1.0f, 0.0f } 836 | ); 837 | ImControl::EndCompositeTransformation(); 838 | 839 | // Control points at vertices of cube 840 | ImControl::PushFreeParameter(&cube_rotation); 841 | ImControl::Point("110", { 1.0f, 1.0f, 0.0f, 1.0f }, flags, 0, 0.05f, { 1, 0, 0, 1 }); 842 | ImControl::Point("101", { 1.0f, 0.0f, 1.0f, 1.0f }, flags, 0, 0.05f, { 1, 0, 0, 1 }); 843 | ImControl::Point("011", { 0.0f, 1.0f, 1.0f, 1.0f }, flags, 0, 0.05f, { 1, 0, 0, 1 }); 844 | ImControl::PopFreeParameter(); 845 | 846 | ImControl::PushFreeParameter(&camera_vertical_angle); 847 | ImControl::Point("100", { 1.0f, 0.0f, 0.0f, 1.0f }, flags, 0, 0.05f, { 1, 0, 0, 1 }); 848 | ImControl::Point("010", { 0.0f, 1.0f, 0.0f, 1.0f }, flags, 0, 0.05f, { 1, 0, 0, 1 }); 849 | ImControl::Point("001", { 0.0f, 0.0f, 1.0f, 1.0f }, flags, 0, 0.05f, { 1, 0, 0, 1 }); 850 | ImControl::PopFreeParameter(); 851 | 852 | ImControl::PushFreeParameter(&camera_distance); 853 | ImControl::Point("111", { 1.0f, 1.0f, 1.0f, 1.0f }, flags, 0, 0.05f, { 1, 1, 1, 1 }); 854 | ImControl::PopFreeParameter(); 855 | 856 | ImControl::Button("000", { 0.0f, 0.0f, 0.0f, 1.0f }, flags, 0, 0.05f, { 1, 1, 0, 1 }); 857 | 858 | ImControl::PopTransformation(); 859 | ImControl::EndView(); 860 | 861 | } 862 | 863 | // Defined for the following tests 864 | static ImVec4 operator-(ImVec4 const& v, ImVec4 const& w) { return { v.x - w.x, v.y - w.y, v.z - w.z, v.w - w.w }; } 865 | static ImVec4 operator/(ImVec4 const& v, float d) { return { v.x / d, v.y / d, v.z / d, v.w / d }; } 866 | static float dot(ImVec4 const& v, ImVec4 const& w) { return v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w; } 867 | static float length(ImVec4 const& v) { return dot(v, v); } 868 | static void output_vector(ImVec4 const& v) { ImGui::Text("(%.2f, %.2f, %.2f, %.2f)", v.x, v.y, v.z, v.w); } 869 | static void output_vector(const char* text, ImVec4 const& v) { ImGui::Text(text); ImGui::SameLine(); output_vector(v); } 870 | 871 | static void comparison(ImVec4 const& d, ImVec4 const& est_d, ImVec4 const& est_2nd_d, ImVec4 const& sd, float eps, float threshold) { 872 | output_vector("Estimated derivative:", est_d); 873 | output_vector("Calculated derivative:", d); 874 | float a = length(est_d - d) / (eps * eps); 875 | ImGui::TextColored((a < threshold) ? ImVec4{ 0, 1, 0, 1 } : ImVec4{ 1, 0, 0, 1 }, "Difference / epsilon^2: %.2f", a); 876 | 877 | output_vector("Estimated second derivative:", est_2nd_d); 878 | output_vector("Calculated second derivative:", sd); 879 | } 880 | 881 | void ShowTestsWindow(bool* p_open) 882 | { 883 | ImGui::Begin("Tests", p_open); 884 | 885 | ImGui::Text("Calculated derivatives vs numerical approximations"); 886 | static float epsilon = 0.005f; 887 | static float param = 0.2f; 888 | static float param_plus_eps; 889 | ImGui::SliderFloat("parameter", ¶m, 0.0f, 1.0f, "%.4f"); 890 | ImGui::SliderFloat("epsilon", &epsilon, 0.0001f, 1.0f, "%.4f", ImGuiSliderFlags_Logarithmic); 891 | param_plus_eps = epsilon + param; 892 | ImVec4 v{ 2, 1, 3, 1 }; // Some point 893 | 894 | ImGui::Separator(); 895 | 896 | // Tests of individual transformations 897 | ImVec4 w{}, derivative{}, estimated_derivative{}, second_derivative, estimated_second_derivative; 898 | 899 | ImGui::Text("Rotation about x-axis"); 900 | ImControl::PushRotationAboutX(¶m); 901 | w = ImControl::ApplyTransformation(v); 902 | derivative = ImControl::GetDerivativeAt(v, ¶m); 903 | ImControl::PopTransformation(); 904 | 905 | ImControl::PushRotationAboutX(¶m_plus_eps); 906 | estimated_derivative = (ImControl::ApplyTransformation(v) - w) / epsilon; 907 | second_derivative = ImControl::GetSecondDerivativeAt(v, ¶m_plus_eps, ¶m_plus_eps); 908 | estimated_second_derivative = (ImControl::GetDerivativeAt(v, ¶m_plus_eps) - derivative) / epsilon; 909 | ImControl::PopTransformation(); 910 | 911 | comparison(derivative, estimated_derivative, second_derivative, estimated_second_derivative, epsilon, 10.0f); 912 | 913 | ImGui::Separator(); 914 | ImGui::Text("Rotation about axis (1, 1, 1)"); 915 | ImControl::PushRotationAboutAxis(¶m, { 1, 1, 1, 0 }); 916 | w = ImControl::ApplyTransformation(v); 917 | derivative = ImControl::GetDerivativeAt(v, ¶m); 918 | ImControl::PopTransformation(); 919 | 920 | ImControl::PushRotationAboutAxis(¶m_plus_eps, { 1, 1, 1, 0 }); 921 | estimated_derivative = (ImControl::ApplyTransformation(v) - w) / epsilon; 922 | second_derivative = ImControl::GetSecondDerivativeAt(v, ¶m_plus_eps, ¶m_plus_eps); 923 | estimated_second_derivative = (ImControl::GetDerivativeAt(v, ¶m_plus_eps) - derivative) / epsilon; 924 | ImControl::PopTransformation(); 925 | 926 | comparison(derivative, estimated_derivative, second_derivative, estimated_second_derivative, epsilon, 10.0f); 927 | 928 | ImGui::Separator(); 929 | ImGui::Text("Translation along y-axis"); 930 | ImControl::PushTranslationAlongY(¶m); 931 | w = ImControl::ApplyTransformation(v); 932 | derivative = ImControl::GetDerivativeAt(v, ¶m); 933 | ImControl::PopTransformation(); 934 | 935 | ImControl::PushTranslationAlongY(¶m_plus_eps); 936 | estimated_derivative = (ImControl::ApplyTransformation(v) - w) / epsilon; 937 | second_derivative = ImControl::GetSecondDerivativeAt(v, ¶m_plus_eps, ¶m_plus_eps); 938 | estimated_second_derivative = (ImControl::GetDerivativeAt(v, ¶m_plus_eps) - derivative) / epsilon; 939 | ImControl::PopTransformation(); 940 | 941 | comparison(derivative, estimated_derivative, second_derivative, estimated_second_derivative, epsilon, 10.0f); 942 | 943 | ImGui::Separator(); 944 | ImGui::Text("Scale along z-axis"); 945 | ImControl::PushScaleZ(¶m); 946 | w = ImControl::ApplyTransformation(v); 947 | derivative = ImControl::GetDerivativeAt(v, ¶m); 948 | ImControl::PopTransformation(); 949 | 950 | ImControl::PushScaleZ(¶m_plus_eps); 951 | estimated_derivative = (ImControl::ApplyTransformation(v) - w) / epsilon; 952 | second_derivative = ImControl::GetSecondDerivativeAt(v, ¶m_plus_eps, ¶m_plus_eps); 953 | estimated_second_derivative = (ImControl::GetDerivativeAt(v, ¶m_plus_eps) - derivative) / epsilon; 954 | ImControl::PopTransformation(); 955 | 956 | comparison(derivative, estimated_derivative, second_derivative, estimated_second_derivative, epsilon, 10.0f); 957 | 958 | // Test for a composition of a number of transformations 959 | static float p2 = -0.5f; 960 | ImGui::Separator(); 961 | ImGui::Text("Composite transformation"); 962 | 963 | ImControl::PushScale(¶m); 964 | ImControl::PushTranslation(&p2, { 1, 2, 3, 0 }); 965 | ImControl::PushConstantPerspectiveMatrix(0.5f, 1.0f, 0.1f, 10.0f); 966 | ImControl::PushScaleY(Cosine(Exponential(Parameter{ ¶m }))); 967 | 968 | w = ImControl::ApplyTransformation(v); 969 | derivative = ImControl::GetDerivativeAt(v, ¶m); 970 | ImVec4 deriv_p2 = ImControl::GetDerivativeAt(v, &p2); 971 | ImVec4 calc_sd2 = ImControl::GetSecondDerivativeAt(v, &p2, ¶m); 972 | ImControl::ClearTransformations(); 973 | 974 | ImControl::PushScale(¶m_plus_eps); 975 | ImControl::PushTranslation(&p2, { 1, 2, 3, 0 }); 976 | ImControl::PushConstantPerspectiveMatrix(0.5f, 1.0f, 0.1f, 10.0f); 977 | ImControl::PushScaleY(Cosine(Exponential(Parameter{ ¶m_plus_eps }))); 978 | 979 | estimated_derivative = (ImControl::ApplyTransformation(v) - w) / epsilon; 980 | second_derivative = ImControl::GetSecondDerivativeAt(v, ¶m_plus_eps, ¶m_plus_eps); 981 | estimated_second_derivative = (ImControl::GetDerivativeAt(v, ¶m_plus_eps) - derivative) / epsilon; 982 | ImVec4 est_sd2 = (ImControl::GetDerivativeAt(v, &p2) - deriv_p2) / epsilon; 983 | ImControl::ClearTransformations(); 984 | 985 | comparison(derivative, estimated_derivative, second_derivative, estimated_second_derivative, epsilon, 50.0f); // Larger threshold as a composite 986 | output_vector("Estimated second derivative (offdiagonal):", est_sd2); 987 | output_vector("Calculated second derivative (offdiagonal):", calc_sd2); 988 | 989 | ImGui::Separator(); 990 | 991 | ImGui::End(); 992 | } 993 | 994 | template 995 | inline T max(const ImVector& v) { 996 | T max{}; 997 | for (const auto& w : v) 998 | max = (w > max) ? w : max; 999 | return max; 1000 | } 1001 | 1002 | class BenchmarkedValue { 1003 | ImVector history{}; 1004 | int history_ix{}; 1005 | bool first_save{ true }; 1006 | float smoothed_value{}; 1007 | float max_value{}; 1008 | 1009 | public: 1010 | const int history_size{ 1 }; 1011 | float smoothing{ 0.99f }; 1012 | 1013 | BenchmarkedValue(int size) : history_size{ size } { 1014 | history.resize(size); 1015 | }; 1016 | float saveValue(float value) { 1017 | if (first_save) { 1018 | smoothed_value = value; 1019 | max_value = value; 1020 | first_save = false; 1021 | } 1022 | 1023 | history[history_ix] = value; 1024 | if (++history_ix == history_size) history_ix = 0; 1025 | 1026 | smoothed_value = smoothing * smoothed_value + (1 - smoothing) * value; 1027 | 1028 | max_value = 0.9f * max_value + 0.1f * max(history); 1029 | return smoothed_value; 1030 | } 1031 | float getSmoothedValue() const { return smoothed_value; } 1032 | void plotValues(const char* label, ImVec2 plot_size = { 0, 0 }) { 1033 | ImGui::PlotLines(label, history.Data, history_size, 0, nullptr, 0, max_value, plot_size); 1034 | } 1035 | void doSomeWork(void (*f)(ImVector&, int), ImVector& parameters, int n) { 1036 | auto t_start = std::chrono::high_resolution_clock::now(); 1037 | f(parameters, n); 1038 | auto t_end = std::chrono::high_resolution_clock::now(); 1039 | 1040 | auto duration = (t_end - t_start).count(); 1041 | saveValue((float)duration / n); 1042 | } 1043 | }; 1044 | 1045 | 1046 | void push_some_rotations(ImVector& parameters, int num_transformations) { 1047 | float* p = parameters.begin(); 1048 | for (int i = 0; i < num_transformations; i += 3) { 1049 | if (++p == parameters.end()) 1050 | p = parameters.begin(); 1051 | ImControl::PushRotationAboutX(p); 1052 | ImControl::PushRotationAboutY(p); 1053 | ImControl::PushRotationAboutZ(p); 1054 | } 1055 | } 1056 | 1057 | void push_some_matrices(ImVector& parameters, int num_transformations) { 1058 | ImMat4 M{}; 1059 | for (int i = 0; i < num_transformations; ++i) { 1060 | M[0].x += 0.001f; // Make some minor changes so our compiler won't do anything too unexpected (probably not needed) 1061 | ImControl::PushConstantMatrix(M); 1062 | } 1063 | } 1064 | 1065 | void push_some_translations(ImVector& parameters, int num_transformations) { 1066 | float* p = parameters.begin(); 1067 | for (int i = 0; i < num_transformations; i += 3) { 1068 | if (++p == parameters.end()) 1069 | p = parameters.begin(); 1070 | ImControl::PushTranslationAlongX(p); 1071 | ImControl::PushTranslationAlongY(p); 1072 | ImControl::PushTranslationAlongZ(p); 1073 | } 1074 | } 1075 | 1076 | void push_some_scalings(ImVector& parameters, int num_transformations) { 1077 | float* p = parameters.begin(); 1078 | for (int i = 0; i < num_transformations; i += 3) { 1079 | if (++p == parameters.end()) 1080 | p = parameters.begin(); 1081 | ImControl::PushScaleX(p); 1082 | ImControl::PushScaleY(p); 1083 | ImControl::PushScaleZ(p); 1084 | //ImControl::PushScale(p); 1085 | } 1086 | } 1087 | 1088 | void ShowBenchmarkWindow(bool* p_open) 1089 | { 1090 | 1091 | static bool record_data = true; 1092 | static bool show_plots = false; 1093 | static float num_transformations_float = 1000; // Number of transformations to perform 1094 | constexpr int max_derivatives = 20; 1095 | static int num_derivatives = 5; // Number of derivatives to process 1096 | static float factor = 0.9f; 1097 | static float dist = 0.01f; 1098 | static ImVector parameters{}; 1099 | if (parameters.empty()) parameters.resize(num_derivatives + 1, 0.2f); 1100 | 1101 | ImGui::Begin("Benchmarks", p_open); 1102 | ImGui::TextWrapped("These benchmarks shouldn't be taken too seriously, running the tests in a different order can give different results."); 1103 | if (ImControl::GetTransformationStackWidth() != 1 + num_derivatives) 1104 | ImGui::TextColored({ 1, 0, 0, 1 }, "Warning, there are extra derivatives in the stack."); 1105 | ImGui::Checkbox("Record data", &record_data); 1106 | ImGui::Checkbox("Show plots", &show_plots); 1107 | ImGui::SliderFloat("Number of transformations", &num_transformations_float, 5, 2000, "%.0f", ImGuiSliderFlags_Logarithmic); 1108 | int num_transformations = static_cast(num_transformations_float); // Workaround for logarithmic flag non-interoperable with a SliderInt 1109 | if (ImGui::SliderInt("Number of derivative parameters", &num_derivatives, 0, max_derivatives, "%d", ImGuiSliderFlags_AlwaysClamp)) { 1110 | parameters.resize(num_derivatives + 1, 0.2f); 1111 | } 1112 | 1113 | constexpr int history_size = 500; 1114 | static BenchmarkedValue rotations{ history_size }; 1115 | static BenchmarkedValue rotations_in_place{ history_size }; 1116 | static BenchmarkedValue apply_matrices{ history_size }; 1117 | static BenchmarkedValue apply_matrices_in_place{ history_size }; 1118 | static BenchmarkedValue translations{ history_size }; 1119 | static BenchmarkedValue translations_in_place{ history_size }; 1120 | static BenchmarkedValue scalings{ history_size }; 1121 | static BenchmarkedValue scalings_in_place{ history_size }; 1122 | 1123 | if (record_data) { 1124 | // Get derivatives for each parameter (skipping the first) so they'll be computed in the transformation stack 1125 | for (float* p = parameters.begin() + 1; p != parameters.end(); ++p) 1126 | ImControl::GetDerivativeAt({ 0, 0, 0, 1 }, p); 1127 | 1128 | for (int i = 0; i < 5; ++i) // Performing multiple repeats interleaves the computations (the ordering makes a significant difference, perhaps due to branch prediction) 1129 | { 1130 | rotations.doSomeWork(&push_some_rotations, parameters, num_transformations); 1131 | ImControl::ClearTransformations(); 1132 | apply_matrices.doSomeWork(&push_some_matrices, parameters, num_transformations); 1133 | ImControl::ClearTransformations(); 1134 | translations.doSomeWork(&push_some_translations, parameters, num_transformations); 1135 | ImControl::ClearTransformations(); 1136 | scalings.doSomeWork(&push_some_scalings, parameters, num_transformations); 1137 | ImControl::ClearTransformations(); 1138 | 1139 | ImControl::BeginCompositeTransformation(); 1140 | rotations_in_place.doSomeWork(&push_some_rotations, parameters, num_transformations); 1141 | apply_matrices_in_place.doSomeWork(&push_some_matrices, parameters, num_transformations); 1142 | translations_in_place.doSomeWork(&push_some_translations, parameters, num_transformations); 1143 | scalings_in_place.doSomeWork(&push_some_scalings, parameters, num_transformations); 1144 | ImControl::EndCompositeTransformation(); 1145 | ImControl::ClearTransformations(); 1146 | } 1147 | } 1148 | ImGui::Text("Taking %.1fns per rotation", rotations.getSmoothedValue()); 1149 | if (show_plots) rotations.plotValues("Rotations"); 1150 | ImGui::Text("Taking %.1fns per rotation in place", rotations_in_place.getSmoothedValue()); 1151 | if (show_plots) rotations_in_place.plotValues("Rotations (in place)"); 1152 | 1153 | ImGui::Text("Taking %.1fns per matrix", apply_matrices.getSmoothedValue()); 1154 | if (show_plots) apply_matrices.plotValues("Matrices"); 1155 | ImGui::Text("Taking %.1fns per matrix in place", apply_matrices_in_place.getSmoothedValue()); 1156 | if (show_plots) apply_matrices_in_place.plotValues("Matrices (in place)"); 1157 | 1158 | ImGui::Text("Taking %.1fns per translation", translations.getSmoothedValue()); 1159 | if (show_plots) translations.plotValues("Translations"); 1160 | ImGui::Text("Taking %.1fns per translation in place", translations_in_place.getSmoothedValue()); 1161 | if (show_plots) translations_in_place.plotValues("Translations (in place)"); 1162 | 1163 | ImGui::Text("Taking %.1fns per scaling", scalings.getSmoothedValue()); 1164 | if (show_plots) scalings.plotValues("Scalings"); 1165 | ImGui::Text("Taking %.1fns per scaling in place", scalings_in_place.getSmoothedValue()); 1166 | if (show_plots) scalings_in_place.plotValues("Scalings (in place)"); 1167 | 1168 | ImGui::Separator(); 1169 | ImGui::TextWrapped("We compare against pushing a scaling transformation."); 1170 | ImGui::SameLine(); HelpMarker("We choose a scaling as it is the simplest and should be most stable under optimisation choices."); 1171 | 1172 | if (ImGui::BeginTable("comparisons", 5)) { 1173 | float denom = scalings.getSmoothedValue(); 1174 | ImGui::TableSetupColumn("Comparisons"); 1175 | ImGui::TableSetupColumn("Scalings"); 1176 | ImGui::TableSetupColumn("Translations"); 1177 | ImGui::TableSetupColumn("Rotations"); 1178 | ImGui::TableSetupColumn("Matrices"); 1179 | ImGui::TableHeadersRow(); 1180 | 1181 | ImGui::TableNextRow(); 1182 | ImGui::TableSetColumnIndex(0); ImGui::Text("Simple push"); 1183 | ImGui::TableNextColumn(); ImGui::Text("1.0"); 1184 | ImGui::TableNextColumn(); ImGui::Text("%.2f", translations.getSmoothedValue() / denom); 1185 | ImGui::TableNextColumn(); ImGui::Text("%.2f", rotations.getSmoothedValue() / denom); 1186 | ImGui::TableNextColumn(); ImGui::Text("%.2f", apply_matrices.getSmoothedValue() / denom); 1187 | 1188 | ImGui::TableNextRow(); 1189 | ImGui::TableSetColumnIndex(0); ImGui::Text("In-place push"); 1190 | ImGui::TableNextColumn(); ImGui::Text("%.2f", scalings_in_place.getSmoothedValue() / denom); 1191 | ImGui::TableNextColumn(); ImGui::Text("%.2f", translations_in_place.getSmoothedValue() / denom); 1192 | ImGui::TableNextColumn(); ImGui::Text("%.2f", rotations_in_place.getSmoothedValue() / denom); 1193 | ImGui::TableNextColumn(); ImGui::Text("%.2f", apply_matrices_in_place.getSmoothedValue() / denom); 1194 | 1195 | ImGui::EndTable(); 1196 | } 1197 | 1198 | ImGui::End(); 1199 | } 1200 | 1201 | } // namespace ImControl -------------------------------------------------------------------------------- /imcontrol_example_widgets.cpp: -------------------------------------------------------------------------------- 1 | #include "imcontrol_example_widgets.h" 2 | 3 | #include "imcontrol_internal.h" 4 | 5 | inline static void PushFreeParameters(float* p1, float* p2) { ImControl::PushFreeParameter(p1); ImControl::PushFreeParameter(p2); } 6 | inline static void ReplaceFreeParameter(float* p) { ImControl::PopFreeParameter(); ImControl::PushFreeParameter(p); } 7 | inline static void ReplaceFreeParameters(float* p1, float* p2) { ImControl::PopFreeParameter(); ImControl::PopFreeParameter(); ImControl::PushFreeParameter(p1); ImControl::PushFreeParameter(p2); } 8 | 9 | namespace CustomWidgets 10 | { 11 | void TranslationWidget(ImMat4* M, float axes_size, ImControlPointFlags control_point_flags, bool include_planes) 12 | { 13 | static ImVec4 t{}; // The translation vector (only three coords are used) 14 | t = {}; // set to zero each frame 15 | 16 | ImControl::BeginCompositeTransformation(); 17 | ImControl::PushConstantMatrix(*M); 18 | ImControl::PushTranslation(&t.x, &t.y, &t.z); 19 | ImControl::PushConstantScale(axes_size); 20 | ImControl::EndCompositeTransformation(); 21 | 22 | // We don't change any parameters at the control points or at the end of a view, instead when the deferral slot is popped 23 | ImControl::PushDeferralSlot(); 24 | 25 | //ImControl::Button(ImVec4{ 0, 0, 0, 1 }, control_point_flags, 0, 0.025f, { 1, 1, 1, 1 }); 26 | 27 | ImControl::PushFreeParameter(&t.x); 28 | ImControl::Point("x-axis", ImVec4{ 1, 0, 0, 1 }, control_point_flags, 0, 0.025f, { 1, 0, 0, 1 }); 29 | ReplaceFreeParameter(&t.y); 30 | ImControl::Point("y-axis", ImVec4{ 0, 1, 0, 1 }, control_point_flags, 0, 0.025f, { 0, 1, 0, 1 }); 31 | ReplaceFreeParameter(&t.z); 32 | ImControl::Point("z-axis", ImVec4{ 0, 0, 1, 1 }, control_point_flags, 0, 0.025f, { 0, 0, 1, 1 }); 33 | ImControl::PopFreeParameter(); 34 | 35 | if (include_planes) { 36 | PushFreeParameters(&t.x, &t.y); 37 | ImControl::Point("xy-plane", ImVec4{ 0.5f, 0.5f, 0, 1 }, control_point_flags, 0, 0.02f, { 1, 1, 0, 1 }); 38 | ReplaceFreeParameters(&t.x, &t.y); 39 | ImControl::Point("yz-plane", ImVec4{ 0, 0.5f, 0.5f, 1 }, control_point_flags, 0, 0.02f, { 0, 1, 1, 1 }); 40 | ReplaceFreeParameters(&t.x, &t.y); 41 | ImControl::Point("zx-plane", ImVec4{ 0.5f, 0, 0.5f, 1 }, control_point_flags, 0, 0.02f, { 1, 0, 1, 1 }); 42 | ImControl::PopFreeParameter(); ImControl::PopFreeParameter(); 43 | } 44 | 45 | // This will apply any changes made to t as intended, without the unintended consequence of 46 | // applying any previously made changes. 47 | ImControl::PopDeferralSlot(); 48 | 49 | ImControl::PopTransformation(); // composite 50 | 51 | // Update the matrix if any of our parameters are non-zero 52 | M->col[3] += *M * t; 53 | } 54 | 55 | 56 | void RotationWidget(ImMat4* M, float widget_size, ImControlPointFlags control_point_flags) 57 | { 58 | static ImVec4 r{}; // The translation vector (only three coords are used) 59 | r = {}; // set to zero each frame 60 | 61 | ImControl::BeginCompositeTransformation(); 62 | ImControl::PushConstantMatrix(*M); 63 | ImControl::PushRotationAboutX(ImControl::Parameters::Linear(10.0f, 0, ImControl::Parameters::Parameter{ &r.x })); // the reparametrisation is to make the widget more responsive with default regularisation parameter 64 | ImControl::PushRotationAboutY(ImControl::Parameters::Linear(10.0f, 0, ImControl::Parameters::Parameter{ &r.y })); 65 | ImControl::PushRotationAboutZ(ImControl::Parameters::Linear(10.0f, 0, ImControl::Parameters::Parameter{ &r.z })); 66 | ImControl::PushConstantScale(widget_size); 67 | ImControl::EndCompositeTransformation(); 68 | 69 | // We don't change any parameters at the control points or at the end of a view, instead when the deferral slot is popped 70 | ImControl::PushDeferralSlot(); 71 | 72 | //ImControl::Button(ImVec4{ 0, 0, 0, 1 }, control_point_flags, 0, 0.02f, { 1, 1, 1, 1 }); 73 | 74 | constexpr float marker_size = 0.015f; 75 | constexpr int n_markers = 48; 76 | for (int i = 0; i < n_markers; ++i) { 77 | ImGui::PushID(i); 78 | float angle = 2 * 3.14159f * i / n_markers; 79 | float a = cosf(angle), b = sinf(angle); 80 | ImControl::PushFreeParameter(&r.x); 81 | ImControl::Point("rotate-x", ImVec4{ 0, a, b, 1 }, control_point_flags, 0, marker_size, { 1, 0, 0, 1 }); 82 | ReplaceFreeParameter(&r.y); 83 | ImControl::Point("rotate-y", ImVec4{ a, 0, b, 1 }, control_point_flags, 0, marker_size, { 0, 1, 0, 1 }); 84 | ReplaceFreeParameter(&r.z); 85 | ImControl::Point("rotate-z", ImVec4{ a, b, 0, 1 }, control_point_flags, 0, marker_size, { 0, 0, 1, 1 }); 86 | ImControl::PopFreeParameter(); 87 | ImGui::PopID(); 88 | } 89 | 90 | // This will apply any changes made to r as intended, without the unintended consequence of 91 | // applying any previously made changes. 92 | ImControl::PopDeferralSlot(); 93 | 94 | ImControl::PopTransformation(); // all transformations 95 | 96 | // Update the matrix if any of our parameters are non-zero 97 | if (r.x != 0.0f) { 98 | ImControl::Transformations::RotationX T{ r.x }; 99 | T.applyTransformationOnRightInPlace(M, M+1); 100 | } 101 | if (r.y != 0.0f) { 102 | ImControl::Transformations::RotationY T{ r.y }; 103 | T.applyTransformationOnRightInPlace(M, M+1); 104 | } 105 | if (r.z != 0.0f) { 106 | ImControl::Transformations::RotationZ T{ r.z }; 107 | T.applyTransformationOnRightInPlace(M, M+1); 108 | } 109 | } 110 | } // namespace CustomWidgets -------------------------------------------------------------------------------- /imcontrol_example_widgets.h: -------------------------------------------------------------------------------- 1 | #include "imcontrol.h" 2 | 3 | namespace CustomWidgets 4 | { 5 | void TranslationWidget(ImMat4* M, float axes_size, ImControlPointFlags control_point_flags, bool include_planes); 6 | void RotationWidget(ImMat4* M, float axes_size, ImControlPointFlags control_point_flags); 7 | } -------------------------------------------------------------------------------- /imcontrol_internal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui_internal.h" // Would like to remove this, possible only needed for ImRect 4 | 5 | namespace { 6 | // If imgui_internal.h has not been included then define the operations from it that we use 7 | #ifndef IMGUI_DEFINE_MATH_OPERATORS 8 | inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } 9 | inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } 10 | 11 | inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } 12 | inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } 13 | inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } 14 | inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } 15 | inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } 16 | inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } 17 | inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } 18 | inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } 19 | inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } 20 | inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } 21 | inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } 22 | inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } 23 | #endif 24 | 25 | // Fill in the operators we require but that ImGui internal does not provide 26 | inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } 27 | inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } 28 | inline ImVec4& operator*=(ImVec4& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; lhs.z *= rhs; lhs.w *= rhs; return lhs; } 29 | inline ImVec4& operator/=(ImVec4& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; lhs.z /= rhs; lhs.w /= rhs; return lhs; } 30 | inline ImVec4& operator+=(ImVec4& lhs, const ImVec4& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; lhs.z += rhs.z; lhs.w += rhs.w; return lhs; } 31 | inline ImVec4& operator-=(ImVec4& lhs, const ImVec4& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; lhs.z -= rhs.z; lhs.w -= rhs.w; return lhs; } 32 | inline ImVec4& operator/=(ImVec4& lhs, const ImVec4& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; lhs.z /= rhs.z; lhs.w /= rhs.w; return lhs; } 33 | 34 | // Some simple geometric functions 35 | inline float dot(ImVec4 const& a, ImVec4 const& b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; } 36 | inline float length(ImVec4 const& v) { return sqrtf(dot(v, v)); } 37 | inline ImVec4 normalise(ImVec4 const& v) { return v / length(v); } 38 | inline ImVec4 cross(ImVec4 const& v, ImVec4 const& w) { return ImVec4{ v.y * w.z - v.z * w.y, v.z * w.x - v.x * w.z, v.x * w.y - v.y * w.x, 0.0f }; } 39 | 40 | // Operators for matrix type - not complete, just the ones we need 41 | inline ImVec4 operator*(ImMat4 const& A, ImVec4 const& v) { return A.col[0] * v.x + A.col[1] * v.y + A.col[2] * v.z + A.col[3] * v.w; } 42 | inline ImMat4 operator*(ImMat4 const& A, ImMat4 const& B) { return ImMat4{ A * B.col[0], A * B.col[1], A * B.col[2], A * B.col[3] }; } 43 | inline ImMat4 operator*(float a, ImMat4 const& B) { return ImMat4{ B.col[0] * a, B.col[1] * a, B.col[2] * a, B.col[3] * a }; } 44 | inline ImMat4 operator+(ImMat4 const& A, ImMat4 const& B) { return ImMat4{ A.col[0] + B.col[0], A.col[1] + B.col[1], A.col[2] + B.col[2], A.col[3] + B.col[3] }; } 45 | inline ImMat4& operator+=(ImMat4& A, ImMat4 const& B) { A.col[0] += B.col[0]; A.col[1] += B.col[1]; A.col[2] += B.col[2]; A.col[3] += B.col[3]; return A; } 46 | // NB *= is right multiplication 47 | inline ImMat4& operator*=(ImMat4& A, ImMat4 const& B) { A = A * B; return A; } 48 | } // end anonymous namespace 49 | 50 | namespace ImControl { 51 | // Parameters are recognised by a pointer to their values 52 | using param_t = float*; 53 | 54 | namespace Transformations 55 | { 56 | // The classes below implement parametrised transformations, precisely this 57 | // is a (twice differentiable) function T(t) from real numbers to 4x4 58 | // matrices. Here the matrices act on vectors by left multiplication, i.e. 59 | // v ---> T(t)v. Such a function can be differentiated with respect to t to 60 | // get a matrix T'(t) and then again to get a matrix T''(t). 61 | // 62 | // The classes should be initialised with the required value of t (and any 63 | // other required parameters). The only functionally the classes need 64 | // implement is right multiplication by each of the matrices T(t), T'(t) and 65 | // T''(t). NB this is not the usual left action on vectors. The member 66 | // functions has the signatures, 67 | // 68 | // ImMat4& ApplyTransformationOnRightInPlace(ImMat4*, ImMat4*) const, 69 | // ImMat4& ApplyDerivativeOnRightInPlace(ImMat4&) const, ImMat4& 70 | // Apply2ndDerivOnRightInPlace(ImMat4&) const. 71 | // 72 | // The first calculates the action M ---> M T(t), over an array of matrices 73 | // M specified by begin and end pointers. The second calculates M ---> M 74 | // T'(t) in place for the matrix M passed by reference. The third is the 75 | // same but for M ---> M T''(t). 76 | // 77 | // These implementation choices were taken with efficiency in mind. When 78 | // implementing one should keep in mind that it is the base transformation 79 | // this is applied most often in the object's lifetime and one should assume 80 | // that it could be applied 1-20 times, while the first derivative might be 81 | // applied 0-5 times and the second derivative will only be applied at most 82 | // once and usually not at all. So for complicated transformations it might 83 | // be worth caching the transformation matrix, may be counterproductive 84 | // caching the first derivative (if it is not used) and one should never 85 | // cache the second derivative matrix. 86 | 87 | using scalar_t = float; // Perhaps it should be an option to use double precision 88 | 89 | template 90 | class TranslationOfCoordinate { 91 | const scalar_t m_t; 92 | public: 93 | TranslationOfCoordinate(scalar_t t) : m_t{ t } {}; 94 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 95 | for (ImVec4* it = m_begin->col; it < m_end->col; it += 4) 96 | it[3] += it[C] * m_t; 97 | } 98 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 99 | M.col[3] = M[C]; 100 | M.col[0] = M.col[1] = M.col[2] = {}; 101 | return M; 102 | } 103 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 104 | M = {}; 105 | return M; 106 | } 107 | }; 108 | 109 | class Translation { 110 | const scalar_t m_t; 111 | const ImVec4 m_dir; 112 | public: 113 | Translation(const ImVec4& dir, scalar_t t) : m_t{ t }, m_dir{ dir } { IM_ASSERT(m_dir.w == 0.0f); }; 114 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 115 | for (ImVec4* it = m_begin->col; it < m_end->col; it += 4) 116 | it[3] += it[0] * (m_dir.x * m_t) + it[1] * (m_dir.y * m_t) + it[2] * (m_dir.z * m_t); 117 | } 118 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 119 | M.col[3] = M.col[0] * m_dir.x + M.col[1] * m_dir.y + M.col[2] * m_dir.z; 120 | M.col[0] = M.col[1] = M.col[2] = {}; 121 | return M; 122 | } 123 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 124 | M = {}; 125 | return M; 126 | } 127 | }; 128 | 129 | class ConstantMatrix { 130 | const ImMat4 m_M; 131 | public: 132 | ConstantMatrix(const ImMat4& M) : m_M{ M } {}; 133 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 134 | for (ImMat4* it = m_begin; it != m_end; ++it) 135 | *it = *it * m_M; 136 | } 137 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 138 | M = {}; 139 | return M; 140 | } 141 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 142 | M = {}; 143 | return M; 144 | } 145 | }; 146 | 147 | template 148 | class ScaleCoordinate { 149 | const scalar_t m_t; 150 | public: 151 | ScaleCoordinate(scalar_t t) : m_t{ t } {}; 152 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 153 | for (ImMat4* it = m_begin; it < m_end; ++it) 154 | it->operator[](C) *= m_t; 155 | } 156 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 157 | ImVec4 m = M[C]; 158 | M = {}; 159 | M[C] = m; 160 | return M; 161 | } 162 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 163 | M = {}; 164 | return M; 165 | } 166 | }; 167 | 168 | class Scale { 169 | const scalar_t m_t; 170 | public: 171 | Scale(scalar_t t) : m_t{ t } {}; 172 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 173 | for (ImMat4* it = m_begin; it < m_end; ++it) { 174 | it->operator[](0) *= m_t; 175 | it->operator[](1) *= m_t; 176 | it->operator[](2) *= m_t; 177 | } 178 | } 179 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 180 | M[3] = {}; 181 | return M; 182 | } 183 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 184 | M = {}; 185 | return M; 186 | } 187 | }; 188 | 189 | class ScaleAboutAxis { 190 | const scalar_t m_t; 191 | const ImVec4 m_dir; 192 | public: 193 | ScaleAboutAxis(scalar_t t, ImVec4 const& dir) : m_t{ t }, m_dir{ normalise(dir) } { IM_ASSERT(dir.w == 0.0f); }; 194 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 195 | for (ImMat4* it = m_begin; it < m_end; ++it) { 196 | ImVec4 a = ((*it) * m_dir) * (1 - m_t); 197 | it->operator[](0) = it->operator[](0) * m_t + a * m_dir.x; 198 | it->operator[](1) = it->operator[](1) * m_t + a * m_dir.y; 199 | it->operator[](2) = it->operator[](2) * m_t + a * m_dir.z; 200 | } 201 | } 202 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 203 | ImVec4 a = M * m_dir * -1; 204 | M[0] += a * m_dir.x; 205 | M[1] += a * m_dir.y; 206 | M[2] += a * m_dir.z; 207 | M[3] = {}; 208 | return M; 209 | } 210 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 211 | M = {}; 212 | return M; 213 | } 214 | }; 215 | 216 | class ScaleInDirection { 217 | const scalar_t m_t; 218 | const ImVec4 m_dir; 219 | public: 220 | ScaleInDirection(scalar_t t, ImVec4 const& dir) : m_t{ t }, m_dir{ normalise(dir) } { IM_ASSERT(dir.w == 0.0f); }; 221 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 222 | for (ImMat4* it = m_begin; it < m_end; ++it) { 223 | ImVec4 a = ((*it) * m_dir) * (m_t - 1); 224 | it->operator[](0) += a * m_dir.x; 225 | it->operator[](1) += a * m_dir.y; 226 | it->operator[](2) += a * m_dir.z; 227 | } 228 | } 229 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 230 | ImVec4 a = M * m_dir; 231 | M[0] = a * m_dir.x; 232 | M[1] = a * m_dir.y; 233 | M[2] = a * m_dir.z; 234 | M[3] = {}; 235 | return M; 236 | } 237 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 238 | M = {}; 239 | return M; 240 | } 241 | }; 242 | 243 | // Is specialised to rotations about a particular axis 244 | template 245 | class RotationIJ { 246 | const scalar_t m_c, m_s; // cache of cos(t) and sin(t) 247 | public: 248 | RotationIJ(scalar_t t) : m_c{ cosf(t) }, m_s{ sinf(t) } {} 249 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 250 | ImVec4* const end_col = m_end->col; 251 | ImVec4* itI = m_begin->col + I; 252 | ImVec4* itJ = m_begin->col + J; 253 | while (itI < end_col) { 254 | ImVec4 temp = *itI; // copy 255 | *itI = *itI * m_c + *itJ * m_s; 256 | *itJ = temp * (-m_s) + *itJ * m_c; 257 | itI += 4; itJ += 4; 258 | } 259 | } 260 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 261 | ImVec4 mI = M[I]; 262 | ImVec4 mJ = M[J]; 263 | M = {}; // Zero M 264 | M[I] = mI * (-m_s) + mJ * m_c; 265 | M[J] = mI * (-m_c) - mJ * m_s; 266 | return M; 267 | } 268 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 269 | ImVec4 mI = M[I]; 270 | ImVec4 mJ = M[J]; 271 | M = {}; // Zero M 272 | M[I] = mI * (-m_c) - mJ * m_s; 273 | M[J] = mI * m_s - mJ * m_c; 274 | return M; 275 | } 276 | }; 277 | 278 | using RotationX = RotationIJ<1, 2>; 279 | using RotationY = RotationIJ<2, 0>; 280 | using RotationZ = RotationIJ<0, 1>; 281 | 282 | class RotationAroundAxis { 283 | ImMat4 m_M{}; // cache of rotation matrix 284 | ImVec4 m_axis{}; 285 | const scalar_t m_c, m_s; // cache of cos(t) and sin(t) 286 | public: 287 | RotationAroundAxis(const ImVec4& axis, scalar_t t) : m_axis{ normalise(axis) }, m_c{ cosf(t) }, m_s{ sinf(t) } { 288 | IM_ASSERT(axis.w == 0.0f); // We could allow non-zero values but then should project the axis in the proper way 289 | 290 | // Calculate rotation matrix in constructor, adapted from the glm implementation of rotate 291 | ImVec4 temp = m_axis * (1 - m_c); 292 | m_M[0].x = m_c + temp.x * m_axis.x; 293 | m_M[0].y = temp.x * m_axis.y + m_s * m_axis.z; 294 | m_M[0].z = temp.x * m_axis.z - m_s * m_axis.y; 295 | 296 | m_M[1].x = temp.y * m_axis.x - m_s * m_axis.z; 297 | m_M[1].y = m_c + temp.y * m_axis.y; 298 | m_M[1].z = temp.y * m_axis.z + m_s * m_axis.x; 299 | 300 | m_M[2].x = temp.z * m_axis.x + m_s * m_axis.y; 301 | m_M[2].y = temp.z * m_axis.y - m_s * m_axis.x; 302 | m_M[2].z = m_c + temp.z * m_axis.z; 303 | 304 | m_M[3].w = 1.0f; 305 | }; 306 | inline void applyTransformationOnRightInPlace(ImMat4* m_begin, ImMat4* m_end) const { 307 | for (ImMat4* it = m_begin; it != m_end; ++it) 308 | *it = *it * m_M; 309 | } 310 | inline ImMat4& applyDerivativeOnRightInPlace(ImMat4& M) const { 311 | ImVec4 temp = m_axis * m_s; 312 | 313 | ImMat4 dM{}; 314 | dM[0].x = -m_s + temp.x * m_axis.x; 315 | dM[0].y = temp.x * m_axis.y + m_c * m_axis.z; 316 | dM[0].z = temp.x * m_axis.z - m_c * m_axis.y; 317 | 318 | dM[1].x = temp.y * m_axis.x - m_c * m_axis.z; 319 | dM[1].y = -m_s + temp.y * m_axis.y; 320 | dM[1].z = temp.y * m_axis.z + m_c * m_axis.x; 321 | 322 | dM[2].x = temp.z * m_axis.x + m_c * m_axis.y; 323 | dM[2].y = temp.z * m_axis.y - m_c * m_axis.x; 324 | dM[2].z = -m_s + temp.z * m_axis.z; 325 | 326 | M = M * dM; 327 | return M; 328 | } 329 | inline ImMat4& apply2ndDerivOnRightInPlace(ImMat4& M) const { 330 | ImVec4 temp = m_axis * m_c; 331 | 332 | ImMat4 d2M{}; 333 | d2M[0].x = -m_c + temp.x * m_axis.x; 334 | d2M[0].y = temp.x * m_axis.y - m_s * m_axis.z; 335 | d2M[0].z = temp.x * m_axis.z + m_s * m_axis.y; 336 | 337 | d2M[1].x = temp.y * m_axis.x + m_s * m_axis.z; 338 | d2M[1].y = -m_c + temp.y * m_axis.y; 339 | d2M[1].z = temp.y * m_axis.z - m_s * m_axis.x; 340 | 341 | d2M[2].x = temp.z * m_axis.x - m_s * m_axis.y; 342 | d2M[2].y = temp.z * m_axis.y + m_s * m_axis.x; 343 | d2M[2].z = -m_c + temp.z * m_axis.z; 344 | 345 | M = M * d2M; 346 | return M; 347 | } 348 | }; 349 | 350 | 351 | // The transformation stack holds matrix representations of transformations and their (second) derivatives 352 | class TransformationStack 353 | { 354 | public: 355 | 356 | inline ImVec4 apply(const ImVec4& p) const { return isEmpty() ? p : getMatrix() * p; }; 357 | inline ImVec4 applyDerivative(const ImVec4& v, float* param) const { return isEmpty() ? ImVec4{} : getDerivativeMatrix(param) * v; }; 358 | inline ImVec4 applySecondDerivative(const ImVec4& v, float* p1, float* p2) const { return isEmpty() ? ImVec4{} : getSecondDerivativeMatrix(p1, p2) * v; }; 359 | 360 | 361 | inline const ImMat4& getMatrix() const { return isEmpty() ? m_identity : *beginLevel(); } 362 | const ImMat4& getDerivativeMatrix(param_t param) const; 363 | const ImMat4& getSecondDerivativeMatrix(param_t p1, param_t p2) const; 364 | 365 | inline size_t getStackCapacity() const { return static_cast(m_data.capacity()); } // In number of matrices 366 | inline size_t getStackDepth() const { return m_depth; } // In number of transformations 367 | inline size_t getStackSize() const { return m_data.Size; } // In number of matrices 368 | inline size_t getStackWidth() const { return m_width; } // In number of matrices 369 | 370 | inline size_t getNumDerivatives() const { return static_cast(m_derivative_parameters.size()); } 371 | inline size_t getNumSecondDerivatives() const { return m_num_second_derivs; } 372 | 373 | 374 | void setDerivativeParameters(const ImVector& d_params, int n_second_derivatives); 375 | 376 | void pushCompositeLevel() { if (m_composite_level == 0) pushIdentity(); ++m_composite_level; } 377 | void popCompositeLevel() { IM_ASSERT(m_composite_level > 0); m_composite_level--; }; 378 | 379 | template void pushConstantTransformation(const T_t& T); 380 | template void pushTransformation(const T_t& T, param_t param); 381 | template void pushTransformation(const T_t& T, const Parameters::Parameter& param); 382 | 383 | inline void popTransformation() { IM_ASSERT(!isEmpty()); IM_ASSERT(m_composite_level == 0); resize(m_depth - 1); } 384 | 385 | inline bool isEmpty() const { return m_depth == 0; } 386 | void clear() { resize(0); }; 387 | void reset(); 388 | inline void resize(size_t new_size) { m_data.resize(static_cast(new_size * m_width)); m_depth = new_size; } 389 | 390 | 391 | private: 392 | // This is here so that we can return references to the identity when the stack is empty (could be made static) 393 | const ImMat4 m_identity{ ImVec4{1,0,0,0}, ImVec4{0,1,0,0}, ImVec4{0,0,1,0}, ImVec4{0,0,0,1} }; 394 | 395 | // This is here so that we can return references to zero when a parameter isn't in the list of derivatives (could be made static) 396 | const ImMat4 m_zero_matrix{ ImVec4{0,0,0,0}, ImVec4{0,0,0,0}, ImVec4{0,0,0,0}, ImVec4{0,0,0,0} }; 397 | 398 | size_t m_depth{}; // depth of stack 399 | size_t m_width{ 1 }; // width of stack, determined by number of derivatives required 400 | 401 | ImVector m_data{}; // should have size m_depth * m_width at any time 402 | 403 | // Contains the list of parameters we are differentiating against 404 | ImVector m_derivative_parameters{}; 405 | 406 | // We compute second derivatives for all pairs of parameters in the first m_num_second_derivs entries of m_derivative_parameters 407 | size_t m_num_second_derivs{}; 408 | 409 | // The depth of composite transformation calls 410 | unsigned int m_composite_level{}; 411 | 412 | void copyWithinStack(size_t source_ix, size_t target_ix); 413 | void pushIdentity(); 414 | 415 | inline ImMat4* beginLevel() { return m_data.Data + (m_depth - 1) * m_width; } 416 | inline ImMat4* endLevel() { return m_data.Data + m_depth * m_width; } 417 | inline ImMat4 const* beginLevel() const { return m_data.Data + (m_depth - 1) * m_width; } 418 | inline ImMat4 const* endLevel() const { return m_data.Data + m_depth * m_width; } 419 | 420 | inline ImMat4* beginDerivatives() { return m_data.Data + (m_depth - 1) * m_width + 1; } 421 | inline ImMat4* endDerivatives() { return m_data.Data + (m_depth - 1) * m_width + 1 + static_cast(m_derivative_parameters.Size); } 422 | inline ImMat4 const* beginDerivatives() const { return m_data.Data + (m_depth - 1) * m_width + 1; } 423 | inline ImMat4 const* endDerivatives() const { return m_data.Data + (m_depth - 1) * m_width + 1 + static_cast(m_derivative_parameters.Size); } 424 | 425 | inline ImMat4* beginSecondDerivatives() { return m_data.Data + (m_depth - 1) * m_width + 1 + static_cast(m_derivative_parameters.Size); } 426 | inline ImMat4* endSecondDerivatives() { return endLevel(); } 427 | inline ImMat4 const* beginSecondDerivatives() const { return m_data.Data + (m_depth - 1) * m_width + 1 + static_cast(m_derivative_parameters.Size); } 428 | inline ImMat4 const* endSecondDerivatives() const { return endLevel(); } 429 | }; 430 | 431 | template 432 | inline T symmetric_index_ordered(T i, T j) { IM_ASSERT(i <= j); return i + (j * (j + 1)) / 2; } 433 | template 434 | inline T symmetric_index(T i, T j) { if (i > j) { /* swap */ T k = i; i = j; j = k; } return symmetric_index_ordered(i, j); } 435 | template inline T symmetric_size(T n) { return symmetric_index_ordered(0, n); } 436 | 437 | // A class that holds a list of parameters, a value of type T and the derivatives of that value with respect to the parameters 438 | template 439 | class ImJet1 { 440 | public: 441 | const ImVector parameters; // The number of parameters, a constant 442 | ImJet1(const ImVector& params) : parameters{ params } { int s = size(); m_data.resize(1 + s, {}); } 443 | 444 | int size() const { return parameters.Size; } 445 | 446 | T& value() { return m_data[0]; } 447 | const T& value() const { return m_data[0]; } 448 | T& derivative(int i) { IM_ASSERT(i < size()); return m_data[1 + i]; } 449 | const T& derivative(int i) const { IM_ASSERT(i < size()); return m_data[1 + i]; } 450 | 451 | private: 452 | ImVector m_data{}; 453 | }; 454 | 455 | // A class that holds a list of parameters, a value of type T, the derivatives of that value with respect to the parameters and the second derivatives of that value wrt those same parameters 456 | template 457 | class ImJet2 { 458 | public: 459 | const ImVector parameters; // The list of parameters, a constant 460 | ImJet2(const ImVector& params) : parameters{ params } { int s = size(); m_data.resize(1 + s + symmetric_size(s), {}); } 461 | 462 | int size() const { return parameters.Size; } 463 | 464 | T& value() { return m_data[0]; } 465 | const T& value() const { return m_data[0]; } 466 | T& derivative(int i) { IM_ASSERT(i < size()); return m_data[1 + i]; } 467 | const T& derivative(int i) const { IM_ASSERT(i < size()); return m_data[1 + i]; } 468 | const T& second_derivative(int i, int j) const { IM_ASSERT((i < size()) && (j < size())); return m_data[1 + size() + symmetric_index(i, j)]; } 469 | T& second_derivative(int i, int j) { IM_ASSERT((i < size()) && (j < size())); return m_data[1 + size() + symmetric_index(i, j)]; } 470 | 471 | void push_back_parameter(param_t p) { 472 | parameters.push_back(p); 473 | auto n = parameters.size(); 474 | m_data.insert(m_data.begin() + n, {}); // Add slot for new derivative, shifting current second derivatives back 475 | m_data.resize(1 + n + symmetric_size(0, n), {}); // Make space at end for new second derivatives 476 | } 477 | 478 | ImJet2& operator+=(const ImJet2& b) { 479 | value() += b.value(); 480 | ImVector b_param_indices{}; 481 | for (int i = 0; i < b.size(); ++i) { 482 | const param_t* q = parameters.find(b.parameters[i]); 483 | if (q == parameters.end()) 484 | push_back_parameter(b.parameters[i]); 485 | const int ix = q - parameters.begin(); 486 | b_param_indices.push_back(ix); 487 | derivative(ix) += b.derivative(i); 488 | for (int j = 0; j <= i; ++j) 489 | second_derivative(b_param_indices[j], ix) += b.second_derivative(j, i); 490 | } 491 | } 492 | 493 | private: 494 | ImVector m_data{}; // 1 value, n derivatives and n * (n + 1) / 2 second derivatives 495 | }; 496 | 497 | 498 | ImJet1 apply_stack_to_1jet(const TransformationStack& transform, const ImJet1& jet); 499 | ImJet2 apply_stack_to_2jet(const TransformationStack& transform, const ImJet2& jet); 500 | 501 | ImJet1 project_1jet(const ImJet1& jet); 502 | ImJet2 project_2jet(const ImJet2& jet); 503 | 504 | ImJet2 distance_squared(const ImJet2& jet, const ImVec4& target); 505 | 506 | ImVector apply_conjugate_gradient_method(const ImJet2& jet, float kappa = 1.0f); 507 | 508 | inline ImVec4 calculate_tangent_after_projection(const TransformationStack& transform, const ImVec4& pos, param_t param); 509 | 510 | ImVector bring_together(const TransformationStack& transform, const ImVec4& pos, const ImVec4& target, const ImVector& free_parameters, float kappa = 1.0f); 511 | 512 | } // namespace Transformations 513 | 514 | struct parameter_change_t { 515 | param_t parameter{}; 516 | float change{ 0 }; 517 | }; 518 | 519 | // The deferral stack gives control over when a parameter change happens, rather 520 | // than applying a change immediately the change is placed in a slot and the 521 | // change happens when the slot is popped from the stack. In practice we only 522 | // have one change queued and so instead of having a stack of changes we just 523 | // record which slot the single change is contained in. 524 | class ParameterChangeDeferralStack { 525 | public: 526 | void addParameterChange(ImVector const& changes); 527 | bool applyParameterChanges(); 528 | 529 | void pushDeferralSlot(); 530 | bool popDeferralSlot(); 531 | 532 | void reset(); 533 | 534 | private: 535 | ImVector m_deferred_changes{}; // The deferred change 536 | unsigned int m_deferral_stack_size{ 1 }; // Stack is never empty, there is always a slot 537 | int m_deferred_change_position{ -1 }; // -1 means there is no deferred step in the stack 538 | }; 539 | 540 | /* 541 | A single manager handles all the control points, this works because only one 542 | item is typically active at once. A number of control points may be 543 | activated at the same time if they overlap, but only the one with the least 544 | z value will be active in the next frame. 545 | 546 | In order to move a control point, the (second) derivatives with respect to 547 | its free parameters are required. The (second) derivatives are calculated as 548 | the stack is built, so the required parameters are registered when an item 549 | is activated, then the derivatives for the registered parameters are 550 | calculated for the next frame when the item is active. 551 | 552 | The user has some control of when parameters are changed. Clearly they can 553 | only be changed when an item is active. The default is that the change 554 | happens at the end of frame. By passing the relevant ControlPointFlag the 555 | user can specify that the change happens after the control point function is 556 | called. Or the user can use the PushDeferralSlot() and PopDeferralSlot() 557 | functions. This causes the parameter to be changed when the corresponding 558 | slot is popped. 559 | */ 560 | class ImControlContext { 561 | public: 562 | void newFrame(Transformations::TransformationStack& transform); 563 | void endFrame(); 564 | 565 | bool beginView(const char* name, ImVec2 size, ImVec4 const& border_color, bool defer_changes); 566 | bool endView(bool include_button = false, ImGuiButtonFlags flags = 0); 567 | 568 | void pushDeferralSlot(); 569 | bool popDeferralSlot(); // Returns true if any parameters are changed 570 | 571 | ImVec2 getViewBoundsMin() const; 572 | ImVec2 getViewBoundsMax() const; 573 | const ImVec2& getViewSize() const; 574 | ImVec2 const& getLastControlPointPosition() const { return m_last_control_point_position; } 575 | float getLastControlPointRadius() const { return m_last_control_point_radius; } 576 | float const& getLastParameterStep(int ix) const { return m_last_step[ix].change; } 577 | 578 | void registerFreeParameterDerivativeForNextFrame(param_t param); 579 | void registerFreeParameterSecondDerForNextFrame(param_t p); 580 | 581 | ImVec2 pointInScreenCoords(const Transformations::TransformationStack& transform, const ImVec4& pos); 582 | 583 | void setRegularisationParam(float p) { m_regularisation_constant = p; } 584 | 585 | // Manage vector of free parameters 586 | void pushFreeParameter(param_t param) { IM_ASSERT(m_free_parameters.find(param) == m_free_parameters.end()); m_free_parameters.push_back(param); } 587 | void popFreeParameter() { IM_ASSERT(m_free_parameters.size() > 0); m_free_parameters.pop_back(); } 588 | void restrictParameter(param_t param) { bool found = m_free_parameters.find_erase(param); IM_ASSERT(found); } // Maintains order of parameters 589 | void clearParameters() { m_free_parameters.resize(0); } // Maintains order of parameters 590 | 591 | bool staticControlPoint(const char* str_id, const Transformations::TransformationStack& transform, const ImVec4& pos, ImControlPointFlags flags = 0, ImGuiButtonFlags button_flags = 0, float marker_radius = 0.1f, ImVec4 marker_col = { 1, 1, 1, 1 }); 592 | bool controlPoint(const char* str_id, const Transformations::TransformationStack& transform, const ImVec4& pos, ImControlPointFlags flags, ImGuiButtonFlags button_flags, float marker_radius, ImVec4 marker_col); 593 | 594 | private: 595 | bool m_view_active{ false }; 596 | ImGuiID m_view_id{}; 597 | bool m_view_defers_changes{ false }; 598 | float m_view_marker_size_factor{ 1 }; 599 | ImVec2 m_view_size{}; 600 | ImRect m_view_bb{}; 601 | 602 | ImVec2 m_cursor_pos_before_view{}; 603 | ImVec2 m_cursor_position_after_view{}; 604 | 605 | ImVector m_free_parameters{}; 606 | 607 | ImVector m_registered_der_params{}; 608 | ImVector m_registered_2nd_der_params{}; 609 | 610 | void drawDerivative(ImVec2 const& pos, ImVec2 const& d, ImVec4 const& col, float thickness = 1.0f) const; 611 | 612 | ImVec2 getMousePositionInViewCoords() const; 613 | ImVec2 viewCoordsToScreenCoords(ImVec2 const& p) const; 614 | ImVec2 screenCoordsToViewCoords(ImVec2 const& p) const; 615 | bool createPoint(const char* str_id, ImVec4 const& transformed_pos, ImControlPointFlags const& flags, ImGuiButtonFlags const& button_flags, float marker_radius, ImVec4 marker_col); 616 | 617 | ImGuiID m_activated_point_id{}; 618 | void* m_activated_point_window{}; 619 | float m_activated_point_z_order{}; 620 | ImVector m_activated_point_parameters{}; 621 | 622 | float m_last_point_z_order{}; 623 | 624 | bool saveActivatedPoint(const ImVector& params = {}); 625 | 626 | ImVector m_last_step{}; 627 | ImVec2 m_last_control_point_position{}; 628 | float m_last_control_point_radius{}; 629 | 630 | ParameterChangeDeferralStack m_deferral_stack{}; 631 | 632 | float m_regularisation_constant = 0.5f; 633 | 634 | bool updateParameters(ImVector const& changes, ImControlPointFlags const& flags); 635 | }; 636 | } --------------------------------------------------------------------------------