Skip to content

Commit 6ded1a5

Browse files
committed
add function to draw "coordinate frames"
e.g., labelled XYZ axes that can be used to show multibody kinematics. I've been using variants of this for years, but there are still lots of customisations needed to make it generally useful...
1 parent 040de4e commit 6ded1a5

File tree

1 file changed

+141
-0
lines changed

1 file changed

+141
-0
lines changed

draw_coord_frame.m

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
function draw_coord_frame(origin,varargin)
2+
%% draw_coord_frame( origin , <opts> )
3+
%
4+
% Plots a 3D coordinate system origin.
5+
% By default is aligned with the right-handed XYZ global coordinate system.
6+
% Use optional arguments to customise:
7+
%
8+
% ------------------------------------------------------------------------
9+
% KEY VALUE DESCRIPTION
10+
% ------------------------------------------------------------------------
11+
% 'rotate' [u v w] Rotate coordinates 'u' degrees around the X-axis,
12+
% 'v' degrees around the Y-axis,
13+
% 'w' degrees around the Z-axis.
14+
% 'axes' ['x' and/or 'y' and/or 'z']
15+
% Only plot axes listed
16+
% 'labels' [true/false]
17+
% Whether to print coordinate system labels
18+
% 'index' str String index on labels (default str='1')
19+
% ------------------------------------------------------------------------
20+
% 'length' L Length of axes lines
21+
% 'headlength' R Length of arrow head
22+
% 'arrowangle' a Angle of arrowhead "quills"
23+
% 'linecolour' [R G B] Red-Green-Blue colour of axis lines
24+
% 'headcolour' [R G B] Red-Green-Blue colour of arrowhead faces
25+
% 'headopacity' C Opacity of arrowhead faces
26+
% ------------------------------------------------------------------------
27+
28+
%% Parse inputs:
29+
30+
p = inputParser;
31+
p.addRequired('origin');
32+
p.addOptional('rotate',[0 0 0]);
33+
p.addOptional('length',0.05);
34+
p.addOptional('headlength',0.02);
35+
p.addOptional('arrowangle',25);
36+
p.addOptional('index','1');
37+
p.addOptional('labels',true);
38+
p.addOptional('axes','xyz');
39+
p.addParameter('linecolour',[0 0 0]);
40+
p.addParameter('headcolour',0.5*[1 1 1]);
41+
p.addParameter('headopacity',0.9);
42+
p.parse(origin,varargin{:})
43+
44+
O = p.Results.origin;
45+
al = p.Results.length;
46+
hl = p.Results.headlength;
47+
ang = p.Results.arrowangle;
48+
49+
r = p.Results.rotate;
50+
ni = p.Results.index;
51+
ecol = p.Results.linecolour;
52+
col = p.Results.headcolour;
53+
opac = p.Results.headopacity;
54+
labels_bool = p.Results.labels;
55+
plot_axes = p.Results.axes;
56+
57+
% Constant that should actually be optional input
58+
nameshift = 0.01;
59+
60+
%% Definition of a single axis (X)
61+
%
62+
% This is rotated to plot the other two in Y and Z.
63+
64+
ax = [al; 0; 0]; % axis end point
65+
pxy = ax - hl*[cosd(ang); sind(ang); 0]; % one point of the arrowhead
66+
pxz = ax - hl*[cosd(ang); 0; sind(ang)]; % one point of the other arrowhead
67+
head1 = [ax pxy pxy.*[1; -1; 1]]; % arrowhead points in XY plane
68+
head2 = [ax pxz pxz.*[1; 1; -1]]; % arrowhead points in XZ plane
69+
70+
% Rotation matrix (note inverse order of application)
71+
if numel(r) == 3
72+
R = Rz(r(3))*Ry(r(2))*Rx(r(1));
73+
elseif all(size(r)==[3,3])
74+
R = r;
75+
else
76+
error('Rotation must be 3 cardan angles or a 3x3 rotation matrix.')
77+
end
78+
79+
%% Plot
80+
81+
hold on
82+
83+
if strcmp(plot_axes,'xyz')
84+
if labels_bool
85+
text(-nameshift+O(1),-nameshift+O(2),O(3),['O_{',ni,'}']);
86+
end
87+
end
88+
89+
for s = plot_axes
90+
switch s
91+
case 'x'
92+
plot_one_coord(O,R*ax, R*head1, R*head2, ['x_{',ni,'}'],[2*nameshift; 0; 0])
93+
case 'y'
94+
plot_one_coord(O,R*Rz(+90)*ax,R*Rz(+90)*head1,R*Rz(+90)*head2,['y_{',ni,'}'],[0; nameshift; 0])
95+
case 'z'
96+
plot_one_coord(O,R*Ry(-90)*ax,R*Ry(-90)*head1,R*Ry(-90)*head2,['z_{',ni,'}'],[0; 0; nameshift])
97+
end
98+
end
99+
100+
%% Nested functions
101+
102+
function plot_one_coord(O,a,head1,head2,name,ns)
103+
if labels_bool
104+
text(ns(1)+O(1)+a(1),ns(2)+O(2)+a(2),ns(3)+O(3)+a(3),name);
105+
end
106+
107+
plot3(O(1)+[0 a(1)],O(2)+[0 a(2)],O(3)+[0 a(3)],'color',ecol);
108+
patch(O(1)+head1(1,:),O(2)+head1(2,:),O(3)+head1(3,:),ecol,'facealpha',opac,'facecolor',col);
109+
patch(O(1)+head2(1,:),O(2)+head2(2,:),O(3)+head2(3,:),ecol,'facealpha',opac,'facecolor',col);
110+
end
111+
112+
end
113+
114+
%% Rotation matrices
115+
%
116+
% These could all be anonymous functions if we wanted.
117+
118+
function R = Rz(t)
119+
R = [cosd(t) -sind(t) 0;
120+
sind(t) cosd(t) 0;
121+
0 0 1];
122+
end
123+
124+
function R = Ry(t)
125+
R = [ cosd(t) 0 sind(t);
126+
0 1 0;
127+
-sind(t) 0 cosd(t)];
128+
end
129+
130+
function R = Rx(t)
131+
R = [1 0 0 ;
132+
0 cosd(t) -sind(t);
133+
0 sind(t) cosd(t)];
134+
end
135+
136+
% assert( all( Rx(90)*[0;1;0]==[ 0; 0; 1]) )
137+
% assert( all( Rz(90)*[0;1;0]==[-1; 0; 0]) )
138+
% assert( all( Ry(90)*[1;0;0]==[ 0; 0;-1]) )
139+
% assert( all( Rz(90)*[1;0;0]==[ 0; 1; 0]) )
140+
% assert( all( Rx(90)*[0;0;1]==[ 0;-1; 0]) )
141+
% assert( all( Ry(90)*[0;0;1]==[ 1; 0; 0]) )

0 commit comments

Comments
 (0)