1% =========================================================================== %
2%> @brief animates a quaternion vector
4%>
animateQuat(quat) animates the orientation data quat. The animation is
5%> done in a
new figure window with a poseplot. The data must be provided
6%> either as a Nx4 numeric vector or as Nx1 quaternion vector.
8%>
animateQuat(quat, options) allows to specify additional options
for the
11%>
animateQuat(quat1, quat2, ..., quatN) animates multiple orientation data
12%> sets in the same figure window.
14%> @param quat - Nx4 numeric vector or Nx1 quaternion vector
15%> @param options (optional) -
struct with optional input arguments
16%> - names - cell array with names
for the orientation data (
default: 1:N)
17%> - freq - Sampling frequency of the data [Hz] (default: 200)
18%> - fps - Desired Frames per second [Hz] (default: 30)
19%> - speedup - Speedup factor of the animation (default: 1)
20%> - title - Title of the figure (default:
"Orientation Animation")
21%> - position - Position of the figure window (default: [0,0,0.5,0.5])
27%> @copyright see the file @ref LICENSE in the root directory of the repository
28% =========================================================================== %
34 quat (:,:) {mustBeNonmissing}
38 options.names (:,1) string = string.empty(1,0)
39 options.freq (1,1) double = 200;
40 options.fps (1,1)
double = 30;
41 options.speedup (1,1)
double = 1;
42 options.title (1,1)
string =
"Orientation Animation"
43 options.position = [0,0,0.5,0.5]
48% check
if quaternion vector is provided
49% define max sample size
51 if ~isa(quat{i},
'quaternion')
52 if size(quat{i}, 2) ~= 4
53 error(
'Quaternion vector must be Nx4');
55 quat{i} = quaternion(quat{i});
60 N_samples = max(N_samples, numel(quat{i}));
63% check
if names are provided
64if isempty(options.names) || numel(options.names) ~= N_quat
65 options.names = string(1:N_quat);
70smoothed_fps = options.fps;
73t = timer(
'ExecutionMode',
'fixedRate',
'Period', round(1/options.fps,3),
'TimerFcn', @
timer_callback);
77fig = figure(
'Name', options.title,
'NumberTitle',
'off',
'MenuBar',
'none',
'ToolBar',
'figure',
'Units',
'normalized',
"Position",options.position);
81 title(options.names(i));
84% add a close request function to the figure
87% add a time passed indicator to the bottom left of the figure
88text_passed = uicontrol(
'Style',
'text',
'String', sprintf(
'Time passed: %.2f s', 0),
'Units',
'normalized',
'Position', [0.01 0.01 0.3 0.05],
'FontSize', 12,
'FontWeight',
'bold');
90% add a fps indicator to the bottom right of the figure
91text_fps = uicontrol(
'Style',
'text',
'String', sprintf(
'FPS: %d', options.fps),
'Units',
'normalized',
'Position', [0.69 0.01 0.3 0.05],
'FontSize', 12,
'FontWeight',
'bold');
93% add a line to the top of the figure that indicates the current time
94line_curr_time = annotation(
'line', [0 0], [1,1],
'Units',
'normalized',
'LineWidth', 5,
'Color',
'r');
100% timer callback function
102 if t.UserData > N_samples
107 if t.UserData < numel(quat{i})
108 pp(i).Orientation = quat{i}(t.UserData);
110 pp(i).PatchFaceColor =
'r';
114 % update time passed indicator
115 text_passed.String = sprintf(
'Time passed: %.2f s', t.UserData/options.freq);
117 % update frequency indicator
118 curr_fps = 1/(t.InstantPeriod);
120 curr_fps = options.fps;
123 % Smooth the FPS value
using an IIR filter
124 alpha = 0.05; % Smoothing factor
125 smoothed_fps = alpha * round(curr_fps) + (1 - alpha) * smoothed_fps;
126 text_fps.String = sprintf(
'FPS: %d', round(smoothed_fps));
128 % calculate steps to skip to keep up with the desired frequency
129 skip = round(options.speedup * options.freq/curr_fps);
131 % update current time indicator
132 line_curr_time.X = [0, t.UserData/N_samples];
135 t.UserData = t.UserData + skip;
139% close request function
function animateQuat(in quat, in options)
function timer_callback(in ignoredArg, in ignoredArg)
function my_closereq(in ignoredArg, in ignoredArg)