-
Notifications
You must be signed in to change notification settings - Fork 28
/
showmotion.m
3724 lines (2975 loc) · 107 KB
/
showmotion.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
function showmotion( model, t_data, q_data )
% SHOWMOTION 3D animation of a robot or other mechanical system
% showmotion(model,t_data,q_data) presents an interactive 3D animation of
% a robot or other mechanical system. The first argument is a model data
% structure describing the robot; the second is a vector of time values;
% and the third is an array of position variable values. The dimension of
% q_data can be either (np)x(nt) or (np)x1x(nt), where np is the number of
% position variables required by the model and nt is the number of time
% values supplied (i.e., nt==length(t_data)). The second option is the
% array output format of Simulink. The second and third arguments can be
% omitted, in which case default values are supplied that exercise each
% joint in turn. Showmotion can also be called as follows:
% showmotion(filename) -- load an animation from a matlab .mat file;
% showmotion('save',filename) -- save the current animation to a matlab
% .mat file; showmotion('about') -- print a description of the current
% animation in the matlab command window; or showmotion('about',newdata) --
% supply or modify the description data.
global ShoMoData;
function resizefn(source,event) % figure resize callback
if ishandle(ShoMoData.fig)
ShoMoData.figrect = get( ShoMoData.fig, 'Position' );
end
end
function deletefn(source,event) % figure delete callback
try
ShoMoData.figrect = get( ShoMoData.fig, 'Position' );
if ishandle(ShoMoData.panel.fig)
delete( ShoMoData.panel.fig );
end
command_summary(0);
ShoMoData.fig = NaN; % because Matlab recycles handles
catch
% ShoMoData has been cleared (e.g. by 'clear all' command)
end
end
firstcall = ~isstruct(ShoMoData); % no pre-existing data structure
if nargin == 0 % check argument list
bad_arg_list = 1;
elseif isstruct(model)
bad_arg_list = ( ~isfield(model,'NB') || nargin == 2 );
elseif ~ischar(model)
bad_arg_list = 1;
elseif strcmp(model,'save')
bad_arg_list = ( nargin ~= 2 || ~ischar(t_data) );
elseif strcmp(model,'about')
bad_arg_list = ( nargin > 2 || nargin == 2 && ~isstruct(t_data) );
else
bad_arg_list = ( nargin ~= 1 );
end
if bad_arg_list
msg = sprintf(['Bad argument list. Call showmotion as follows:\n' ...
' showmotion( model )\n' ...
' showmotion( model, time_data, position_data )\n' ...
' showmotion( filename )\n' ...
' showmotion( ''save'', filename )\n' ...
' showmotion( ''about'' )\n' ...
' showmotion( ''about'', new_data )']);
error( msg );
end
if ischar(model) % save, load and about commands
switch model
case 'save'
filename = t_data;
savefile( filename );
case 'about'
if nargin == 2
newdata = t_data;
about( newdata );
else
about;
end
otherwise
filename = model;
loadfile( filename );
end
return
end
if nargin == 1
[t_data,q_data] = default_t_q_data( model );
end
if ~isnumeric(t_data) || ~isvector(t_data) || length(t_data) < 2 || ...
min( t_data(2:end) - t_data(1:end-1) ) < 0
error( ['second argument must be a vector of ' ...
'monotonically increasing time values'] );
end
if t_data(end) <= t_data(1)
error( 'final time value must be greater than first' );
end
if ~isnumeric(q_data)
error( 'third argument (position data) must be numeric' );
end
if ndims(q_data) == 3 && size(q_data,2) == 1
tmp(:,:) = q_data(:,1,:);
q_data = tmp;
end
if ndims(q_data) ~= 2 || size(q_data,1) ~= model.NB || ...
size(q_data,2) ~= length(t_data)
error( ['dimensions of third argument must be (np)x(nt) or ' ...
'(np)x1x(nt) where np=model.NB and nt=length(t_data)'] );
end
ShoMoData.model = model;
ShoMoData.t_data = t_data;
ShoMoData.q_data = q_data;
ShoMoData.about.author = '';
ShoMoData.about.title = '';
ShoMoData.about.description = {};
ShoMoData.about.date = date;
ShoMoData.about.creator = 2; % spatial_v2
ShoMoData.animating = 0;
ShoMoData.autorepeat = 0;
ShoMoData.vw_mode = 'normal';
speedctrl('initialize');
timectrl('initialize');
if firstcall
ShoMoData.figrect = [];
ShoMoData.fig = NaN;
ShoMoData.panel = makepanel;
end
if ~ishandle(ShoMoData.fig) % no current figure exists
fig = figure( 'Renderer', 'OpenGL', ...
'Color', 'k', ...
'HandleVisibility', 'callback', ...
'Name', 'ShowMotion', ...
'NumberTitle', 'off', ...
'MenuBar', 'none', ...
'DeleteFcn', @deletefn, ...
'ResizeFcn', @resizefn, ...
'KeyPressFcn', @keyhandler, ...
'WindowButtonDownFcn', {@mousehandler,'down'}, ...
'WindowButtonUpFcn', {@mousehandler,'up'}, ...
'WindowButtonMotionFcn', {@mousehandler,'move'}, ...
'WindowScrollWheelFcn', {@mousehandler,'scroll'} );
if firstcall
ShoMoData.figrect = get( fig, 'Position' );
else
set( fig, 'Position', ShoMoData.figrect );
end
ShoMoData.fig = fig;
newfigure = 1;
else % reuse current figure
fig = ShoMoData.fig;
delete( ShoMoData.ax ); % but delete its axes
newfigure = 0;
end
ax = axes( 'Parent', fig, ...
'Position', [0 0 1 1], ...
'Visible', 'off', ...
'DataAspectRatioMode', 'manual', ...
'PlotBoxAspectRatioMode', 'manual' );
ShoMoData.ax = ax;
ShoMoData.ball = crystalball( ax );
ShoMoData.handles = makedrawing( model, ax );
ShoMoData.camera = makecamera( model );
ShoMoData.lights = makelights( ShoMoData.camera, ax );
reposition;
viewreset;
makemenu;
if newfigure
showpanel;
end
updatepanel;
drawnow;
end % because there are nested functions
%----------
%{
SHOWMOTION -- a few words of explanation
----------
This is the source code for the 3D animator/viewer ShowMotion, which is part
of the software package spatial_v2. It was developed as a collection of
separate files, which were concatenated to make this single file. Comments of
the form %---------- mark the boundaries between the files. Because a few
files contain nested functions, it is necessary that all files finish their
functions with 'end'. The next file after this one is the shell script that
assembles everything into a single file. You can use it as a table of
contents.
The functions communicate via a single global variable called ShoMoData, which
is explained below. This strategy is necessitated by the decision to develop
ShowMotion in separate files and concatenate them only as the final step. All
vectors and 1D arrays in this data structure are row vectors, except t_data,
which is free to be either a row or a column vector.
ShoMoData:
model model data structure for this animation
t_data vector of time values for this animation
q_data 2D array of position variable data, one row for each joint
variable in the model, and one column for each time value
about data structure containing description (meta-)data from a
loaded animation file, or data to store with the current
animation when it is saved
author string containing author(s) name(s)
title string containing animation's title
description cell array of strings, one per line, providing a plain-text
description of the animation
date creation-date string (date stamp issued when an animation is
saved to a file)
creator version number of the software that created the file (=2)
playable list of versions capable of playing it (=2)
animating boolean: true when animation in progress
autorepeat boolean: true when autorepeat (infinite loop) enabled
vw_mode string used by mousehandler() to track current viewing mode,
values are 'normal', 'rotate' and 'pan'
speed data structure containing speed-related quantities
scale array defining the nonlinear scale on the speed slider
range defines the size and value range of the speed slider
(a slider with a range of N produces integers in the range
0..N and has a slot N+3 pixels wide with a working range of
N+1 pixels)
unity index of the element in .scale having unit value
ix index into .scale identifying current speed magnitude
sgn defines the sign of the speed, values: +/-1
speed actual speed, defined by .speed = .sgn * .scale(.ix)
sliderpos position of the speed slider's pointer, 0<=.sliderpos<=.range
displaystring string containing the numeric value of .speed
tmin minimum time, a copy of .t_data(1)
tmax maximum time, a copy of .t_data(end)
tview time of the current view in the main window
time data structure containing time-related quantities
coarseRange (=400) defines the size and range of the coarse time slider
fineRange (=100) defines the size and range of the fine time slider
cSliderPos visible position of the coarse slider's pointer
fSliderPos visible position of the fine slider's pointer
figrect array containing the figure rectangle: [x y wid ht]
fig handle of the main figure, or NaN if figure does not exist
panel structure relating to the control panel -- see below
ax handle of the main figure's axes
ball handle of the crystal ball, visible when .vw_mode=='rotate'
handles handles of the moving bodies: .handles(i) is the handle of an
hgtransform graphics object, and manipulating its matrix
alters the position of body i; see reposition()
camera structure containing camera-related quantities
trackbody body number of body to track, or zero for a fixed camera
trackpoint point to be tracked in moving body (3D vector expressed in
body coordinates)
direction defines initial viewing direction of camera (3D vector
expressed in scene coords pointing from scene to camera)
up defines initial camera 'up' vector (3D vector in scene coords)
zoom zoom factor relative to default
locus 2D vector in normalized window coordinates specifying where in
the window the trackpoint should appear
oldtrackpos previous location of .trackpoint in scene coordinates
lights structure relating to lighting
overhead handle of the overhead light
camera handle of the camera headlight
overheadPower intensity of overhead light (range 0..1)
cameraPower intensity of camera headlight (range 0..1)
view structure defining camera viewing parameters
on_hold boolean: true if camera has not been updated (update is
immediate if not animating, batched if animating)
angle camera viewing angle, set to 25 degrees
upvec camera up vector }
focus camera target point } all in scene coordinates
campos camera position }
treal tracks the passage of real time since the start of the current
animation, used by animate() to get the speed right
ShoMoData.panel:
red }
orange } RGB values defining the colours used to draw the control
green } panel
grey }
width width of control panel figure (=427)
height height of control panel figure (=80)
xy data structure containing the x-y coordinates of everything
that appears in the control panel (i.e., it defines the
control panel's layout)
figpos position of control panel figure on screen
fig control panel figure handle, or NaN if figure does not exist
repeat_off icon showing that auto-repeat is switched off
repeat_on icon showing that auto-repeat is switched on
forward icon showing that speed is positive (animations play forward)
reverse icon showing that speed is negative (animations play backwards)
ax handle of control panel's axes
playing boolean: true if the 'play' triangle is glowing orange
play handle of the play triangle
repeating boolean: true if the auto-repeat button is displaying the icon
.repeat_on
repeat handle of the image displaying the auto-repeat on/off icon
timeDisplay handle of the string displaying the current time (.tview)
updated time when .timeDisplay was last updated, used by updatepanel()
to limit the frequency at which .timeDisplay is updated
coarseSlider handle of coarse time slider's pointer
fineSlider handle of fine time slider's pointer
goingfwd boolean: true if the direction button is displaying the icon
.forward
direction handle of the image displaying the forward/reverse icon
speedSlider handle of the speed slider's pointer
speedDisplay handle of the string displaying the current speed
drag structure used when sliders are being dragged
pos unclipped slider position calculated from .x
mode string identifying which slider is being dragged, values are
'speed', 'fine', 'coarse'
x most recent x coordinate of cursor (pixels)
Overall, ShowMotion works by storing its arguments in ShoMoData, initializing
various other fields of ShoMoData, and creating one or two figures: a main
figure, showing 3D graphics, and (optionally) a control panel with sliders,
buttons and numeric displays. The latter is done the hard way: with basic
handle graphics and low-level code in callback functions. This is partly
because Matlab's high-level user-interface functions are not trustworthy
enough, and partly because they are not well suited to applications where the
user interface itself is being animated in real time. ShowMotion can also
display a command-summary window if you ask for help, but that is just static
text.
Calling showmotion(model,...) or showmotion(filename) starts a new ShowMotion
session, terminating the current session if any. Other ways to terminate the
current session are: via ShowMotion's pop-up menu (right mouse) in the main
window, closing the main window, or exiting Matlab. When ShowMotion starts a
new session, it tries to re-use the existing windows, or, if they have been
deleted, to create new windows in the same places. This is why ShowMotion
tries to keep track of where its windows are.
After the initial setup phase, everything is done by callback routines, and
every action except animation is essentially instantaneous. Animation itself
is implemented as a busy loop in which the processing of the next frame begins
as soon as the previous one is completed. During animation, all ShowMotion
keyboard, mouse and control-panel commands are still operational and have
practically instantaneous effect. However, the main Matlab command-line
window will be unresponsive. Animation uses the Matlab functions tic and toc
to measure the passage of real time during the animation, to make sure it
proceeds at the correct speed. If the time taken to draw a frame varies
significantly from one frame to the next then you will see a degree of visual
jitter in the displayed motion.
To allow smooth mouse-based view rotation and panning during animation, it is
necessary to defer and accumulate the incremental changes from each drag
event, rather than update the camera immediately with each event. This policy
is implemented via the function viewupdate() and ShoMoData.view.
BUGS:
1. Matlab does not play nicely with Ubuntu window managers, and perhaps not
with other window managers either. Both the position and size of the
window you get can be different from what you asked for, and the reported
position and size (from get()) can differ from what is on the screen.
Worse, the place where you have to click in order to hit a graphical object
can be several pixels above where that object appears on the screen. This
can adversely affect the usability of the control panel. I have no cure
for this problem. However, as it is intermittent in nature, a last-resort
remedy is to alert the user to the problem (in the command summary window's
tips) and recommend deleting the offending window and creating a new one,
which is easily done using the keyboard command 'p'.
2. Issuing a 'clear all' or 'clear global' Matlab command will clear
ShoMoData. If this is done while showmotion windows exist, these windows
become broken, and must be deleted manually. The callback routines check
for this possibility, with the effect that the main window will disappear
as soon as you move the cursor over it, and the control panel and command
summary windows will disappear if you press any key while they have the
keyboard focus or click on any clickable object.
3. Certain viewing parameters cause the scene to vanish (i.e., the main window
goes black). This bug is repeatable, and is probably due to Matlab. Only
a very small percentage of viewing parameters cause this problem, and their
exact values probably depend on the contents of the scene. If this happens
to you, then you simply have to move the camera a bit.
4. Rendering of models with a lot of bodies is slower than it should be. For
example, rendering a 100-link robot with each body drawn as a box is
significantly slower than rendering a 10-link robot with each body drawn as
10 boxes. Again, this seems to be attributable to Matlab.
%}
%----------
%{
echo 'concatenating all source files to make showmotion.all'
echo -e '\n\n%----------\n\n' > x
echo '%{' > y
echo '%}' > z
cat showmotion.m x y comments.txt z x y assemble z x > a0
cat about.m x adjustball.m x adjustlighting.m x > a1
cat animate.m x bounds.m x box.m x > a2
cat cameraxes.m x command_summary.m x crystalball.m x > a3
cat cylinder.m x defaultcolour.m x default_t_q_data.m x > a4
cat floortiles.m x interpolate.m x keyhandler.m x > a5
cat loadfile.m x lyne.m x makecamera.m x > a6
cat makedrawing.m x makelights.m x makemenu.m x > a7
cat makepanel.m x mousehandler.m x mousepos.m x > a8
cat mouseray.m x panelhandler.m x reposition.m x > a9
cat savefile.m x showpanel.m x speedctrl.m x > aa
cat sphere.m x timectrl.m x triangles.m x > ab
cat updatepanel.m x viewkeypan.m x viewmousepan.m x > ac
cat viewmouserot.m x viewpan.m x viewrefocus.m x > ad
cat viewreset.m x viewrotate.m x viewtrack.m x > ae
cat viewupdate.m x viewzoom.m > af
cat a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af > showmotion.all
rm a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af x y z
%}
%----------
function about( newdata )
% about print/alter author/title/description data
% about(newdata) prints or alters the author, title and description data
% stored with the current animation. If no argument is supplied then the
% current stored data is printed to standard output (i.e., the Matlab
% command window). If an argument is supplied then it must be a structure
% containing one or more of the following fields: .author, .title and
% .description. All three are strings, but the string in .description is
% interpreted as the name (including extension) of a plain text file
% containing the description text. For each field supplied, the new data
% replaces the old. To remove the current value, set the new value to ''
% (the empty string). This works for .description too; i.e.,
% .description='' is treated as a special case meaning "remove the current
% description data". Note: when description data is printed, it is
% automatically wrapped at the 80th column.
global ShoMoData;
if ~isstruct(ShoMoData)
error('There is no animation to describe');
end
if nargin == 1 % modify current data
% error checking -- is the argument OK?
if ~isstruct(newdata)
error('Argument must be a structure');
else
ok = 0;
if isfield(newdata,'author')
if ~ischar(newdata.author)
error('Author field must be a string');
end
ok = 1;
end
if isfield(newdata,'title')
if ~ischar(newdata.title)
error('Title field must be a string');
end
ok = 1;
end
if isfield(newdata,'description')
if ~ischar(newdata.description)
error('Description field must be a string');
end
ok = 1;
end
if ~ok
error(['Argument must contain at least one field ''author'','...
' ''title'' or ''description''']);
end
end
% everything OK, so make the changes
if isfield( newdata, 'author' )
ShoMoData.about.author = newdata.author;
end
if isfield( newdata, 'title' )
ShoMoData.about.title = newdata.title;
end
if isfield( newdata, 'description' )
if length(newdata.description) == 0
ShoMoData.about.description = {};
else
ShoMoData.about.description = read_desc( newdata.description );
end
end
else % print current data
if length(ShoMoData.about.title) > 0
fprintf( 1, 'Title: %s\n', ShoMoData.about.title );
else
fprintf( 1, 'Title: not specified\n' );
end
if length(ShoMoData.about.author) > 0
fprintf( 1, 'Author: %s\n', ShoMoData.about.author );
else
fprintf( 1, 'Author: not specified\n' );
end
fprintf( 1, 'Date: %s\n', ShoMoData.about.date );
fprintf( 1, 'creator: spatial_v%g\n\n', ShoMoData.about.creator );
if length(ShoMoData.about.description) > 0
fprintf( 1, 'Description:\n\n' );
desc = ShoMoData.about.description;
for i = 1:length(desc)
print_line( desc{i} );
end
else
fprintf( 1, 'Description: not specified\n' );
end
end
end
function desc = read_desc( filename )
% read a description (a cell array of strings) from a text file
[fid, message] = fopen( filename );
if length(message) > 0
error(['Unable to read file ',filename,': ',message]);
end
desc = {};
i = 1;
while 1
txt = fgetl(fid);
if ~ischar(txt) % end of file
break
end
desc{i} = deblank(txt); % remove trailing white space
i=i+1;
end
fclose(fid);
end
function print_line( txt )
% print a line, auto-wrapping at the 80th column if necessary
maxlen = 80;
while 1
spc = strfind( txt, ' ' );
if length(spc) == 0 || length(txt) <= maxlen
fprintf( 1, '%s\n', txt );
return
elseif spc(1) >= maxlen
fprintf( 1, '%s\n', txt(1:spc(1)-1) );
txt = strtrim(txt(spc(1)+1:end));
else
j = sum( spc < maxlen );
fprintf( 1, '%s\n', deblank(txt(1:spc(j)-1)) );
txt = strtrim(txt(spc(j)+1:end));
end
end
end
%----------
function adjustball( on )
% ADJUSTBALL adjust position, radius and visibility of crystal ball
% adjustball(on) alters the position and radius of the ShowMotion crystal
% ball so that it is centred on the camera's current target and its radius
% is such that it occupies 80% (linearly) of the current view. If an
% argument is supplied then this function makes the ball visible (on==1) or
% invisible (on==0), otherwise it leaves the visibility unaltered.
global ShoMoData;
if nargin == 1
if on
set( ShoMoData.ball, 'Visible', 'on' );
else
set( ShoMoData.ball, 'Visible', 'off' );
end
else
on = strcmp( 'on', get(ShoMoData.ball,'Visible') );
end
if on
vw = ShoMoData.view;
d = norm( vw.campos - vw.focus );
phi = atan(0.8*tan(vw.angle/2));
r = d * sin(phi); % radius that will fill 80% of view
M = [ r*eye(3), vw.focus'; 0 0 0 1 ];
set( ShoMoData.ball, 'Matrix', M );
end
end
%----------
function adjustlighting( cam, sun )
% ADJUSTLIGHTING adjust lights in a showmotion scene
% adjustlighting(cam,sun) allows one to adjust the intensity of the camera
% headlight and the overhead light (sunshine). Arguments take values of
% +1, -1 or 0, meaning more, less or no change. If this function is called
% with no arguments, then it updates the position of the camera headlight.
% (This must be done every time the camera is moved.)
global ShoMoData;
N = 12; % number of intensity levels
CAM_AZ = 10; % camera headlight relative azimuth
CAM_EL = 20; % and elevation in degrees
if nargin == 0
camlight( ShoMoData.lights.camera, CAM_AZ, CAM_EL );
else
if cam ~= 0
p = ShoMoData.lights.cameraPower;
if cam > 0
if p == 0
set( ShoMoData.lights.camera, 'Visible', 'on' );
end
p = min( 1, p+1/N );
else
p = max( 0, p-1/N );
if p < 1e-6/N
set( ShoMoData.lights.camera, 'Visible', 'off' );
p = 0;
end
end
ShoMoData.lights.cameraPower = p;
set( ShoMoData.lights.camera, 'Color', p*[1 1 1] );
end
if sun ~= 0
p = ShoMoData.lights.overheadPower;
if sun > 0
p = min( 1, p+1/N );
else
p = max( 1/N, p-1/N );
end
ShoMoData.lights.overheadPower = p;
set( ShoMoData.lights.overhead, 'Color', p*[1 1 1] );
end
end
end
%----------
function animate
% ANIMATE perform a ShowMotion animation
% animate commences or resumes a ShowMotion animation, exiting only when
% the animation is complete or has been stopped by some other event.
% Animation is implemented as a free-running busy loop in which the drawing
% of the next frame starts as soon as the current one is finished. Thus,
% the frame rate is as fast as the computer can go. animate uses the
% Matlab functions tic and toc to calculate the time interval between
% frames; so you should refrain from using these functions in other code
% while an animation is in progress.
global ShoMoData;
tic; % .treal tracks elapsed real time
ShoMoData.treal = toc; % since start of animation
% perform automatic rewind if required
if ShoMoData.speed.speed > 0 && ShoMoData.tview >= ShoMoData.tmax
timectrl( 'set', ShoMoData.tmin );
elseif ShoMoData.speed.speed < 0 && ShoMoData.tview <= ShoMoData.tmin
timectrl( 'set', ShoMoData.tmax );
end
ShoMoData.animating = 1; % now start animation
reposition;
viewtrack;
viewupdate;
updatepanel;
drawnow;
while ishandle(ShoMoData.fig) && ShoMoData.animating
treal = toc;
tview = ShoMoData.tview + ShoMoData.speed.speed * (treal-ShoMoData.treal);
if ShoMoData.speed.speed > 0
if tview >= ShoMoData.tmax
if ShoMoData.autorepeat
tview = tview - (ShoMoData.tmax - ShoMoData.tmin);
else
tview = ShoMoData.tmax;
ShoMoData.animating = 0;
end
end
elseif ShoMoData.speed.speed < 0
if tview <= ShoMoData.tmin
if ShoMoData.autorepeat
tview = tview + (ShoMoData.tmax - ShoMoData.tmin);
else
tview = ShoMoData.tmin;
ShoMoData.animating = 0;
end
end
end
ShoMoData.treal = treal;
timectrl('set',tview);
reposition;
viewtrack;
viewupdate;
updatepanel;
drawnow;
end
end
%----------
function [X,Y,Z] = bounds( h, T )
% BOUNDS calculate bounding box for all moving bodies
% [X,Y,Z]=bounds calculates the coordinates of a bounding box (in scene
% coordinates) containing all of the moving bodies. Each returned value is
% a low/high pair.
% Note: This function privately calls itself recursively with arguments h:
% a handle and T: a 4x4 homogeneous coordinate transform from local to
% scene coordinates (for column vectors); but the main call must have no
% arguments.
% Note: The drawing instructions for a moving body must consist of patch,
% line, hggroup and hgtransform objects only.
function mm = minmax( vec )
mm = [ min(vec), max(vec) ];
end
global ShoMoData;
if nargin == 0 % main call
X=[]; Y=[]; Z=[];
for h = ShoMoData.handles
[x,y,z] = bounds( h, eye(4) );
X = minmax( [x,X] );
Y = minmax( [y,Y] );
Z = minmax( [z,Z] );
end
if length(X) == 0
error('no moving body has drawing instructions');
end
else % internal (private) recursive call
type = get( h, 'Type' );
switch type
case { 'patch', 'line' }
if strcmp( type, 'patch' )
vertices = get( h, 'Vertices' );
else
vx = get( h, 'XData' );
vy = get( h, 'YData' );
vz = get( h, 'ZData' );
vertices = [vx' vy' vz'];
end
vertices(:,4) = 1;
vertices = vertices * T';
X = minmax(vertices(:,1));
Y = minmax(vertices(:,2));
Z = minmax(vertices(:,3));
case { 'hggroup', 'hgtransform' }
if strcmp( type, 'hgtransform' )
t = get( h, 'Matrix' );
T = T * t;
end
X=[]; Y=[]; Z=[];
c = get( h, 'Children' );
for h = c'
[x,y,z] = bounds( h, T );
X = minmax( [x,X] );
Y = minmax( [y,Y] );
Z = minmax( [z,Z] );
end
otherwise
error(['drawing instructions must consist of patch, line, hggroup' ...
' and hgtransform objects only']);
end
end
end % of main function (because there is a nested function)
%----------
function h = box( parent, coords, colour )
% BOX make a patch surface in the shape of a box
% h=box(parent,coords,colour) creates a box having the specified parent,
% coordinates and colour, and returns its handle. Coords is a 2x3 matrix
% containing any two diametrically opposite vertices of the box. The box
% itself is aligned with the coordinate axes. Colour is an RGB colour
% vector.
if any(coords(1,:)==coords(2,:))
error('a box must have positive extent in all 3 dimensions');
end
x0 = min(coords(:,1)); x1 = max(coords(:,1));
y0 = min(coords(:,2)); y1 = max(coords(:,2));
z0 = min(coords(:,3)); z1 = max(coords(:,3));
vertices = [ x0 y0 z0; x1 y0 z0; x1 y1 z0; x0 y1 z0; ...
x0 y0 z1; x1 y0 z1; x1 y1 z1; x0 y1 z1 ];
faces = [ 5 6 7 8; 6 5 1 2; 7 6 2 3; 8 7 3 4; 5 8 4 1; 4 3 2 1 ];
facenormals = [ 0 0 1; 0 -1 0; 1 0 0; 0 1 0; -1 0 0; 0 0 -1 ];
h = hggroup( 'Parent', parent );
for i = 1:6
patch( 'Parent', h, ...
'Vertices', vertices(faces(i,:),:), ...
'VertexNormals', ones(4,1)*facenormals(i,:), ...
'Faces', [1 2 3 4], ...
'FaceColor', colour, ...
'EdgeColor', 'none', ...
'FaceLighting', 'gouraud', ...
'BackFaceLighting', 'unlit' );
end
end
%----------
function [X,Y,Z] = cameraxes
% CAMERAXES returns the axes of the camera coordinate system
% [X,Y,Z]=cameraxes returns the scene coordinates of the three unit
% vectors that define the camera coordinate system. Z points directly
% towards the camera, X points right, and Y points up. All three are
% column vectors. The matrix E = [X Y Z] is the coordinate transform from
% camera to scene coordinates.
global ShoMoData;
vw = ShoMoData.view;
camdir = vw.campos - vw.focus;
Z = camdir / norm(camdir);
Y = vw.upvec - Z * dot(Z,vw.upvec);
Y = Y / norm(Y);
X = cross( Y, Z );
X=X'; Y=Y'; Z=Z'; % return column vectors
end
%----------
function command_summary(on)
% COMMAND_SUMMARY display a ShowMotion command summary
% command_summary(on) displays (on==1) or deletes (on==0) a summary of
% ShowMotion commands in a separate window.
persistent fig;
persistent figxy;
function deletefn(source,event) % window delete callback function
pos = get( source, 'Position' );
figxy = pos(1:2); % remember where the window was
fig = []; % indicate that it has been deleted
end
if on == 0 % delete request
if length(fig) ~= 0 % figure exists
delete( fig ); % so delete it
end
return
end
if length(fig) ~= 0 % figure exists
figure(fig); % so just raise it
return
end
% the code from here on is concerned with creating a new command summary
% window
headings = {...
'Keyboard Commands', ...
'Mouse Commands (main window)', ...
'Control Panel Commands', ...
'Tips' };
content{1}{1} = {...
'space', ...
'0 (zero)', ...
'r', ...
'p', ...
'h, ?', ...
'comma, dot, slash', ...
'1', ...
'arrow keys', ...
'shift-arrow keys', ...
'ctrl-arrow keys', ...
'z, Z', ...
'i', ...
'l, L, o, O', ...
'n, m, N, M, ctrl-N/M' };
content{1}{2} = { ...
'play/pause toggle', ...
'pause (if playing), rewind (if paused)', ...
'auto-repeat on/off', ...
'display/hide control panel (see tip 1)', ...
'show command summary (this window)', ...
'adjust animation speed and direction', ...
'reset animation speed to +1', ...
'pan', ...
'rotate view', ...
'rotate, zoom', ...
'zoom', ...
'restore initial view', ...
'adjust camera & overhead light intensity', ...
'adjust time in big/medium/small steps' };
content{2}{1} = { ...
'left button', ...
'shift-left button', ...
'middle button', ...
'left & right buttons', ...
'scroll wheel', ...
'right button' };
content{2}{2} = { ...
'rotate (with crystal ball - see tip 2)', ...
'pan', ...
'pan (UNIX)', ...
'pan (Windows)', ...
'zoom, refocus (see tip 3)', ...
'menu' };
content{3}{1} = { ...
'triangle', ...
'square', ...
'loop icon', ...
'help', ...
'two-arrow icon', ...
'speed slider', ...
'time slider', ...
'fine time slider' };
content{3}{2} = { ...
'play/pause toggle', ...
'pause (if playing), rewind (if paused)', ...
'auto-repeat on/off', ...
'display command summary (this window)', ...
'reverse animation direction', ...
'adjust animation speed', ...
'coarse time adjustment', ...
'fine time adjustment' };
content{4}{1} = { ...
'1.', ' ', '2.', ' ', '3.' };
content{4}{2} = { ...
'Control panel bug: clickable items sometimes appear below', ...