polygon weird redrawing by adding & dragging dynamic points (flickering)

Viewed 133

I have co-ordinates for the points by taking which I draw a polygon. I can add points dynamically on the edges of the polygon and when I drag any point it should drag only the connected lines. As points can be added later on the edges so the point co-ordinates need to be ordered/sorted and the polygon should be redrawn by taking the ordered/sorted points so that on dragging any point the lines connected to the dragged point only should be dragged/updated. So to order/sort the points I am sorting the co-ordinates(2D-points) clockwise using Graham Scan/ sorting by polar angle.

My sorting code is

I find the center of the polygon like

function findCenter(points) {
  let x = 0,
    y = 0,
    i,
    len = points.length;

  for (i = 0; i < len; i++) {
    x += Number(points[i][0]);
    y += Number(points[i][1]);
  }

  return { x: x / len, y: y / len }; // return average position
}

Then I sort the points by finding angles of each point from the center like

function findAngle(points) {
  const center = findCenter(points);

  // find angle
  points.forEach((point) => {
    point.angle = Math.atan2(point[1] - center.y, point[0] - center.x);
  });
}

//arrVertexes is the array of points
arrVertexes.sort(function (a, b) {
    return a.angle >= b.angle ? 1 : -1;
  });

But the problem I am facing is if I drag any point more towards opposite side and add a new point on the edges afterward and drag the newly added point the sorting of co-ordinates is not ordered exactly because of which there is a flickering while dragging.

Here is a pictorial view of the problem I am facing for quick understanding.

  • Initially my svg looks like

enter image description here

  • After this I add a point and dragged like

enter image description here

  • Then I added one more point like

enter image description here

  • once I drag the added point towards down, it redraws the polygon something like (is not it weird ?)

enter image description here

  • Actually It should be like

enter image description here


NOTE: I really don't know what logic should I apply to get the desire functionality. Seeking help from the community leads.

Demo App

So I am looking for a solution that won't give me weird redrawing of the lines. Only the connected lines to the dragged point should be dragged.


EDIT

I came up with MUCH BETTER solution. The only problem with this approach is, When I try to add a new point on left-vertical line and If I try to move it, that newly added point moves to top-horizontal line

Updated-Demo

2 Answers

I've fixed this bug with left line. Take a look: codepen.

  1. I changed getClosestPointOnLines function (actually refactored a little):

    • as I understood, the result here is to get i - the index for the new point in array, so I moved the algorithm to new function getI
    • I changed getI to use not only n (current index), but just 2 any indexes: n1 and n2: const getI = (n1, n2) => {
    • So all your aXys[n] is now a1 and aXys[n - 1] is now a2.
    • the result of getI is return i; - this is what we want from this function
  2. I added new function-helper updateI. It calls getI and check if there any positive result.

const updateI = (n1, n2) => {
      const newI = getI(n1, n2);
      if (newI !== undefined) {
        i = newI;
        return true;
      }
    };
  1. So your loop over points is now:
for (let n = 1; n < aXys.length; n++) {
      updateI(n, n - 1);
    }
  1. But we need to check "left" line separately (because it connects begin and end of the array):
if (updateI(aXys.length - 1, 0)) i = aXys.length;
  1. Sorry, but I disabled part of your code. I did not check where do you use it:
if (i < aXys.length) {
      let dx = aXys[i - 1][0] - aXys[i][0];
      let dy = aXys[i - 1][1] - aXys[i][1];

      x = aXys[i - 1][0] - dx * fTo;
      y = aXys[i - 1][1] - dy * fTo;
    }
  1. So the final version of getClosestPointOnLines now looks like this:

function getClosestPointOnLines(pXy, aXys) {
  var minDist;
  var fTo;
  var fFrom;
  var x;
  var y;
  var i;
  var dist;

  if (aXys.length > 1) {
    const getI = (n1, n2) => {
      let i;
      const a1 = aXys[n1];
      const a2 = aXys[n2];
      if (a1[0] != a2[0]) {
        let a = (a1[1] - a2[1]) / (a1[0] - a2[0]);
        let b = a1[1] - a * a1[0];
        dist = Math.abs(a * pXy[0] + b - pXy[1]) / Math.sqrt(a * a + 1);
      } else dist = Math.abs(pXy[0] - a1[0]);

      // length^2 of line segment
      let rl2 = Math.pow(a1[1] - a2[1], 2) + Math.pow(a1[0] - a2[0], 2);

      // distance^2 of pt to end line segment
      let ln2 = Math.pow(a1[1] - pXy[1], 2) + Math.pow(a1[0] - pXy[0], 2);

      // distance^2 of pt to begin line segment
      let lnm12 = Math.pow(a2[1] - pXy[1], 2) + Math.pow(a2[0] - pXy[0], 2);

      // minimum distance^2 of pt to infinite line
      let dist2 = Math.pow(dist, 2);

      // calculated length^2 of line segment
      let calcrl2 = ln2 - dist2 + lnm12 - dist2;

      // redefine minimum distance to line segment (not infinite line) if necessary
      if (calcrl2 > rl2) dist = Math.sqrt(Math.min(ln2, lnm12));

      if (minDist == null || minDist > dist) {
        if (calcrl2 > rl2) {
          if (lnm12 < ln2) {
            fTo = 0; //nearer to previous point
            fFrom = 1;
          } else {
            fFrom = 0; //nearer to current point
            fTo = 1;
          }
        } else {
          // perpendicular from point intersects line segment
          fTo = Math.sqrt(lnm12 - dist2) / Math.sqrt(rl2);
          fFrom = Math.sqrt(ln2 - dist2) / Math.sqrt(rl2);
        }
        minDist = dist;
        i = n1;
      }
      return i;
    };

    const updateI = (n1, n2) => {
      const newI = getI(n1, n2);
      if (newI !== undefined) {
        i = newI;
        return true;
      }
    };

    for (let n = 1; n < aXys.length; n++) {
      updateI(n, n - 1);
    }
    if (updateI(aXys.length - 1, 0)) i = aXys.length;

    if (i < aXys.length) {
      let dx = aXys[i - 1][0] - aXys[i][0];
      let dy = aXys[i - 1][1] - aXys[i][1];

      x = aXys[i - 1][0] - dx * fTo;
      y = aXys[i - 1][1] - dy * fTo;
    }
  }

  console.log(aXys[i - 1]);
  return { x: x, y: y, i: i, fTo: fTo, fFrom: fFrom };
}

Working example on codepen.

You should not allow any point to be added that is not close to a line.

When the user clicks, use the distance from a point to a line algorithm to check each line to see if the click is within an acceptable distance of the line. Perhaps a few pixels. If more than one line is within an acceptable distance, perhaps choose the one that is closest.

You now know where in the array to insert the new point. It will be between the first and second points of the line that just matched.

If you do that, the shape drawing should just work.

Related