function lineage(filePath)
%LINEAGE Opens a viewer for the Peg Solitaire contest data.
% LINEAGE(FILE) opens a viewer for exploring the contest data in the file
% specified by the string FILE (a relative or absolute file path). If
% FILE is not specified, LINEAGE will will attempt to load a file called
% 'contest_data.mat' in the current directory.
%
% For a detailed tutorial, please refer to the published documentation.
% Author: Ken Eaton
% Last modified: 4/8/09
%--------------------------------------------------------------------------
% Check input arguments:
if (nargin == 0),
filePath = 'contest_data.mat';
elseif ~ischar(filePath),
error('Input argument must be a character string!');
end
% Initialize variables used by the nested mouse-based plot navigation
% function ("mouse_action"):
axesPosition = [401 2 399 399];
isActive = false;
selection = 'none';
origin = 0;
% Initialize the figure:
screenSize = get(0,'ScreenSize');
figurePosition = [1+(screenSize(3:4)-[800 600])./2 800 600];
hFigure = figure('Units','pixels','Position',figurePosition,...
'BusyAction','cancel','Color',[0.7 0.7 0.7],...
'DockControls','off','HandleVisibility','off',...
'IntegerHandle','off','Interruptible','on',...
'MenuBar','none','Name','Lineage v1.0',...
'NumberTitle','off','Resize','off','Toolbar','none');
% Initialize the axes:
hAxes = axes('Parent',hFigure,'Units','pixels',...
'Position',axesPosition,'DataAspectRatio',[1 1 1],...
'HandleVisibility','off','NextPlot','add',...
'PlotBoxAspectRatioMode','manual','Visible','off',...
'XLim',[0 1],'YLim',[0 1]);
% Initialize variables for use by GUI components:
addToPanel = 'top';
currentDisplay = cell(1,2);
% Initialize submission data uicontrols (top panel):
hTop = uipanel('Parent',hFigure,'Units','pixels',...
'Position',[1 301 400 300],...
'BackgroundColor',[0.4 0.4 0.4],...
'BorderType','beveledout','BorderWidth',1,...
'HandleVisibility','off','HighlightColor',[1 1 1],...
'ShadowColor',[0 0 0]);
hTopInfo = uicontrol('Style','text','Parent',hTop,'Units','pixels',...
'Position',[10 210 300 80],...
'BackgroundColor',[0.4 0.4 0.4],...
'FontAngle','normal','FontName','FixedWidth',...
'FontUnits','pixels','FontSize',10,...
'FontWeight','bold','ForegroundColor',[0 1 1],...
'HandleVisibility','off',...
'HorizontalAlignment','left',...
'String','(no submission selected)');
hTopButton = uicontrol('Style','pushbutton','Parent',hTop,...
'Units','pixels','Position',[320 240 70 25],...
'BackgroundColor',[0 1 0],...
'BusyAction','cancel','Callback',@gui_callback,...
'FontAngle','normal','FontName','FixedWidth',...
'FontUnits','pixels','FontSize',10,...
'FontWeight','normal',...
'ForegroundColor',[0 0 0],...
'HandleVisibility','off',...
'HorizontalAlignment','center',...
'Interruptible','off','String','Replace',...
'Tag','BUTTON');
hTopCode = uicontrol('Style','listbox','Parent',hTop,'Units','pixels',...
'Position',[5 5 390 200],...
'BackgroundColor',[0 0 0],...
'Callback',@gui_callback,'FontAngle','normal',...
'FontName','FixedWidth','FontUnits','pixels',...
'FontSize',10,'FontWeight','normal',...
'ForegroundColor',[0 1 1],...
'HandleVisibility','off',...
'HorizontalAlignment','center','ListboxTop',1,...
'Max',2,'Min',0,'SliderStep',[1 3],'String','',...
'Tag','LIST','UserData',[],'Value',[]);
% Initialize submission data uicontrols (bottom panel):
hBottom = uipanel('Parent',hFigure,'Units','pixels',...
'Position',[1 1 400 300],...
'BackgroundColor',[0.4 0.4 0.4],...
'BorderType','beveledout','BorderWidth',1,...
'HandleVisibility','off','HighlightColor',[1 1 1],...
'ShadowColor',[0 0 0]);
hBottomInfo = uicontrol('Style','text','Parent',hBottom,...
'Units','pixels','Position',[10 210 300 80],...
'BackgroundColor',[0.4 0.4 0.4],...
'FontAngle','normal','FontName','FixedWidth',...
'FontUnits','pixels','FontSize',10,...
'FontWeight','bold','ForegroundColor',[0 1 1],...
'HandleVisibility','off',...
'HorizontalAlignment','left',...
'String','(no submission selected)');
hBottomButton = uicontrol('Style','pushbutton','Parent',hBottom,...
'Units','pixels','Position',[320 240 70 25],...
'BackgroundColor',[1 0 0],...
'BusyAction','cancel',...
'Callback',@gui_callback,...
'FontAngle','normal',...
'FontName','FixedWidth',...
'FontUnits','pixels','FontSize',10,...
'FontWeight','normal',...
'ForegroundColor',[0 0 0],...
'HandleVisibility','off',...
'HorizontalAlignment','center',...
'Interruptible','off','String','Keep',...
'Tag','BUTTON');
hBottomCode = uicontrol('Style','listbox','Parent',hBottom,...
'Units','pixels','Position',[5 5 390 200],...
'BackgroundColor',[0 0 0],...
'Callback',@gui_callback,'FontAngle','normal',...
'FontName','FixedWidth','FontUnits','pixels',...
'FontSize',10,'FontWeight','normal',...
'ForegroundColor',[0 1 1],...
'HandleVisibility','off',...
'HorizontalAlignment','center','ListboxTop',1,...
'Max',2,'Min',0,'SliderStep',[1 3],...
'String','','Tag','LIST','UserData',[],...
'Value',[]);
% Initialize submission data preview:
hPreview = uipanel('Parent',hFigure,'Units','pixels',...
'Position',[401 401 400 200],...
'BackgroundColor',[0.4 0.4 0.4],...
'BorderType','beveledout','BorderWidth',1,...
'HandleVisibility','off','HighlightColor',[1 1 1],...
'ShadowColor',[0 0 0]);
hPreviewInfo = uicontrol('Style','text','Parent',hPreview,...
'Units','pixels','Position',[50 60 300 80],...
'BackgroundColor',[0.4 0.4 0.4],...
'FontAngle','normal','FontName','FixedWidth',...
'FontUnits','pixels','FontSize',10,...
'FontWeight','bold',...
'ForegroundColor',[0 1 1],...
'HandleVisibility','off',...
'HorizontalAlignment','left','String','');
% Display loading message:
hLoading = uicontrol('Style','text','Parent',hFigure,'Units','pixels',...
'Position',[550 190 100 20],...
'BackgroundColor',[0.7 0.7 0.7],...
'FontAngle','normal','FontName','FixedWidth',...
'FontUnits','pixels','FontSize',10,...
'FontWeight','bold','ForegroundColor',[0 1 1],...
'HandleVisibility','off',...
'HorizontalAlignment','left','String','Loading...');
drawnow;
% Load the data:
try
data = load(filePath);
codeList = data.allLineList;
data = data.d;
catch
delete(hFigure);
drawnow;
error('Invalid or corrupt data file!');
end
nNodes = numel(data);
[timestamps,sortIndex] = sort([data.timestamp]);
data = data(sortIndex);
ids = [data.id];
parents = [data.parent];
[isChild,parents] = ismember(parents,ids);
metric = [data.score];
% Initialize data for plotting family groups:
originIndex = find(~isChild);
nFamilies = numel(originIndex);
familyList = cell(1,nFamilies);
position = zeros(1,nNodes);
branchEnd = zeros(1,nNodes);
isImproved = false(1,nNodes);
offset = 0;
% Recursively compute the family group data:
for iFamily = 1:nFamilies,
familyList{iFamily} = get_children(originIndex(iFamily));
end
parents(originIndex) = originIndex;
% Remove the loading message:
delete(hLoading);
% Adjust the time stamps and find the edges of 24 hour blocks:
timestamps = timestamps-floor(timestamps(1));
timeEdges = timestamps(1)+(0:7);
% Set the axes limits and camera properties:
xLimit = [timeEdges(1) timeEdges(8)];
yLimit = [min(position)-1 max(position)+1];
viewAngle = 5; % in degrees
cameraPoint = [mean(xLimit) mean(yLimit) 0.5/tan(viewAngle*pi/360)];
targetPoint = [mean(xLimit) mean(yLimit) 0];
set(hAxes,'CameraPosition',cameraPoint,'CameraTarget',targetPoint,...
'CameraUpVectorMode','manual','CameraViewAngle',viewAngle,...
'DataAspectRatio',[diff(xLimit) diff(yLimit) 1],'XLim',xLimit,...
'YLim',yLimit);
% Set the gains for the mouse-based plot navigation:
panGain = [diff(xLimit) diff(yLimit)]./min(axesPosition(3:4));
zoomGain = 0.03;
% Plot the darkness, twilight, and daylight panels:
patch(timeEdges([1 1 2 2]),yLimit([1 2 2 1]),[0.3 0.3 0.3],...
'Parent',hAxes,'Clipping','off','HandleVisibility','off');
patch(timeEdges([2 2 3 3]),yLimit([1 2 2 1]),[0.6 0.6 0.6],...
'Parent',hAxes,'Clipping','off','HandleVisibility','off');
patch(timeEdges([3 3 8 8]),yLimit([1 2 2 1]),[1 1 1],...
'Parent',hAxes,'Clipping','off','HandleVisibility','off');
line(timeEdges([4 5 6 7; 4 5 6 7]),yLimit([1 1 1 1; 2 2 2 2]),...
'Parent',hAxes,'Clipping','off','Color',[0 0 0],'LineStyle',':',...
'HandleVisibility','off');
% Create the color map for the family group data:
familyRGB = zeros(nFamilies,3);
nMembers = cellfun('length',familyList);
notDeadEnd = (nMembers > 1);
[temp,sortIndex] = sort(nMembers(notDeadEnd));
cmap(sortIndex,:) = grayless_color_cube(sum(notDeadEnd));
familyRGB(notDeadEnd,:) = cmap;
% Plot the family group data:
for iFamily = 1:nFamilies,
familyIndex = familyList{iFamily};
familyColor = familyRGB(iFamily,:);
line([timestamps(parents(familyIndex)); ...
timestamps([1 1],familyIndex)],...
[position([1 1],familyIndex); branchEnd(familyIndex)],...
'Parent',hAxes,'Color',familyColor,'Clipping','off',...
'HandleVisibility','off');
plot(timestamps(familyIndex),position(familyIndex),'o',...
'Parent',hAxes,'Clipping','off','HandleVisibility','off',...
'MarkerEdgeColor','none','MarkerFaceColor',familyColor,...
'MarkerSize',4);
end
% Plot the markers for the improved and best submissions:
[topScore,topIndex] = min([data.score]);
isImproved(topIndex) = false;
plot(timestamps(isImproved),position(isImproved),'s','Parent',hAxes,...
'Clipping','off','HandleVisibility','off',...
'MarkerEdgeColor',[0 0 0],'MarkerFaceColor','none','MarkerSize',4);
plot(timestamps(topIndex),position(topIndex),'p','Parent',hAxes,...
'Clipping','off','HandleVisibility','off',...
'MarkerEdgeColor',[0 0 0],'MarkerFaceColor','none','MarkerSize',14);
% Plot markers for selected files:
hMarkers = [text(0,0,'\leftarrow 1','Parent',hAxes,'Color',[0 0 0],...
'Clipping','off','FontAngle','normal',...
'FontName','FixedWidth','FontWeight','normal',...
'FontUnits','pixels','FontSize',12,...
'HandleVisibility','off',...
'HorizontalAlignment','left',...
'VerticalAlignment','middle','Visible','off') ...
text(0,0,'\leftarrow 2','Parent',hAxes,'Color',[0 0 0],...
'Clipping','off','FontAngle','normal',...
'FontName','FixedWidth','FontWeight','normal',...
'FontUnits','pixels','FontSize',12,...
'HandleVisibility','off',...
'HorizontalAlignment','left',...
'VerticalAlignment','middle','Visible','off')];
% Activate the mouse-based plot navigation:
set(hFigure,'WindowButtonDownFcn',{@mouse_action; 'down'},...
'WindowButtonMotionFcn',{@mouse_action; 'none'},...
'WindowButtonUpFcn',{@mouse_action; 'up'});
drawnow;
%~~~Begin nested functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%------------------------------------------------------------------------
function childList = get_children(parentIndex)
%
% Recursive function for finding children.
%
%------------------------------------------------------------------------
childList = parentIndex;
position(parentIndex) = offset;
childIndex = find(parents == parentIndex);
if isempty(childIndex),
branchEnd(parentIndex) = offset;
offset = offset+1;
return;
end
[bestScore,bestIndex] = min(metric([childIndex parentIndex]));
if (bestIndex <= numel(childIndex)),
isImproved(childIndex(bestIndex)) = true;
end
metric(childIndex) = bestScore;
for iChild = childIndex,
branchEnd(parentIndex) = offset;
childList = [childList get_children(iChild)];
end
end
%------------------------------------------------------------------------
function display_info(hInfo,index)
%
% Function for displaying submission info in a text uicontrol.
%
%------------------------------------------------------------------------
if isempty(index),
set(hInfo,'String',{});
else
displayString = {data(index).title; ...
[' Author: ' data(index).author]; ...
[' Submission time: ' ...
datestr(data(index).timestamp,0)]; ...
[' Score: ' num2str(data(index).score)]};
set(hInfo,'String',displayString);
end
end
%------------------------------------------------------------------------
function display_code(index)
%
% Function for displaying submission code in a list uicontrol.
%
%------------------------------------------------------------------------
% if strcmp(addToPanel,'top'),
% set(hTopCode,'ListboxTop',1,'String',codeList(data(index).lines));
% else
% set(hBottomCode,'ListboxTop',1,'String',codeList(data(index).lines));
% end
codeLines = data(index).lines;
lineNumbers = cellstr(int2str((1:numel(codeLines)).'));
if strcmp(addToPanel,'top'),
hCode = hTopCode;
hCompareCode = hBottomCode;
compareIndex = currentDisplay{2};
else
hCode = hBottomCode;
hCompareCode = hTopCode;
compareIndex = currentDisplay{1};
end
if isempty(compareIndex),
values = [];
else
compareLines = data(compareIndex).lines;
values = find(~ismember(codeLines,compareLines));
compareValues = find(~ismember(compareLines,codeLines));
listboxTop = get(hCompareCode,'ListboxTop');
set(hCompareCode,'UserData',compareValues,'Value',compareValues);
drawnow;
set(hCompareCode,'ListboxTop',listboxTop);
end
set(hCode,'Max',numel(codeLines),...
'String',strcat(lineNumbers,{': '},codeList(codeLines).'),...
'UserData',values,'Value',values);
drawnow;
set(hCode,'ListboxTop',1);
end
%------------------------------------------------------------------------
function gui_callback(source,event)
%
% Callback function for GUI controls.
%
%------------------------------------------------------------------------
switch get(source,'Tag'),
case 'BUTTON',
if strcmp(addToPanel,'top'),
addToPanel = 'bottom';
set(hTopButton,'BackgroundColor',[1 0 0],'String','Keep');
set(hBottomButton,'BackgroundColor',[0 1 0],'String','Replace');
else
addToPanel = 'top';
set(hTopButton,'BackgroundColor',[0 1 0],'String','Replace');
set(hBottomButton,'BackgroundColor',[1 0 0],'String','Keep');
end
case 'LIST',
listboxTop = get(source,'ListboxTop');
set(source,'Value',get(source,'UserData'));
drawnow;
set(source,'ListboxTop',listboxTop);
end
end
%------------------------------------------------------------------------
function mouse_action(source,event,mouseOperation)
%
% Function for mouse-based plot navigation.
%
%------------------------------------------------------------------------
switch mouseOperation,
case 'none',
currentPoint = get(source,'CurrentPoint');
nodeIndex = find_nearest;
display_info(hPreviewInfo,nodeIndex);
update_pointer;
drawnow;
case 'down',
if (~isActive),
isActive = true;
currentPoint = get(source,'CurrentPoint');
if within_rectangle(currentPoint,axesPosition),
selection = get(source,'SelectionType');
set(source,'WindowButtonMotionFcn',{@mouse_action; 'motion'});
initialize_mouse;
update_pointer;
drawnow;
else
isActive = false;
end
end
case 'motion',
currentPoint = get(source,'CurrentPoint');
track_mouse;
drawnow;
case 'up',
if isActive,
currentPoint = get(source,'CurrentPoint');
track_mouse;
set(source,'WindowButtonMotionFcn',{@mouse_action; 'none'});
selection = 'none';
update_pointer;
isActive = false;
drawnow;
end
end
%----------------------------------------------------------------------
function initialize_mouse
%
% Initialization function for mouse-based plot navigation.
%
%----------------------------------------------------------------------
switch selection,
case 'normal', % Left button: pan operation
cameraPoint = get(hAxes,'CameraPosition');
targetPoint = get(hAxes,'CameraTarget');
viewAngle = get(hAxes,'CameraViewAngle');
origin = currentPoint;
case 'alt', % Right button: select operation
nodeIndex = find_nearest;
if ~isempty(nodeIndex),
if strcmp(addToPanel,'top'),
currentDisplay{1} = nodeIndex;
hMarker = hMarkers(1);
display_info(hTopInfo,nodeIndex);
else
currentDisplay{2} = nodeIndex;
hMarker = hMarkers(2);
display_info(hBottomInfo,nodeIndex);
end
set(hMarker,'Visible','on',...
'Position',[timestamps(nodeIndex) position(nodeIndex)]);
display_code(nodeIndex);
end
case 'extend', % Middle button: zoom operation
viewAngle = get(hAxes,'CameraViewAngle');
origin = currentPoint(2);
case 'open', % Double-click any button: reset operation
viewAngle = 5; % in degrees
cameraPoint = [mean(xLimit) mean(yLimit) ...
0.5/tan(viewAngle*pi/360)];
targetPoint = [mean(xLimit) mean(yLimit) 0];
set(hAxes,'CameraPosition',cameraPoint,...
'CameraTarget',targetPoint,'CameraViewAngle',viewAngle);
end
end
%----------------------------------------------------------------------
function track_mouse
%
% Tracking function for mouse-based plot navigation.
%
%----------------------------------------------------------------------
switch selection,
case 'normal', % Left button: pan operation
scaleGain = 2*cameraPoint(3)*tan(viewAngle*pi/360);
newPoint = targetPoint(1:2)+...
scaleGain.*panGain.*(origin-currentPoint);
newPoint(1) = min(max(newPoint(1),xLimit(1)),xLimit(2));
newPoint(2) = min(max(newPoint(2),yLimit(1)),yLimit(2));
newCameraPoint = [newPoint cameraPoint(3)];
newTargetPoint = [newPoint targetPoint(3)];
set(hAxes,'CameraPosition',newCameraPoint,...
'CameraTarget',newTargetPoint);
case 'extend', % Middle button: zoom operation
newViewAngle = viewAngle*2^(zoomGain*(origin-currentPoint(2)));
newViewAngle = min(max(newViewAngle,0.02),5);
set(hAxes,'CameraViewAngle',newViewAngle);
end
end
%----------------------------------------------------------------------
function index = find_nearest
%
% Function for finding the nearest node to the mouse cursor.
%
%----------------------------------------------------------------------
cameraPoint = get(hAxes,'CameraPosition');
viewAngle = get(hAxes,'CameraViewAngle');
origin = axesPosition(1:2)+0.5.*axesPosition(3:4);
scaleGain = 2*cameraPoint(3)*tan(viewAngle*pi/360);
cursorPoint = cameraPoint(1:2)+...
scaleGain.*panGain.*(currentPoint-origin);
scale = diff(yLimit)/diff(xLimit);
distances = sqrt((cursorPoint(2)-position).^2+...
(scale*(cursorPoint(1)-timestamps)).^2);
[minValue,minIndex] = min(distances);
if (minValue <= 1),
index = minIndex;
else
index = [];
end
end
%----------------------------------------------------------------------
function update_pointer
%
% Updates the pointer to match the current selection.
%
%----------------------------------------------------------------------
o = nan;
switch selection,
case 'normal', % Left button: pan operation
set(hFigure,'Pointer','custom','PointerShapeHotSpot',[8 8],...
'PointerShapeCData',...
[o o o o o o o 2 2 o o o o o o o; ...
o o o o o o 2 1 1 2 o o o o o o; ...
o o o o o 2 1 1 1 1 2 o o o o o; ...
o o o o 2 1 1 1 1 1 1 2 o o o o; ...
o o o 2 2 2 2 1 1 2 2 2 2 o o o; ...
o o 2 1 2 o 2 1 1 2 o 2 1 2 o o; ...
o 2 1 1 2 2 2 1 1 2 2 2 1 1 2 o; ...
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2; ...
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2; ...
o 2 1 1 2 2 2 1 1 2 2 2 1 1 2 o; ...
o o 2 1 2 o 2 1 1 2 o 2 1 2 o o; ...
o o o 2 2 2 2 1 1 2 2 2 2 o o o; ...
o o o o 2 1 1 1 1 1 1 2 o o o o; ...
o o o o o 2 1 1 1 1 2 o o o o o; ...
o o o o o o 2 1 1 2 o o o o o o; ...
o o o o o o o 2 2 o o o o o o o]);
case 'extend', % Middle button: zoom operation
set(hFigure,'Pointer','custom','PointerShapeHotSpot',[8 8],...
'PointerShapeCData',...
[o o o o o o o 2 2 o o o o o o o; ...
o o o o o 2 2 1 1 2 2 o o o o o; ...
o o o 2 2 1 1 1 1 1 1 2 2 o o o; ...
o o 2 1 1 1 1 1 1 1 1 1 1 2 o o; ...
o o 2 2 2 2 1 1 1 1 2 2 2 2 o o; ...
o o o o o 2 1 1 1 1 2 o o o o o; ...
o o o o o 2 1 1 1 1 2 o o o o o; ...
o o o o o 2 1 1 1 1 2 o o o o o; ...
o o o o 2 1 1 1 1 1 1 2 o o o o; ...
o o o o 2 1 1 1 1 1 1 2 o o o o; ...
2 2 2 2 2 1 1 1 1 1 1 2 2 2 2 2; ...
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2; ...
o 2 2 1 1 1 1 1 1 1 1 1 1 2 2 o; ...
o o o 2 2 1 1 1 1 1 1 2 2 o o o; ...
o o o o o 2 2 1 1 2 2 o o o o o; ...
o o o o o o o 2 2 o o o o o o o]);
case 'none', % No current selection
if within_rectangle(currentPoint,axesPosition),
set(hFigure,'Pointer','custom','PointerShapeHotSpot',[8 8],...
'PointerShapeCData',...
[o o o 2 2 2 o o o o 2 2 2 o o o; ...
o o o 2 1 2 o o o o 2 1 2 o o o; ...
o o o 2 1 2 o o o o 2 1 2 o o o; ...
2 2 2 2 1 2 o o o o 2 1 2 2 2 2; ...
2 1 1 1 1 2 o o o o 2 1 1 1 1 2; ...
2 2 2 2 2 2 o o o o 2 2 2 2 2 2; ...
o o o o o o o 2 2 o o o o o o o; ...
o o o o o o 2 1 1 2 o o o o o o; ...
o o o o o o 2 1 1 2 o o o o o o; ...
o o o o o o o 2 2 o o o o o o o; ...
2 2 2 2 2 2 o o o o 2 2 2 2 2 2; ...
2 1 1 1 1 2 o o o o 2 1 1 1 1 2; ...
2 2 2 2 1 2 o o o o 2 1 2 2 2 2; ...
o o o 2 1 2 o o o o 2 1 2 o o o; ...
o o o 2 1 2 o o o o 2 1 2 o o o; ...
o o o 2 2 2 o o o o 2 2 2 o o o]);
else
set(hFigure,'Pointer','default',...
'PointerShapeHotSpot','default',...
'PointerShapeCData','default');
end
end
end
end
%~~~End nested functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
end
%~~~Begin subfunctions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
%--------------------------------------------------------------------------
function map = grayless_color_cube(N)
%
% Mimics the colorcube function, with no grays and fewer pure colors.
%
%--------------------------------------------------------------------------
nSteps = roots([1 0 -1 -N]);
nSteps = ceil(nSteps(1));
[R,G,B] = meshgrid(linspace(0,1,nSteps));
map = [R(:) G(:) B(:)];
map(1:(nSteps*(nSteps+1)+1):(nSteps^3),:) = [];
map = map(1:N,:);
end
%--------------------------------------------------------------------------
function isWithin = within_rectangle(point,position)
%
% Determines if a point is within a rectangular region.
%
%--------------------------------------------------------------------------
isWithin = ((point(:,1) >= position(1)) & ...
(point(:,2) >= position(2)) & ...
(point(:,1) <= position(1)+position(3)) & ...
(point(:,2) <= position(2)+position(4)));
end
%~~~End subfunctions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~