๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ‘จ‍๐Ÿ’ป web.dev/fe

Canvas ๋„ํ˜• ํšŒ์ „์˜ ์›๋ฆฌ์™€ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

by HandHand 2023. 8. 29.

 

๐Ÿ“Œ ์ด๋ฏธ์ง€ ํšŒ์ „ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ

ํšŒ์ „๊ธฐ๋Šฅ์„ ๋„ฃ์–ด๋ณด์ž!

 

์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ์—์„œ ์บ”๋ฒ„์Šค์— ๊ทธ๋ ค์ง„ ์ด๋ฏธ์ง€ ํšŒ์ „ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

canvas ์—์„œ ๊ฐ์ฒด์— ์ œ๊ณตํ•ด์ฃผ๋Š” rotate API ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ์š”.

ํ•™์Šต๋„ ํ•ด๋ณผ ๊ฒธ ์ด๋ฏธ์ง€ ๋ฐ”์šด๋”๋ฆฌ ์˜์—ญ์— ๋Œ€ํ•œ ํšŒ์ „ ์—ฐ์‚ฐ์€ ์ง์ ‘ ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ง€๊ธˆ๋ถ€ํ„ฐ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ“Œ ๋ฐ์นด๋ฅดํŠธ ํ‰๋ฉด์ขŒํ‘œ์—์„œ ํšŒ์ „ํ•˜๊ธฐ

์ด๋ฏธ์ง€ ์ œ์–ด์˜์—ญ์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ์„ค๋ช…

์—ฌ๊ธฐ์„œ ์ œ์–ด์˜์—ญ ์€ ์ด๋ฏธ์ง€ ํ…Œ๋‘๋ฆฌ๋ฅผ ๋‘˜๋Ÿฌ์‹ผ ์˜์—ญ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

ํฌ์ปค์‹ฑ๋˜๋ฉด ๋‚˜ํƒ€๋‚˜๋ฉฐ ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œ, ํšŒ์ „, ๋ฆฌ์‚ฌ์ด์ฆˆ ํ•  ์ˆ˜ ์žˆ๋Š” ์•ต์ปค๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์ œ์–ด์˜์—ญ์€ ์บ”๋ฒ„์Šค์˜ ํ„ฐ์น˜ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„ point ๊ฐ’์„ ๋ฐ”ํƒ•์œผ๋กœ ์„ ํƒ๋œ ์•ต์ปคํƒ€์ž…์„ ๊ตฌ๋ถ„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  ์บ”๋ฒ„์Šค ๊ฐ์ฒด๋“ค์€ path ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋ฉฐ, isPointInPath API ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด

point ๊ฐ€ path ๋‚ด๋ถ€์— ์กด์žฌํ•˜๋Š”์ง€ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function findAnchorInPath({ context, anchors, point }) {
  return anchors.find((anchor) => context.isPointInPath(anchor.path2d, point.x, point.y))
}

 

2์ฐจ์›ํ‰๋ฉด ํšŒ์ „ ๋ณ€ํ™˜

๋จผ์ € ๋ฐ์นด๋ฅดํŠธ ์ขŒํ‘œ๊ณ„์—์„œ ์‚ฌ๊ฐํ˜•์˜ ํšŒ์ „ ๋ฐฉ๋ฒ•์„ ์•Œ์•„์•ผํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด ์œ ํด๋ฆฌ๋“œ ๊ณต๊ฐ„์—์„œ ํšŒ์ „๋ณ€ํ™˜์„ ์œ„ํ•ด ์‚ฌ์šฉ ๋˜๋Š” ๋ณ€ํ™˜ ํ–‰๋ ฌ(transformation matrix) ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

 

2์ฐจ์› ํ‰๋ฉด์ขŒํ‘œ v(x, y) ์—์„œ ๋ฐ˜์‹œ๊ณ„๋ฐฉํ–ฅ์œผ๋กœ θ ๋งŒํผ ํšŒ์ „ ํ–ˆ์„ ๋•Œ, v ๋ฅผ ์—ด ๋ฒกํ„ฐ๋กœ ํ•˜๋Š” ๊ณฑ ์—ฐ์‚ฐ์„ ํ†ตํ•ด ๋ณ€ํ™˜ ์ขŒํ‘œ๊ฐ’์„ ๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

์ฃผ์˜ํ• ์ ์€ ์บ”๋ฒ„์Šค๊ฐ€ ์ผ๋ฐ˜์ ์ธ ๋ฐ์นด๋ฅดํŠธ ์ขŒํ‘œ๊ณ„์™€๋Š” ๋‹ค๋ฅด๊ฒŒ ์ขŒ์ธก์ƒ๋‹จ์ด (0, 0) ์ธ ์ขŒํ‘œ๊ณต๊ฐ„์œผ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

์บ”๋ฒ„์Šค ์ขŒํ‘œ๊ณ„

 

๋”ฐ๋ผ์„œ ํšŒ์ „๊ฐ θ ๋ฅผ ๋ถ€ํ˜ธ ๋ณ€ํ™˜์—†์ด ์‚ฌ์šฉํ•˜๋ฉด ์บ”๋ฒ„์Šค ์ขŒํ‘œ๊ณต๊ฐ„์—์„œ๋Š” ์‹œ๊ณ„๋ฐฉํ–ฅ ํšŒ์ „๊ฐ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐Ÿ‘จ‍๐Ÿ’ป ์‚ฌ๊ฐํ˜• ์ค‘์  ํšŒ์ „๋ณ€ํ™˜ ์ฝ”๋“œ ๊ตฌํ˜„

1๏ธโƒฃ ์‚ฌ๊ฐํ˜• ์ค‘์  ๊ตฌํ•˜๊ธฐ

์šฐ๋ฆฌ๋Š” ์‚ฌ๊ฐํ˜•์˜ ์ค‘์ ์„ ๊ธฐ์ค€์œผ๋กœ ํšŒ์ „ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด ๋จผ์ € ์‚ฌ๊ฐํ˜•์˜ ์ค‘์ ์„ ๊ตฌํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ๊ฐํ˜•์˜ ์ค‘์ ์€ north-west, south-east ๋‘ ์ขŒํ‘œ๋งŒ ์•Œ๋ฉด ๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function getCenterOfBoundingRect({ nw, se }) {
  const x = Math.round((nw.x + se.x) / 2)
  const y = Math.round((nw.y + se.y) / 2)

  return { x, y }
}

 

2๏ธโƒฃ ํšŒ์ „๋ณ€ํ™˜ ํ–‰๋ ฌ

์ด์ œ ์•ž์„œ ์ •์˜ํ•œ ํšŒ์ „๋ณ€ํ™˜ ํ–‰๋ ฌ์„ ์ ์šฉํ•  ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

function getRotatedPoint({ point, degree }) {
  const { x, y } = point
  const radian = degreeToRadian(degree)
  const vector = {
    x: Math.round(x * Math.cos(radian) - y * Math.sin(radian)),
    y: Math.round(x * Math.sin(radian) + y * Math.cos(radian)),
  }

  return vector
}

function degreeToRadian(degree) {
  return (degree * Math.PI) / 180
}

 

3๏ธโƒฃ ์‚ฌ๊ฐํ˜•์˜ ์ค‘์ ์„ ๊ตฌํ•˜๊ณ  ์บ”๋ฒ„์Šค ์›์ ์œผ๋กœ ์ด๋™

์œ„์—์„œ ๊ตฌํ˜„ํ•œ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์‚ฌ๊ฐํ˜•์˜ ์ค‘์ ์„ ์›์ ์œผ๋กœ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค.

function getRotatedBoundingRectVertices({
  vertices,
  degree,
}) {
  const center = getCenterOfBoundingRect(vertices)
  const shiftedToOrigin = {
    nw: { x: vertices.nw.x - center.x, y: vertices.nw.y - center.y },
    ne: { x: vertices.ne.x - center.x, y: vertices.ne.y - center.y },
    sw: { x: vertices.sw.x - center.x, y: vertices.sw.y - center.y },
    se: { x: vertices.se.x - center.x, y: vertices.se.y - center.y },
  }

    // ...
}

 

4๏ธโƒฃ ๋ณ€ํ™˜ํ–‰๋ ฌ ์ ์šฉ

์ด์ œ ์ด๋™๋œ ์ขŒํ‘œ๊ฐ’์— ํ–‰๋ ฌ๊ณฑ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

function getRotatedBoundingRectVertices({
  vertices,
  degree,
}) {
  const center = getCenterOfBoundingRect(vertices)
  const shiftedToOrigin = {/** */}
  const rotated = {
    nw: getRotatedPoint({ point: shiftedToOrigin.nw, degree }),
    ne: getRotatedPoint({ point: shiftedToOrigin.ne, degree }),
    sw: getRotatedPoint({ point: shiftedToOrigin.sw, degree }),
    se: getRotatedPoint({ point: shiftedToOrigin.se, degree }),
  }
  // ...
}

 

5๏ธโƒฃ ์‚ฌ๊ฐํ˜• ์ค‘์ ์„ ๋‹ค์‹œ ์›๋ž˜์žˆ๋˜ ์œ„์น˜๋กœ ์ด๋™์‹œํ‚ค๊ธฐ

๋ณ€ํ™˜์ด ๋๋‚œ ๋’ค์— ๋‹ค์‹œ ์‚ฌ๊ฐํ˜•์„ ์›๋ž˜์žˆ๋˜ ์œ„์น˜๋กœ ์ด๋™์‹œ์ผœ์ค๋‹ˆ๋‹ค.

function getRotatedBoundingRectVertices({
  vertices,
  degree,
}) {
  const center = getCenterOfBoundingRect(vertices)
  const shiftedToOrigin = {/** */}
  const rotated = {/** */}
    const shiftBack = {
    nw: { x: rotated.nw.x + center.x, y: rotated.nw.y + center.y },
    ne: { x: rotated.ne.x + center.x, y: rotated.ne.y + center.y },
    sw: { x: rotated.sw.x + center.x, y: rotated.sw.y + center.y },
    se: { x: rotated.se.x + center.x, y: rotated.se.y + center.y },
  }

  return shiftBack
}

 

ํ…Œ์ŠคํŠธ ํ•ด๋ณด์ž

์œ„์—์„œ ๊ตฌํ˜„ํ•œ ์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

const example = {
  vertices: {
    nw: { x: 10, y: 10 },
    ne: { x: 20, y: 10 },
    sw: { x: 10, y: 20 },
    se: { x: 20, y: 20 },
  },
  degree: 30,
}

const result = getRotatedBoundingRectCoordinate(example)

expect(result).toStrictEqual({
  nw: { x: 13, y: 8 },
  ne: { x: 22, y: 13 },
  sw: { x: 8, y: 17 },
  se: { x: 17, y: 22 },
})

 

์ขŒํ‘œ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ณ€ํ™˜ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ˜ํ™˜๋œ ์ขŒํ‘œ๊ฐ’์„ ์ขŒํ‘œ๊ณ„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์•ฑ์œผ๋กœ ํ™•์ธํ•ด๋ดค์Šต๋‹ˆ๋‹ค.

 

์˜ˆ์ƒํ•œ๋Œ€๋กœ ํšŒ์ „์ด ์ž˜ ๋˜์—ˆ๋„ค์š”!

 

์ด์ œ ๋งˆ์ง€๋ง‰์œผ๋กœ ํšŒ์ „๊ฐ๊ณผ ์ขŒํ‘œ๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„ ์‚ฌ๊ฐํ˜• ํ…Œ๋‘๋ฆฌ๋ฅผ ๊ทธ๋ฆฌ๋Š” ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ด์ค๋‹ˆ๋‹ค.

function drawingBorder({ context, degree, vertices, }) {
    const rotatedVertices = getRotatedBoundingRectVertices({ degree, vertices })

    drawRect({ context, vertices: rotatedVertices, color: 'rgba(151, 222, 255, 0.7)' })
}

 

๊ทธ๋ฆฌ๊ณ  drawImageWithAnchor ์—์„œ๋Š” ํ…Œ๋‘๋ฆฌ์™€ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ฆฌ๋Š” ์—ญํ• ์„ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.

 

function drawImageWithAnchor({ image, context }) {
    const vertices = getBoundingRectVertices(image) // โœ… ์ด๋ฏธ์ง€ ๊ฐ์ฒด์˜ ๊ผญ์ง“์ ์„ ๊ตฌํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ์ด๋ฏธ ๊ตฌํ˜„๋˜์–ด์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.

    drawImage({ context, image }) // ๐Ÿ’ก ๋’ค์— ์ด์–ด์„œ ์ด ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

    if (image.focused) {
        drawingBorder({ context, degree: image.degree, vertices: }) // โœ… ์ด๋ฏธ์ง€๊ฐ€ ํฌ์ปค์‹ฑ๋œ ์ƒํƒœ์—์„œ๋งŒ ํ…Œ๋‘๋ฆฌ๋ฅผ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
    }
}

 

๐Ÿ“Œ ์บ”๋ฒ„์Šค์— ํšŒ์ „๋œ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๋Š” ๋ฐฉ๋ฒ•

์•„์ง ์ด๋ฏธ์ง€ ์ž์ฒด๋ฅผ ๊ทธ๋ ค์ฃผ๋Š” drawImage ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ํ˜„์žฌ๋Š” ํšŒ์ „๊ฐ์„ ์ž„์‹œ ์ƒ์ˆ˜๊ฐ’์„ ์‚ฌ์šฉํ•ด์„œ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ์š”,

์ด์ œ๋ถ€ํ„ฐ๋Š” ํšŒ์ „๊ฐ์„ ๊ตฌํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ํ•จ๊ป˜ ์บ”๋ฒ„์Šค ๊ฐ์ฒด๋ฅผ ํšŒ์ „์‹œํ‚ฌ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” rotate API ๋„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์บ”๋ฒ„์Šค์—์„œ ํšŒ์ „์„ ์œ„ํ•œ ๋ณ„๋„์˜ API ๋ฅผ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ๊ณ , ์ด๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜๋ฉด ํšŒ์ „๋œ ๋„ํ˜• ๋ฐ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

CanvasRenderingContext2D.rotate(angle)

 

์—ฌ๊ธฐ์„œ angle ์€ ์‹œ๊ณ„๋ฐฉํ–ฅ์˜ radian ๊ฐ’์ž…๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ๋žŒ์—๊ฒŒ ์นœ์ˆ™ํ•œ ๊ฐ๋„๋Š” degree ์ด๊ธฐ ๋•Œ๋ฌธ์— ์•ž์„  ๋ณ€ํ™˜์‹์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด์ œ ์ด๋ฏธ์ง€๋ฅผ ํšŒ์ „ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๋ชจ๋“  ๊ฒƒ๋“ค์„ ์•Œ์•˜์Šต๋‹ˆ๋‹ค.

ํšŒ์ „์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์€ ์บ”๋ฒ„์Šค ๋ฅผ ํšŒ์ „ ์‹œํ‚จ๋‹ค๋Š” ์ฐจ์ด์ ์„ ์ œ์™ธํ•˜๋ฉด ๋ฐ์นด๋ฅดํŠธ ์ขŒํ‘œ๊ณ„์—์„œ์™€ ๋™์ผํ•œ mental model ์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

 

  1. ์ด๋ฏธ์ง€๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ํšŒ์ „ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ค‘์ ๊ณผ ์›์ (origin) ์˜ ๊ฑฐ๋ฆฌ๋งŒํผ ์บ”๋ฒ„์Šค๋ฅผ ์˜ฎ๊ฒจ์ค๋‹ˆ๋‹ค.
  2. ๊ทธ๋ฆฌ๊ณ  ์บ”๋ฒ„์Šค๋ฅผ angle ๋งŒํผ ํšŒ์ „ํ•ด์ค€ ๋’ค์—
  3. ๋Œ€์ƒ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ ค์ฃผ๊ณ ,
  4. ๋‹ค์‹œ ์บ”๋ฒ„์Šค๋ฅผ ์›๋ž˜ ์œ„์น˜ํ–ˆ๋˜ ์ค‘์ ์œผ๋กœ ์˜ฎ๊ฒจ์ค๋‹ˆ๋‹ค.

 

๐Ÿ‘จ‍๐Ÿ’ป ์ฝ”๋“œ๋กœ ์‚ดํŽด๋ด…์‹œ๋‹ค.

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์— ์ •์˜๋˜์–ด์žˆ๋Š” ImageObject ํƒ€์ž…์€ ์ด๋ฏธ์ง€ ๊ด€๋ จ ์†์„ฑ ์ด์™ธ์—๋„

์‚ฌ๊ฐํ˜•๊ณผ ๊ด€๋ จ๋œ ๋ฉ”ํƒ€์ •๋ณด๋ฅผ ์œ„ํ•ด BoundingRect ํƒ€์ž…์„ ์ƒ์†๋ฐ›๊ณ  ์žˆ๋‹ค๊ณ  ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

interface ImageObject extends BoundingRect {
  /** image related props */
}
interface BoundingRect {
  /** top-left x position */
  sx: number
  /** top-left y position */
  sy: number
  width: number
  height: number
}

 

1๏ธโƒฃ degree ํƒ€์ž… ์ถ”๊ฐ€

์ด์ œ BoundingRect ์— degree ํƒ€์ž…์„ ์ถ”๊ฐ€ํ•ด์„œ ํšŒ์ „ ๊ฐ๋„ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

interface BoundingRect {
    // same as above ...
  degree: number // โœ… ํšŒ์ „๊ฐ ์ •๋ณด๋„ ์ถ”๊ฐ€!
}

 

2๏ธโƒฃ ํšŒ์ „๋œ ์ด๋ฏธ์ง€ ๊ทธ๋ฆฌ๊ธฐ

์ด๋ฏธ์ง€๋ฅผ ๋ฐฐ์—ด ์ธ์ž๋กœ ๋ฐ›์•„ ์บ”๋ฒ„์Šค์— ๊ทธ๋ฆฌ๋Š” ์ฝ”๋“œ๋Š” ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ images ๋Š” ImageObject[] ํƒ€์ž…์ž…๋‹ˆ๋‹ค.

function paintImages({ images, context }) {
  images.forEach((image) => 
        drawImageWithAnchor({ image, context }) // โœ… ์•ž์„œ ๊ตฌํ˜„ํ•œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    )
}

 

์ด์ œ degree ๊ฐ’์„ ์ด์šฉํ•  ์ฐจ๋ก€์ž…๋‹ˆ๋‹ค.

๊ฐ๊ฐ์˜ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ฆฌ๊ธฐ์ „์— ํ˜„์žฌ context ์ •๋ณด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

์ด๋Š” ํšŒ์ „๋œ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ฆฐ ์ดํ›„์— ํšŒ์ „๋˜์ง€ ์•Š์€ context ๋กœ ์‰ฝ๊ฒŒ ๋˜๋Œ๋ฆฌ๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

 

function drawImage({ context, image }) {
    const { ref, sx, sy, width, height } = image

    context.save() // โœ… ํ˜„์žฌ ์บ”๋ฒ„์Šค context ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    context.drawImage(ref, sx, sy, width, height)
    context.restore() // โœ… ์ด์ „ context ๋กœ ๋˜๋Œ์•„๊ฐ‘๋‹ˆ๋‹ค.    
}

 

์ด์ œ ์ด๋ฏธ์ง€์˜ ์ค‘์ ์„ ๊ณ„์‚ฐํ•˜๊ณ , ์ด ์ขŒํ‘œ๊ฐ’๋งŒํผ ์บ”๋ฒ„์Šค๋ฅผ ์›์  (0, 0) ์œผ๋กœ ์ด๋™์‹œ์ผœ์ค๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ด์ œ ์บ”๋ฒ„์Šค์˜ ์›์ ์€ ์ด๋ฏธ์ง€์˜ ์ค‘์ ์ด ๋ฉ๋‹ˆ๋‹ค.

์‚ฌ๊ฐํ˜•์˜ ์ค‘์ ์€ ํšŒ์ „๊ฐ์— ์ƒ๊ด€์—†์ด ํ•ญ์ƒ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ํšŒ์ „๋˜์ง€ ์•Š์€ BoundingRect ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

function drawImage({ context, image }) {
    const { ref, sx, sy, width, height } = image

    // โœ… ํ˜„์žฌ ์บ”๋ฒ„์Šค context ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
    context.save()

    // โœ… ์‚ฌ๊ฐํ˜•์˜ ์ค‘์ ์„ ๊ตฌํ•ฉ๋‹ˆ๋‹ค.
    const vertices = getBoundingRectVertices(image)
    const center = getCenterOfBoundingRect(vertices)

    // โœ… offset ๋งŒํผ ์ด๋™ํ•ด์„œ ์บ”๋ฒ„์Šค์˜ ์›์ ์„ ์‚ฌ๊ฐํ˜•์˜ ์ค‘์ ์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
    context.translate(center.x, center.y)
    context.rotate(degreeToRadian(10)) // ๐Ÿ’ก ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ž„์‹œ๊ฐ’์„ ๋„ฃ์Šต๋‹ˆ๋‹ค.

    // โœ… ๋‹ค์‹œ ์›๋ž˜ ์œ„์น˜๋กœ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค.
    context.translate(-center.x, -center.y)
    context.drawImage(ref, sx, sy, width, height)
    context.restore()
}

function degreeToRadian(degree) {
  return (degree * Math.PI) / 180
}

 

ํšŒ์ „๋œ ๊ฒฐ๊ณผ

 

ํ•˜๋“œ์ฝ”๋”ฉ์œผ๋กœ 10๋„ ํšŒ์ „๋œ ์ด๋ฏธ์ง€๋ฅผ ๊ทธ๋ ค๋ณด๋‹ˆ ์ž˜ ๋‚˜์˜ค๋„ค์š”!

 

๐Ÿ“ ํšŒ์ „๊ฐ ๊ตฌํ•˜๊ธฐ

์ด์ œ ๋งˆ์ง€๋ง‰ ํ•œ๊ฐ€์ง€ ์ž‘์—…์ด ๋‚จ์•˜์Šต๋‹ˆ๋‹ค.

์ด์ „์—๋Š” ๊ณ ์ •๋œ ๊ฐ๋„๋กœ ํšŒ์ „๋œ ์ด๋ฏธ์ง€์™€ ์ œ์–ด ์•ต์ปค๋ฅผ ๊ทธ๋ ธ๋Š”๋ฐ,

์ด์ œ ์บ”๋ฒ„์Šค์— ํ„ฐ์น˜๋œ ์ง€์ ์„ ๋ฐ”ํƒ•์œผ๋กœ ํšŒ์ „๊ฐ์„ ๊ตฌํ•˜๋Š” ๊ณผ์ •์„ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋ฒกํ„ฐ์˜ ๋ฐฉ์œ„๊ฐ์„ ๊ตฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

 

๊ณ ์ • ์œ„์น˜ A ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์บ”๋ฒ„์Šค ์„ ํƒ ์ง€์  B ์— ๋Œ€ํ•œ ๋ฒกํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ A ๋Š” ์ด๋ฏธ์ง€์˜ ํšŒ์ „ ์ œ์–ด๋ฅผ ์œ„ํ•œ ์บ”๋ฒ„์Šค ๊ฐ์ฒด์˜ ์ค‘์  ์ขŒํ‘œ๊ฐ€ ๋˜๊ฒ ๋„ค์š”.

 

 

function getBearingDegree(vector: Vector) {
  const thetaA = Math.atan2(vector.end.x - vector.begin.x, -vector.end.y + vector.begin.y)
  const theta = thetaA >= 0 ? thetaA : Math.PI * 2 + thetaA

  return Math.floor(radianToDegree(theta))
}

์œ„ ๊ณต์‹์€ ๋ฐ์นด๋ฅดํŠธ ์ขŒํ‘œ๊ณ„๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์—,

์บ”๋ฒ„์Šค ์ขŒํ‘œ๊ณต๊ฐ„์„ ๊ณ ๋ คํ•˜์—ฌ x์ถ• ๋Œ€์นญ ๋œ ์ขŒํ‘œ๊ฐ’์„ ๊ณต์‹์— ๋Œ€์ž…ํ•ฉ๋‹ˆ๋‹ค.

 

โœจ ์ตœ์ข… ๊ฒฐ๊ณผ

 

๐Ÿ“Œ ์ฐธ๊ณ ์ž๋ฃŒ

CanvasRenderingContext2D: rotate() method - Web APIs | MDN

Find the bearing angle between two points in a 2D space

๋ฐ˜์‘ํ˜•

๐Ÿ’ฌ ๋Œ“๊ธ€