Рисование гладкой кривой из множества точек Безье через объект Path
Нужно нарисовать кривую состоящую из множества точек Безье, можно или квадратичных или кубических.
Проблема в том, что при добавлении точки Безье в объект Path они не связаны между собой и рисуются ломаными фрагментами.
Теоретически нужен метод который добавляет помимо точки вне кривой и конечной (опорной) точки еще следующую точку вне кривой, которая не рисуется, но служит для коррекции при рисовании следующей и т.п.
Есть ли какие-либо идеи?
Ответы (1 шт):
"Точки Безье" – нет такого понятия. Кривая Безье состоит из сегментов, каждый из которых определяется набором точек, которые не все взаимозаменяемы. Так что нельзя нарисовать "кривую Безье" а затем в середину вставить ещё одну "точку Безье". Для таких манипуляций есть другие сплайны и интерполяционные многочлены.
Если вы хотите соединить два сегмента кубической кривой Безье без стыка, вам нужно чтобы их крайние точки совпали, а полусумма третьей точки предыдущего сегмента и второй точки следующего совпала с крайней точкой. Запустите код и подвигайте точки чтобы понять идею:
const makeDraggablePoint = svg => {
const subscribers = [];
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
svg.appendChild(group);
const p = () => {
const ctm = group.getCTM();
return [ctm.e, ctm.f];
};
const moveGroup = (x, y) => {
const ctm = group.getCTM();
ctm.e = x;
ctm.f = y;
const t = svg.createSVGTransform();
t.setMatrix(ctm);
group.transform.baseVal.initialize(t);
subscribers.forEach(e => e());
};
const handle = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle'
);
handle.setAttribute('r', 20);
handle.setAttribute('stroke', 'transparent');
handle.setAttribute('fill', 'transparent');
handle.style.cursor = 'grab';
group.appendChild(handle);
const point = document.createElementNS(
'http://www.w3.org/2000/svg',
'circle'
);
point.setAttribute('r', 4);
point.setAttribute('stroke', 'red');
point.setAttribute('fill', 'red');
point.style.cursor = 'grab';
group.appendChild(point);
const onPointerDown = e => {
e.preventDefault();
const [x, y] = p();
const offset_x = x - e.clientX;
const offset_y = y - e.clientY;
group.setPointerCapture(e.pointerId);
const oldMove = group.onpointermove;
const oldUp = group.onpointerup;
group.onpointermove = e => {
e.preventDefault();
const sx = e.clientX + offset_x;
const sy = e.clientY + offset_y;
moveGroup(sx, sy);
};
group.onpointerup = e => {
e.preventDefault();
group.onpointerup = oldUp;
group.onpointermove = oldMove;
group.releasePointerCapture(e.pointerId);
};
};
handle.onpointerdown = onPointerDown;
point.onpointerdown = onPointerDown;
return {
'subscribe': cb => subscribers.push(cb),
'p': p,
'move': moveGroup,
};
};
const link2 = (p2, p1) => {
let [p1x, p1y] = p1.p();
let [p2x, p2y] = p2.p();
let updateP1 = true;
p1.subscribe(() => {
if (updateP1) {
[p1x, p1y] = p1.p();
}
});
p2.subscribe(() => {
const [old_p2x, old_p2y] = [p2x, p2y];
[p2x, p2y] = p2.p();
p1x += p2x - old_p2x;
p1y += p2y - old_p2y;
updateP1 = false;
p1.move(p1x, p1y);
updateP1 = true;
});
};
const link3 = (p1, p2, p3) => {
let [p1x, p1y] = p1.p();
let [p2x, p2y] = p2.p();
let [p3x, p3y] = p3.p();
let updateP1 = true;
let updateP3 = true;
p1.subscribe(() => {
if (updateP1) {
const [old_p1x, old_p1y] = [p1x, p1y];
[p1x, p1y] = p1.p();
p3x -= p1x - old_p1x;
p3y -= p1y - old_p1y;
updateP3 = false;
p3.move(p3x, p3y);
updateP3 = true;
}
});
p3.subscribe(() => {
if (updateP3) {
const [old_p3x, old_p3y] = [p3x, p3y];
[p3x, p3y] = p3.p();
p1x -= p3x - old_p3x;
p1y -= p3y - old_p3y;
updateP1 = false;
p1.move(p1x, p1y);
updateP1 = true;
}
});
p2.subscribe(() => {
const [old_p2x, old_p2y] = [p2x, p2y];
[p2x, p2y] = p2.p();
p1x += p2x - old_p2x;
p1y += p2y - old_p2y;
p3x += p2x - old_p2x;
p3y += p2y - old_p2y;
updateP1 = false;
p1.move(p1x, p1y);
updateP1 = true;
updateP3 = false;
p3.move(p3x, p3y);
updateP3 = true;
});
};
const svg = document.getElementById('board');
const path = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
);
path.setAttribute('stroke', 'black');
path.setAttribute('stroke-width', '2');
path.setAttribute('fill', 'transparent');
path.setAttribute('d', 'M 0 43 C 44 15, 57 0, 73 29.5 C 89 59, 106 59, 91.5 29.5 C 77 0, 31 0, 53 29.5 C 75 59, 42 43, 119 17');
svg.appendChild(path);
const ps = Array.from({length: 7}, () => makeDraggablePoint(svg));
for (const p of ps) {
const s = i => {
const [x, y] = ps[i].p();
return `${x} ${y}`;
}
p.subscribe(() => {
path.setAttribute(
'd',
`M ${s(0)} C ${s(1)}, ${s(2)}, ${s(3)} C ${s(4)}, ${s(5)}, ${s(6)}`
);
});
}
ps[0].move(100, 100);
ps[1].move(100, 150);
ps[2].move(200, 50);
ps[3].move(200, 100);
ps[4].move(200, 150);
ps[5].move(300, 50);
ps[6].move(300, 100);
link2(ps[0], ps[1]);
link3(ps[2], ps[3], ps[4]);
link2(ps[6], ps[5]);
<svg id="board" width="400" height="200" style="border: 1px solid gray;"></svg>