Source code used by "arrow" to select default values

% Copyright 2015, Leonard R. Wayne, Washington, District of Columbia,
% United States of America.  ALL RIGHTS RESERVED.

function [ Head, Shaft ] = fill_in_defaults( Head, Shaft, ArrowType, vlen )
% Compute and fill in default values where needed for each
% of the following four parameters.  The parameter value is
% considered to be undefined (and thus in need of a default
% value) if the parameter value is [] when the present
% function is called.  We refer to these four parameters as
% the "P4 Group."
%       Head.Length
%       Head.Sweep
%       Head.Width
%       Shaft.Width
%
% The present function does not alter the values of any
% parameters that are already defined (i.e., not []) when
% the present function is called.
%
% INPUT:
%   -- "ArrowType" is 'single' or 'double'.
%   -- "vlen" is the distance from [x1 y1] to [x2 y2].
%
% ASSUMPTIONS (The calling function is responsible for
%              ensuring these assumptions are valid,
%              though the present function may perform
%              additional, if redundant, checks.):
%   -- The "Units" for each of the above four input values
%      (e.g., Head.LengthUnits) is 'data' (not 'normalized').
%      This is true even if the value is [].  The calling
%      function is assumed to have converted any 'normalized'
%      values to 'data' units.
%   -- The arrow length ("vlen") is not zero.
%   -- The following three input parameters, if non-empty,
%      are not equal to zero (a check is performed to confirm):
%           Head.Length
%           Head.Width
%           Shaft.Width
%      It simplifies the present function if these parameters
%      can be assumed to be non-zero, in part because it
%      obviates the need to consider divide-by-zero issues
%      in certain cases.  It also reduces the number of other
%      special cases the code must handle.
%         -- The way the calling function blocks these
%            parameter values from equaling zero is via the
%            attributes specified in the PARAMETERS struct.
%      
% OUTPUT:
%   -- The output values for the four parameters listed above
%      are always in data units.
%        -- The present function does not alter the "Units"
%           of the four parameters, which are always 'data'
%           on both input (even if the parameter value is [] -
%           see the assumptions above) and output.

    % Don't set any of the following constants to zero, since
    % that might lead to a value of zero for one or more of
    % the three parameters discussed above that are not
    % supposed to equal zero.

    % Define some constants used with Shaft.Type == 'line'.
    %   --> Some of these are also used with Shaft.Type == 'rectangle'.
    DHL = 0.1;   % "Default Head.Length".  [normalized units].
    DHW = 0.1;   % "Default Head.Width".   [normalized units].
    DHS = 0.1;   % "Default Head.Sweep".   [normalized units].
    
    % Define some constants used exclusively with Shaft.Type = 'rectangle'.
    HW_over_SW = 5;            % "Head.Width over Shaft.Width".
    HW_over_HS = 3;            % "Head.Width over Head.Sweep".
    HW_over_HL = 1;            % "Head.Width over Head.Length".
    HS_CUTOFF = 1/3;           % "Head.Sweep cutoff".
    SW_CUTOFF = 1/10;          % "Shaft.Width cutoff"
    HS_FRACTION_OF_MAX = 0.5;  % "Head.Sweep fraction of HeadSweepMax".
    SW_FRACTION_OF_MAX = 0.15; % "Shaft.Width fraction of ShaftWidthMax".
    HW_MULTIPLE_OF_MIN = 1.2;  % "Head.Width multiple of HeadWidthMin".
    DHLwHWleSW = 0.1;          % "Default Head.Length when Head.Width <= Shaft.Width".  [normalized units]
                               %    --> Can be anything from 0+ (i.e., epsilon) to vlen.
                        
    % =============== BLOCK #A ===============
    
    % "BLOCK #A" operates only on 'double' arrows, not 'single'.

    if strcmp( ArrowType, 'double' )

        % When computing default values for the "P4 Group", we treat
        % a 'double' arrow as two 'single' arrows, where each 'single'
        % arrow has "vlen" equal to half the "vlen" of the 'double'
        % arrow, as shown below.  We run the same code as we run for
        % the 'single' case (that's the "BLOCK #B" code), but with
        % "vlen" set to half its original value, and only after
        % running the present block ("BLOCK #A") which is designed
        % to steer default value selections away from values that
        % would cause the two arrow heads to overlap each other (a
        % constraint that exists on a 'double' arrow but not on a
        % 'single' arrow).
        %
        %    /|             |\
        %   / |      |      | \
        %  /  |      |      |  \
        % /   -------|-------   \       Rectangle shaft.
        % \   -------|-------   /
        %  \  |      |      |  /
        %   \ |      |      | /
        %    \|             |/
        %
        % <--------------------->  original "vlen"
        %            <---------->  new "vlen"
        %
        %    /|             |\
        %   / |      |      | \
        %  /  |      |      |  \
        % /   -------|-------   \       Line shaft.
        %  \  |      |      |  /
        %   \ |      |      | /
        %    \|             |/
        %
        vlen = vlen/2;
        
        % The strategy for the rest of "BLOCK #A" is as follows:
        %   -- If "Head.Sweep" is not yet defined, set it to 0.
        %      This will help keep the two arrow heads away from
        %      each other.
        %   -- If "Head.Length" is not yet defined, pick a value
        %      no larger than what would make the two arrow heads
        %      overlap each other.

        if isempty( Head.Sweep )  % If Head.Sweep is not yet defined...
            Head.Sweep = 0;   % This helps keep the two arrow heads away from each other.
        end

        if isempty( Head.Length )  % If Head.Length is not yet defined...
            % Set Head.Length to a value no greater what would cause overlap of the two arrows.
            HeadLengthMax = vlen-Head.Sweep;
            Head.Length = min( [ HeadLengthMax DHL*vlen ] );    % Pick a value so that HL+HS < new_vlen
        end
        
    end

    % =============== BLOCK #B ===============    
                               
    % "BLOCK #B" operates on both 'single' and 'double' arrows.
    
    switch Shaft.Type
        
        case 'line'
            
            % Set default values.
            %   -- Choosing default values is easy when Shaft.Type == 'line'
            %      since there is no danger the parameter value choices could
            %      define a patch object for which the lines between patch
            %      vertices cross over each other.
            if isempty( Head.Length ), Head.Length = DHL * vlen; end  % [data units]          
            if isempty( Head.Width ),  Head.Width  = DHW * vlen; end  % [data units]
            if isempty( Head.Sweep ),  Head.Sweep  = DHS * vlen; end  % [data units]
        
        case 'rectangle'
            
            % =============== BLOCK #1 ===============
            
            % Consider the special case where Head.Width <= Shaft.Width.  Here
            % are two examples:
            % 
            % |------------------------
            % |                       |
            % |                       \
            % |                        \     Head.Length = 0.1 * vlen
            % |                        /
            % |                       /
            % |                       |
            % |------------------------
            %
            % <-------- vlen ---------->
            %
            % |\
            % | \
            % |  \
            % |   \                          Head.Length = vlen
            % |   /                            
            % |  /                             
            % | /
            % |/
            %
            % <---> vlen
            %
            % In all these cases, Head.Sweep must equal zero.
            %   --> The user must either (1) set Head.Sweep to zero,
            %       or (2) do not specify Head.Sweep and instead let
            %       the code set Head.Sweep = 0.
            % This block of code also sets a default value for Head.Length
            % for this special case.
            if ~isempty( Head.Width ) && ~isempty( Shaft.Width ) && Head.Width <= Shaft.Width
                if isempty( Head.Sweep )   % If the user did not specify Head.Sweep...
                    Head.Sweep = 0;
                else  % User specified Head.Sweep.
                    if Head.Sweep ~= 0
                        % The following error message is also used in the
                        % calling function, so if we update the message
                        % here, be sure to also update the message there
                        % for consistency.
                        error( 'davinci:arrow:HeadSweepMustEqualZero', ...
                               [ 'If Head.Width <= Shaft.Width, Head.Sweep must equal zero.  Either\n' ...
                                 '(1) set Head.Sweep to zero, or (2) do not specify Head.Sweep and instead\n' ...
                                 'let the code set Head.Sweep to zero.' ] )
                    end
                end
                % Now at least 3 of the 4 members of the "P4 Group" are
                % defined.  Compute the default for the fourth member,
                % if needed.
                if isempty( Head.Length )
                    Head.Length = DHLwHWleSW * vlen;
                end
                % Now all four members of the "P4 Group" are defined,
                % so the code will pass through "BLOCK #2" without
                % doing anything.
            end
    
            % =============== BLOCK #2 ===============
            
            % To avoid patch lines crossing over each other, we require:
            %     Head.Sweep < Head.Length * ( Head.Width/Shaft.Width - 1 )
            %                                  <-------------------->
            %                                             ^
            %                                             |
            %                         The previous block of code ("BLOCK #1")
            %                         handled the case where this term is <= 1.
            %                         So the following block of code ("BLOCK #2")
            %                         can assume this term is > 1.

            % Loop until all four members of the "P4 Group" have defined values.
            while 1

                % For readability.
                HL = ~isempty( Head.Length );
                HW = ~isempty( Head.Width );
                HS = ~isempty( Head.Sweep );
                SW = ~isempty( Shaft.Width );

                % Compute the # of members of the "P4 Group" that are defined.
                ndefine = HL + HW + HS + SW;
                
                % Confirm none of the following three values is zero:
                %          Head.Length
                %          Head.Width
                %          Shaft.Width
                %   -- See "ASSUMPTIONS" above for details.
                %   -- In principle this check is not needed, since there should
                %      not be any input by the user that could make any of
                %      these three values be zero at the present point in
                %      the code.  But better to be safe, especially since the
                %      code is complex and thus there is a greater chance for
                %      subtle errors.
                %   -- This check is run again at the end of the present
                %      function, for good measure.
                confirm_three_nonzeros( Head, Shaft )

                switch ndefine

                    case 0   % None of the four parameters is defined.

                        % We can set any of the four parameters here.  Choose
                        % Head.Length since Head.Length must be less than the
                        % total arrow length and now is the simplest point in
                        % the process to make sure that happens.
                        Head.Length = DHL * vlen;

                    case 1   % Only one of the four parameters is defined.

                        % In this block, make sure we don't set Head.Length > vlen,
                        % since the head length cannot be longer than the arrow.
                        %   --> The way we avoid this is to not set Head.Length
                        %       at all in this block.
                        if     HL,  Head.Sweep  = Head.Length;   % Reasonable choice.
                        elseif HW,  Shaft.Width = Head.Width / HW_over_SW;  % E.g., Head.Width / 5.
                        elseif HS,  Head.Width  = HW_over_HS * Head.Sweep;
                        elseif SW,  Head.Width  = HW_over_SW * Shaft.Width;
                        else
                            error( 'davinci:arrow:internalErrorNdefineCase1', ...
                                   'Internal error.' )
                        end

                    case 2   % Only two of the four parameters are defined.

                        % Define *both* of the remaining parameters.

                        if     HW && SW  
                            % To avoid patch lines crossing over each other, we require:
                            %     Head.Sweep   <   Head.Length * ( Head.Width / Shaft.Width - 1 )
                            % Let's pick whatever Head.Length we want, then choose a
                            % Head.Sweep that maintains the inequality.
                            %   --> Shaft.Width is not allowed to be zero, so there is no
                            %       divide-by-zero issue.
                            %   --> Don't let Head.Sweep default to anything larger than
                            %       one seventh the arrow length.
                            Head.Length = DHL * vlen;
                            HeadSweepMax = Head.Length * ( Head.Width / Shaft.Width - 1 );
                            Head.Sweep = min( [ HS_FRACTION_OF_MAX * HeadSweepMax HS_CUTOFF*vlen ] );
                      
                        elseif HW && HS
                            % To avoid patch lines crossing over each other, we require:
                            %    Shaft.Width   <   Head.Width / ( 1 + Head.Sweep / Head.Length )
                            % Let's pick whatever Head.Length we want, then choose a
                            % Shaft.Width that maintains the inequality.
                            %   --> We know vlen is not equal to zero (confirmed in arrow.m),
                            %       so we know Head.Length will not be set to zero in what
                            %       follows, so we know there will not be any issue with
                            %       divide-by-zero in what follows.
                            %   --> Don't let Shaft.Width default to anything larger than
                            %       one tenth the arrow length.
                            Head.Length = DHL * vlen;
                            ShaftWidthMax = Head.Width / ( 1 + Head.Sweep / Head.Length );
                            Shaft.Width = min( [ SW_FRACTION_OF_MAX * ShaftWidthMax SW_CUTOFF*vlen ] );
                            
                        elseif HW && HL
                            % To avoid patch lines crossing over each other, we require:
                            %    Shaft.Width   <   Head.Width / ( 1 + Head.Sweep / Head.Length )
                            % Let's pick whatever Head.Sweep we want, then choose a
                            % Shaft.Width that maintains the inequality.
                            %   --> Head.Length is not allowed to be zero, so there is no
                            %       divide-by-zero issue.
                            %   --> Don't let Shaft.Width default to anything larger than
                            %       one tenth the arrow length.
                            Head.Sweep = Head.Width / HW_over_HS;
                            ShaftWidthMax = Head.Width / ( 1 + Head.Sweep / Head.Length );
                            Shaft.Width = min( [ SW_FRACTION_OF_MAX * ShaftWidthMax SW_CUTOFF*vlen ] );
                            
                        elseif SW && HS
                            % To avoid patch lines crossing over each other, we require:
                            %    Head.Width   >   Shaft.Width * ( 1 + Head.Sweep / Head.Length )
                            % Let's pick whatever Head.Length we want, then choose a
                            % Head.Width that maintains the inequality.
                            %   --> vlen is not allowed to be zero, so there is no
                            %       divide-by-zero issue with Head.Length.
                            Head.Length = DHL * vlen;
                            HeadWidthMin = Shaft.Width * ( 1 + Head.Sweep / Head.Length );
                            Head.Width = HW_MULTIPLE_OF_MIN * HeadWidthMin;

                        elseif SW && HL
                            % To avoid patch lines crossing over each other, we require:
                            %     Head.Sweep   <   Head.Length * ( Head.Width / Shaft.Width - 1 )
                            % Let's pick whatever Head.Width we want, then choose a
                            % Head.Sweep that maintains the inequality.
                            %   --> Shaft.Width is not allowed to be zero, so there is no
                            %       divide-by-zero issue.                          
                            %   --> Don't let Head.Sweep default to anything larger than
                            %       one seventh the arrow length.
                            Head.Width = HW_over_SW * Shaft.Width;
                            HeadSweepMax = Head.Length * ( Head.Width / Shaft.Width - 1 );
                            Head.Sweep = min( [ HS_FRACTION_OF_MAX * HeadSweepMax HS_CUTOFF*vlen ] );

                        elseif HS && HL
                            % To avoid patch lines crossing over each other, we require:
                            %    Shaft.Width   <   Head.Width / ( 1 + Head.Sweep / Head.Length )
                            % Let's pick whatever Head.Width we want, then choose a
                            % Shaft.Width that maintains the inequality.
                            %   --> Head.Length is not allowed to be zero, so there is no
                            %       divide-by-zero issue.
                            %   --> Don't let Shaft.Width default to anything larger than
                            %       one tenth the arrow length.
                            Head.Width = HW_over_HL * Head.Length;
                            ShaftWidthMax = Head.Width / ( 1 + Head.Sweep / Head.Length );
                            Shaft.Width = min( [ SW_FRACTION_OF_MAX * ShaftWidthMax SW_CUTOFF*vlen ] );

                        else
                            error( 'davinci:arrow:internalErrorNdefineCase2', ...
                                   'Internal error.' )
                        end

                    case 3   % Only three of the four parameters are defined.

                        % Define the last of the four parameters.

                        if ~HL       % If Head.Length is the undefined parameter...
                            % To avoid patch lines crossing over each other, we require:
                            %    Head.Length   >   Head.Sweep / ( Head.Width / Shaft.Width - 1 )
                            % Check whether this is compatible with the given arrow length.
                            %   --> Shaft.Width is not allowed to be zero, so there is no
                            %       divide-by-zero issue.
                            HeadLengthMin = Head.Sweep / ( Head.Width / Shaft.Width - 1 );
                            if HeadLengthMin > vlen
                                % Could a more-proper choice of default settings above
                                % have avoided this error.  No, because there is no way
                                % this case ("case 3") can be triggered except when the
                                % user selects all three of the parameters defined so far.
                                % (Recall the code for "case 2" sets the default values
                                % for *both* of its remaining parameters.)
                                %   -- If the user defines three of the four parameters
                                %      and wants to be sure the present error will not
                                %      be tripped, then make Head.Length one of the
                                %      three parameters.  None of the other blocks for
                                %      "case 3" can produce an error of this sort.
                                error( 'davinci:arrow:MinHeadLengthTooLong', ...
                                       [ 'The given combination of values for "Head.Sweep", "Head.Width",\n' ...
                                         'and "Shaft.Width" are not compatible with an arrow of the length\n' ...
                                         'given by "X" and "Y".  The patch() object would be defined by\n' ...
                                         'lines that cross over each other.' ] )
                            end
                            % Set Head.Length within the acceptable range.
                            Head.Length = HeadLengthMin + DHL * (vlen-HeadLengthMin);

                        elseif ~HW   % If Head.Width is the undefined parameter...
                            % To avoid patch lines crossing over each other, we require:
                            %    Head.Width   >   Shaft.Width * ( 1 + Head.Sweep / Head.Length )
                            %   --> Head.Length is not allowed to be zero, so there is no
                            %       divide-by-zero issue.
                            HeadWidthMin = Shaft.Width * ( 1 + Head.Sweep / Head.Length );
                            Head.Width = HW_MULTIPLE_OF_MIN * HeadWidthMin;

                        elseif ~HS   % If Head.Sweep is the undefined parameter...
                            % To avoid patch lines crossing over each other, we require:
                            %     Head.Sweep   <   Head.Length * ( Head.Width / Shaft.Width - 1 )
                            %   --> Shaft.Width is not allowed to be zero, so there is no
                            %       divide-by-zero issue.
                            %   --> Don't let Head.Sweep default to anything larger than
                            %       one seventh the arrow length.                            
                            HeadSweepMax = Head.Length * ( Head.Width/Shaft.Width - 1 );
                            Head.Sweep = min( [ HS_FRACTION_OF_MAX * HeadSweepMax HS_CUTOFF*vlen ] );

                        elseif ~SW   % If Shaft.Width is the undefined parameter...
                            % To avoid patch lines crossing over each other, we require:
                            %    Shaft.Width   <   Head.Width / ( 1 + Head.Sweep / Head.Length )
                            %   --> Head.Length is not allowed to be zero, so there is no
                            %       divide-by-zero issue.
                            %   --> Don't let Shaft.Width default to anything larger than
                            %       one tenth the arrow length.
                            ShaftWidthMax = Head.Width / ( 1 + Head.Sweep / Head.Length );
                            Shaft.Width = min( [ SW_FRACTION_OF_MAX * ShaftWidthMax SW_CUTOFF*vlen ] );
                            
                        else
                            error( 'davinci:arrow:internalErrorNdefineCase3', ...
                                   'Internal error.' )
                        end

                    case 4   % All four parameters defined.
                        break;

                    otherwise
                        error( 'davinci:arrow:internalSwitchError', ...
                               'Internal switch error.' )

                end

            end
            
        otherwise
        	error( 'davinci:arrow:unrecognizedShaftType', ...
                   'Unrecognized "Shaft.Type":  %s', Shaft.Type )
    end
    
    % Confirm none of the following three values is zero:
    %          Head.Length
    %          Head.Width
    %          Shaft.Width
    %   -- See "ASSUMPTIONS" above for details.
    confirm_three_nonzeros( Head, Shaft )

end

% ----------------------------------------------

function confirm_three_nonzeros( Head, Shaft )
% Sanity check.  See "ASSUMPTIONS" for details.
                
    if ( ~isempty(Head.Length) && Head.Length==0 ) || ...
       ( ~isempty(Head.Width)  && Head.Width==0  ) || ...
       ( ~isempty(Shaft.Width) && Shaft.Width==0 )
        error( 'davinci:arrow:zeroValueNotAllowed', ...
               '"Head.Length", "Head.Width", and/or "Shaft.Width" is zero.' )
    end
                
end