Roadwarrior3

This function makes a picture like below, and saves the information about segments of the road:

To start the program, just run the roadwarrior3 function in Matlab with no arguments. You click on the figure to place points on the road; you can click on an existing point to remove it (when you're near an existing point, it turns red and will be removed when clicked). Once there are three or more points, the smooth road is drawn. Corners are replaced by parts of circles so that the direction of the road changes smoothly. Clicking on the first point again closes the circuit if desired. An optional nx2 matrix with precalculated coordinates of points on the road can be given, example here.

Download the m-files here.


function curveInfo = roadwarrior3(varargin)

% A --- F --- E ===> A --- B u C --- E
global curveInfo coords figindex selectedCoord lastSelectedCoord closeCrit lockImage;
figindex = figure; 
if length(varargin) > 0,
    coords = varargin{1};
    lockImage = 1;
else,
    coords = [];
    lockImage = 0;
end;
curveInfo = [];
selectedCoord = 0;
lastSelectedCoord = 0;
clf;
set(gcf, 'Position', [50, 50, 500, 500]);
set(gcf, 'WindowButtonDownFcn', @addRemCoord);
set(gcf, 'WindowButtonMotionFcn', @motion);
xlim([-1 1]);
ylim([-1 1]);
xl = get(gca, 'XLim');
yl = get(gca, 'YLim');
closeCrit = max(diff(xl), diff(yl)) / 20;
hold on;
if ~isempty(coords),
    plotCoords;
    roundEmUp(coords);
end;

function plotCoords
global coords figindex selectedCoord lastSelectedCoord lockImage;
if lockImage == 1,
    return;
end;
figure(figindex);
cla;
x = coords(:, 1);
y = coords(:, 2);
for n = 1:length(coords(:, 1)),
    if selectedCoord == n,
        p = plot(x(n), y(n), '*r');
    else,
        p = plot(x(n), y(n), '*k');
    end;
    ms0 = get(p, 'Markersize');
    if n == 1,
        set(p, 'Markersize', 2 * ms0);
    end;
end;

function motion(dum1, dum2)
getCurrentClosest;

function getCurrentClosest
global coords figindex selectedCoord lastSelectedCoord closeCrit;
if isempty(coords),
    return;
end;
figure(figindex);
p = get(gca, 'CurrentPoint');
p = [p(1, 1) p(1, 2)];
lastSelectedCoord = selectedCoord;
selectedCoord = 0;
for n = 1:length(coords(:, 1)),
    if leng(p(1) - coords(n, 1), p(2) - coords(n, 2)) < closeCrit,
        selectedCoord = n;
    end;
end;
if lastSelectedCoord == selectedCoord,
    return;
end;
plotCoords;
plotCurve;

function addRemCoord(dum1, dum2)
global coords figindex selectedCoord closeCrit lockImage;
lockImage = 0;
p = get(gca, 'CurrentPoint');
p = [p(1, 1) p(1, 2)];
deleting = 0;
if ~isempty(coords),
    % allow closure
    if length(coords(:, 1)) > 1,
        start = 2;
    else,
        start = 1;
    end;
    for n = start:length(coords(:, 1)),
        if n == selectedCoord,
            coords(n, :) = [];
            deleting = 1;
            break;
        end;
    end;
end;
if deleting == 0,
    if ~isempty(coords),
        if leng(p(1) - coords(1, 1), p(2) - coords(1, 2)) < closeCrit,
            p = coords(1, :);
        end;
    end;
    coords = [coords; p];
    selectedCoord = length(coords(:, 1));
end;
getCurrentClosest;
plotCoords;
roundEmUp(coords);

function roundEmUp(coords)
global curveInfo

if length(coords(:, 1)) < 3,
    return;
end;

x = coords(:, 1);
y = coords(:, 2);
cutProp = 0.25;

% check for closure
closure = 0;
if x(1) == x(end) & y(1) == y(end) & length(x) > 1,
    closure = 1;
    x = [x; x(2)];
    y = [y; y(2)];
end;

% Create curveInfo
% curveInfo: [0 x0 y0 x1 y1 0 0]
% curveInfo: [1 x0 y0 rad arc xc yc]
curveInfo = [];
for n = 2:(length(x) - 1),
    % point n will be rounded if necessary
    Dx1 = diff(x([(n - 1) n]));
    Dy1 = diff(y([(n - 1) n]));
    Dx2 = diff(x([n (n + 1)]));
    Dy2 = diff(y([n (n + 1)]));
    L1 = leng(Dx1, Dy1);
    L2 = leng(Dx2, Dy2);
    dx1 = x(n) - x(n - 1);
    dy1 = y(n) - y(n - 1);
    dx2 = x(n + 1) - x(n);
    dy2 = y(n + 1) - y(n);
    rca1 = atan2(dy1, dx1);
    rca2 = atan2(dy2, dx2);
    if rca1 == rca2,
        curveInfo = [curveInfo; 0 x(n - 1) y(n - 1) x(n) y(n) 0 0]
        continue;
    end;
    % AB
    cutL = cutProp * L2;
    if cutL > L1,
        cutL = cutProp * L1;
    end;
    Bx = x(n - 1) + cos(rca1) * (L1 - cutL);
    By = y(n - 1) + sin(rca1) * (L1 - cutL);
    curveInfo = [curveInfo; 0 x(n - 1) y(n - 1) Bx By 0 0];
    Cx = x(n + 1) - cos(rca2) * (L2 - cutL);
    Cy = y(n + 1) - sin(rca2) * (L2 - cutL);
    % circle touching B, C
    % find crossing point of perpendiculars from B and C, in directions AB
    % and CE
    dxp1 = -dy1;
    dyp1 = dx1;
    dxp2 = -dy2;
    dyp2 = dx2; 
    % draw perps
    figure(1); hold on; tv = (-0.2):0.1:0.2; 
    plot(Bx + tv * dxp1, By + tv * dyp1, 'r:');
    plot(Cx + tv * dxp2, Cy + tv * dyp2, 'r:');
    % find tcr: crossing point scaler
    % Bx + tcr * dxp1 == Cx + qcr * dxp2
    % By + tcr * dyp1 == Cy + qcr * dyp2
    weights = [dxp1, -dxp2; dyp1, -dyp2];
    weighted = [Cx - Bx; Cy - By];
    scalers = weights \ weighted;
    tcr = scalers(1);
    xc = Bx + tcr * dxp1;
    yc = By + tcr * dyp1;
    r = leng(xc - Bx, yc - By);
    dBC = leng(Bx - Cx, By - Cy);
    arc = 2 * asin((dBC / 2) / r);
    % sign of arc
    phiB = atan2(By - yc, Bx - xc);
    postest = leng(xc + r * cos(phiB + arc) - Cx, yc + r * sin(phiB + arc) - Cy);
    negtest = leng(xc + r * cos(phiB - arc) - Cx, yc + r * sin(phiB - arc) - Cy);
    if postest < negtest,
        arcsign = 1;
    else,
        arcsign = -1;
    end;
    arc = arcsign * arc;
    curveInfo = [curveInfo; 1 Bx By r arc xc yc];
    % CD
    % curveInfo = [curveInfo; 0 Cx Cy x(n + 1) y(n + 1) 0 0];
    % replace coordinates
    x(n) = Cx;
    y(n) = Cy;
    % end-line
    if n == length(x) - 1,
        curveInfo = [curveInfo; 0 x(n) y(n) x(n + 1) y(n + 1) 0 0]
    end;
end;
if closure == 1,
    curveInfo(1, [2 3]) = curveInfo(end, [2 3]);
    curveInfo(end, [4 5]) = curveInfo(2, [2 3]);
end;
plotCurve;

function plotCurve
global figindex curveInfo;
if isempty(curveInfo),
    return;
end;
figure(figindex); 
hold on;
for n = 1:length(curveInfo(:, 1)),
    if curveInfo(n, 1) == 0,
        x = curveInfo(n, [2 4]);
        y = curveInfo(n, [3 5]);
        plot(x, y, 'k*-');
    else,
        x0 = curveInfo(n, [2]);
        y0 = curveInfo(n, [3]);
        r = curveInfo(n, 4);
        arc = curveInfo(n, 5);
        xc = curveInfo(n, [6]);
        yc = curveInfo(n, [7]);
        plot(xc, yc, 'ro');
        xv = xc + ((-r):(r / 40):r);
        circ = yc + real(sqrt(r^2 - (xv - xc).^2));
        plot(xv, circ, 'g-');
        circ = yc - real(sqrt(r^2 - (xv - xc).^2));
        plot(xv, circ, 'g-');
        % black bit for actual road
        phi1 = atan2(y0 - yc, x0 - xc);
        nPhi = 4;
        dPhi = arc / nPhi;
        for iPhi = 1:nPhi,
            phi = phi1 + iPhi * dPhi;
            tempx0 = xc + r * cos(phi);
            tempy0 = yc + r * sin(phi);
            tempx1 = xc + r * cos(phi + dPhi);
            tempy1 = yc + r * sin(phi + dPhi);
            plot([tempx0 tempx1], [tempy0 tempy1], 'k-');
        end;
    end;
end;
xlim0 = get(gca, 'XLim');
ylim0 = get(gca, 'YLim');
squareLim = [min([xlim ylim]) max([xlim ylim])];
set(gca, 'XLim', squareLim);
set(gca, 'YLim', squareLim);

function l = leng(dx, dy)
l = sqrt(dx^2 + dy^2);