Source: adjust_event.js

/** @file This file contains the functions to adjust an existing polygon. */
/**
 * Creates the adjusting event
 * @constructor

 
 * @param {string} dom_attach - The html element where the polygon lives
 * @param {array} x - The x coordinates for the polygon points
 * @param {array} y - The y coordinates for the polygon points
 * @param {string} obj_name - The name of the adjusted_polygon
 * @param {function} ExitFunction - the_function to execute once adjusting is done
 * @param {float} scale - Scaling factor for polygon points
*/
function AdjustEvent(dom_attach,x,y,obj_name,ExitFunction,scale) {

  /****************** Private variables ************************/

  // ID of DOM element to attach to:
  this.dom_attach = dom_attach;
  this.scale_button_pressed = false;
  // Polygon:
  this.x = x;
  this.y = y;

  // Object name:
  this.obj_name = obj_name;

  // Function to call when event is finished:
  this.ExitFunction = ExitFunction;

  // Scaling factor for polygon points:
  this.scale = scale;

  // Boolean indicating whether a control point has been edited:
  this.editedControlPoints = false;

  // Boolean indicating whether a control point is being edited:
  this.isEditingControlPoint = false;

// Boolean indicating whether a scaling point is being edited:
  this.isEditingScalingPoint = false;

  // Boolean indicating whether the center of mass of the polygon is being 
  // adjusted:
  this.isMovingCenterOfMass = false;

  // Index into which control point has been selected:
  this.selectedControlPoint;

  // Index into which scaling point has been selected:
  this.selectedScalingPoint;

  // Location of center of mass:
  this.center_x;
  this.center_y;

  // Element ids of drawn control points:
  this.control_ids = null;

  this.scalepoints_ids = null;

  // Element id of drawn center point:
  this.center_id = null;

  // ID of drawn polygon:
  this.polygon_id;

  /****************** Public functions ************************/

  /** This function starts the adjusting event: */
  this.StartEvent = function() {
    console.log('LabelMe: Starting adjust event...');

    // Draw polygon:
    this.polygon_id = this.DrawPolygon(this.dom_attach,this.x,this.y,this.obj_name,this.scale);
    select_anno.polygon_id = this.polygon_id;
    FillPolygon(this.polygon_id);
    
    // Show control points:
    this.ShowControlPoints();
    // Show center of mass:
    this.ShowCenterOfMass();
    
    // Set mousedown action to stop adjust event when user clicks on canvas:

    $('#'+this.dom_attach).unbind();
    $('#'+this.dom_attach).mousedown({obj: this},function(e) {
      return e.data.obj.StopAdjustEvent();
      });
    $(window).keydown({obj: this}, function (e){
      if (!e.data.obj.scale_button_pressed && e.keyCode == 17 && !e.data.obj.isEditingControlPoint){
        e.data.obj.RemoveScalingPoints();
        e.data.obj.RemoveControlPoints();
        e.data.obj.RemoveCenterOfMass();
        e.data.obj.ShowScalingPoints();
        e.data.obj.scale_button_pressed = true;
      }
      
    });
    $(window).keyup({obj: this}, function (e){
      if (e.keyCode == 17 && !e.data.obj.isEditingControlPoint){
      e.data.obj.scale_button_pressed = false;
      e.data.obj.RemoveScalingPoints();
      e.data.obj.RemoveControlPoints();
      e.data.obj.RemoveCenterOfMass();
      e.data.obj.ShowControlPoints();
      e.data.obj.ShowCenterOfMass();
      }
    });
  };
  
  /** This function stops the adjusting event and calls the ExitFunction: */
  this.StopAdjustEvent = function() {
    // Remove polygon:
    $('#'+this.polygon_id).remove();

    // Remove key press action
    $(window).unbind("keydown");
    $(window).unbind("keyup");
    // Remove control points and center of mass point: 
    this.RemoveControlPoints();
    this.RemoveCenterOfMass();
    this.RemoveScalingPoints();
    console.log('LabelMe: Stopped adjust event.');
    
    // Call exit function:
    this.ExitFunction(this.x,this.y,this.editedControlPoints);
  };

  /** This function shows the scaling points for a polygon */
  this.ShowScalingPoints = function (){
    if(!this.scalepoints_ids) this.scalepoints_ids = new Array();
    for (var i = 0; i < this.x.length; i++){
      this.scalepoints_ids.push(DrawPoint(this.dom_attach,this.x[i],this.y[i],'r="5" fill="#0000ff" stroke="#ffffff" stroke-width="2.5"',this.scale));
    }
    for (var i = 0; i < this.scalepoints_ids.length; i++) $('#'+this.scalepoints_ids[i]).mousedown({obj: this,point: i},function(e) {
    return e.data.obj.StartMoveScalingPoint(e.data.point);
  });

  }

  /** This function removes the displayed scaling points for a polygon */
  this.RemoveScalingPoints = function (){
    if(this.scalepoints_ids) {
      for(var i = 0; i < this.scalepoints_ids.length; i++) $('#'+this.scalepoints_ids[i]).remove();
      this.scalepoints_ids = null;
    }
  }

  /** This function shows the control points for a polygon */
  this.ShowControlPoints = function() {
    if(!this.control_ids) this.control_ids = new Array();
    for(var i = 0; i < this.x.length; i++) {
      // Draw control point:
      this.control_ids.push(DrawPoint(this.dom_attach,this.x[i],this.y[i],'r="5" fill="#00ff00" stroke="#ffffff" stroke-width="2.5"',this.scale));
      
      // Set action:
      $('#'+this.control_ids[i]).mousedown({obj: this,point: i},function(e) {
         return e.data.obj.StartMoveControlPoint(e.data.point);
      });
    }
  };

  /** This function removes the displayed control points for a polygon */
  this.RemoveControlPoints = function() {
    if(this.control_ids) {
      for(var i = 0; i < this.control_ids.length; i++) $('#'+this.control_ids[i]).remove();
      this.control_ids = null;
    }
  };

  /** This function shows the middle grab point for a polygon. */
  this.ShowCenterOfMass = function() {
    var MarkerSize = 8;
    if(this.x.length==1) MarkerSize = 6;
    
    // Get center point for polygon:
    this.CenterOfMass(this.x,this.y);
    
    // Draw center point:
    this.center_id = DrawPoint(this.dom_attach,this.center_x,this.center_y,'r="' + MarkerSize + '" fill="red" stroke="#ffffff" stroke-width="' + MarkerSize/2 + '"',this.scale);
    
    // Set action:
    $('#'+this.center_id).mousedown({obj: this},function(e) {
       return e.data.obj.StartMoveCenterOfMass();
      });
  };

  /** This function removes the middle grab point for a polygon */
  this.RemoveCenterOfMass = function() {
    if(this.center_id) {
      $('#'+this.center_id).remove();
      this.center_id = null;
    }
  };
  

  /** This function is called when one scaling point is clicked
   * It prepares the polygon for scaling.
   * @param {int} i - the index of the scaling point being modified
  */
  this.StartMoveScalingPoint = function(i) {
    if(!this.isEditingScalingPoint) {
      $('#'+this.dom_attach).unbind();
      $('#'+this.dom_attach).mousemove({obj: this},function(e) {
      return e.data.obj.MoveScalingPoint(e.originalEvent);
    });
      $('#body').mouseup({obj: this},function(e) {
        return e.data.obj.StopMoveScalingPoint(e.originalEvent);
      });      
      this.selectedScalingPoint = i;
      this.isEditingScalingPoint = true;
      this.editedControlPoints = true;
    }
  };
  /** This function is called when one scaling point is being moved
   * It computes the position of the scaling point in relation to the polygon's center of mass
   * and resizes the polygon accordingly
   * @param {event} event - Indicates a point is being moved and the index of such point
  */
  this.MoveScalingPoint = function(event) {
    var x = GetEventPosX(event);
    var y = GetEventPosY(event);
    if(this.isEditingScalingPoint && this.scale_button_pressed) {
      var origx, origy, pointx, pointy, prx, pry;
      pointx = this.x[this.selectedScalingPoint];
      pointy = this.y[this.selectedScalingPoint];
      this.CenterOfMass(this.x,this.y);
      var sx = pointx - this.center_x;
      var sy = pointy - this.center_y;
      if (sx < 0) origx = Math.max.apply(Math, this.x);
      else origx = Math.min.apply(Math, this.x);
      if (sy < 0) origy = Math.max.apply(Math, this.y);
      else origy = Math.min.apply(Math, this.y);
      prx = (x-origx)/(pointx-origx);
      pry = (y-origy)/(pointy-origy);

      pry = prx;
      if (prx <= 0 || pry  <= 0 ) return;
      for (var i = 0; i < this.x.length; i++){
      // Set point:
        var dx = (this.x[i] - origx)*prx;
        var dy = (this.y[i] - origy)*pry;
        x = origx + dx;
        y = origy + dy;
        this.x[i] = Math.max(Math.min((x/this.scale),main_media.width_orig),1);
        this.y[i] = Math.max(Math.min((y/this.scale),main_media.height_orig),1);
      }
      // Remove polygon and redraw:
      $('#'+this.polygon_id).parent().remove();
      $('#'+this.polygon_id).remove();
      this.polygon_id = this.DrawPolygon(this.dom_attach,this.x,this.y,this.obj_name,this.scale);
      select_anno.polygon_id = this.polygon_id;
      // Adjust control points:
      this.RemoveScalingPoints();
      this.ShowScalingPoints();
    }
  };

  /** This function is called when one scaling point stops being moved
   * It updates the xml with the new coordinates of the polygon.
   * @param {event} event - Indicates a point is being moved and the index of such point
   */
  this.StopMoveScalingPoint = function(event) {
    console.log('Moving scaling point');
    if(this.isEditingScalingPoint) {
      this.MoveScalingPoint(event);
      FillPolygon(this.polygon_id);
      this.isEditingScalingPoint = false;

      select_anno.pts_x = this.x;
      select_anno.pts_y = this.y;
      if (video_mode) main_media.UpdateObjectPosition(select_anno);
      // Set action:
      $('#'+this.dom_attach).unbind();
      $('#'+this.dom_attach).mousedown({obj: this},function(e) {
        return e.data.obj.StopAdjustEvent();
      });

    }
  };

  /** This function is called when one control point is clicked
   * @param {int} i - the index of the control point being modified
  */  

  this.StartMoveControlPoint = function(i) {
    if(!this.isEditingControlPoint) {
      $('#'+this.dom_attach).unbind();
      $('#'+this.dom_attach).mousemove({obj: this},function(e) {
      return e.data.obj.MoveControlPoint(e.originalEvent);
    });
      $('#body').mouseup({obj: this},function(e) {
        return e.data.obj.StopMoveControlPoint(e.originalEvent);
      });      

      this.RemoveCenterOfMass();
      this.selectedControlPoint = i;
      
      this.isEditingControlPoint = true;
      this.editedControlPoints = true;
    }
  };

  /** This function is called when one control point is being moved
   * @param {event} event - Indicates a point is being moved and the index of such point
  */
  this.MoveControlPoint = function(event) {
    if(this.isEditingControlPoint) {
      var x = GetEventPosX(event);
      var y = GetEventPosY(event);
      
      // Set point:
      this.x[this.selectedControlPoint] = Math.max(Math.min(Math.round(x/this.scale),main_media.width_orig),1);
      this.y[this.selectedControlPoint] = Math.max(Math.min(Math.round(y/this.scale),main_media.height_orig),1);

      this.originalx = this.x;
      this.originaly = this.y;
      
      // Remove polygon and redraw:
      $('#'+this.polygon_id).parent().remove();
      $('#'+this.polygon_id).remove();
      this.polygon_id = this.DrawPolygon(this.dom_attach,this.x,this.y,this.obj_name,this.scale);
      select_anno.polygon_id = this.polygon_id;
      // Adjust control points:
      this.RemoveControlPoints();
      this.ShowControlPoints();
    }
  };

  /** This function is called when one control point stops being moved
   * It updates the xml with the new coordinates of the polygon.
   * @param {event} event - Indicates a point is being moved and the index of such point
   */
  this.StopMoveControlPoint = function(event) {
    console.log('Moving control point');
    if(this.isEditingControlPoint) {
      this.MoveControlPoint(event);
      FillPolygon(this.polygon_id);
      this.ShowCenterOfMass();
      this.isEditingControlPoint = false;

      select_anno.pts_x = this.x;
      select_anno.pts_y = this.y;
      if (video_mode) main_media.UpdateObjectPosition(select_anno);
      // Set action:
      $('#'+this.dom_attach).unbind();
      $('#'+this.dom_attach).mousedown({obj: this},function(e) {
        return e.data.obj.StopAdjustEvent();
      });

    }
  };

  /** This function is called when the middle grab point is clicked
   * It prepares the polygon for moving.
  */
  this.StartMoveCenterOfMass = function() {
    if(!this.isMovingCenterOfMass) {
      $('#'+this.dom_attach).unbind();
      $('#'+this.dom_attach).mousemove({obj: this},function(e) {
        return e.data.obj.MoveCenterOfMass(e.originalEvent);
      });
      $('#body').mouseup({obj: this},function(e) {
        return e.data.obj.StopMoveCenterOfMass(e.originalEvent);
      });

      this.RemoveControlPoints();
      
      this.isMovingCenterOfMass = true;
      this.editedControlPoints = true;
    }
  };

  /** This function is called when the middle grab point is being moved
   * @param {event} event - Indicates the middle grab point is moving
   * It modifies the control points to be consistent with the polygon shift
  */
  this.MoveCenterOfMass = function(event) {
    if(this.isMovingCenterOfMass) {
      var x = GetEventPosX(event);
      var y = GetEventPosY(event);
      
      // Get displacement:
      var dx = Math.round(x/this.scale)-this.center_x;
      var dy = Math.round(y/this.scale)-this.center_y;
      
      // Adjust dx,dy to make sure we don't go outside of the image:
      for(var i = 0; i < this.x.length; i++) {
        dx = Math.max(this.x[i]+dx,1)-this.x[i];
        dy = Math.max(this.y[i]+dy,1)-this.y[i];
        dx = Math.min(this.x[i]+dx,main_media.width_orig)-this.x[i];
        dy = Math.min(this.y[i]+dy,main_media.height_orig)-this.y[i];
      }
      // Adjust polygon and center point:
      for(var i = 0; i < this.x.length; i++) {
        this.x[i] = Math.round(this.x[i]+dx);
        this.y[i] = Math.round(this.y[i]+dy);
      }
      this.center_x = Math.round(this.scale*(dx+this.center_x));
      this.center_y = Math.round(this.scale*(dy+this.center_y));
      
      // Remove polygon and redraw:
      $('#'+this.polygon_id).parent().remove();
      $('#'+this.polygon_id).remove();
      this.polygon_id = this.DrawPolygon(this.dom_attach,this.x,this.y,this.obj_name,this.scale);
      select_anno.polygon_id = this.polygon_id;

      // Redraw center of mass:
      this.RemoveCenterOfMass();
      this.ShowCenterOfMass();
    }
  };


  /** This function is called when the middle grab point stops being moved
   * It updates the xml with the new coordinates of the polygon.
   * @param {event} event - Indicates the middle grab point is being moved and the index of such point
   */
  this.StopMoveCenterOfMass = function(event) {
    if(this.isMovingCenterOfMass) {
      // Move to final position:
      this.MoveCenterOfMass(event);
      
      // Refresh control points:
      this.RemoveControlPoints();
      this.RemoveCenterOfMass();
      this.ShowControlPoints();
      this.ShowCenterOfMass();

      FillPolygon(this.polygon_id);
      this.isMovingCenterOfMass = false;

      select_anno.pts_x = this.x;
      select_anno.pts_y = this.y;
      if (video_mode) main_media.UpdateObjectPosition(select_anno);
      // Set action:
      $('#'+this.dom_attach).unbind();
      $('#'+this.dom_attach).mousedown({obj: this},function(e) {
        return e.data.obj.StopAdjustEvent();
      });

    }
  };

  /*************** Helper functions ****************/

  /** Compute center of mass for a polygon given array of points (x,y):

  */
  this.CenterOfMass = function(x,y) {
    var N = x.length;
    
    // Center of mass for a single point:
    if(N==1) {
      this.center_x = x[0];
      this.center_y = y[0];
      return;
    }
    
    // The center of mass is the average polygon edge midpoint weighted by 
    // edge length:
    this.center_x = 0; this.center_y = 0;
    var perimeter = 0;
    for(var i = 1; i <= N; i++) {
      var length = Math.round(Math.sqrt(Math.pow(x[i-1]-x[i%N], 2) + Math.pow(y[i-1]-y[i%N], 2)));
      this.center_x += length*Math.round((x[i-1] + x[i%N])/2);
      this.center_y += length*Math.round((y[i-1] + y[i%N])/2);
      perimeter += length;
    }
    this.center_x /= perimeter;
    this.center_y /= perimeter;
  };


  this.DrawPolygon = function(dom_id,x,y,obj_name,scale) {
    if(x.length==1) return DrawFlag(dom_id,x[0],y[0],obj_name,scale);
    
    var attr = 'fill="none" stroke="' + HashObjectColor(obj_name) + '" stroke-width="4"';
    return DrawPolygon(dom_id,x,y,obj_name,attr,scale);
  };
}