2022.07

Spline Tool Preview
알고리즘 선택 이유
요구사항에 맞는 Algorithm 중 에서는 (Lagrange, Cardinal) Interpolating 두가지로 좁혀졌다.
초기에는 Lagrange 보간법을 사용했으나, Point 가 좁고 많아질수록 보간값이 불안정하여 곡선이 넓게 튀는 현상이 발견되어, 비교적 Point 가 많아도 안정적인 Cardinal 보간법으로 적용하였다.
곡선이 넓게 튀는 것이 왜 문제가 되는가?
고객사가 제공한 라벨링 도구가 별도로 존재하였고, 그 알고리즘을 재현해야 했다. 하지만 자세한 방법은 알려주지 않았기에 최대한 비슷하면서 포인트를 이었을때 곡선의 형태가 거의 같아야 했기 때문에 포인트가 조금 많더라도 정밀도를 우선시 하였다.

Lagrange Interpolating Spline

Cardinal Interpolating Spline
Konva 라이브러리 에서는 커스텀 드로잉 기능을 구현할 수 있게 sceneFunc 라는 함수를 제공한다.
이 함수 내에서 Canvas Context 저수준 객체를 가져와서 직접 알고리즘으로 드로잉하여 Scene 을 생성한다.
사용자가 곡선을 선택하고, 선뿐만이 아닌 영역을 그리기 위해 반복적인 드로잉이 필요하게 되어, 정방향, 역방향 드로잉을 연속적으로 수행하는 한붓그리기 방식으로 영역 생성을 위한 드로잉을 수행하였다.
// **useSpline.ts -** drawSpace
// Spline Space SceneFunc
// calcInterpolatingPoint(X|Y) 함수는 core algorithm 입니다.
function drawSpace(ctx: Context, shape: Shape<ShapeConfig>, hit: boolean) {
let scPx = new Float64Array(n2),
scPy = new Float64Array(n2);
let X, Y;
// Process:
// 1. 첫점에서 너비만큼 X축을 벌려 왼쪽부터 같은 곡선을 그립니다.
// 2. 왼쪽 선에서 선을 이으면서 오른쪽도 같은 곡선을 거꾸로 그리면서 한붓 그리기를 합니다.
// 3. 완성된 선을 지정된 색으로 채웁니다.
if (n > 2) {
for (let dir of [-1, 1]) {
for (let i = 0; i < n; i++) {
X = scPx[i + 1] = Px[i] + dir * infos[i]?.weight;
Y = scPy[i + 1] = Py[i];
}
// [0, last point] not draw, only using by algorithm
initPoint(scPx, scPy)
if (dir === -1) {
ctx.beginPath();
}
if (dir === 1) {
scPx = scPx.reverse();
scPy = scPy.reverse();
}
for (let i = 1; i < n; i++) {
for (let k = 0; k < 26; k++) {
X = calcInterpolatingPointX(scPx, scPy);
Y = calcInterpolatingPointY(scPx, scPy);
ctx.lineTo(X, Y);
}
}
if (dir === 1) {
ctx.closePath();
if (hit) {
// 충돌 영역은 shape 로 그려야 합니다. (화면 표시 안됨, 클릭시 영역 반응)
ctx.fillStrokeShape(shape);
} else {
// gradient 는 shape 로 채우기 불가능하여 raw api 를 사용합니다.
ctx._context.fillStyle = _getGradientSetting(ctx);
ctx._context.fill();
}
}
}
}
}