gusucode.com > Mobile Robotics Simulation Toolbox > src/environment/Visualizer2D.m

    classdef Visualizer2D < matlab.System & matlab.system.mixin.CustomIcon
    % VISUALIZER2D 2D Robot Visualizer
    %
    % Displays the pose (position and orientation) of an object in a 2D
    % environment. Additionally has the option to display a map as a 
    % robotics.OccupancyGrid or robotics.BinaryOccupancyGrid, object
    % trajectory, waypoints, lidar scans, and/or objects.
    %
    % For more information, see <a href="matlab:edit mrsDocVisualizer2D">the documentation page</a>
    %
    % Copyright 2018-2019 The MathWorks, Inc.

    %% PROPERTIES
    % Public (user-visible) properties
    properties(Nontunable)
        robotRadius = 0;    % Robot radius [m]
        mapName = '';       % Map
    end     
    properties(Nontunable, Logical)
        showTrajectory = true;      % Show trajectory
        hasWaypoints = false;       % Accept waypoints
        hasLidar = false;           % Accept lidar inputs
    end
    properties
       sensorOffset = [0 0];        % Lidar sensor offset (x,y) [m] 
       scanAngles = [-pi/4,0,pi/4]; % Scan angles [rad]
    end
    properties(Nontunable, Logical)
        hasObjDetector = false;     % Accept object detections
    end
    properties(Nontunable)
       objDetectorOffset = [0 0];   % Object detector offset (x,y) [m] 
       objDetectorAngle = 0;        % Object detector angle [rad]
       objDetectorFOV = pi/4;       % Object detector field of view [rad] 
       objDetectorMaxRange = 5;     % Object detector maximum range [m]
       objectColors = [1 0 0;0 1 0;0 0 1];  % Object label colors [RGB rows]
       objectMarkers = 's';       % Object markers [character array]
    end

    % Private properties
    properties(Access = private)
        map;                % Occupancy grid
        fig;                % Figure window
        ax;                 % Axes for plotting
        RobotHandle;        % Handle to robot body marker or circle
        OrientationHandle;  % Handle to robot orientation line
        LidarHandles;       % Handle array to lidar lines
        TrajHandle;         % Handle to trajectory plot
        trajX = [];         % X Trajectory points
        trajY = [];         % Y Trajectory points
        WaypointHandle;     % Handle to waypoints
        ObjectHandles;      % Handle to objects
        ObjDetectorHandles; % Handle array to object detector lines
    end

    %% METHODS
    methods(Access = protected)
        
        % Setup method: Initializes all necessary graphics objects
        function setupImpl(obj)
            % Create figure
            FigureName = 'Robot Visualization';
            FigureTag = 'RobotVisualization';
            existingFigures = findobj('type','figure','tag',FigureTag);
            if ~isempty(existingFigures)
                obj.fig = figure(existingFigures(1)); % bring figure to the front
                clf;
            else
                obj.fig = figure('Name',FigureName,'tag',FigureTag);
            end
            obj.ax = axes('parent',obj.fig);   
            hold(obj.ax,'on');
            
            % Show the map
            obj.map = internal.createMapFromName(obj.mapName);
            if ~isempty(obj.map)
                show(obj.map,'Parent',obj.ax);
            end
            
            % Initialize robot plot
            obj.OrientationHandle = plot(obj.ax,0,0,'r','LineWidth',1.5);
            if obj.robotRadius > 0
                % Finite size robot
                [x,y] = internal.circlePoints(0,0,obj.robotRadius,17);
                obj.RobotHandle = plot(obj.ax,x,y,'b','LineWidth',1.5);
            else
                % Point robot
                obj.RobotHandle = plot(obj.ax,0,0,'bo', ...
                    'LineWidth',1.5,'MarkerFaceColor',[1 1 1]);
            end
            
            % Initialize trajectory
            if obj.showTrajectory
                obj.TrajHandle = plot(obj.ax,0,0,'b.-');
            end
            
            % Initialize waypoints
            if obj.hasWaypoints
                obj.WaypointHandle = plot(obj.ax,0,0, ...
                   'rx','MarkerSize',10,'LineWidth',2);
            end
            
            % Initialize lidar lines
            if obj.hasLidar
                for idx = 1:numel(obj.scanAngles)
                    obj.LidarHandles(idx) = plot(obj.ax,0,0,'b--');
                end
            end
            
            % Initialize objects and object detector lines
            if obj.hasObjDetector
                if numel(obj.objectMarkers) == 1
                    obj.ObjectHandles = scatter(obj.ax,0,0,75,... 
                        obj.objectMarkers,'filled','LineWidth',2);
                else
                    for idx = 1:numel(obj.objectMarkers)
                        obj.ObjectHandles(idx) = scatter(obj.ax,0,0,75,... 
                            obj.objectMarkers(idx),'filled','LineWidth',2);
                    end
                end
                objDetectorFormat = 'g-.';
                obj.ObjDetectorHandles(1) = plot(obj.ax,0,0,objDetectorFormat); % Left line
                obj.ObjDetectorHandles(2) = plot(obj.ax,0,0,objDetectorFormat); % Right line
            end
            
            % Final setup
            title(obj.ax,'Robot Visualization');
            hold(obj.ax,'off'); 
            axis equal          
        end

        % Step method: Updates visualization based on inputs
        function stepImpl(obj,pose,varargin)          
            % Unpack the pose input into (x, y, theta)
            x = pose(1);
            y = pose(2);
            theta = pose(3);
            
            % Check for closed figure
            if ~isvalid(obj.fig)
                return;
            end
            
            % Unpack the optional arguments
            idx = 1;
            if obj.hasWaypoints % Waypoints
                waypoints = varargin{idx};
                idx = idx + 1;
            end
            if obj.hasLidar % Lidar ranges
                ranges = varargin{idx};
                idx = idx + 1;
            end
            if obj.hasObjDetector % Objects and object detections
                objects = varargin{idx};
            end
                       
            % Update the trajectory
            if obj.showTrajectory
               obj.trajX = [obj.trajX;x];
               obj.trajY = [obj.trajY;y];
               set(obj.TrajHandle,'xdata',obj.trajX,'ydata',obj.trajY);
            end
            
            % Update waypoints
            if obj.hasWaypoints && numel(waypoints) > 1
                set(obj.WaypointHandle,'xdata',waypoints(:,1), ...
                                       'ydata',waypoints(:,2));
            end

            % Update the robot pose
            xAxesLim = get(obj.ax,'XLim');
            lineLength = diff(xAxesLim)/20;
            if obj.robotRadius > 0
                % Finite radius case
                [xc,yc] = internal.circlePoints(x,y,obj.robotRadius,17);
                set(obj.RobotHandle,'xdata',xc,'ydata',yc);
                len = max(lineLength,2*obj.robotRadius); % Plot orientation based on radius unless it's too small
                xp = [x, x+(len*cos(theta))];
                yp = [y, y+(len*sin(theta))];
                set(obj.OrientationHandle,'xdata',xp,'ydata',yp);
            else
                % Point robot case
                xp = [x, x+(lineLength*cos(theta))];
                yp = [y, y+(lineLength*sin(theta))];
                set(obj.RobotHandle,'xdata',x,'ydata',y);
                set(obj.OrientationHandle,'xdata',xp,'ydata',yp);
            end
            
            % Update lidar lines
            if obj.hasLidar
                
                % Find the sensor location(s)
                offsetVec = [cos(theta) -sin(theta); ... 
                             sin(theta)  cos(theta)]*obj.sensorOffset';
                sensorLoc = [x,y] + offsetVec';  
                
                for idx = 1:numel(ranges)
                    if ~isnan(ranges(idx))
                       
                        % If there is a single sensor offset, use that value
                        if size(sensorLoc,1) == 1
                           sensorPos = sensorLoc;
                        % Else, use the specific index's sensor location
                        else
                           sensorPos = sensorLoc(idx,:); 
                        end
                        
                        alpha = theta + obj.scanAngles(idx);                      
                        lidarX = sensorPos(1) + [0, ranges(idx)*cos(alpha)];
                        lidarY = sensorPos(2) + [0, ranges(idx)*sin(alpha)];
                        set(obj.LidarHandles(idx),'xdata',lidarX, ...
                                                  'ydata',lidarY); 
                    else
                        set(obj.LidarHandles(idx),'xdata',[],'ydata',[]); 
                    end 
                end
            end         
            
            % Update object and object detector lines
            if obj.hasObjDetector
                
                if isempty(objects)
                    set(obj.ObjectHandles,'xdata',[],'ydata',[],'cdata',[]);
                else
                    % Find the corresponding colors of each object
                    if size(obj.objectColors,1) == 1
                        colorData = obj.objectColors; % Use the single color specified
                    else
                        colorData = obj.objectColors(objects(:,3),:); % Use the color based on labels
                    end
                    
                    % If single marker, plot markers in the single object handle
                    if numel(obj.objectMarkers) == 1
                        
                        set(obj.ObjectHandles,'xdata',objects(:,1),... 
                            'ydata',objects(:,2),'cdata',colorData);
                    % If multiple markers, plot markers in the single object handle
                    else
                        for idx = 1:numel(obj.objectMarkers)
                            indices = (objects(:,3) == idx);
                            set(obj.ObjectHandles(idx),'xdata',objects(indices,1),... 
                                'ydata',objects(indices,2),'cdata',colorData(indices,:));
                        end
                    end
                end
                
                % Find the sensor location(s)
                offsetVec = [cos(theta) -sin(theta); ... 
                             sin(theta)  cos(theta)]*obj.objDetectorOffset';
                sensorLoc = [x,y] + offsetVec';  
                                
                % Plot the object detector lines
                % Left line
                alphaLeft = theta + obj.objDetectorAngle + obj.objDetectorFOV/2;        
                if ~isempty(obj.map) 
                    intPtsLeft = rayIntersection(obj.map,[sensorLoc alphaLeft], ...
                                                 0,obj.objDetectorMaxRange);
                else
                    intPtsLeft = NaN;
                end
                if ~isnan(intPtsLeft)
                    objLeftX = [sensorLoc(1) intPtsLeft(1)];
                    objLeftY = [sensorLoc(2) intPtsLeft(2)];    
                else
                    objLeftX = sensorLoc(1) + [0, obj.objDetectorMaxRange*cos(alphaLeft)];
                    objLeftY = sensorLoc(2) + [0, obj.objDetectorMaxRange*sin(alphaLeft)];   
                end
                set(obj.ObjDetectorHandles(1),'xdata',objLeftX, ...
                                              'ydata',objLeftY); 
                % Right line
                alphaRight = theta + obj.objDetectorAngle - obj.objDetectorFOV/2;        
                if ~isempty(obj.map) 
                    intPtsRight = rayIntersection(obj.map,[sensorLoc alphaRight], ...
                                                  0,obj.objDetectorMaxRange);
                else
                   intPtsRight = NaN; 
                end
                if ~isnan(intPtsRight)
                    objRightX = [sensorLoc(1) intPtsRight(1)];
                    objRightY = [sensorLoc(2) intPtsRight(2)];    
                else
                    objRightX = sensorLoc(1) + [0, obj.objDetectorMaxRange*cos(alphaRight)];
                    objRightY = sensorLoc(2) + [0, obj.objDetectorMaxRange*sin(alphaRight)];   
                end
                set(obj.ObjDetectorHandles(2),'xdata',objRightX, ...
                                              'ydata',objRightY); 
            end
            
            % Update the figure
            drawnow('limitrate')
            
        end

        % Define total number of inputs for system with optional inputs
        function num = getNumInputsImpl(obj)
            num = 1;
            if obj.hasWaypoints
               num = num + 1; 
            end
            if obj.hasLidar
               num = num + 1;
            end
            if obj.hasObjDetector
               num = num + 1; 
            end
        end
        
        % Define input port names
        function [namePose,varargout] = getInputNamesImpl(obj)
            namePose = 'pose';
            idx = 1;
            if obj.hasWaypoints
               varargout{idx} = 'waypoints';
               idx = idx + 1;
            end
            if obj.hasLidar
               varargout{idx} = 'ranges';
               idx = idx + 1;
            end
            if obj.hasObjDetector
               varargout{idx} = 'objects';
            end
        end

        % Define icon for System block
        function icon = getIconImpl(~)
            icon = {'Robot','Visualizer'};
        end
        
        % Inactivate properties if there's no lidar or object detector
        function flag = isInactivePropertyImpl(obj,prop)
             flag = false;
             switch prop
                 case 'sensorOffset'
                     flag = ~obj.hasLidar;
                 case 'scanAngles'
                     flag = ~obj.hasLidar;
                 case 'objDetectorOffset'
                     flag = ~obj.hasObjDetector;
                 case 'objDetectorAngle'
                     flag = ~obj.hasObjDetector;
                 case 'objDetectorFOV'
                     flag = ~obj.hasObjDetector;
                 case 'objDetectorMaxRange'
                     flag = ~obj.hasObjDetector;
                 case 'objectColors'
                     flag = ~obj.hasObjDetector;
                 case 'objectMarkers'
                     flag = ~obj.hasObjDetector;
             end
        end
        
    end
    
    methods (Access = public)        
        % Attaches all properties associated with a LidarSensor object
        function attachLidarSensor(obj,lidar)
            obj.hasLidar = true;
            obj.sensorOffset = lidar.sensorOffset;
            obj.scanAngles = lidar.scanAngles;
            
            % Ensure to use the same map as the visualizer
            release(lidar);
            lidar.mapName = obj.mapName;
        end
        
        % Attaches all properties associated with an ObjectDetector object
        function attachObjectDetector(obj,detector)
            obj.hasObjDetector = true;
            obj.objDetectorOffset = detector.sensorOffset;
            obj.objDetectorAngle = detector.sensorAngle;
            obj.objDetectorFOV = detector.fieldOfView;
            obj.objDetectorMaxRange = detector.maxRange;
            
            % Ensure to use the same map as the visualizer
            release(detector);
            detector.mapName = obj.mapName;
        end
        
    end
    
    methods (Static, Access = protected)
        % Do not show "Simulate using" option
        function flag = showSimulateUsingImpl
            flag = false;
        end
        % Always run in interpreted mode
        function simMode = getSimulateUsingImpl
            simMode = 'Interpreted execution';
        end
    end
        
end