Code covered by the BSD License  

Highlights from
3d Clock with geartrain

image thumbnail
from 3d Clock with geartrain by Michael Agostini
A clock built using a general 3d rendering engine which supports shafts, gears, and clock-hands.

myclock
%MYCLOCK 3D Clock with gear train.
%   MYCLOCK() generates and plots a 3D graphical representation of a
%   clock, complete with a transparent face and a working gear train.
%
%   This file implements a simple 3D graphics engine with support for
%   three basic primitive objects: shafts, hands (of a clock), and gears
%   The clock is made exclusively of these three types of objects.
%
%   Functionality is broken into sections: parameter loading, object
%   creation and visualization, and object updating.
%      Parameter Loading and object creation: 
%         This subsystem is responsible for loading the macroscopic 
%         1information for each primitive object (object thickness,
%         initial position, diameter, etc.) and converting that into
%         object data that MATLAB needs to render the objects of interest.
%      Object visualization:
%         Plots object data to a figure window.
%      Object updating:
%         Each object is capable of undergoing general motion, including
%         translation and rotation about an arbitrary fixed point.  The
%         object updating routines are responsible for moving the objects
%         every time step.  This is accomplished via access to the x,y,z
%         surface data accessible from the object graphics.
%
%   Future work will involve creating (or implementing an existing) higher
%   level gear connectivity over the existing low level object motion
%   commands currently available.  This would allow the operator to interact
%   with a gear train, rather than create rotating objects that happen to
%   look like gears.

% Michael Agostini
% Copyright 2005-2009 The MathWorks, Inc.
% This is provided as an example only.

% --------------------------------------------------------------------------
% myclock()
% --------------------------------------------------------------------------
% Main entry point to the program
% --------------------------------------------------------------------------
function myclock

% Create a clock, if one does not exist

   objectData          = loadObject('myclock.xml')     ;%filename meaningless
   canvas.figurehandle = figure                        ;
   canvas.axis         = [-12 12 -12 12   0 10]        ;

% Set a new close request handle, which will also stop the timer object

   set(canvas.figurehandle, 'CloseRequestFcn', @myclosereq);

% Splash the object onto a figure window, if a window does not exist

  [objectData,canvas]  = drawObject(objectData,canvas) ;

   setappdata(0, 'objectData', objectData   );
   setappdata(0,  'updateFcn', @updateObject);
   
% Set the view

   view([0 0 90])
   axis square
   axis off

% Create and start a timer

   mytimer     = timer('TimerFcn'     ,@timerUpdate , ...
                       'StartDelay'   , 0           , ...
                       'Period'       , 1           , ...
                       'Name'         ,'objectTimer', ...
                       'ExecutionMode','fixedrate'       );

   start(mytimer);



% --------------------------------------------------------------------------
% timerUpdate()
% --------------------------------------------------------------------------
% Function for timer object to call.
% --------------------------------------------------------------------------
function timerUpdate(varargin)

   objectData = getappdata(0, 'objectData')  ;
   fcnHandle  = getappdata(0, 'updateFcn' )  ;
   objectData = fcnHandle (objectData, 1  )  ;

   setappdata(0, 'objectData', objectData )  ;


% --------------------------------------------------------------------------
% myclosereq()
% --------------------------------------------------------------------------
% Deletes the timer when the figure is closed 
% --------------------------------------------------------------------------
function myclosereq(varargin)

% Stop and delete timer

   mytimer = timerfind('Name', 'objectTimer') ;

   if ~isempty(mytimer)
      stop  (mytimer)                         ;
      delete(mytimer)                         ;
   end

% Call normal close request

   closereq                                   ;


% ***************************************************************************
% Object loading/creation subroutines
% ***************************************************************************

% ---------------------------------------------------------------------------
% function object = createObject
% ---------------------------------------------------------------------------
% Loads object parameters from file and calls the appropriate creation
% function.
% 
% Currently the 'filename' variable is not used.  Eventually the object
% features will be loaded from an xml file.
% ---------------------------------------------------------------------------
function object = loadObject(filename)

% Load object feature array

   feature = clockFeatures;
%  feature = readobjectxml(filename);
 
% Create the 3d Objects from the paramaterized data
 
   for n = 1:length(feature)
      switch feature(n).type
       case {'shaft'}
         object.(feature(n).name) = createShaft (feature(n));
       case {'gear'}
         object.(feature(n).name) = createGear  (feature(n));
       case {'hand'}
         object.(feature(n).name) = createHand  (feature(n));
       case {'spring'}
         object.(feature(n).name) = createSpring(feature(n));
       otherwise
         disp(['Ignoring invalid feature type: ' feature(n).type])
      end
   end
 
% ---------------------------------------------------------------------------
% createGear()
% ---------------------------------------------------------------------------
% Creates a gear object
% ---------------------------------------------------------------------------
function gear = createGear(gearObject)

% Define local variables for constructing the gear

   gearRad         = gearObject.contactRad       ;
   toothHeight     = gearObject.toothheight      ;
   numTeeth        = gearObject.numTeeth         ;

   toothMin        = gearRad - toothHeight/2     ;
   toothMax        = gearRad + toothHeight/2     ;
   numEdges        = numTeeth*4                  ;

   inner           = gearRad - toothHeight*1.5   ;

% Create two cylinders of differing diameters

   [x,y,z]         = cylinder([inner inner toothMax toothMax inner], numEdges);
   [xp,yp,zp]      = cylinder([inner inner toothMin toothMin inner], numEdges);

   thickness       = gearObject.thickness            ;
   z(5,:)          =  ones(size(z(5,:)))*thickness   ;%set the correct length
   z(4,:)          =  ones(size(z(4,:)))*thickness   ;%set the correct length
   z(3,:)          = zeros(size(z(3,:)))             ;%set the bottom
   z(2,:)          = zeros(size(z(2,:)))             ;%set the bottom
   z(1,:)          =  ones(size(z(1,:)))*thickness   ;%set the wrap around

% Weave the two outer cylinders together to create the teeth

   for n = 1:size(x,2)
       type = rem(n,4);
       switch type
        case 0
          x(:,n) = xp(:,n);
          y(:,n) = yp(:,n);
        case 1
          x(:,n) = xp(:,n);
          y(:,n) = yp(:,n);
       end
   end

% Create the gear object
 
   gear.teeth.xdata     = x                        ;
   gear.teeth.ydata     = y                        ;
   gear.teeth.zdata     = z                        ;
   gear.teeth.handle    = []                       ;
   gear.teeth.facecolor = gearObject.facecolor     ;
   gear.teeth.center    = [ 0 0 0 ]                ;
   gear.teeth.location  = [ 0 0 0 ]                ;
   gear.teeth.rotAngle  = gearObject.angularVel    ;
   gear.teeth.translate = gearObject.linearVel     ;
 
% Set the optional data

   gear.teeth.static    = myset(gearObject,'static'   , 0     );
   gear.teeth.edgecolor = myset(gearObject,'edgecolor', 'k'   );
   gear.teeth.edgealpha = myset(gearObject,'edgealpha', 1     );
   gear.teeth.facecolor = myset(gearObject,'facecolor', 'k'   );
   gear.teeth.facealpha = myset(gearObject,'facealpha', 1     );
   gear.teeth.edgewidth = myset(gearObject,'edgewidth', 0.0001);

% Orient the object

   gear.teeth      = translateObject(gear.teeth, gearObject.location   );
   gear.teeth      = rotateObject   (gear.teeth, gearObject.orientation);

% Create the spokes of the gear

   hole            = gearObject.innerRad             ;
   hub             = hole*1.5                        ;
   [x,y,z]         = cylinder([hole  hole  hub   hub   hole  ], 36);
   [xb,yb,zb]      = cylinder([hole  hole  inner inner hole  ], 36);

   thickness       = gearObject.thickness            ;
   z(5,:)          =  ones(size(z(5,:)))*thickness   ;%set the correct length
   z(4,:)          =  ones(size(z(4,:)))*thickness   ;%set the correct length
   z(3,:)          = zeros(size(z(3,:)))             ;%set the bottom
   z(2,:)          = zeros(size(z(2,:)))             ;%set the bottom
   z(1,:)          =  ones(size(z(1,:)))*thickness   ;%set the wrap around

% Weave the two outer cylinders together to create the spokes

   for n = 1:size(x,2)
       type = rem(n,9);
       switch type
        case 4
          x(:,n) = xb(:,n);
          y(:,n) = yb(:,n);
        case 5
          x(:,n) = xb(:,n);
          y(:,n) = yb(:,n);
       end
   end

% Create the spoke object

   gear.spoke.xdata     = x                        ;
   gear.spoke.ydata     = y                        ;
   gear.spoke.zdata     = z                        ;
   gear.spoke.handle    = []                       ;
   gear.spoke.facecolor = gearObject.facecolor     ;
   gear.spoke.center    = [ 0 0 0 ]                ;
   gear.spoke.location  = [ 0 0 0 ]                ;
   gear.spoke.rotAngle  = gearObject.angularVel    ;
   gear.spoke.translate = gearObject.linearVel     ;

% Set the optional data

   gear.spoke.static    = myset(gearObject,'static'   , 0   );
   gear.spoke.edgecolor = myset(gearObject,'edgecolor', 'k' );
   gear.spoke.edgealpha = myset(gearObject,'edgealpha', 1   );
   gear.spoke.facecolor = myset(gearObject,'facecolor', 'k' );
   gear.spoke.facealpha = myset(gearObject,'facealpha', 1   );
   gear.spoke.edgewidth = myset(gearObject,'edgewidth', 0.01);

% Position the object

   gear.spoke  = translateObject(gear.spoke, gearObject.location   );
   gear.spoke  = rotateObject   (gear.spoke, gearObject.orientation);


% ---------------------------------------------------------------------------
% createShaft()
% ---------------------------------------------------------------------------
% Creates a shaft.
% ---------------------------------------------------------------------------
function shaft = createShaft(shaftObject)

% Create a basic cylinder

   inner           = shaftObject.innerRad             ;
   outer           = shaftObject.outerRad             ;
   numSides        = shaftObject.numSides             ;
   [x,y,z]         = cylinder([inner inner  outer  outer  inner], numSides);

% Cylinders are hollow, fill in the inside of the shaft

   length          = shaftObject.length              ;
   z(5,:)          =  ones(size(z(5,:)))*length      ;%set the correct length
   z(4,:)          =  ones(size(z(4,:)))*length      ;%set the correct length
   z(3,:)          = zeros(size(z(3,:)))             ;%set the bottom
   z(2,:)          = zeros(size(z(2,:)))             ;%set the bottom
   z(1,:)          =  ones(size(z(1,:)))*length      ;%set the wrap around

% Fill in the shaft data
 
   shaft.xdata     = x                        ;
   shaft.ydata     = y                        ;
   shaft.zdata     = z                        ;
   shaft.handle    = []                       ;
   shaft.facecolor = shaftObject.facecolor    ;
   shaft.center    = [ 0 0 0 ]                ;
   shaft.location  = [ 0 0 0 ]                ;
   shaft.rotAngle  = shaftObject.angularVel   ;
   shaft.translate = shaftObject.linearVel    ;

% Set the optional data

   shaft.static    = myset(shaftObject,'static'   , 0   );
   shaft.edgecolor = myset(shaftObject,'edgecolor', 'k' );
   shaft.edgealpha = myset(shaftObject,'edgealpha', 1   );
   shaft.facecolor = myset(shaftObject,'facecolor', 'k' );
   shaft.facealpha = myset(shaftObject,'facealpha', 1   );
   shaft.edgewidth = myset(shaftObject,'edgewidth', 0.01);

% Give it the correct orientation

   shaft           = translateObject(shaft, shaftObject.location)   ;
   shaft           = rotateObject   (shaft, shaftObject.orientation);


% ---------------------------------------------------------------------------
% createHand()
% ---------------------------------------------------------------------------
% Creates a clock hand
% ---------------------------------------------------------------------------
function hand = createHand(handObject)

% Create collar 'ring'

   ringData            = handObject               ;
   ringData.length     = ringData.thickness       ;

   hand.ring           = createShaft(ringData)    ;

% Create 'arm' of the hand

   offset              = handObject.innerRad      ;
   width               = handObject.thickness     ;
   [x,y,z]             = cylinder([width/2 width/2 0], 4);

   z(2,:)              = z(2,:)*handObject.length/2 ;
   z(3,:)              = z(3,:)*handObject.length   ;

   hand.arm.xdata      = x                        ;
   hand.arm.ydata      = y                        ;
   hand.arm.zdata      = z                        ;
   hand.arm.handle     = []                       ;
   hand.arm.facecolor  = handObject.facecolor     ;
   hand.arm.center     = [ 0 0 0 ]                ;
   hand.arm.location   = [ 0 0 0 ]                ;
   hand.arm.rotAngle   = handObject.angularVel    ;
   hand.arm.translate  = handObject.linearVel     ;

% Set the optional data

   hand.arm.static     = myset(handObject,'static'   , 0   );
   hand.arm.edgecolor  = myset(handObject,'edgecolor', 'k' );
   hand.arm.edgealpha  = myset(handObject,'edgealpha', 1   );
   hand.arm.facecolor  = myset(handObject,'facecolor', 'k' );
   hand.arm.facealpha  = myset(handObject,'facealpha', 1   );
   hand.arm.edgewidth  = myset(handObject,'edgewidth', 0.01);

% Orient the arm to point parallel to the face

   hand.arm            = translateObject(hand.arm, handObject.location   );
   hand.arm            = rotateObject   (hand.arm, [-pi/2 0 0]           );
   hand.arm            = translateObject(hand.arm, [0  offset width/2 ]  );
   hand.arm.center     = [ 0  offset 0 ]                                  ;
   hand.arm            = rotateObject   (hand.arm, handObject.orientation);

% ---------------------------------------------------------------------------
% myset()
% ---------------------------------------------------------------------------
% A utility function for assigning data to an object field
% ---------------------------------------------------------------------------
function outdata = myset(object, fieldname, altdata)

   if isfield(object, fieldname) && ~isempty(object.(fieldname))
      outdata = object.(fieldname) ;
   else
      outdata = altdata            ;
   end


% *************************************************************************
% Object drawing subroutines
% *************************************************************************

% --------------------------------------------------------------------------
% drawObject()
% --------------------------------------------------------------------------
% A recursive function that decends into a structure and draws 3d graphics
% objects using draw3d().
% At one point, each object was going to have its own drawing function. 
% Future versions may go to that model for maximum flexibility.  Currently
% all objects can be drawn with the draw3d() function.
% --------------------------------------------------------------------------
function [imageData, canvas] = drawObject(imageData, canvas)

% If this is a graphic3d object, use draw it using its 'draw' method

   if isfield(imageData,'handle')
      imageData = draw3d(imageData, canvas) ;%all objects use draw3d()
      return;
   end

% Otherwise, assume the oject is a collection of sub-objects

   nodes = fields(imageData) ;

   for n = 1:length(nodes)
      leaf                      = char(nodes(n))                      ;
     [imageData.(leaf), canvas] = drawObject(imageData.(leaf), canvas);
   end


% ---------------------------------------------------------------------------
% draw3d()
% ---------------------------------------------------------------------------
% Default rendering code for all objects
% --------------------------------------------------------------------------
function this = draw3d(this, canvas)

% Set the correct figure

   figure(canvas.figurehandle)

% Draw the surface of interest and set the color

   this.handle = surf (this.xdata , this.ydata , this.zdata     );
                 set  (this.handle, 'FaceColor', this.facecolor );
                 set  (this.handle, 'EdgeColor', this.edgecolor );
                 set  (this.handle, 'FaceAlpha', this.facealpha );
                 set  (this.handle, 'EdgeAlpha', this.edgealpha );
                 set  (this.handle, 'LineWidth', this.edgewidth );

% Set the axis to the correct dimensions

   axis manual
   axis(canvas.axis)

   hold on

% *************************************************************************
% Object updating subroutines
% *************************************************************************

% ---------------------------------------------------------------------------
% updateObject()
% ---------------------------------------------------------------------------
% A recursive function that decends into a structure and updates 3d graphics
% objects using update3d().
% At one point, each object was going to have its own updating function. 
% Future versions may go to that model for maximum flexibility.  Currently
% all objects can be updated with the update3d() function.
% --------------------------------------------------------------------------
function [imageData] = updateObject(imageData, updateFreq)

% If this is an object3d, update

   if isfield(imageData,'handle')
      imageData = update3d(imageData, updateFreq);
      return;
   end

% Otherwise, assume the oject is a collection of sub-objects

   nodes = fields(imageData)                     ;

   for n = 1:length(nodes)
      leaf = char(nodes(n))                      ;
      imageData.(leaf) = updateObject(imageData.(leaf), updateFreq);
   end


% ---------------------------------------------------------------------------
% update3d()
% ---------------------------------------------------------------------------
% Default updating code for all objects.
% --------------------------------------------------------------------------
function [this] = update3d(this, updateFreq)

% Do nothing if the object has been labeled 'static'

   if(this.static)
      return;
   end

% Rotate then translate the object as required

   transAmount  =  this.translate*updateFreq  ;
   rotateAmount =  this.rotAngle *updateFreq  ;

   this = translateObject(this, transAmount  );
   this = rotateObject   (this, rotateAmount );

   set(this.handle, 'XData', this.xdata, ...
                    'YData', this.ydata, ...
                    'ZData', this.zdata      );


% ---------------------------------------------------------------------------
% translateObject()
% ---------------------------------------------------------------------------
% Translates an object through space
% ---------------------------------------------------------------------------
function object  = translateObject(object, transAmount)

   object.xdata    =  object.xdata    + transAmount(1)   ;
   object.ydata    =  object.ydata    + transAmount(2)   ;
   object.zdata    =  object.zdata    + transAmount(3)   ;

   object.location =  object.location + transAmount      ;


% ---------------------------------------------------------------------------
% rotateObject
% ---------------------------------------------------------------------------
% Rotates an object in space using an X,Y,Z Euler Angle rotation
% ---------------------------------------------------------------------------
function object  = rotateObject(object, rotateAmount)

% Get the current shape

   [x,y]         =  size(object.xdata)                             ;

% Find location of center of rotation

   xPos          =  object.location(1) - object.center(1)          ;
   yPos          =  object.location(2) - object.center(2)          ;
   zPos          =  object.location(3) - object.center(3)          ;

% Reshape data into a vector for the math operation of choice

   xdata         =  reshape(object.xdata,1,[]) - xPos              ;
   ydata         =  reshape(object.ydata,1,[]) - yPos              ;
   zdata         =  reshape(object.zdata,1,[]) - zPos              ;

% Create the rotation matrix (dcm)

   xAng          =  rotateAmount(1)                                ;
   yAng          =  rotateAmount(2)                                ;
   zAng          =  rotateAmount(3)                                ;
   dcm           =  zRotate(zAng)*yRotate(yAng)*xRotate(xAng)      ;

% Transform the orientation using the dcm (direction cosine matrix)

   tmp           =  dcm*[xdata; ydata; zdata ]                     ;
   xdata         =  tmp(1,:) + xPos                                ;
   ydata         =  tmp(2,:) + yPos                                ;
   zdata         =  tmp(3,:) + zPos                                ;

% Put the transformed data back into the correct shape

   object.xdata  =  reshape(xdata,x,y)                             ;
   object.ydata  =  reshape(ydata,x,y)                             ;
   object.zdata  =  reshape(zdata,x,y)                             ;


% ---------------------------------------------------------------------------
% xRotate
% ---------------------------------------------------------------------------
% Rotate an object about the 'x' axis
% ---------------------------------------------------------------------------
function dcm = xRotate(ang)

   dcm = [         1,         0,         0 ; ...
                   0,  cos(ang), -sin(ang) ; ...
                   0,  sin(ang),  cos(ang)       ] ;
         
% ---------------------------------------------------------------------------
% yRotate
% ---------------------------------------------------------------------------
% Rotate an object about the 'y' axis
% ---------------------------------------------------------------------------
function dcm = yRotate(ang)

   dcm = [  cos(ang),         0,  sin(ang) ; ...
                   0,         1,         0 ; ...
           -sin(ang),         0,  cos(ang)       ] ;

% ---------------------------------------------------------------------------
% zRotate
% ---------------------------------------------------------------------------
% Rotate an object about the 'z' axis
% ---------------------------------------------------------------------------
function dcm = zRotate(ang)

   dcm = [  cos(ang), -sin(ang),         0 ; ...
            sin(ang),  cos(ang),         0 ; ...
                   0,         0,         1       ] ;

Contact us at files@mathworks.com