๐ ์ด๋ฏธ์ง ํ์ ๊ธฐ๋ฅ ์ถ๊ฐํ๊ธฐ
์ฌ์ด๋ ํ๋ก์ ํธ์์ ์บ๋ฒ์ค์ ๊ทธ๋ ค์ง ์ด๋ฏธ์ง ํ์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ค๊ณ ํฉ๋๋ค.
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
์ ๊ณต์ ํฉ๋๋ค.
- ์ด๋ฏธ์ง๋ฅผ ์ค์ฌ์ผ๋ก ํ์ ํ๊ธฐ ์ํด์ ์ค์ ๊ณผ ์์ (
origin
) ์ ๊ฑฐ๋ฆฌ๋งํผ ์บ๋ฒ์ค๋ฅผ ์ฎ๊ฒจ์ค๋๋ค. - ๊ทธ๋ฆฌ๊ณ ์บ๋ฒ์ค๋ฅผ
angle
๋งํผ ํ์ ํด์ค ๋ค์ - ๋์ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ ค์ฃผ๊ณ ,
- ๋ค์ ์บ๋ฒ์ค๋ฅผ ์๋ ์์นํ๋ ์ค์ ์ผ๋ก ์ฎ๊ฒจ์ค๋๋ค.
๐จ๐ป ์ฝ๋๋ก ์ดํด๋ด ์๋ค.
ํ์ฌ ํ๋ก์ ํธ์ ์ ์๋์ด์๋ 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์ถ ๋์นญ
๋ ์ขํ๊ฐ์ ๊ณต์์ ๋์
ํฉ๋๋ค.
โจ ์ต์ข ๊ฒฐ๊ณผ
๐ ์ฐธ๊ณ ์๋ฃ
'๐จโ๐ป web.dev > fe' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
point-in-polygon ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๊ตญ๋ด์ธ ํ๋จํ๊ธฐ (0) | 2024.01.30 |
---|---|
Vanilla JS ๋ก FigJam ์น์ฑ ๋ง๋ค๊ธฐ (1) | 2024.01.23 |
Storybook useArgs ํ์ฉํ๊ธฐ (1) | 2023.08.28 |
Web Component์ ํจ๊ป ์ปดํฌ๋ํธ ๋ง๋ค๊ธฐ (0) | 2023.08.25 |
Storybook loaders ๋ก story์ ๋น๋๊ธฐ ๋ฐ์ดํฐ ์ฃผ์ ํ๊ธฐ (0) | 2023.06.24 |
๐ฌ ๋๊ธ