그리기 응용 프로그램의 경우 마우스 움직임 좌표를 배열에 저장 한 다음 lineTo를 사용하여 그립니다. 결과 라인이 부드럽 지 않습니다. 수집 된 모든 점 사이에 단일 곡선을 생성하려면 어떻게해야합니까?
나는 봤지만 선 그리기를위한 3 개의 함수 만 찾았습니다 .2 개의 샘플 포인트에는 간단히을 사용하십시오 lineTo
. 3 샘플 포인트를 들어 quadraticCurveTo
, 4 샘플 포인트, bezierCurveTo
.
( bezierCurveTo
배열에서 4 포인트마다 a 를 그리려고 시도했지만 연속 부드러운 곡선 대신 4 샘플 포인트마다 꼬임이 발생했습니다.)
5 개 이상의 샘플 포인트로 부드러운 곡선을 그리는 함수를 작성하려면 어떻게해야합니까?
답변
후속 샘플 포인트를 분리 된 “curveTo”유형 함수와 함께 결합 할 때의 문제는 곡선이 만나는 곳이 부드럽 지 않다는 것입니다. 두 곡선이 종점을 공유하지만 완전히 분리 된 제어점의 영향을 받기 때문입니다. 한 가지 해결책은 다음 2 개의 후속 샘플 포인트 사이의 중간 점을 “곡선”시키는 것입니다. 이 새로운 보간 된 점을 사용하여 곡선을 결합하면 끝점에서 부드럽게 전환됩니다 (한 반복의 끝 점이 다음 반복 의 제어점이 됩니다). 즉, 두 개의 분리 된 곡선은 이제 훨씬 더 공통적입니다.
이 솔루션은 “Foundation ActionScript 3.0 애니메이션 : 사물 이동”책에서 추출되었습니다. p.95-렌더링 기법 : 여러 커브 만들기
참고 :이 솔루션은 실제로 내 질문의 제목 인 각 점을 통과하지는 않지만 (샘플 점을 통과하는 곡선에 가깝지만 샘플 점을 통과하지는 않습니다) 내 목적 (도면 응용 프로그램), 그것은 나를 위해 충분하고 시각적으로 차이를 말할 수 없습니다. 가 있다 모든 샘플 포인트를 통과 할 수있는 솔루션이 있지만 훨씬 더 (참조 복잡 http://www.cartogrammar.com/blog/actionscript-curves-update/를 )
근사법에 대한 도면 코드는 다음과 같습니다.
// move to the first point
ctx.moveTo(points[0].x, points[0].y);
for (i = 1; i < points.length - 2; i ++)
{
var xc = (points[i].x + points[i + 1].x) / 2;
var yc = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
}
// curve through the last two points
ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y);
답변
조금 늦었지만 기록을 위해.
기본 스플라인 (일명 표준 스플라인)을 사용하여 점을 통과하는 부드러운 곡선을 그려서 부드러운 선을 얻을 수 있습니다 .
캔버스 에이 기능을 만들었습니다. 다목적 성을 높이기 위해 세 가지 기능으로 나뉩니다. 기본 랩퍼 기능은 다음과 같습니다.
function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) {
showPoints = showPoints ? showPoints : false;
ctx.beginPath();
drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));
if (showPoints) {
ctx.stroke();
ctx.beginPath();
for(var i=0;i<ptsa.length-1;i+=2)
ctx.rect(ptsa[i] - 2, ptsa[i+1] - 2, 4, 4);
}
}
곡선을 그리려면 x, y가 다음과 같은 순서로 배열됩니다 x1,y1, x2,y2, ...xn,yn
.
다음과 같이 사용하십시오.
var myPoints = [10,10, 40,30, 100,10]; //minimum two points
var tension = 1;
drawCurve(ctx, myPoints); //default tension=0.5
drawCurve(ctx, myPoints, tension);
위의 함수는 스무딩 포인트를 계산하기위한 두 개의 하위 함수를 호출합니다. 이것은 새로운 포인트를 가진 배열을 반환합니다-이것은 스무딩 포인트를 계산하는 핵심 함수입니다.
function getCurvePoints(pts, tension, isClosed, numOfSegments) {
// use input value if provided, or use a default value
tension = (typeof tension != 'undefined') ? tension : 0.5;
isClosed = isClosed ? isClosed : false;
numOfSegments = numOfSegments ? numOfSegments : 16;
var _pts = [], res = [], // clone array
x, y, // our x,y coords
t1x, t2x, t1y, t2y, // tension vectors
c1, c2, c3, c4, // cardinal points
st, t, i; // steps based on num. of segments
// clone array so we don't change the original
//
_pts = pts.slice(0);
// The algorithm require a previous and next point to the actual point array.
// Check if we will draw closed or open curve.
// If closed, copy end points to beginning and first points to end
// If open, duplicate first points to befinning, end points to end
if (isClosed) {
_pts.unshift(pts[pts.length - 1]);
_pts.unshift(pts[pts.length - 2]);
_pts.unshift(pts[pts.length - 1]);
_pts.unshift(pts[pts.length - 2]);
_pts.push(pts[0]);
_pts.push(pts[1]);
}
else {
_pts.unshift(pts[1]); //copy 1. point and insert at beginning
_pts.unshift(pts[0]);
_pts.push(pts[pts.length - 2]); //copy last point and append
_pts.push(pts[pts.length - 1]);
}
// ok, lets start..
// 1. loop goes through point array
// 2. loop goes through each segment between the 2 pts + 1e point before and after
for (i=2; i < (_pts.length - 4); i+=2) {
for (t=0; t <= numOfSegments; t++) {
// calc tension vectors
t1x = (_pts[i+2] - _pts[i-2]) * tension;
t2x = (_pts[i+4] - _pts[i]) * tension;
t1y = (_pts[i+3] - _pts[i-1]) * tension;
t2y = (_pts[i+5] - _pts[i+1]) * tension;
// calc step
st = t / numOfSegments;
// calc cardinals
c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1;
c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2);
c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st;
c4 = Math.pow(st, 3) - Math.pow(st, 2);
// calc x and y cords with common control vectors
x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;
y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;
//store points in array
res.push(x);
res.push(y);
}
}
return res;
}
그리고 실제로 점을 부드러운 곡선 또는 x, y 배열이있는 한 다른 세그먼트 선으로 그리려면 :
function drawLines(ctx, pts) {
ctx.moveTo(pts[0], pts[1]);
for(i=2;i<pts.length-1;i+=2) ctx.lineTo(pts[i], pts[i+1]);
}
결과는 다음과 같습니다.
캔버스를 쉽게 확장하여 대신 다음과 같이 호출 할 수 있습니다.
ctx.drawCurve(myPoints);
자바 스크립트에 다음을 추가하십시오.
if (CanvasRenderingContext2D != 'undefined') {
CanvasRenderingContext2D.prototype.drawCurve =
function(pts, tension, isClosed, numOfSegments, showPoints) {
drawCurve(this, pts, tension, isClosed, numOfSegments, showPoints)}
}
NPM ( npm i cardinal-spline-js
) 또는 GitLab 에서보다 최적화 된 버전을 찾을 수 있습니다 .
답변
첫 번째 답변은 모든 포인트를 통과하지는 않습니다. 이 그래프는 모든 점을 정확하게 통과하며 [{x :, y :}] n 점과 같은 점을 가진 완벽한 곡선이됩니다.
var points = [{x:1,y:1},{x:2,y:3},{x:3,y:4},{x:4,y:2},{x:5,y:6}] //took 5 example points
ctx.moveTo((points[0].x), points[0].y);
for(var i = 0; i < points.length-1; i ++)
{
var x_mid = (points[i].x + points[i+1].x) / 2;
var y_mid = (points[i].y + points[i+1].y) / 2;
var cp_x1 = (x_mid + points[i].x) / 2;
var cp_x2 = (x_mid + points[i+1].x) / 2;
ctx.quadraticCurveTo(cp_x1,points[i].y ,x_mid, y_mid);
ctx.quadraticCurveTo(cp_x2,points[i+1].y ,points[i+1].x,points[i+1].y);
}
답변
마찬가지로 다니엘 하워드는 지적 , 롭 스펜서는 당신이에 원하는 것을 설명 http://scaledinnovation.com/analytics/splines/aboutSplines.html .
대화식 데모는 다음과 같습니다. http://jsbin.com/ApitIxo/2/
여기 jsbin이 다운 된 경우 스 니펫입니다.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Demo smooth connection</title>
</head>
<body>
<div id="display">
Click to build a smooth path.
(See Rob Spencer's <a href="http://scaledinnovation.com/analytics/splines/aboutSplines.html">article</a>)
<br><label><input type="checkbox" id="showPoints" checked> Show points</label>
<br><label><input type="checkbox" id="showControlLines" checked> Show control lines</label>
<br>
<label>
<input type="range" id="tension" min="-1" max="2" step=".1" value=".5" > Tension <span id="tensionvalue">(0.5)</span>
</label>
<div id="mouse"></div>
</div>
<canvas id="canvas"></canvas>
<style>
html { position: relative; height: 100%; width: 100%; }
body { position: absolute; left: 0; right: 0; top: 0; bottom: 0; }
canvas { outline: 1px solid red; }
#display { position: fixed; margin: 8px; background: white; z-index: 1; }
</style>
<script>
function update() {
$("tensionvalue").innerHTML="("+$("tension").value+")";
drawSplines();
}
$("showPoints").onchange = $("showControlLines").onchange = $("tension").onchange = update;
// utility function
function $(id){ return document.getElementById(id); }
var canvas=$("canvas"), ctx=canvas.getContext("2d");
function setCanvasSize() {
canvas.width = parseInt(window.getComputedStyle(document.body).width);
canvas.height = parseInt(window.getComputedStyle(document.body).height);
}
window.onload = window.onresize = setCanvasSize();
function mousePositionOnCanvas(e) {
var el=e.target, c=el;
var scaleX = c.width/c.offsetWidth || 1;
var scaleY = c.height/c.offsetHeight || 1;
if (!isNaN(e.offsetX))
return { x:e.offsetX*scaleX, y:e.offsetY*scaleY };
var x=e.pageX, y=e.pageY;
do {
x -= el.offsetLeft;
y -= el.offsetTop;
el = el.offsetParent;
} while (el);
return { x: x*scaleX, y: y*scaleY };
}
canvas.onclick = function(e){
var p = mousePositionOnCanvas(e);
addSplinePoint(p.x, p.y);
};
function drawPoint(x,y,color){
ctx.save();
ctx.fillStyle=color;
ctx.beginPath();
ctx.arc(x,y,3,0,2*Math.PI);
ctx.fill()
ctx.restore();
}
canvas.onmousemove = function(e) {
var p = mousePositionOnCanvas(e);
$("mouse").innerHTML = p.x+","+p.y;
};
var pts=[]; // a list of x and ys
// given an array of x,y's, return distance between any two,
// note that i and j are indexes to the points, not directly into the array.
function dista(arr, i, j) {
return Math.sqrt(Math.pow(arr[2*i]-arr[2*j], 2) + Math.pow(arr[2*i+1]-arr[2*j+1], 2));
}
// return vector from i to j where i and j are indexes pointing into an array of points.
function va(arr, i, j){
return [arr[2*j]-arr[2*i], arr[2*j+1]-arr[2*i+1]]
}
function ctlpts(x1,y1,x2,y2,x3,y3) {
var t = $("tension").value;
var v = va(arguments, 0, 2);
var d01 = dista(arguments, 0, 1);
var d12 = dista(arguments, 1, 2);
var d012 = d01 + d12;
return [x2 - v[0] * t * d01 / d012, y2 - v[1] * t * d01 / d012,
x2 + v[0] * t * d12 / d012, y2 + v[1] * t * d12 / d012 ];
}
function addSplinePoint(x, y){
pts.push(x); pts.push(y);
drawSplines();
}
function drawSplines() {
clear();
cps = []; // There will be two control points for each "middle" point, 1 ... len-2e
for (var i = 0; i < pts.length - 2; i += 1) {
cps = cps.concat(ctlpts(pts[2*i], pts[2*i+1],
pts[2*i+2], pts[2*i+3],
pts[2*i+4], pts[2*i+5]));
}
if ($("showControlLines").checked) drawControlPoints(cps);
if ($("showPoints").checked) drawPoints(pts);
drawCurvedPath(cps, pts);
}
function drawControlPoints(cps) {
for (var i = 0; i < cps.length; i += 4) {
showPt(cps[i], cps[i+1], "pink");
showPt(cps[i+2], cps[i+3], "pink");
drawLine(cps[i], cps[i+1], cps[i+2], cps[i+3], "pink");
}
}
function drawPoints(pts) {
for (var i = 0; i < pts.length; i += 2) {
showPt(pts[i], pts[i+1], "black");
}
}
function drawCurvedPath(cps, pts){
var len = pts.length / 2; // number of points
if (len < 2) return;
if (len == 2) {
ctx.beginPath();
ctx.moveTo(pts[0], pts[1]);
ctx.lineTo(pts[2], pts[3]);
ctx.stroke();
}
else {
ctx.beginPath();
ctx.moveTo(pts[0], pts[1]);
// from point 0 to point 1 is a quadratic
ctx.quadraticCurveTo(cps[0], cps[1], pts[2], pts[3]);
// for all middle points, connect with bezier
for (var i = 2; i < len-1; i += 1) {
// console.log("to", pts[2*i], pts[2*i+1]);
ctx.bezierCurveTo(
cps[(2*(i-1)-1)*2], cps[(2*(i-1)-1)*2+1],
cps[(2*(i-1))*2], cps[(2*(i-1))*2+1],
pts[i*2], pts[i*2+1]);
}
ctx.quadraticCurveTo(
cps[(2*(i-1)-1)*2], cps[(2*(i-1)-1)*2+1],
pts[i*2], pts[i*2+1]);
ctx.stroke();
}
}
function clear() {
ctx.save();
// use alpha to fade out
ctx.fillStyle = "rgba(255,255,255,.7)"; // clear screen
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.restore();
}
function showPt(x,y,fillStyle) {
ctx.save();
ctx.beginPath();
if (fillStyle) {
ctx.fillStyle = fillStyle;
}
ctx.arc(x, y, 5, 0, 2*Math.PI);
ctx.fill();
ctx.restore();
}
function drawLine(x1, y1, x2, y2, strokeStyle){
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
if (strokeStyle) {
ctx.save();
ctx.strokeStyle = strokeStyle;
ctx.stroke();
ctx.restore();
}
else {
ctx.save();
ctx.strokeStyle = "pink";
ctx.stroke();
ctx.restore();
}
}
</script>
</body>
</html>
답변
나는 이것이 잘 작동하는 것을 발견했다.
function drawCurve(points, tension) {
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
var t = (tension != null) ? tension : 1;
for (var i = 0; i < points.length - 1; i++) {
var p0 = (i > 0) ? points[i - 1] : points[0];
var p1 = points[i];
var p2 = points[i + 1];
var p3 = (i != points.length - 2) ? points[i + 2] : p2;
var cp1x = p1.x + (p2.x - p0.x) / 6 * t;
var cp1y = p1.y + (p2.y - p0.y) / 6 * t;
var cp2x = p2.x - (p3.x - p1.x) / 6 * t;
var cp2y = p2.y - (p3.y - p1.y) / 6 * t;
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
}
ctx.stroke();
}
답변
솔루션을 다른 게시물에 게시하지 않고 추가하기로 결정했습니다. 아래는 내가 구축 한 솔루션이며 완벽하지는 않지만 지금까지 출력은 좋습니다.
중요 : 모든 포인트를 통과합니다!
당신이 어떤 아이디어가 있다면, 그것을 더 나은 만들기 위해 나에게 공유하십시오. 감사.
이전 이후의 비교는 다음과 같습니다.
테스트하려면이 코드를 HTML로 저장하십시오.
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="1200" height="700" style="border:1px solid #d3d3d3;">Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var cv = document.getElementById("myCanvas");
var ctx = cv.getContext("2d");
function gradient(a, b) {
return (b.y-a.y)/(b.x-a.x);
}
function bzCurve(points, f, t) {
//f = 0, will be straight line
//t suppose to be 1, but changing the value can control the smoothness too
if (typeof(f) == 'undefined') f = 0.3;
if (typeof(t) == 'undefined') t = 0.6;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
var m = 0;
var dx1 = 0;
var dy1 = 0;
var preP = points[0];
for (var i = 1; i < points.length; i++) {
var curP = points[i];
nexP = points[i + 1];
if (nexP) {
m = gradient(preP, nexP);
dx2 = (nexP.x - curP.x) * -f;
dy2 = dx2 * m * t;
} else {
dx2 = 0;
dy2 = 0;
}
ctx.bezierCurveTo(preP.x - dx1, preP.y - dy1, curP.x + dx2, curP.y + dy2, curP.x, curP.y);
dx1 = dx2;
dy1 = dy2;
preP = curP;
}
ctx.stroke();
}
// Generate random data
var lines = [];
var X = 10;
var t = 40; //to control width of X
for (var i = 0; i < 100; i++ ) {
Y = Math.floor((Math.random() * 300) + 50);
p = { x: X, y: Y };
lines.push(p);
X = X + t;
}
//draw straight line
ctx.beginPath();
ctx.setLineDash([5]);
ctx.lineWidth = 1;
bzCurve(lines, 0, 1);
//draw smooth line
ctx.setLineDash([0]);
ctx.lineWidth = 2;
ctx.strokeStyle = "blue";
bzCurve(lines, 0.3, 1);
</script>
</body>
</html>
답변
KineticJS를 사용해보십시오-점의 배열로 스플라인을 정의 할 수 있습니다. 예를 들면 다음과 같습니다.
기존 URL : http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-spline-tutorial/
