From 9edf03718c87a6de447b80e67f83066e3adb32aa Mon Sep 17 00:00:00 2001 From: Emil Harlan <eharlan@uni-bremen.de> Date: Tue, 23 May 2023 12:24:15 +0200 Subject: [PATCH] Upload New File --- json/ros2d.js | 1206 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1206 insertions(+) create mode 100644 json/ros2d.js diff --git a/json/ros2d.js b/json/ros2d.js new file mode 100644 index 0000000..f61d9c7 --- /dev/null +++ b/json/ros2d.js @@ -0,0 +1,1206 @@ +/** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + +var ROS2D = ROS2D || { + /** + * @description Library version + */ + REVISION : '0.10.0' + }; + + // convert the given global Stage coordinates to ROS coordinates + createjs.Stage.prototype.globalToRos = function(x, y) { + var rosX = (x - this.x) / this.scaleX; + var rosY = (this.y - y) / this.scaleY; + return new ROSLIB.Vector3({ + x : rosX, + y : rosY + }); + }; + + // convert the given ROS coordinates to global Stage coordinates + createjs.Stage.prototype.rosToGlobal = function(pos) { + var x = pos.x * this.scaleX + this.x; + var y = pos.y * this.scaleY + this.y; + return { + x : x, + y : y + }; + }; + + // convert a ROS quaternion to theta in degrees + createjs.Stage.prototype.rosQuaternionToGlobalTheta = function(orientation) { + // See https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Rotation_matrices + // here we use [x y z] = R * [1 0 0] + var q0 = orientation.w; + var q1 = orientation.x; + var q2 = orientation.y; + var q3 = orientation.z; + // Canvas rotation is clock wise and in degrees + return -Math.atan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2 * q2 + q3 * q3)) * 180.0 / Math.PI; + }; + + /** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + + /** + * An image map is a PNG image scaled to fit to the dimensions of a OccupancyGrid. + * + * @constructor + * @param options - object with following keys: + * * message - the occupancy grid map meta data message + * * image - the image URL to load + */ + ROS2D.ImageMap = function(options) { + options = options || {}; + var message = options.message; + var image = options.image; + + // save the metadata we need + this.pose = new ROSLIB.Pose({ + position : message.origin.position, + orientation : message.origin.orientation + }); + + // set the size + this.width = message.width; + this.height = message.height; + + // create the bitmap + createjs.Bitmap.call(this, image); + // change Y direction + this.y = -this.height * message.resolution; + + // scale the image + this.scaleX = message.resolution; + this.scaleY = message.resolution; + this.width *= this.scaleX; + this.height *= this.scaleY; + + // set the pose + this.x += this.pose.position.x; + this.y -= this.pose.position.y; + }; + ROS2D.ImageMap.prototype.__proto__ = createjs.Bitmap.prototype; + + /** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + + /** + * A image map is a PNG image scaled to fit to the dimensions of a OccupancyGrid. + * + * Emits the following events: + * * 'change' - there was an update or change in the map + * + * @constructor + * @param options - object with following keys: + * * ros - the ROSLIB.Ros connection handle + * * topic (optional) - the map meta data topic to listen to + * * image - the image URL to load + * * rootObject (optional) - the root object to add this marker to + */ + ROS2D.ImageMapClient = function(options) { + var that = this; + options = options || {}; + var ros = options.ros; + var topic = options.topic || '/map'; + this.image = options.image; + this.rootObject = options.rootObject || new createjs.Container(); + + // create an empty shape to start with + this.currentImage = new createjs.Shape(); + + // subscribe to the topic + var rosTopic = new ROSLIB.Topic({ + ros : ros, + name : topic, + messageType : 'nav_msgs/OccupancyGrid' + }); + + rosTopic.subscribe(function(message) { + // we only need this once + rosTopic.unsubscribe(); + + // create the image + that.currentImage = new ROS2D.ImageMap({ + message : message, + image : that.image + }); + that.rootObject.addChild(that.currentImage); + // work-around for a bug in easeljs -- needs a second object to render correctly + that.rootObject.addChild(new ROS2D.Grid({size:1})); + + that.emit('change'); + }); + }; + ROS2D.ImageMapClient.prototype.__proto__ = EventEmitter2.prototype; + + /** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + + /** + * An OccupancyGrid can convert a ROS occupancy grid message into a createjs Bitmap object. + * + * @constructor + * @param options - object with following keys: + * * message - the occupancy grid message + */ + ROS2D.OccupancyGrid = function(options) { + options = options || {}; + var message = options.message; + + // internal drawing canvas + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + + // save the metadata we need + this.pose = new ROSLIB.Pose({ + position : message.info.origin.position, + orientation : message.info.origin.orientation + }); + + // set the size + this.width = message.info.width; + this.height = message.info.height; + canvas.width = this.width; + canvas.height = this.height; + + var imageData = context.createImageData(this.width, this.height); + for ( var row = 0; row < this.height; row++) { + for ( var col = 0; col < this.width; col++) { + // determine the index into the map data + var mapI = col + ((this.height - row - 1) * this.width); + // determine the value + var data = message.data[mapI]; + var val; + if (data === 100) { + val = 0; + } else if (data === 0) { + val = 255; + } else { + val = 127; + } + + // determine the index into the image data array + var i = (col + (row * this.width)) * 4; + // r + imageData.data[i] = val; + // g + imageData.data[++i] = val; + // b + imageData.data[++i] = val; + // a + imageData.data[++i] = 255; + } + } + context.putImageData(imageData, 0, 0); + + // create the bitmap + createjs.Bitmap.call(this, canvas); + // change Y direction + this.y = -this.height * message.info.resolution; + + // scale the image + this.scaleX = message.info.resolution; + this.scaleY = message.info.resolution; + this.width *= this.scaleX; + this.height *= this.scaleY; + + // set the pose + this.x += this.pose.position.x; + this.y -= this.pose.position.y; + }; + ROS2D.OccupancyGrid.prototype.__proto__ = createjs.Bitmap.prototype; + + /** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + + /** + * A map that listens to a given occupancy grid topic. + * + * Emits the following events: + * * 'change' - there was an update or change in the map + * + * @constructor + * @param options - object with following keys: + * * ros - the ROSLIB.Ros connection handle + * * topic (optional) - the map topic to listen to + * * rootObject (optional) - the root object to add this marker to + * * continuous (optional) - if the map should be continuously loaded (e.g., for SLAM) + */ + ROS2D.OccupancyGridClient = function(options) { + var that = this; + options = options || {}; + var ros = options.ros; + var topic = options.topic || '/map'; + this.continuous = options.continuous; + this.rootObject = options.rootObject || new createjs.Container(); + + // current grid that is displayed + // create an empty shape to start with, so that the order remains correct. + this.currentGrid = new createjs.Shape(); + this.rootObject.addChild(this.currentGrid); + // work-around for a bug in easeljs -- needs a second object to render correctly + this.rootObject.addChild(new ROS2D.Grid({size:1})); + + // subscribe to the topic + var rosTopic = new ROSLIB.Topic({ + ros : ros, + name : topic, + messageType : 'nav_msgs/OccupancyGrid', + //compression : 'png' + }); + + rosTopic.subscribe(function(message) { + // check for an old map + var index = null; + if (that.currentGrid) { + index = that.rootObject.getChildIndex(that.currentGrid); + that.rootObject.removeChild(that.currentGrid); + } + + that.currentGrid = new ROS2D.OccupancyGrid({ + message : message + }); + if (index !== null) { + that.rootObject.addChildAt(that.currentGrid, index); + } + else { + that.rootObject.addChild(that.currentGrid); + } + + that.emit('change'); + + // check if we should unsubscribe + if (!that.continuous) { + rosTopic.unsubscribe(); + } + }); + }; + ROS2D.OccupancyGridClient.prototype.__proto__ = EventEmitter2.prototype; + + /** + * @fileOverview + * @author Jihoon Lee- jihoonlee.in@gmail.com + * @author Russell Toris - rctoris@wpi.edu + */ + + /** + * A static map that receives from map_server. + * + * Emits the following events: + * * 'change' - there was an update or change in the map + * + * @constructor + * @param options - object with following keys: + * * ros - the ROSLIB.Ros connection handle + * * service (optional) - the map topic to listen to, like '/static_map' + * * rootObject (optional) - the root object to add this marker to + */ + ROS2D.OccupancyGridSrvClient = function(options) { + var that = this; + options = options || {}; + var ros = options.ros; + var service = options.service || '/static_map'; + this.rootObject = options.rootObject || new createjs.Container(); + + // current grid that is displayed + this.currentGrid = null; + + // Setting up to the service + var rosService = new ROSLIB.Service({ + ros : ros, + name : service, + serviceType : 'nav_msgs/GetMap', + compression : 'png' + }); + + rosService.callService(new ROSLIB.ServiceRequest(),function(response) { + // check for an old map + if (that.currentGrid) { + that.rootObject.removeChild(that.currentGrid); + } + + that.currentGrid = new ROS2D.OccupancyGrid({ + message : response.map + }); + that.rootObject.addChild(that.currentGrid); + + that.emit('change', that.currentGrid); + }); + }; + ROS2D.OccupancyGridSrvClient.prototype.__proto__ = EventEmitter2.prototype; + + /** + * @fileOverview + * @author Bart van Vliet - bart@dobots.nl + */ + + /** + * An arrow with line and triangular head, based on the navigation arrow. + * Aims to the left at 0 rotation, as would be expected. + * + * @constructor + * @param {Object} options + * @param {Int} [options.size] - The size of the marker + * @param {Int} [options.strokeSize] - The size of the outline + * @param {String} [options.strokeColor] - The createjs color for the stroke + * @param {String} [options.fillColor] - The createjs color for the fill + * @param {Bool} [options.pulse] - If the marker should "pulse" over time + */ + ROS2D.ArrowShape = function(options) { + var that = this; + options = options || {}; + var size = options.size || 10; + var strokeSize = options.strokeSize || 3; + var strokeColor = options.strokeColor || createjs.Graphics.getRGB(0, 0, 0); + var fillColor = options.fillColor || createjs.Graphics.getRGB(255, 0, 0); + var pulse = options.pulse; + + // draw the arrow + var graphics = new createjs.Graphics(); + + var headLen = size / 3.0; + var headWidth = headLen * 2.0 / 3.0; + + graphics.setStrokeStyle(strokeSize); + graphics.beginStroke(strokeColor); + graphics.moveTo(0, 0); + graphics.lineTo(size-headLen, 0); + + graphics.beginFill(fillColor); + graphics.moveTo(size, 0); + graphics.lineTo(size-headLen, headWidth / 2.0); + graphics.lineTo(size-headLen, -headWidth / 2.0); + graphics.closePath(); + graphics.endFill(); + graphics.endStroke(); + + // create the shape + createjs.Shape.call(this, graphics); + + // check if we are pulsing + if (pulse) { + // have the model "pulse" + var growCount = 0; + var growing = true; + createjs.Ticker.addEventListener('tick', function() { + if (growing) { + that.scaleX *= 1.035; + that.scaleY *= 1.035; + growing = (++growCount < 10); + } else { + that.scaleX /= 1.035; + that.scaleY /= 1.035; + growing = (--growCount < 0); + } + }); + } + }; + ROS2D.ArrowShape.prototype.__proto__ = createjs.Shape.prototype; + + /** + * @fileOverview + * @author Raffaello Bonghi - raffaello.bonghi@officinerobotiche.it + */ + + /** + * A Grid object draw in map. + * + * @constructor + * @param options - object with following keys: + * * size (optional) - the size of the grid + * * cellSize (optional) - the cell size of map + * * lineWidth (optional) - the width of the lines in the grid + */ + ROS2D.Grid = function(options) { + var that = this; + options = options || {}; + var size = options.size || 10; + var cellSize = options.cellSize || 0.1; + var lineWidth = options.lineWidth || 0.001; + // draw the arrow + var graphics = new createjs.Graphics(); + // line width + graphics.setStrokeStyle(lineWidth*5); + graphics.beginStroke(createjs.Graphics.getRGB(0, 0, 0)); + graphics.beginFill(createjs.Graphics.getRGB(255, 0, 0)); + graphics.moveTo(-size*cellSize, 0); + graphics.lineTo(size*cellSize, 0); + graphics.moveTo(0, -size*cellSize); + graphics.lineTo(0, size*cellSize); + graphics.endFill(); + graphics.endStroke(); + + graphics.setStrokeStyle(lineWidth); + graphics.beginStroke(createjs.Graphics.getRGB(0, 0, 0)); + graphics.beginFill(createjs.Graphics.getRGB(255, 0, 0)); + for (var i = -size; i <= size; i++) { + graphics.moveTo(-size*cellSize, i * cellSize); + graphics.lineTo(size*cellSize, i * cellSize); + graphics.moveTo(i * cellSize, -size*cellSize); + graphics.lineTo(i * cellSize, size*cellSize); + } + graphics.endFill(); + graphics.endStroke(); + // create the shape + createjs.Shape.call(this, graphics); + + }; + ROS2D.Grid.prototype.__proto__ = createjs.Shape.prototype; + + /** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + + /** + * A navigation arrow is a directed triangle that can be used to display orientation. + * + * @constructor + * @param options - object with following keys: + * * size (optional) - the size of the marker + * * strokeSize (optional) - the size of the outline + * * strokeColor (optional) - the createjs color for the stroke + * * fillColor (optional) - the createjs color for the fill + * * pulse (optional) - if the marker should "pulse" over time + */ + ROS2D.NavigationArrow = function(options) { + var that = this; + options = options || {}; + var size = options.size || 10; + var strokeSize = options.strokeSize || 3; + var strokeColor = options.strokeColor || createjs.Graphics.getRGB(0, 0, 0); + var fillColor = options.fillColor || createjs.Graphics.getRGB(255, 0, 0); + var pulse = options.pulse; + + // draw the arrow + var graphics = new createjs.Graphics(); + // line width + graphics.setStrokeStyle(strokeSize); + graphics.moveTo(-size / 2.0, -size / 2.0); + graphics.beginStroke(strokeColor); + graphics.beginFill(fillColor); + graphics.lineTo(size, 0); + graphics.lineTo(-size / 2.0, size / 2.0); + graphics.closePath(); + graphics.endFill(); + graphics.endStroke(); + + // create the shape + createjs.Shape.call(this, graphics); + + // check if we are pulsing + if (pulse) { + // have the model "pulse" + var growCount = 0; + var growing = true; + createjs.Ticker.addEventListener('tick', function() { + if (growing) { + that.scaleX *= 1.035; + that.scaleY *= 1.035; + growing = (++growCount < 10); + } else { + that.scaleX /= 1.035; + that.scaleY /= 1.035; + growing = (--growCount < 0); + } + }); + } + }; + ROS2D.NavigationArrow.prototype.__proto__ = createjs.Shape.prototype; + + /** + * @fileOverview + * @author Inigo Gonzalez - ingonza85@gmail.com + */ + + /** + * A navigation image that can be used to display orientation. + * + * @constructor + * @param options - object with following keys: + * * size (optional) - the size of the marker + * * image - the image to use as a marker + * * pulse (optional) - if the marker should "pulse" over time + */ + ROS2D.NavigationImage = function(options) { + var that = this; + options = options || {}; + var size = options.size || 10; + var image_url = options.image; + var pulse = options.pulse; + var alpha = options.alpha || 1; + + var originals = {}; + + var paintImage = function(){ + createjs.Bitmap.call(that, image); + var scale = calculateScale(size); + that.alpha = alpha; + that.scaleX = scale; + that.scaleY = scale; + that.regY = that.image.height/2; + that.regX = that.image.width/2; + originals['rotation'] = that.rotation; + Object.defineProperty( that, 'rotation', { + get: function(){ return originals['rotation'] + 90; }, + set: function(value){ originals['rotation'] = value; } + }); + if (pulse) { + // have the model "pulse" + var growCount = 0; + var growing = true; + var SCALE_SIZE = 1.020; + createjs.Ticker.addEventListener('tick', function() { + if (growing) { + that.scaleX *= SCALE_SIZE; + that.scaleY *= SCALE_SIZE; + growing = (++growCount < 10); + } else { + that.scaleX /= SCALE_SIZE; + that.scaleY /= SCALE_SIZE; + growing = (--growCount < 0); + } + }); + } + }; + + var calculateScale = function(_size){ + return _size / image.width; + }; + + var image = new Image(); + image.onload = paintImage; + image.src = image_url; + + }; + + ROS2D.NavigationImage.prototype.__proto__ = createjs.Bitmap.prototype; + + /** + * @fileOverview + * @author Bart van Vliet - bart@dobots.nl + */ + + /** + * A shape to draw a nav_msgs/Path msg + * + * @constructor + * @param options - object with following keys: + * * path (optional) - the initial path to draw + * * strokeSize (optional) - the size of the outline + * * strokeColor (optional) - the createjs color for the stroke + */ + ROS2D.PathShape = function(options) { + options = options || {}; + var path = options.path; + this.strokeSize = options.strokeSize || 3; + this.strokeColor = options.strokeColor || createjs.Graphics.getRGB(0, 0, 0); + + // draw the line + this.graphics = new createjs.Graphics(); + + if (path !== null && typeof path !== 'undefined') { + this.graphics.setStrokeStyle(this.strokeSize); + this.graphics.beginStroke(this.strokeColor); + this.graphics.moveTo(path.poses[0].pose.position.x / this.scaleX, path.poses[0].pose.position.y / -this.scaleY); + for (var i=1; i<path.poses.length; ++i) { + this.graphics.lineTo(path.poses[i].pose.position.x / this.scaleX, path.poses[i].pose.position.y / -this.scaleY); + } + this.graphics.endStroke(); + } + + // create the shape + createjs.Shape.call(this, this.graphics); + }; + + /** + * Set the path to draw + * + * @param path of type nav_msgs/Path + */ + ROS2D.PathShape.prototype.setPath = function(path) { + this.graphics.clear(); + if (path !== null && typeof path !== 'undefined') { + this.graphics.setStrokeStyle(this.strokeSize); + this.graphics.beginStroke(this.strokeColor); + this.graphics.moveTo(path.poses[0].pose.position.x / this.scaleX, path.poses[0].pose.position.y / -this.scaleY); + for (var i=1; i<path.poses.length; ++i) { + this.graphics.lineTo(path.poses[i].pose.position.x / this.scaleX, path.poses[i].pose.position.y / -this.scaleY); + } + this.graphics.endStroke(); + } + }; + + ROS2D.PathShape.prototype.__proto__ = createjs.Shape.prototype; + + /** + * @fileOverview + * @author Bart van Vliet - bart@dobots.nl + */ + + /** + * A polygon that can be edited by an end user + * + * @constructor + * @param options - object with following keys: + * * pose (optional) - the first pose of the trace + * * lineSize (optional) - the width of the lines + * * lineColor (optional) - the createjs color of the lines + * * pointSize (optional) - the size of the points + * * pointColor (optional) - the createjs color of the points + * * fillColor (optional) - the createjs color to fill the polygon + * * lineCallBack (optional) - callback function for mouse interaction with a line + * * pointCallBack (optional) - callback function for mouse interaction with a point + */ + ROS2D.PolygonMarker = function(options) { + // var that = this; + options = options || {}; + this.lineSize = options.lineSize || 3; + this.lineColor = options.lineColor || createjs.Graphics.getRGB(0, 0, 255, 0.66); + this.pointSize = options.pointSize || 10; + this.pointColor = options.pointColor || createjs.Graphics.getRGB(255, 0, 0, 0.66); + this.fillColor = options.pointColor || createjs.Graphics.getRGB(0, 255, 0, 0.33); + this.lineCallBack = options.lineCallBack; + this.pointCallBack = options.pointCallBack; + + // Array of point shapes + // this.points = []; + this.pointContainer = new createjs.Container(); + + // Array of line shapes + // this.lines = []; + this.lineContainer = new createjs.Container(); + + this.fillShape = new createjs.Shape(); + + // Container with all the lines and points + createjs.Container.call(this); + + this.addChild(this.fillShape); + this.addChild(this.lineContainer); + this.addChild(this.pointContainer); + }; + + /** + * Internal use only + */ + ROS2D.PolygonMarker.prototype.createLineShape = function(startPoint, endPoint) { + var line = new createjs.Shape(); + // line.graphics.setStrokeStyle(this.strokeSize); + // line.graphics.beginStroke(this.strokeColor); + // line.graphics.moveTo(startPoint.x, startPoint.y); + // line.graphics.lineTo(endPoint.x, endPoint.y); + this.editLineShape(line, startPoint, endPoint); + + var that = this; + line.addEventListener('mousedown', function(event) { + if (that.lineCallBack !== null && typeof that.lineCallBack !== 'undefined') { + that.lineCallBack('mousedown', event, that.lineContainer.getChildIndex(event.target)); + } + }); + + return line; + }; + + /** + * Internal use only + */ + ROS2D.PolygonMarker.prototype.editLineShape = function(line, startPoint, endPoint) { + line.graphics.clear(); + line.graphics.setStrokeStyle(this.lineSize); + line.graphics.beginStroke(this.lineColor); + line.graphics.moveTo(startPoint.x, startPoint.y); + line.graphics.lineTo(endPoint.x, endPoint.y); + }; + + /** + * Internal use only + */ + ROS2D.PolygonMarker.prototype.createPointShape = function(pos) { + var point = new createjs.Shape(); + point.graphics.beginFill(this.pointColor); + point.graphics.drawCircle(0, 0, this.pointSize); + point.x = pos.x; + point.y = -pos.y; + + var that = this; + point.addEventListener('mousedown', function(event) { + if (that.pointCallBack !== null && typeof that.pointCallBack !== 'undefined') { + that.pointCallBack('mousedown', event, that.pointContainer.getChildIndex(event.target)); + } + }); + + return point; + }; + + /** + * Adds a point to the polygon + * + * @param position of type ROSLIB.Vector3 + */ + ROS2D.PolygonMarker.prototype.addPoint = function(pos) { + var point = this.createPointShape(pos); + this.pointContainer.addChild(point); + var numPoints = this.pointContainer.getNumChildren(); + + // 0 points -> 1 point, 0 lines + // 1 point -> 2 points, lines: add line between previous and new point, add line between new point and first point + // 2 points -> 3 points, 3 lines: change last line, add line between new point and first point + // 3 points -> 4 points, 4 lines: change last line, add line between new point and first point + // etc + + if (numPoints < 2) { + // Now 1 point + } + else if (numPoints < 3) { + // Now 2 points: add line between previous and new point + var line = this.createLineShape(this.pointContainer.getChildAt(numPoints-2), point); + this.lineContainer.addChild(line); + } + if (numPoints > 2) { + // Now 3 or more points: change last line + this.editLineShape(this.lineContainer.getChildAt(numPoints-2), this.pointContainer.getChildAt(numPoints-2), point); + } + if (numPoints > 1) { + // Now 2 or more points: add line between new point and first point + var lineEnd = this.createLineShape(point, this.pointContainer.getChildAt(0)); + this.lineContainer.addChild(lineEnd); + } + + this.drawFill(); + }; + + /** + * Removes a point from the polygon + * + * @param obj either an index (integer) or a point shape of the polygon + */ + ROS2D.PolygonMarker.prototype.remPoint = function(obj) { + var index; + // var point; + if (obj instanceof createjs.Shape) { + index = this.pointContainer.getChildIndex(obj); + // point = obj; + } + else { + index = obj; + // point = this.pointContainer.getChildAt(index); + } + + // 0 points -> 0 points, 0 lines + // 1 point -> 0 points, 0 lines + // 2 points -> 1 point, 0 lines: remove all lines + // 3 points -> 2 points, 2 lines: change line before point to remove, remove line after point to remove + // 4 points -> 3 points, 3 lines: change line before point to remove, remove line after point to remove + // etc + + var numPoints = this.pointContainer.getNumChildren(); + + if (numPoints < 2) { + + } + else if (numPoints < 3) { + // 2 points: remove all lines + this.lineContainer.removeAllChildren(); + } + else { + // 3 or more points: change line before point to remove, remove line after point to remove + this.editLineShape( + this.lineContainer.getChildAt((index-1+numPoints)%numPoints), + this.pointContainer.getChildAt((index-1+numPoints)%numPoints), + this.pointContainer.getChildAt((index+1)%numPoints) + ); + this.lineContainer.removeChildAt(index); + } + this.pointContainer.removeChildAt(index); + // this.points.splice(index, 1); + + this.drawFill(); + }; + + /** + * Moves a point of the polygon + * + * @param obj either an index (integer) or a point shape of the polygon + * @param position of type ROSLIB.Vector3 + */ + ROS2D.PolygonMarker.prototype.movePoint = function(obj, newPos) { + var index; + var point; + if (obj instanceof createjs.Shape) { + index = this.pointContainer.getChildIndex(obj); + point = obj; + } + else { + index = obj; + point = this.pointContainer.getChildAt(index); + } + point.x = newPos.x; + point.y = -newPos.y; + + var numPoints = this.pointContainer.getNumChildren(); + if (numPoints > 1) { + // line before moved point + var line1 = this.lineContainer.getChildAt((index-1+numPoints)%numPoints); + this.editLineShape(line1, this.pointContainer.getChildAt((index-1+numPoints)%numPoints), point); + + // line after moved point + var line2 = this.lineContainer.getChildAt(index); + this.editLineShape(line2, point, this.pointContainer.getChildAt((index+1)%numPoints)); + } + + this.drawFill(); + }; + + /** + * Splits a line of the polygon: inserts a point at the center of the line + * + * @param obj either an index (integer) or a line shape of the polygon + */ + ROS2D.PolygonMarker.prototype.splitLine = function(obj) { + var index; + var line; + if (obj instanceof createjs.Shape) { + index = this.lineContainer.getChildIndex(obj); + line = obj; + } + else { + index = obj; + line = this.lineContainer.getChildAt(index); + } + var numPoints = this.pointContainer.getNumChildren(); + var xs = this.pointContainer.getChildAt(index).x; + var ys = this.pointContainer.getChildAt(index).y; + var xe = this.pointContainer.getChildAt((index+1)%numPoints).x; + var ye = this.pointContainer.getChildAt((index+1)%numPoints).y; + var xh = (xs+xe)/2.0; + var yh = (ys+ye)/2.0; + var pos = new ROSLIB.Vector3({ x:xh, y:-yh }); + + // Add a point in the center of the line to split + var point = this.createPointShape(pos); + this.pointContainer.addChildAt(point, index+1); + ++numPoints; + + // Add a line between the new point and the end of the line to split + var lineNew = this.createLineShape(point, this.pointContainer.getChildAt((index+2)%numPoints)); + this.lineContainer.addChildAt(lineNew, index+1); + + // Set the endpoint of the line to split to the new point + this.editLineShape(line, this.pointContainer.getChildAt(index), point); + + this.drawFill(); + }; + + /** + * Internal use only + */ + ROS2D.PolygonMarker.prototype.drawFill = function() { + var numPoints = this.pointContainer.getNumChildren(); + if (numPoints > 2) { + var g = this.fillShape.graphics; + g.clear(); + g.setStrokeStyle(0); + g.moveTo(this.pointContainer.getChildAt(0).x, this.pointContainer.getChildAt(0).y); + g.beginStroke(); + g.beginFill(this.fillColor); + for (var i=1; i<numPoints; ++i) { + g.lineTo(this.pointContainer.getChildAt(i).x, this.pointContainer.getChildAt(i).y); + } + g.closePath(); + g.endFill(); + g.endStroke(); + } + else { + this.fillShape.graphics.clear(); + } + }; + + + ROS2D.PolygonMarker.prototype.__proto__ = createjs.Container.prototype; + + /** + * @fileOverview + * @author Bart van Vliet - bart@dobots.nl + */ + + /** + * A trace of poses, handy to see where a robot has been + * + * @constructor + * @param options - object with following keys: + * * pose (optional) - the first pose of the trace + * * strokeSize (optional) - the size of the outline + * * strokeColor (optional) - the createjs color for the stroke + * * maxPoses (optional) - the maximum number of poses to keep, 0 for infinite + * * minDist (optional) - the minimal distance between poses to use the pose for drawing (default 0.05) + */ + ROS2D.TraceShape = function(options) { + // var that = this; + options = options || {}; + var pose = options.pose; + this.strokeSize = options.strokeSize || 3; + this.strokeColor = options.strokeColor || createjs.Graphics.getRGB(0, 0, 0); + this.maxPoses = options.maxPoses || 100; + this.minDist = options.minDist || 0.05; + + // Store minDist as the square of it + this.minDist = this.minDist*this.minDist; + + // Array of the poses + // TODO: do we need this? + this.poses = []; + + // Create the graphics + this.graphics = new createjs.Graphics(); + this.graphics.setStrokeStyle(this.strokeSize); + this.graphics.beginStroke(this.strokeColor); + + // Add first pose if given + if (pose !== null && typeof pose !== 'undefined') { + this.poses.push(pose); + } + + // Create the shape + createjs.Shape.call(this, this.graphics); + }; + + /** + * Adds a pose to the trace and updates the graphics + * + * @param pose of type ROSLIB.Pose + */ + ROS2D.TraceShape.prototype.addPose = function(pose) { + var last = this.poses.length-1; + if (last < 0) { + this.poses.push(pose); + this.graphics.moveTo(pose.position.x / this.scaleX, pose.position.y / -this.scaleY); + } + else { + var prevX = this.poses[last].position.x; + var prevY = this.poses[last].position.y; + var dx = (pose.position.x - prevX); + var dy = (pose.position.y - prevY); + if (dx*dx + dy*dy > this.minDist) { + this.graphics.lineTo(pose.position.x / this.scaleX, pose.position.y / -this.scaleY); + this.poses.push(pose); + } + } + if (this.maxPoses > 0 && this.maxPoses < this.poses.length) { + this.popFront(); + } + }; + + /** + * Removes front pose and updates the graphics + */ + ROS2D.TraceShape.prototype.popFront = function() { + if (this.poses.length > 0) { + this.poses.shift(); + // TODO: shift drawing instructions rather than doing it all over + this.graphics.clear(); + this.graphics.setStrokeStyle(this.strokeSize); + this.graphics.beginStroke(this.strokeColor); + this.graphics.lineTo(this.poses[0].position.x / this.scaleX, this.poses[0].position.y / -this.scaleY); + for (var i=1; i<this.poses.length; ++i) { + this.graphics.lineTo(this.poses[i].position.x / this.scaleX, this.poses[i].position.y / -this.scaleY); + } + } + }; + + ROS2D.TraceShape.prototype.__proto__ = createjs.Shape.prototype; + + /** + * @fileOverview + * @author Bart van Vliet - bart@dobots.nl + */ + + /** + * Adds panning to a view + * + * @constructor + * @param options - object with following keys: + * * rootObject (optional) - the root object to apply panning to + */ + ROS2D.PanView = function(options) { + options = options || {}; + this.rootObject = options.rootObject; + + // get a handle to the stage + if (this.rootObject instanceof createjs.Stage) { + this.stage = this.rootObject; + } + else { + this.stage = this.rootObject.getStage(); + } + + this.startPos = new ROSLIB.Vector3(); + }; + + + ROS2D.PanView.prototype.startPan = function(startX, startY) { + this.startPos.x = startX; + this.startPos.y = startY; + }; + + ROS2D.PanView.prototype.pan = function(curX, curY) { + this.stage.x += curX - this.startPos.x; + this.startPos.x = curX; + this.stage.y += curY - this.startPos.y; + this.startPos.y = curY; + }; + + /** + * @fileOverview + * @author Russell Toris - rctoris@wpi.edu + */ + + /** + * A Viewer can be used to render an interactive 2D scene to a HTML5 canvas. + * + * @constructor + * @param options - object with following keys: + * * divID - the ID of the div to place the viewer in + * * width - the initial width, in pixels, of the canvas + * * height - the initial height, in pixels, of the canvas + * * background (optional) - the color to render the background, like '#efefef' + */ + ROS2D.Viewer = function(options) { + var that = this; + options = options || {}; + var divID = options.divID; + this.width = options.width; + this.height = options.height; + var background = options.background || '#111111'; + + // create the canvas to render to + var canvas = document.createElement('canvas'); + canvas.width = this.width; + canvas.height = this.height; + canvas.style.background = background; + document.getElementById(divID).appendChild(canvas); + // create the easel to use + this.scene = new createjs.Stage(canvas); + + // change Y axis center + this.scene.y = this.height; + + // add the renderer to the page + document.getElementById(divID).appendChild(canvas); + + // update at 30fps + createjs.Ticker.setFPS(30); + createjs.Ticker.addEventListener('tick', this.scene); + }; + + /** + * Add the given createjs object to the global scene in the viewer. + * + * @param object - the object to add + */ + ROS2D.Viewer.prototype.addObject = function(object) { + this.scene.addChild(object); + }; + + /** + * Scale the scene to fit the given width and height into the current canvas. + * + * @param width - the width to scale to in meters + * @param height - the height to scale to in meters + */ + ROS2D.Viewer.prototype.scaleToDimensions = function(width, height) { + // restore to values before shifting, if ocurred + this.scene.x = typeof this.scene.x_prev_shift !== 'undefined' ? this.scene.x_prev_shift : this.scene.x; + this.scene.y = typeof this.scene.y_prev_shift !== 'undefined' ? this.scene.y_prev_shift : this.scene.y; + + // save scene scaling + this.scene.scaleX = this.width / width; + this.scene.scaleY = this.height / height; + }; + + /** + * Shift the main view of the canvas by the given amount. This is based on the + * ROS coordinate system. That is, Y is opposite that of a traditional canvas. + * + * @param x - the amount to shift by in the x direction in meters + * @param y - the amount to shift by in the y direction in meters + */ + ROS2D.Viewer.prototype.shift = function(x, y) { + // save current offset + this.scene.x_prev_shift = this.scene.x; + this.scene.y_prev_shift = this.scene.y; + + // shift scene by scaling the desired offset + this.scene.x -= (x * this.scene.scaleX); + this.scene.y += (y * this.scene.scaleY); + }; + + /** + * @fileOverview + * @author Bart van Vliet - bart@dobots.nl + */ + + /** + * Adds zooming to a view + * + * @constructor + * @param options - object with following keys: + * * rootObject (optional) - the root object to apply zoom to + * * minScale (optional) - minimum scale to set to preserve precision + */ + ROS2D.ZoomView = function(options) { + options = options || {}; + this.rootObject = options.rootObject; + this.minScale = options.minScale || 0.001; + + // get a handle to the stage + if (this.rootObject instanceof createjs.Stage) { + this.stage = this.rootObject; + } + else { + this.stage = this.rootObject.getStage(); + } + + this.center = new ROSLIB.Vector3(); + this.startShift = new ROSLIB.Vector3(); + this.startScale = new ROSLIB.Vector3(); + }; + + + ROS2D.ZoomView.prototype.startZoom = function(centerX, centerY) { + this.center.x = centerX; + this.center.y = centerY; + this.startShift.x = this.stage.x; + this.startShift.y = this.stage.y; + this.startScale.x = this.stage.scaleX; + this.startScale.y = this.stage.scaleY; + }; + + ROS2D.ZoomView.prototype.zoom = function(zoom) { + // Make sure scale doesn't become too small + if (this.startScale.x*zoom < this.minScale) { + zoom = this.minScale/this.startScale.x; + } + if (this.startScale.y*zoom < this.minScale) { + zoom = this.minScale/this.startScale.y; + } + + this.stage.scaleX = this.startScale.x*zoom; + this.stage.scaleY = this.startScale.y*zoom; + + this.stage.x = this.startShift.x - (this.center.x-this.startShift.x) * (this.stage.scaleX/this.startScale.x - 1); + this.stage.y = this.startShift.y - (this.center.y-this.startShift.y) * (this.stage.scaleY/this.startScale.y - 1); + }; + \ No newline at end of file -- GitLab