summaryrefslogtreecommitdiffhomepage
path: root/filter-accela-hamilton/accela_hamilton.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'filter-accela-hamilton/accela_hamilton.cpp')
-rw-r--r--filter-accela-hamilton/accela_hamilton.cpp121
1 files changed, 121 insertions, 0 deletions
diff --git a/filter-accela-hamilton/accela_hamilton.cpp b/filter-accela-hamilton/accela_hamilton.cpp
new file mode 100644
index 00000000..7fde90b8
--- /dev/null
+++ b/filter-accela-hamilton/accela_hamilton.cpp
@@ -0,0 +1,121 @@
+/* Copyright (c) 2012-2015 Stanislaw Halik
+ * Copyright (c) 2023-2024 Michael Welter
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+#include "accela_hamilton.h"
+#include "api/plugin-api.hpp"
+#include "opentrack/defs.hpp"
+
+#include <algorithm>
+
+#include "compat/math.hpp"
+#include "compat/hamilton-tools.h"
+#include "compat/math-imports.hpp"
+#include "compat/time.hpp"
+
+
+accela_hamilton::accela_hamilton()
+{
+ s.make_splines(spline_rot, spline_pos);
+}
+
+
+void accela_hamilton::filter(const double* input, double *output)
+{
+ constexpr double EPSILON = 1e-15F;
+
+ const tQuat current_rot = QuatFromYPR(input + Yaw);
+ const tVector current_pos(input[TX], input[TY], input[TZ]);
+
+ if (unlikely(first_run))
+ {
+ first_run = false;
+ last_rotation = current_rot;
+ last_position = current_pos;
+ t.start();
+#if defined DEBUG_ACCELA
+ debug_max = 0;
+ debug_timer.start();
+#endif
+ return;
+ }
+
+ const double pos_thres{s.pos_smoothing};
+ const double pos_dz{ s.pos_deadzone};
+
+ const double dt = t.elapsed_seconds();
+ t.start();
+
+ // Position
+ {
+ const tVector delta = current_pos - last_position;
+ const double delta_len = VectorLength(delta);
+ const tVector delta_normed = delta_len>0. ? delta/delta_len : tVector(); // Zero vector when length was zero.
+ const double gain = dt*spline_pos.get_value_no_save(std::max(0., delta_len-pos_dz) / pos_thres);
+ const tVector output_pos = last_position + (delta_normed * gain);
+ output[TX] = output_pos.v[0];
+ output[TY] = output_pos.v[1];
+ output[TZ] = output_pos.v[2];
+ last_position = output_pos;
+ }
+
+ // Zoom smoothing:
+ const double zoomed_smoothing = [this](double output_z) {
+ // Local copies because accessing settings involves thread synchronization
+ // and I don't like this in the middle of math.
+ const double max_zoomed_smoothing {s.max_zoomed_smoothing};
+ const double max_z {s.max_z};
+ // Movement toward the monitor is negative. Negate and clamp it to get a positive value
+ const double z = std::clamp(-output_z, 0., max_z);
+ return max_zoomed_smoothing * z / (max_z + EPSILON);
+ }(output[TZ]);
+
+ const double rot_dz{ s.rot_deadzone};
+ const double rot_thres = double{s.rot_smoothing} + zoomed_smoothing;
+
+ // Rotation
+ {
+ // Inter/extrapolates along the arc between the old and new orientation.
+ // It's basically a quaternion spherical linear interpolation, where the
+ // accela gain x dt is the blending parameter. Might actually overshoot
+ // the new orientation, but that's fine.
+
+ // Compute rotation angle and axis which brings the previous orientation to the current rotation
+ const double angle = AngleBetween(last_rotation, current_rot);
+ // Apply the Accela gain magic. The "gain_angle" is the desired rotation from the old orientation
+ // towards the current. Then alpha is the blending factor for the SLerp operation. It is normalized
+ // to the range [0,1] where 1 corresponds to the current orientation, i.e. it is the fractional
+ // rotation relative to the "gain_angle". EPSILON is added to prevent division by zero.
+ // Additionally we use std::min(1., ...) to clamp the blending. alpha>1 would probably not make much
+ // sense since it would mean extrapolation beyond the current orientation. And it would be a rare
+ // edge case. Secondly idk if Slerp supports alpha>1.
+ const double normalized_angle = std::max(0., angle - rot_dz) / rot_thres;
+ const double gain_angle = dt*spline_rot.get_value_no_save(std::abs(normalized_angle));
+ const double alpha = std::min(1., gain_angle / (angle + EPSILON));
+ // Rotate toward the measured orientation.
+ const tQuat output_rot = Slerp(last_rotation, current_rot, alpha);
+ // And back to Euler angles
+ QuatToYPR(output_rot, output + Yaw);
+ last_rotation = output_rot;
+ }
+}
+
+namespace detail::accela_hamilton {
+
+void settings_accela_hamilton::make_splines(spline& rot, spline& pos)
+{
+ rot.clear(); pos.clear();
+
+ for (const auto& val : rot_gains)
+ rot.add_point({ val.x, val.y });
+
+ for (const auto& val : pos_gains)
+ pos.add_point({ val.x, val.y });
+}
+
+} // ns detail::accela_hamilton
+
+OPENTRACK_DECLARE_FILTER(accela_hamilton, dialog_accela_hamilton, accela_hamiltonDll)