Expanding quad to be oriented along control points

23 views Asked by At

I have a problem with expanding and orienting a 4 vertices quad along two control points. I made this picture to illustrate my problem:

enter image description here

Here is my HTML5 canvas code trying to solve this problem. Assume I have array of { x: number, y: number } for each 2D position:

Here is a full code snippet:

console.clear()

const c = document.createElement('canvas')
const ctx = c.getContext("2d")

c.width = innerWidth
c.height = innerHeight
document.body.appendChild(c)

ctx.fillStyle = "black"
ctx.fillRect(0, 0, c.width, c.height)

ctx.fillStyle = "red"
ctx.strokeStyle = "white"

const offset = 10

const points = []
const pointsNum = 10
const spacingX = 800 / pointsNum

const positions = []
for (let i = 0; i < pointsNum + 1; i++) {
  const x = i * spacingX + 100
  const y = (Math.random() * 2 - 1) * 100 + 200
  positions.push({x, y})
}

for (let i = 0; i < pointsNum; i++) {
  const { x: currX, y: currY } = positions[i]
  const { x: nextX, y: nextY } = positions[i + 1]
  
  const dx = nextX - currX
  const dy = nextY - currY
  const angle = Math.atan2(dy, dx)
  
  const x1 = currX - offset
  const y1 = currY - offset
  const x2 = currX - offset
  const y2 = currY + offset
  
  const x3 = nextX + offset
  const y3 = nextY + offset
  const x4 = nextX + offset
  const y4 = nextY - offset
  
  ctx.fillRect(currX - 3, currY - 3, 6, 6)
  
  ctx.moveTo(x1, y1)
  ctx.lineTo(x2, y2)
  ctx.lineTo(x3, y3)
  ctx.lineTo(x4, y4)
  ctx.closePath()
  ctx.stroke()
}

As you can see I am stuck at step 3. I understand some kind of rotation needs to be applied to each quad vertex to orient it (notice I already calculate angle), but can't work out what exactly needs to happen.

Thanks in advance!

1

There are 1 answers

0
obscure On

If we look at your control point P1 and it's four surrounding vertices - without any rotation applied yet - we can see that the vertices are at specific angles yet.

Since in Javascript an angle of 0° is at the 3 'o clock position and increases clockwise, we can say that:

  • V1==-135°
  • V2==-45°
  • V3==45°
  • V4==135°

In an unit-circle, given an angle we can calculate any point on it's circumference by computing the sine and it's cosine. If we then multiply the result by a number (offset in your case) and add the origin of P1 we know exactly where to draw this point on screen.

In your case though the quad described by the four vertices can be rotated - which does not make things any more complicated. All you have to do is adding the rotation to the vertex initial angle.

So say we want to draw V1 and the quad is rotated by 22° it's actual rotation is -113°.

Here's an example:

const c = document.createElement('canvas')
const ctx = c.getContext("2d")

c.width = innerWidth
c.height = innerHeight
document.body.appendChild(c)

ctx.fillStyle = "black"
ctx.fillRect(0, 0, c.width, c.height)

ctx.fillStyle = "red"
ctx.strokeStyle = "white"

const offset = 10

const points = []
const pointsNum = 10
const spacingX = 800 / pointsNum

const positions = []
for (let i = 0; i < pointsNum + 1; i++) {
    const x = i * spacingX + 100
    const y = (Math.random() * 2 - 1) * 100 + 200
    positions.push({
        x,
        y
    })
}

for (let i = 0; i < pointsNum; i++) {
    const {
        x: currX,
        y: currY
    } = positions[i]
    const {
        x: nextX,
        y: nextY
    } = positions[i + 1]

    const dx = nextX - currX
    const dy = nextY - currY
    const angle = Math.atan2(dy, dx)

    const x1 = currX + Math.cos(-135 * (Math.PI / 180) + angle) * offset;
    const y1 = currY + Math.sin(-135 * (Math.PI / 180) + angle) * offset;
    const x2 = currX + Math.cos(135 * (Math.PI / 180) + angle) * offset;
    const y2 = currY + Math.sin(135 * (Math.PI / 180) + angle) * offset;

    const x3 = nextX + Math.cos(-45 * (Math.PI / 180) + angle) * offset;
    const y3 = nextY + Math.sin(-45 * (Math.PI / 180) + angle) * offset;
    const x4 = nextX + Math.cos(45 * (Math.PI / 180) + angle) * offset;
    const y4 = nextY + Math.sin(45 * (Math.PI / 180) + angle) * offset;

    ctx.fillRect(currX - 3, currY - 3, 6, 6);
    ctx.moveTo(x1, y1)
    ctx.lineTo(x2, y2)
    ctx.lineTo(x4, y4)
    ctx.lineTo(x3, y3)
    ctx.closePath()
    ctx.stroke()
}