% MERGESEG - Line segment merging function.
%
% Usage: newseglist = mergeseg(seglist, angtol, linkrad)
%
% Arguments:  seglist - an Nx4 array storing line segments in the form
%                       [x1 y1 x2 y2
%                        x1 y1 x2 y2
%                           . . .   ] etc
%
%             angtol  - Angle tolerance used when attempting to merge line
%                       segements (radians).
%             linkrad - Maximum distance between end points of line segments
%                       for segments to be elegible for linking (pixels).
%
% Function scans through the list of line segments seeking to merge segments
% together.  Segments are merged if the orientation difference is less than
% angtol and if the ends of the segments are within linkrad of each other.
% The comparison is performed by first sorting line segments by angle, then
% each line segment is only tested against other line segments that satisfy
% the orientation constraint.
%
% See also:  EDGELINK, LINESEG, MAXLINEDEV, DRAWSEG
%

% Copyright (c) 2000-2005 Peter Kovesi
% School of Computer Science & Software Engineering
% The University of Western Australia
% http://www.csse.uwa.edu.au/
% 
% Permission is hereby granted, free of charge, to any person obtaining a copy
% of this software and associated documentation files (the "Software"), to deal
% in the Software without restriction, subject to the following conditions:
% 
% The above copyright notice and this permission notice shall be included in 
% all copies or substantial portions of the Software.
%
% The Software is provided "as is", without warranty of any kind.

% March    2000 - Original version
% November 2005 - Bug fix for case Nseg < 4 (thanks to Ming Luo)
% May      2006 - Bug fix for termination of while loop (thanks to Martin Labrecque)

function newseglist = mergeseg(seglist, angtol, linkrad, linedevtol)
    
    Nseg = size(seglist,1);
    
    if Nseg <= 1    % Nothing to merge
	newseglist = seglist;
	return;
    end
    
    cosAngTol = cos(angtol);
 
    % Build up some data in the following form
    % [x1 y1 x2 y2 dx dy angle merged]
    
    seg = zeros(Nseg,8);
    seg(:,1:4) = seglist;
    seg(:,5:6) = seg(:,3:4)-seg(:,1:2);     % delta x and delta y
    seg(:,7) = atan2(seg(:,6), seg(:,5));   % =segment angle [-pi ... pi]
    neg = seg(:,7) < 0;                     % compress angle range [0...pi]
    seg(:,7) = seg(:,7) + neg*pi;
    [Y,I] = sort(seg(:,7));                 % sort by angle
    seg = seg(I,:);                      
    seg(:,7) = 2*seg(:,7);                  % double the angles
    
    TotalSegs = Nseg;
    
    for s1 = 1:Nseg
%	fprintf('Merging Segment %d/%d\r',s1,Nseg);	    
	if ~seg(s1,8)  % if segment s1 has not already been merged
	    s2 = s1+1; if s2>Nseg, s2=s2-Nseg; end  % `modulo' arithetic
            ang1 = seg(s1,7); ang2 = seg(s2,7);
	    deltaSin = sin(ang1)*cos(ang2) - cos(ang1)*sin(ang2);
	    deltaCos = cos(ang1)*cos(ang2) + sin(ang1)*sin(ang2);
	    deltaAng = 0.5*abs(atan2(deltaSin,deltaCos));

	    while (s2 ~= s1) && (deltaAng < angtol)
		if ~seg(s2,8)   % if segment s2 has not already been merged
		    [newseg, merged] = compareseg(seg(s1,:), seg(s2,:),linkrad, ...
						  linedevtol);
		    if merged
			seg(s1,1:4) = newseg;
			%  update delta x, delta y, and angle ? (I think not)
			seg(s2,8) = merged;  % eliminate s2;
			TotalSegs = TotalSegs - 1;
		    end
		end
		s2 = s2+1; if s2>Nseg, s2=s2-Nseg; end		
		ang2 = seg(s2,7);
		deltaSin = sin(ang1)*cos(ang2) - cos(ang1)*sin(ang2);
		deltaCos = cos(ang1)*cos(ang2) + sin(ang1)*sin(ang2); 
		deltaAng = 0.5*abs(atan2(deltaSin,deltaCos));
	    end
	end
    end

%    fprintf('\n');
    
    % Now clean up list by extracting the unmerged entries
    
    newseglist = zeros(TotalSegs,4);
    newNseg = 0;
    for s = 1:Nseg
	if ~seg(s,8)                 % if this segment has not been merged
	    newNseg = newNseg+1;     % store it in the final list.
	    newseglist(newNseg,:) =  seg(s,1:4);  
	end
    end
    
    
%----------------------------------------------------------------------    
% Internal function that does all the work.  
% Two segments, that already meet the angle constraint, are tested for end
% point proximity.  If they meet this constraint they then tested to see
% what the maximum line deviation would be if they were merged.  If this
% is within tolerance they are merged.
%----------------------------------------------------------------------    
    
function [newseglist, merged] = compareseg(seg1, seg2, linkrad, linedevtol)
    
    s1p1 = seg1(1:2);   % point1 of segment1
    s1p2 = seg1(3:4);   % point2 of segment1
    
    s2p1 = seg2(1:2);   % point1 of segment2
    s2p2 = seg2(3:4);   % point2 of segment2
    
    dir1 = seg1(5:6);
    dir2 = seg2(5:6);
    
    cosAng = dot(dir1,dir2);
    
    if cosAng < 0          % Reverse order of points on seg2 so that both
                           % segments are in the `same' direction
        tmp  = s2p1;
	s2p1 = s2p2;
	s2p2 = tmp;
    end

    merged = 0;                        % Default result: not merged
    newseglist = [seg1; seg2];         % and segments left unchanged
    
    if norm(s1p2-s2p1) < linkrad       % If we satisfy endpoint proximity
	rc = [s1p1; s1p2; s2p1; s2p2];
	[maxdev, index, D] = maxlinedev(rc(:,1),rc(:,2));  % test line deviation
	if maxdev < linedevtol
	    newseglist = [s1p1 s2p2];      % Merge the segments one way
	    merged = 1;
	end
    elseif norm(s2p2-s1p1) < linkrad 
	rc = [s2p1; s2p2; s1p1; s1p2];
	[maxdev, index, D] = maxlinedev(rc(:,1),rc(:,2));
	if maxdev < linedevtol	
	    newseglist = [s2p1 s1p2];      % ... or merge the other way.
	    merged = 1;	
	end
    end	    

    


