This is the 8th day of my participation in the More text Challenge. For details, see more text Challenge

preface

In the last article, I simply implemented moving the rectangle (dot, line and plane) in canvas. For those who are not clear, please refer to this article: Using Canvas to move the rectangle (dot, line and plane) (1). Ok, no more nonsense, let’s jump right into the topic of the article, I left a lot of questions in the last article, how do I know what type of movement I am moving in the drawing step, whether it is a point or a line or a plane, that is the problem of this article. Here’s what you can learn from reading this article:

  1. Judge the distance between points
  2. Determine the relationship between a point and a line (use of cross product)
  3. How to draw n-side shape in canvas. (Rotation of the vector)

The whole point of all this is to create an effect in 2d graphics: snap, to determine the relationship between the current point and the polygon on the canvas.

Adsorption — realization point

You can think about what you would do if you were asked to do it. Let’s say there’s a lot of polygons on the canvas, and there’s a lot of points. Someone said, whoever comes near it is the one. Ok, you are right. In fact, it is to determine the current point compared to all the points on the canvas, which point is close, which point is selected, there is a query performance problem, right? Some of you are asking what if there are a lot of dots on the canvas? We are going to go through the size of the comparison, of course, not here to give you a popular science of a spatial geometric index algorithm Rbush

RBush is a high-performance JavaScript library for two-dimensional spatial indexes of points and rectangles. It is based on an optimized R-tree data structure and supports bulk inserts.

I’ll give you a wanking of Rbush later when I have time, but I’ll give you a reference link if you’re interested. Instead of Rbush, we use collections to store data. The other thing to emphasize here is that every point on the canvas should be an instance and have a unique ID. We’re going to reinvent it:

const current = 0;
const pointMap = []

constructor(x,y) {
    this.x = x || 0;
    this.y = y || 0;
    this.id = ++current;
}
// Add to Map
add2Map() {
  pointMap.push(this)
  return this
}
// To randomly generate a point
random(width,height){
    this.x = Math.random() * width;
    this.y = Math.random() * height;
    return this;
}

// Take the absolute value
abs() {
    return [Math.abs(this.x), Math.abs(this.y)]
}

// Calculate the distance between two points
distance(p) {
    const [x,y] = this.clone().sub(p).abs();
    return Math.sqrt(x*x + y * y);
}

Copy the code

I rewrote a method code for drawing polygons as follows:

// Draw polygons
function drawAnyPolygon(points) {
    if(!Array.isArray(points)) {
        return;
    }
    ctx.strokeStyle = 'black'
    ctx.setLineDash([]); 
    ctx.beginPath();
    // The first point is the starting point
    const start = points[0];
    ctx.moveTo(start.x,start.y)
    points.slice(1).forEach(item= > {
        ctx.lineTo(item.x,item.y)
    })
    ctx.closePath()
    ctx.stroke()
}
Copy the code

It doesn’t matter what’s the most important thing, how do we generate a list of regular polygons from a single point

How to draw regular polygons in Canvas?

Here’s the definition of a polygon:

Regular polygon is to point to the two dimensional plane each side is equal, each Angle is also equal polygon, also call regular polygon.

I give the following schematic diagram:

From the figure we can see that there are no more than two forms of regular polymorphisms

  1. Draw an outer circle centered on the current point, then divide the circle equally according to the number of edges
  2. Draw an inner circle centered on the current point, then divide it equally according to the number of edges

We know the principle, how to implement the application to our canvas? It’s really easy. We start with the center of the circle and a point on the circle. And then you rotate it by two PI over n and you get all the points. And then we can draw regular polygons. Here is the outer circle to calculate the idea of polygon, as for the inner circle how to calculate, give you a thinking after class 🤔 to think about it. I’ll give you the following code implementation:

Of or relating to a point in the first part revolving around a central point:

 rotate(center, angle) {
      const c = Math.cos( angle ), s = Math.sin( angle );
      const x = this.x - center.x;
      const y = this.y - center.y;
      this.x = x * c - y * s + center.x;
      this.y = x * s + y * c + center.y;
      return this;

  }
Copy the code

The general idea here is that the rotation of the vector and then add the center point. If that doesn’t make sense, let me give you a derivation: portal

The second part is how to generate the vertices of the polygon:

function getAnyPolygonPoints(start, end, n = 3) {
    const angle = (Math.PI * 2) / n
    const points = [end]
    for (let i = 1; i < n; i++) {
      points.push(
        end
          .clone()
          .rotate(start.clone(), angle * i)
          .add2Map()
      )
    }
    return points
  }
Copy the code

For I will give you a look n = 5 10 20 | | | 50 of the regular polygon. Then you will notice that as the number of sides increases, the polygon will look more and more like a circle.

Have you unlocked your new world? Dear readers. If you find this helpful.Point a praiseRead on. 👇 there are also some mathematical methods introduced.

Achieve any regular polygon point movement

Let’s imagine that the mouse is constantly moving over the canvas, and I’m sure which point is closer to me, so I choose which point. So it is constantly comparing the distance between the point where the mouse moves and the point that already exists. Ok, I have the following code:

function calcClosestPoint() {
    const minMap = []
    for (let value of pointMap) {
      constdis = value.distance(start.clone()) minMap.push({ ... value, dis }) }// Find the nearest point
    const sort = minMap.sort((a, b) = > a.dis - b.dis)
    return sort[0]}Copy the code

What this code might say is find the distance between two points, right? So this is easy, just subtract the absolute value of the two coordinates, and then take the square root. That’s what most people think, right? That’s what I thought at first. It’s fine to think of it that way, but I don’t really need the square root, we want to compare distances. There will be a small performance optimization. Because you have to square root, and then the CPU has to calculate, and if there are too many points in the canvas, and the number is very large, there is an important problem with Js accuracy. The code is as follows:

distance(p) {
  const [x, y] = this.clone().sub(p).abs()
  return x * x + y * y
}

distanceSq(p) {
  const [x, y] = this.clone().sub(p).abs()
  return Math.sqrt(x * x + y * y)
}
Copy the code

When we find the smallest point, we can repeat the previous article to achieve the move. I’m not going to go into too much detail here, but if you don’t know, you can read the previous article. Given the following code:

// Draw any polygon that is clockwise
  function drawAnyPolygon(points) {
    if (!Array.isArray(points)) {
      return
    }
    ctx.strokeStyle = 'black'
    ctx.setLineDash([])
    ctx.beginPath()
    // There are moving points
    if (movePoint.length > 0) {
      // Move the vector
      const moveVec = end.clone().sub(start)
      points = points.map((item) = > {
        // In the list of points to find the same as the move is to change its data
        if (item.equal(movePoint[0]) {return item.clone().add(moveVec)
        }
        return item
      })
    }
    ctx.moveTo(points[0].x, points[0].y)
    points.slice(1).forEach((item) = > {
      ctx.lineTo(item.x, item.y)
    })
    ctx.closePath()
    ctx.stroke()
  }

canvas.addEventListener('click'.(e) = > {
  if (e.altKey) {
    isMove = false
    return} isMove = ! isMoveconst x = e.clientX
  const y = e.clientY
  start = new Point2d(x, y)
  movePoint.length = 0
  movePoint.push(calcClosestPoint())
  isSelect = true
})
Copy the code

So here I’m going to click below to determine where I’m going to move and where I’m going to start my move vector, so movePoint is actually all the points that I’m going to move. Let’s go straight to the drawing.

Achieve any regular polygon line movement

The point movement and the moment that we achieve the point of our mouse, how do we make sure that we are clicking on a line, which is also a mathematical problem? It’s the distance from the point to the line, the distance from the point to the line, and the first way to do that is to solve the equation of the line. What’s the equation of a line?

Finding the distance between a point and a line method 1

Let the equation of line L be Ax+By+C=0, and the coordinates of point P are (x0,y0), then the distance between point P and line L is:

Similarly, if P (x0,y0), the analytic expression of line L is y=kx+b, then the distance between point P and line L is

Consider point (x0, y0, z0) and linear space x – x1 = y/l – y1 / m = z – z1 / n, d = | (x1 – x0, y1 – y0, z1 – z0) x (l, m, n) | / square root squared squared (l + m + n squared)

It’s going to be two points where you figure out the slope and the intercept, but you have to consider the special case of the line and the Y-axis, where the slope is infinite. This distance is going to be the x-coordinate minus. So we can calculate the distance from the point to the line, and then we can compare and find the line with the smallest distance, and then we can find the point that moved, but that’s not optimal. The optimal solution is to do it with the vector, and if you want to do the foot drop you can do it this way.

To find the distance between a point and a line, method 2

Let me ask you a question first, huh? What’s the geometric meaning of the cross product of vectors, is the area of the parallelogram between two vectors. Well, when we figure out the distance from the point to the line, we just figure out the height of the parallelogram, so we just figure out the area and divide by the base and we’ll figure out the distance from the line. Ha ha ha, is not once again by the charm of mathematics. Let me show you a picture:

The red line is the distance from the point to the line. We started coding directly, the theory had a direct start.

Let me first write a way to turn a point into a line segment, because we’re going to go head to tail, so the number of points, the last one should be the same as the starting point.

function points2Segs(points) {
    const start = points[0]
    points.push(start)
    const segs = []
    points.forEach((point, index) = > {
      if(index ! == points.length -1) {
        segs.push([point, points[index + 1]])}})return segs
}
Copy the code

The method of cross product is as follows:

cross(v) {
   return this.x * v.y - this.y * v.x
}
Copy the code

Calculate the distance from the point to the straight line as follows:

function pointDistanceToLine(target, line) {
  const [start, end] = line
  const vec1 = start.clone().sub(target)
  const vec2 = end.clone().sub(target)
  return vec1.clone().cross(vec2) / start.clone().distanceSq(target)
}
// Find the nearest line
function calcClosestLine() {
  let minMap = []
  segs.forEach((line) = > {
    const dis = pointDistanceToLine(start, line)
    minMap.push({
      dis,
      line,
    })
  })
  minMap = minMap.sort((a, b) = > a.dis - b.dis)
  // Find the nearest line and put the point in the movePointmovePoint.push(... minMap[0].line)
}
Copy the code

Move the code there and rewrite it:

 if (movePoint.length > 0) {
    const moveVec = end.clone().sub(start)
    points = points.map((item) = > {
      // If the line is moved by two points, it should be all points
      if (item.equal(movePoint[0]) || item.equal(movePoint[1]) {return item.clone().add(moveVec)
      }
      return item
    })
  }
Copy the code

Look directly at the effect:

Thank you so much for being here. I’m going to go here because I can actually do the dots and the lines, and the faces are all the points moving and that’s not too hard to do, so you can practice that on your own.

conclusion

This article mainly introduces the movement of 2d graphics, points, lines and planes. It’s essentially a movement of points, plus a movement vector. This is the core, in fact, there are a lot of things we need to slowly experience. For a closed region to form, the order of the points must be end to end in one direction or the other. And some understanding of the cross product and the dot product. It can be used flexibly in the implementation project. All of the code for this article is on github, so if you think it will help you, you can star it.

Finally the last or hope everyone to click 👍 and comment. Knowledge output is not easy, if you are interested in graphics, you can follow my official account: Front-end Graphics continue to share canvas, SVG, WebGL knowledge.