2 * @author Russell Toris - rctoris@wpi.edu
9 // convert the given global Stage coordinates to ROS coordinates
10 createjs.Stage.prototype.globalToRos = function(x, y) {
11 var rosX = (x - this.x) / this.scaleX;
12 var rosY = (this.y - y) / this.scaleY;
13 return new ROSLIB.Vector3({
19 // convert the given ROS coordinates to global Stage coordinates
20 createjs.Stage.prototype.rosToGlobal = function(pos) {
21 var x = pos.x * this.scaleX + this.x;
22 var y = pos.y * this.scaleY + this.y;
29 // convert a ROS quaternion to theta in degrees
30 createjs.Stage.prototype.rosQuaternionToGlobalTheta = function(orientation) {
31 // See https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles#Rotation_matrices
32 // here we use [x y z] = R * [1 0 0]
33 var q0 = orientation.w;
34 var q1 = orientation.x;
35 var q2 = orientation.y;
36 var q3 = orientation.z;
37 // Canvas rotation is clock wise and in degrees
38 return -Math.atan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2 * q2 + q3 * q3)) * 180.0 / Math.PI;
42 * @author Russell Toris - rctoris@wpi.edu
46 * An image map is a PNG image scaled to fit to the dimensions of a OccupancyGrid.
49 * @param options - object with following keys:
50 * * message - the occupancy grid map meta data message
51 * * image - the image URL to load
53 ROS2D.ImageMap = function(options) {
54 options = options || {};
55 var message = options.message;
56 var image = options.image;
58 // save the metadata we need
59 this.pose = new ROSLIB.Pose({
60 position : message.origin.position,
61 orientation : message.origin.orientation
65 this.width = message.width;
66 this.height = message.height;
69 createjs.Bitmap.call(this, image);
71 this.y = -this.height * message.resolution;
74 this.scaleX = message.resolution;
75 this.scaleY = message.resolution;
76 this.width *= this.scaleX;
77 this.height *= this.scaleY;
80 this.x += this.pose.position.x;
81 this.y -= this.pose.position.y;
83 ROS2D.ImageMap.prototype.__proto__ = createjs.Bitmap.prototype;
86 * @author Russell Toris - rctoris@wpi.edu
90 * A image map is a PNG image scaled to fit to the dimensions of a OccupancyGrid.
92 * Emits the following events:
93 * * 'change' - there was an update or change in the map
96 * @param options - object with following keys:
97 * * ros - the ROSLIB.Ros connection handle
98 * * topic (optional) - the map meta data topic to listen to
99 * * image - the image URL to load
100 * * rootObject (optional) - the root object to add this marker to
102 ROS2D.ImageMapClient = function(options) {
104 options = options || {};
105 var ros = options.ros;
106 var topic = options.topic || '/map_metadata';
107 this.image = options.image;
108 this.rootObject = options.rootObject || new createjs.Container();
110 // create an empty shape to start with
111 this.currentImage = new createjs.Shape();
113 // subscribe to the topic
114 var rosTopic = new ROSLIB.Topic({
117 messageType : 'nav_msgs/MapMetaData'
120 rosTopic.subscribe(function(message) {
121 // we only need this once
122 rosTopic.unsubscribe();
125 that.currentImage = new ROS2D.ImageMap({
129 that.rootObject.addChild(that.currentImage);
130 // work-around for a bug in easeljs -- needs a second object to render correctly
131 that.rootObject.addChild(new ROS2D.Grid({size:1}));
136 ROS2D.ImageMapClient.prototype.__proto__ = EventEmitter2.prototype;
139 * @author Russell Toris - rctoris@wpi.edu
143 * An OccupancyGrid can convert a ROS occupancy grid message into a createjs Bitmap object.
146 * @param options - object with following keys:
147 * * message - the occupancy grid message
149 ROS2D.OccupancyGrid = function(options) {
150 options = options || {};
151 var message = options.message;
153 // internal drawing canvas
154 var canvas = document.createElement('canvas');
155 var context = canvas.getContext('2d');
157 // save the metadata we need
158 this.pose = new ROSLIB.Pose({
159 position : message.info.origin.position,
160 orientation : message.info.origin.orientation
164 this.width = message.info.width;
165 this.height = message.info.height;
166 canvas.width = this.width;
167 canvas.height = this.height;
169 var imageData = context.createImageData(this.width, this.height);
170 for ( var row = 0; row < this.height; row++) {
171 for ( var col = 0; col < this.width; col++) {
172 // determine the index into the map data
173 var mapI = col + ((this.height - row - 1) * this.width);
174 // determine the value
175 var data = message.data[mapI];
179 } else if (data === 0) {
185 // determine the index into the image data array
186 var i = (col + (row * this.width)) * 4;
188 imageData.data[i] = val;
190 imageData.data[++i] = val;
192 imageData.data[++i] = val;
194 imageData.data[++i] = 255;
197 context.putImageData(imageData, 0, 0);
200 createjs.Bitmap.call(this, canvas);
201 // change Y direction
202 this.y = -this.height * message.info.resolution;
205 this.scaleX = message.info.resolution;
206 this.scaleY = message.info.resolution;
207 this.width *= this.scaleX;
208 this.height *= this.scaleY;
211 this.x += this.pose.position.x;
212 this.y -= this.pose.position.y;
214 ROS2D.OccupancyGrid.prototype.__proto__ = createjs.Bitmap.prototype;
217 * @author Russell Toris - rctoris@wpi.edu
221 * A map that listens to a given occupancy grid topic.
223 * Emits the following events:
224 * * 'change' - there was an update or change in the map
227 * @param options - object with following keys:
228 * * ros - the ROSLIB.Ros connection handle
229 * * topic (optional) - the map topic to listen to
230 * * rootObject (optional) - the root object to add this marker to
231 * * continuous (optional) - if the map should be continuously loaded (e.g., for SLAM)
233 ROS2D.OccupancyGridClient = function(options) {
235 options = options || {};
236 var ros = options.ros;
237 var topic = options.topic || '/map';
238 this.continuous = options.continuous;
239 this.rootObject = options.rootObject || new createjs.Container();
241 // current grid that is displayed
242 // create an empty shape to start with, so that the order remains correct.
243 this.currentGrid = new createjs.Shape();
244 this.rootObject.addChild(this.currentGrid);
245 // work-around for a bug in easeljs -- needs a second object to render correctly
246 this.rootObject.addChild(new ROS2D.Grid({size:1}));
248 // subscribe to the topic
249 var rosTopic = new ROSLIB.Topic({
252 messageType : 'nav_msgs/OccupancyGrid',
256 rosTopic.subscribe(function(message) {
257 // check for an old map
259 if (that.currentGrid) {
260 index = that.rootObject.getChildIndex(that.currentGrid);
261 that.rootObject.removeChild(that.currentGrid);
264 that.currentGrid = new ROS2D.OccupancyGrid({
267 if (index !== null) {
268 that.rootObject.addChildAt(that.currentGrid, index);
271 that.rootObject.addChild(that.currentGrid);
276 // check if we should unsubscribe
277 if (!that.continuous) {
278 rosTopic.unsubscribe();
282 ROS2D.OccupancyGridClient.prototype.__proto__ = EventEmitter2.prototype;
285 * @author Jihoon Lee- jihoonlee.in@gmail.com
286 * @author Russell Toris - rctoris@wpi.edu
290 * A static map that receives from map_server.
292 * Emits the following events:
293 * * 'change' - there was an update or change in the map
296 * @param options - object with following keys:
297 * * ros - the ROSLIB.Ros connection handle
298 * * service (optional) - the map topic to listen to, like '/static_map'
299 * * rootObject (optional) - the root object to add this marker to
301 ROS2D.OccupancyGridSrvClient = function(options) {
303 options = options || {};
304 var ros = options.ros;
305 var service = options.service || '/static_map';
306 this.rootObject = options.rootObject || new createjs.Container();
308 // current grid that is displayed
309 this.currentGrid = null;
311 // Setting up to the service
312 var rosService = new ROSLIB.Service({
315 serviceType : 'nav_msgs/GetMap',
319 rosService.callService(new ROSLIB.ServiceRequest(),function(response) {
320 // check for an old map
321 if (that.currentGrid) {
322 that.rootObject.removeChild(that.currentGrid);
325 that.currentGrid = new ROS2D.OccupancyGrid({
326 message : response.map
328 that.rootObject.addChild(that.currentGrid);
330 that.emit('change', that.currentGrid);
333 ROS2D.OccupancyGridSrvClient.prototype.__proto__ = EventEmitter2.prototype;
336 * @author Bart van Vliet - bart@dobots.nl
340 * An arrow with line and triangular head, based on the navigation arrow.
341 * Aims to the left at 0 rotation, as would be expected.
344 * @param options - object with following keys:
345 * * size (optional) - the size of the marker
346 * * strokeSize (optional) - the size of the outline
347 * * strokeColor (optional) - the createjs color for the stroke
348 * * fillColor (optional) - the createjs color for the fill
349 * * pulse (optional) - if the marker should "pulse" over time
351 ROS2D.ArrowShape = function(options) {
353 options = options || {};
354 var size = options.size || 10;
355 var strokeSize = options.strokeSize || 3;
356 var strokeColor = options.strokeColor || createjs.Graphics.getRGB(0, 0, 0);
357 var fillColor = options.fillColor || createjs.Graphics.getRGB(255, 0, 0);
358 var pulse = options.pulse;
361 var graphics = new createjs.Graphics();
363 var headLen = size / 3.0;
364 var headWidth = headLen * 2.0 / 3.0;
366 graphics.setStrokeStyle(strokeSize);
367 graphics.beginStroke(strokeColor);
368 graphics.moveTo(0, 0);
369 graphics.lineTo(size-headLen, 0);
371 graphics.beginFill(fillColor);
372 graphics.moveTo(size, 0);
373 graphics.lineTo(size-headLen, headWidth / 2.0);
374 graphics.lineTo(size-headLen, -headWidth / 2.0);
375 graphics.closePath();
377 graphics.endStroke();
380 createjs.Shape.call(this, graphics);
382 // check if we are pulsing
384 // have the model "pulse"
387 createjs.Ticker.addEventListener('tick', function() {
389 that.scaleX *= 1.035;
390 that.scaleY *= 1.035;
391 growing = (++growCount < 10);
393 that.scaleX /= 1.035;
394 that.scaleY /= 1.035;
395 growing = (--growCount < 0);
400 ROS2D.ArrowShape.prototype.__proto__ = createjs.Shape.prototype;
403 * @author Raffaello Bonghi - raffaello.bonghi@officinerobotiche.it
407 * A Grid object draw in map.
410 * @param options - object with following keys:
411 * * size (optional) - the size of the grid
412 * * cellSize (optional) - the cell size of map
413 * * lineWidth (optional) - the width of the lines in the grid
415 ROS2D.Grid = function(options) {
417 options = options || {};
418 var size = options.size || 10;
419 var cellSize = options.cellSize || 0.1;
420 var lineWidth = options.lineWidth || 0.001;
422 var graphics = new createjs.Graphics();
424 graphics.setStrokeStyle(lineWidth*5);
425 graphics.beginStroke(createjs.Graphics.getRGB(0, 0, 0));
426 graphics.beginFill(createjs.Graphics.getRGB(255, 0, 0));
427 graphics.moveTo(-size*cellSize, 0);
428 graphics.lineTo(size*cellSize, 0);
429 graphics.moveTo(0, -size*cellSize);
430 graphics.lineTo(0, size*cellSize);
432 graphics.endStroke();
434 graphics.setStrokeStyle(lineWidth);
435 graphics.beginStroke(createjs.Graphics.getRGB(0, 0, 0));
436 graphics.beginFill(createjs.Graphics.getRGB(255, 0, 0));
437 for (var i = -size; i <= size; i++) {
438 graphics.moveTo(-size*cellSize, i * cellSize);
439 graphics.lineTo(size*cellSize, i * cellSize);
440 graphics.moveTo(i * cellSize, -size*cellSize);
441 graphics.lineTo(i * cellSize, size*cellSize);
444 graphics.endStroke();
446 createjs.Shape.call(this, graphics);
449 ROS2D.Grid.prototype.__proto__ = createjs.Shape.prototype;
452 * @author Russell Toris - rctoris@wpi.edu
456 * A navigation arrow is a directed triangle that can be used to display orientation.
459 * @param options - object with following keys:
460 * * size (optional) - the size of the marker
461 * * strokeSize (optional) - the size of the outline
462 * * strokeColor (optional) - the createjs color for the stroke
463 * * fillColor (optional) - the createjs color for the fill
464 * * pulse (optional) - if the marker should "pulse" over time
466 ROS2D.NavigationArrow = function(options) {
468 options = options || {};
469 var size = options.size || 10;
470 var strokeSize = options.strokeSize || 3;
471 var strokeColor = options.strokeColor || createjs.Graphics.getRGB(0, 0, 0);
472 var fillColor = options.fillColor || createjs.Graphics.getRGB(255, 0, 0);
473 var pulse = options.pulse;
476 var graphics = new createjs.Graphics();
478 graphics.setStrokeStyle(strokeSize);
479 graphics.moveTo(-size / 2.0, -size / 2.0);
480 graphics.beginStroke(strokeColor);
481 graphics.beginFill(fillColor);
482 graphics.lineTo(size, 0);
483 graphics.lineTo(-size / 2.0, size / 2.0);
484 graphics.closePath();
486 graphics.endStroke();
489 createjs.Shape.call(this, graphics);
491 // check if we are pulsing
493 // have the model "pulse"
496 createjs.Ticker.addEventListener('tick', function() {
498 that.scaleX *= 1.035;
499 that.scaleY *= 1.035;
500 growing = (++growCount < 10);
502 that.scaleX /= 1.035;
503 that.scaleY /= 1.035;
504 growing = (--growCount < 0);
509 ROS2D.NavigationArrow.prototype.__proto__ = createjs.Shape.prototype;
512 * @author Inigo Gonzalez - ingonza85@gmail.com
516 * A navigation image that can be used to display orientation.
519 * @param options - object with following keys:
520 * * size (optional) - the size of the marker
521 * * image - the image to use as a marker
522 * * pulse (optional) - if the marker should "pulse" over time
524 ROS2D.NavigationImage = function(options) {
526 options = options || {};
527 var size = options.size || 10;
528 var image_url = options.image;
529 var pulse = options.pulse;
530 var alpha = options.alpha || 1;
534 var paintImage = function(){
535 createjs.Bitmap.call(that, image);
536 var scale = calculateScale(size);
540 that.regY = that.image.height/2;
541 that.regX = that.image.width/2;
542 originals['rotation'] = that.rotation;
543 Object.defineProperty( that, 'rotation', {
544 get: function(){ return originals['rotation'] + 90; },
545 set: function(value){ originals['rotation'] = value; }
548 // have the model "pulse"
551 var SCALE_SIZE = 1.020;
552 createjs.Ticker.addEventListener('tick', function() {
554 that.scaleX *= SCALE_SIZE;
555 that.scaleY *= SCALE_SIZE;
556 growing = (++growCount < 10);
558 that.scaleX /= SCALE_SIZE;
559 that.scaleY /= SCALE_SIZE;
560 growing = (--growCount < 0);
566 var calculateScale = function(_size){
567 return _size / image.width;
570 var image = new Image();
571 image.onload = paintImage;
572 image.src = image_url;
576 ROS2D.NavigationImage.prototype.__proto__ = createjs.Bitmap.prototype;
579 * @author Bart van Vliet - bart@dobots.nl
583 * A shape to draw a nav_msgs/Path msg
586 * @param options - object with following keys:
587 * * path (optional) - the initial path to draw
588 * * strokeSize (optional) - the size of the outline
589 * * strokeColor (optional) - the createjs color for the stroke
591 ROS2D.PathShape = function(options) {
592 options = options || {};
593 var path = options.path;
594 this.strokeSize = options.strokeSize || 3;
595 this.strokeColor = options.strokeColor || createjs.Graphics.getRGB(0, 0, 0);
598 this.graphics = new createjs.Graphics();
600 if (path !== null && typeof path !== 'undefined') {
601 this.graphics.setStrokeStyle(this.strokeSize);
602 this.graphics.beginStroke(this.strokeColor);
603 this.graphics.moveTo(path.poses[0].pose.position.x / this.scaleX, path.poses[0].pose.position.y / -this.scaleY);
604 for (var i=1; i<path.poses.length; ++i) {
605 this.graphics.lineTo(path.poses[i].pose.position.x / this.scaleX, path.poses[i].pose.position.y / -this.scaleY);
607 this.graphics.endStroke();
611 createjs.Shape.call(this, this.graphics);
615 * Set the path to draw
617 * @param path of type nav_msgs/Path
619 ROS2D.PathShape.prototype.setPath = function(path) {
620 this.graphics.clear();
621 if (path !== null && typeof path !== 'undefined') {
622 this.graphics.setStrokeStyle(this.strokeSize);
623 this.graphics.beginStroke(this.strokeColor);
624 this.graphics.moveTo(path.poses[0].pose.position.x / this.scaleX, path.poses[0].pose.position.y / -this.scaleY);
625 for (var i=1; i<path.poses.length; ++i) {
626 this.graphics.lineTo(path.poses[i].pose.position.x / this.scaleX, path.poses[i].pose.position.y / -this.scaleY);
628 this.graphics.endStroke();
632 ROS2D.PathShape.prototype.__proto__ = createjs.Shape.prototype;
635 * @author Bart van Vliet - bart@dobots.nl
639 * A polygon that can be edited by an end user
642 * @param options - object with following keys:
643 * * pose (optional) - the first pose of the trace
644 * * lineSize (optional) - the width of the lines
645 * * lineColor (optional) - the createjs color of the lines
646 * * pointSize (optional) - the size of the points
647 * * pointColor (optional) - the createjs color of the points
648 * * fillColor (optional) - the createjs color to fill the polygon
649 * * lineCallBack (optional) - callback function for mouse interaction with a line
650 * * pointCallBack (optional) - callback function for mouse interaction with a point
652 ROS2D.PolygonMarker = function(options) {
654 options = options || {};
655 this.lineSize = options.lineSize || 3;
656 this.lineColor = options.lineColor || createjs.Graphics.getRGB(0, 0, 255, 0.66);
657 this.pointSize = options.pointSize || 10;
658 this.pointColor = options.pointColor || createjs.Graphics.getRGB(255, 0, 0, 0.66);
659 this.fillColor = options.pointColor || createjs.Graphics.getRGB(0, 255, 0, 0.33);
660 this.lineCallBack = options.lineCallBack;
661 this.pointCallBack = options.pointCallBack;
663 // Array of point shapes
665 this.pointContainer = new createjs.Container();
667 // Array of line shapes
669 this.lineContainer = new createjs.Container();
671 this.fillShape = new createjs.Shape();
673 // Container with all the lines and points
674 createjs.Container.call(this);
676 this.addChild(this.fillShape);
677 this.addChild(this.lineContainer);
678 this.addChild(this.pointContainer);
684 ROS2D.PolygonMarker.prototype.createLineShape = function(startPoint, endPoint) {
685 var line = new createjs.Shape();
686 // line.graphics.setStrokeStyle(this.strokeSize);
687 // line.graphics.beginStroke(this.strokeColor);
688 // line.graphics.moveTo(startPoint.x, startPoint.y);
689 // line.graphics.lineTo(endPoint.x, endPoint.y);
690 this.editLineShape(line, startPoint, endPoint);
693 line.addEventListener('mousedown', function(event) {
694 if (that.lineCallBack !== null && typeof that.lineCallBack !== 'undefined') {
695 that.lineCallBack('mousedown', event, that.lineContainer.getChildIndex(event.target));
705 ROS2D.PolygonMarker.prototype.editLineShape = function(line, startPoint, endPoint) {
706 line.graphics.clear();
707 line.graphics.setStrokeStyle(this.lineSize);
708 line.graphics.beginStroke(this.lineColor);
709 line.graphics.moveTo(startPoint.x, startPoint.y);
710 line.graphics.lineTo(endPoint.x, endPoint.y);
716 ROS2D.PolygonMarker.prototype.createPointShape = function(pos) {
717 var point = new createjs.Shape();
718 point.graphics.beginFill(this.pointColor);
719 point.graphics.drawCircle(0, 0, this.pointSize);
724 point.addEventListener('mousedown', function(event) {
725 if (that.pointCallBack !== null && typeof that.pointCallBack !== 'undefined') {
726 that.pointCallBack('mousedown', event, that.pointContainer.getChildIndex(event.target));
734 * Adds a point to the polygon
736 * @param position of type ROSLIB.Vector3
738 ROS2D.PolygonMarker.prototype.addPoint = function(pos) {
739 var point = this.createPointShape(pos);
740 this.pointContainer.addChild(point);
741 var numPoints = this.pointContainer.getNumChildren();
743 // 0 points -> 1 point, 0 lines
744 // 1 point -> 2 points, lines: add line between previous and new point, add line between new point and first point
745 // 2 points -> 3 points, 3 lines: change last line, add line between new point and first point
746 // 3 points -> 4 points, 4 lines: change last line, add line between new point and first point
752 else if (numPoints < 3) {
753 // Now 2 points: add line between previous and new point
754 var line = this.createLineShape(this.pointContainer.getChildAt(numPoints-2), point);
755 this.lineContainer.addChild(line);
758 // Now 3 or more points: change last line
759 this.editLineShape(this.lineContainer.getChildAt(numPoints-2), this.pointContainer.getChildAt(numPoints-2), point);
762 // Now 2 or more points: add line between new point and first point
763 var lineEnd = this.createLineShape(point, this.pointContainer.getChildAt(0));
764 this.lineContainer.addChild(lineEnd);
771 * Removes a point from the polygon
773 * @param obj either an index (integer) or a point shape of the polygon
775 ROS2D.PolygonMarker.prototype.remPoint = function(obj) {
778 if (obj instanceof createjs.Shape) {
779 index = this.pointContainer.getChildIndex(obj);
784 // point = this.pointContainer.getChildAt(index);
787 // 0 points -> 0 points, 0 lines
788 // 1 point -> 0 points, 0 lines
789 // 2 points -> 1 point, 0 lines: remove all lines
790 // 3 points -> 2 points, 2 lines: change line before point to remove, remove line after point to remove
791 // 4 points -> 3 points, 3 lines: change line before point to remove, remove line after point to remove
794 var numPoints = this.pointContainer.getNumChildren();
799 else if (numPoints < 3) {
800 // 2 points: remove all lines
801 this.lineContainer.removeAllChildren();
804 // 3 or more points: change line before point to remove, remove line after point to remove
806 this.lineContainer.getChildAt((index-1+numPoints)%numPoints),
807 this.pointContainer.getChildAt((index-1+numPoints)%numPoints),
808 this.pointContainer.getChildAt((index+1)%numPoints)
810 this.lineContainer.removeChildAt(index);
812 this.pointContainer.removeChildAt(index);
813 // this.points.splice(index, 1);
819 * Moves a point of the polygon
821 * @param obj either an index (integer) or a point shape of the polygon
822 * @param position of type ROSLIB.Vector3
824 ROS2D.PolygonMarker.prototype.movePoint = function(obj, newPos) {
827 if (obj instanceof createjs.Shape) {
828 index = this.pointContainer.getChildIndex(obj);
833 point = this.pointContainer.getChildAt(index);
838 var numPoints = this.pointContainer.getNumChildren();
840 // line before moved point
841 var line1 = this.lineContainer.getChildAt((index-1+numPoints)%numPoints);
842 this.editLineShape(line1, this.pointContainer.getChildAt((index-1+numPoints)%numPoints), point);
844 // line after moved point
845 var line2 = this.lineContainer.getChildAt(index);
846 this.editLineShape(line2, point, this.pointContainer.getChildAt((index+1)%numPoints));
853 * Splits a line of the polygon: inserts a point at the center of the line
855 * @param obj either an index (integer) or a line shape of the polygon
857 ROS2D.PolygonMarker.prototype.splitLine = function(obj) {
860 if (obj instanceof createjs.Shape) {
861 index = this.lineContainer.getChildIndex(obj);
866 line = this.lineContainer.getChildAt(index);
868 var numPoints = this.pointContainer.getNumChildren();
869 var xs = this.pointContainer.getChildAt(index).x;
870 var ys = this.pointContainer.getChildAt(index).y;
871 var xe = this.pointContainer.getChildAt((index+1)%numPoints).x;
872 var ye = this.pointContainer.getChildAt((index+1)%numPoints).y;
873 var xh = (xs+xe)/2.0;
874 var yh = (ys+ye)/2.0;
875 var pos = new ROSLIB.Vector3({ x:xh, y:-yh });
877 // Add a point in the center of the line to split
878 var point = this.createPointShape(pos);
879 this.pointContainer.addChildAt(point, index+1);
882 // Add a line between the new point and the end of the line to split
883 var lineNew = this.createLineShape(point, this.pointContainer.getChildAt((index+2)%numPoints));
884 this.lineContainer.addChildAt(lineNew, index+1);
886 // Set the endpoint of the line to split to the new point
887 this.editLineShape(line, this.pointContainer.getChildAt(index), point);
895 ROS2D.PolygonMarker.prototype.drawFill = function() {
896 var numPoints = this.pointContainer.getNumChildren();
898 var g = this.fillShape.graphics;
901 g.moveTo(this.pointContainer.getChildAt(0).x, this.pointContainer.getChildAt(0).y);
903 g.beginFill(this.fillColor);
904 for (var i=1; i<numPoints; ++i) {
905 g.lineTo(this.pointContainer.getChildAt(i).x, this.pointContainer.getChildAt(i).y);
912 this.fillShape.graphics.clear();
917 ROS2D.PolygonMarker.prototype.__proto__ = createjs.Container.prototype;
920 * @author Bart van Vliet - bart@dobots.nl
924 * A trace of poses, handy to see where a robot has been
927 * @param options - object with following keys:
928 * * pose (optional) - the first pose of the trace
929 * * strokeSize (optional) - the size of the outline
930 * * strokeColor (optional) - the createjs color for the stroke
931 * * maxPoses (optional) - the maximum number of poses to keep, 0 for infinite
932 * * minDist (optional) - the minimal distance between poses to use the pose for drawing (default 0.05)
934 ROS2D.TraceShape = function(options) {
936 options = options || {};
937 var pose = options.pose;
938 this.strokeSize = options.strokeSize || 3;
939 this.strokeColor = options.strokeColor || createjs.Graphics.getRGB(0, 0, 0);
940 this.maxPoses = options.maxPoses || 100;
941 this.minDist = options.minDist || 0.05;
943 // Store minDist as the square of it
944 this.minDist = this.minDist*this.minDist;
946 // Array of the poses
947 // TODO: do we need this?
950 // Create the graphics
951 this.graphics = new createjs.Graphics();
952 this.graphics.setStrokeStyle(this.strokeSize);
953 this.graphics.beginStroke(this.strokeColor);
955 // Add first pose if given
956 if (pose !== null && typeof pose !== 'undefined') {
957 this.poses.push(pose);
961 createjs.Shape.call(this, this.graphics);
965 * Adds a pose to the trace and updates the graphics
967 * @param pose of type ROSLIB.Pose
969 ROS2D.TraceShape.prototype.addPose = function(pose) {
970 var last = this.poses.length-1;
972 this.poses.push(pose);
973 this.graphics.moveTo(pose.position.x / this.scaleX, pose.position.y / -this.scaleY);
976 var prevX = this.poses[last].position.x;
977 var prevY = this.poses[last].position.y;
978 var dx = (pose.position.x - prevX);
979 var dy = (pose.position.y - prevY);
980 if (dx*dx + dy*dy > this.minDist) {
981 this.graphics.lineTo(pose.position.x / this.scaleX, pose.position.y / -this.scaleY);
982 this.poses.push(pose);
985 if (this.maxPoses > 0 && this.maxPoses < this.poses.length) {
991 * Removes front pose and updates the graphics
993 ROS2D.TraceShape.prototype.popFront = function() {
994 if (this.poses.length > 0) {
996 // TODO: shift drawing instructions rather than doing it all over
997 this.graphics.clear();
998 this.graphics.setStrokeStyle(this.strokeSize);
999 this.graphics.beginStroke(this.strokeColor);
1000 this.graphics.lineTo(this.poses[0].position.x / this.scaleX, this.poses[0].position.y / -this.scaleY);
1001 for (var i=1; i<this.poses.length; ++i) {
1002 this.graphics.lineTo(this.poses[i].position.x / this.scaleX, this.poses[i].position.y / -this.scaleY);
1007 ROS2D.TraceShape.prototype.__proto__ = createjs.Shape.prototype;
1010 * @author Bart van Vliet - bart@dobots.nl
1014 * Adds panning to a view
1017 * @param options - object with following keys:
1018 * * rootObject (optional) - the root object to apply panning to
1020 ROS2D.PanView = function(options) {
1021 options = options || {};
1022 this.rootObject = options.rootObject;
1024 // get a handle to the stage
1025 if (this.rootObject instanceof createjs.Stage) {
1026 this.stage = this.rootObject;
1029 this.stage = this.rootObject.getStage();
1032 this.startPos = new ROSLIB.Vector3();
1036 ROS2D.PanView.prototype.startPan = function(startX, startY) {
1037 this.startPos.x = startX;
1038 this.startPos.y = startY;
1041 ROS2D.PanView.prototype.pan = function(curX, curY) {
1042 this.stage.x += curX - this.startPos.x;
1043 this.startPos.x = curX;
1044 this.stage.y += curY - this.startPos.y;
1045 this.startPos.y = curY;
1049 * @author Russell Toris - rctoris@wpi.edu
1053 * A Viewer can be used to render an interactive 2D scene to a HTML5 canvas.
1056 * @param options - object with following keys:
1057 * * divID - the ID of the div to place the viewer in
1058 * * width - the initial width, in pixels, of the canvas
1059 * * height - the initial height, in pixels, of the canvas
1060 * * background (optional) - the color to render the background, like '#efefef'
1062 ROS2D.Viewer = function(options) {
1064 options = options || {};
1065 var divID = options.divID;
1066 this.width = options.width;
1067 this.height = options.height;
1068 var background = options.background || '#111111';
1070 // create the canvas to render to
1071 var canvas = document.createElement('canvas');
1072 canvas.width = this.width;
1073 canvas.height = this.height;
1074 canvas.style.background = background;
1075 document.getElementById(divID).appendChild(canvas);
1076 // create the easel to use
1077 this.scene = new createjs.Stage(canvas);
1079 // change Y axis center
1080 this.scene.y = this.height;
1082 // add the renderer to the page
1083 document.getElementById(divID).appendChild(canvas);
1086 createjs.Ticker.setFPS(30);
1087 createjs.Ticker.addEventListener('tick', this.scene);
1091 * Add the given createjs object to the global scene in the viewer.
1093 * @param object - the object to add
1095 ROS2D.Viewer.prototype.addObject = function(object) {
1096 this.scene.addChild(object);
1100 * Scale the scene to fit the given width and height into the current canvas.
1102 * @param width - the width to scale to in meters
1103 * @param height - the height to scale to in meters
1105 ROS2D.Viewer.prototype.scaleToDimensions = function(width, height) {
1106 // restore to values before shifting, if ocurred
1107 this.scene.x = typeof this.scene.x_prev_shift !== 'undefined' ? this.scene.x_prev_shift : this.scene.x;
1108 this.scene.y = typeof this.scene.y_prev_shift !== 'undefined' ? this.scene.y_prev_shift : this.scene.y;
1110 // save scene scaling
1111 this.scene.scaleX = this.width / width;
1112 this.scene.scaleY = this.height / height;
1116 * Shift the main view of the canvas by the given amount. This is based on the
1117 * ROS coordinate system. That is, Y is opposite that of a traditional canvas.
1119 * @param x - the amount to shift by in the x direction in meters
1120 * @param y - the amount to shift by in the y direction in meters
1122 ROS2D.Viewer.prototype.shift = function(x, y) {
1123 // save current offset
1124 this.scene.x_prev_shift = this.scene.x;
1125 this.scene.y_prev_shift = this.scene.y;
1127 // shift scene by scaling the desired offset
1128 this.scene.x -= (x * this.scene.scaleX);
1129 this.scene.y += (y * this.scene.scaleY);
1133 * @author Bart van Vliet - bart@dobots.nl
1137 * Adds zooming to a view
1140 * @param options - object with following keys:
1141 * * rootObject (optional) - the root object to apply zoom to
1142 * * minScale (optional) - minimum scale to set to preserve precision
1144 ROS2D.ZoomView = function(options) {
1145 options = options || {};
1146 this.rootObject = options.rootObject;
1147 this.minScale = options.minScale || 0.001;
1149 // get a handle to the stage
1150 if (this.rootObject instanceof createjs.Stage) {
1151 this.stage = this.rootObject;
1154 this.stage = this.rootObject.getStage();
1157 this.center = new ROSLIB.Vector3();
1158 this.startShift = new ROSLIB.Vector3();
1159 this.startScale = new ROSLIB.Vector3();
1163 ROS2D.ZoomView.prototype.startZoom = function(centerX, centerY) {
1164 this.center.x = centerX;
1165 this.center.y = centerY;
1166 this.startShift.x = this.stage.x;
1167 this.startShift.y = this.stage.y;
1168 this.startScale.x = this.stage.scaleX;
1169 this.startScale.y = this.stage.scaleY;
1172 ROS2D.ZoomView.prototype.zoom = function(zoom) {
1173 // Make sure scale doesn't become too small
1174 if (this.startScale.x*zoom < this.minScale) {
1175 zoom = this.minScale/this.startScale.x;
1177 if (this.startScale.y*zoom < this.minScale) {
1178 zoom = this.minScale/this.startScale.y;
1181 this.stage.scaleX = this.startScale.x*zoom;
1182 this.stage.scaleY = this.startScale.y*zoom;
1184 this.stage.x = this.startShift.x - (this.center.x-this.startShift.x) * (this.stage.scaleX/this.startScale.x - 1);
1185 this.stage.y = this.startShift.y - (this.center.y-this.startShift.y) * (this.stage.scaleY/this.startScale.y - 1);