自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

純Javascript實(shí)現(xiàn)平滑曲線(xiàn)生成

開(kāi)發(fā) 前端
很多時(shí)候,我們都需要通過(guò)繪制一些折線(xiàn),然后讓計(jì)算機(jī)平滑的連接起來(lái),或者是生成一些平滑的面。

純Javascript實(shí)現(xiàn)平滑曲線(xiàn)生成

前言

平滑曲線(xiàn)生成是一個(gè)很實(shí)用的技術(shù)。

很多時(shí)候,我們都需要通過(guò)繪制一些折線(xiàn),然后讓計(jì)算機(jī)平滑的連接起來(lái),或者是生成一些平滑的面。

先來(lái)看下最終效果(紅色為我們輸入的直線(xiàn),藍(lán)色為擬合過(guò)后的曲線(xiàn)) 首尾可以特殊處理讓圖形看起來(lái)更好)。

實(shí)現(xiàn)思路是利用貝塞爾曲線(xiàn)進(jìn)行擬合。

貝塞爾曲線(xiàn)簡(jiǎn)介

貝塞爾曲線(xiàn)(英語(yǔ):Bézier curve)是計(jì)算機(jī)圖形學(xué)中相當(dāng)重要的參數(shù)曲線(xiàn)。

二次貝塞爾曲線(xiàn)

二次方貝塞爾曲線(xiàn)的路徑由給定點(diǎn)P0、P1、P2的函數(shù)B(t)追蹤:

三次貝塞爾曲線(xiàn)

對(duì)于三次曲線(xiàn),可由線(xiàn)性貝塞爾曲線(xiàn)描述的中介點(diǎn)Q0、Q1、Q2,和由二次曲線(xiàn)描述的點(diǎn)R0、R1所建構(gòu):

貝塞爾曲線(xiàn)計(jì)算函數(shù)

根據(jù)上面的公式我們可以得到計(jì)算函數(shù)。

二階

/**
*
*
? @param {number} p0
? @param {number} p1
? @param {number} p2
? @param {number} t
? @return {*}
? @memberof Path
*/
bezier2P(p0: number, p1: number, p2: number, t: number) {
const P0 = p0 * Math.pow(1 - t, 2);
const P1 = p1 * 2 * t * (1 - t);
const P2 = p2 * t * t;
return P0 + P1 + P2;
}
/**
?
?
? @param {Point} p0
? @param {Point} p1
? @param {Point} p2
? @param {number} num
? @param {number} tick
? @return {*} {Point}
? @memberof Path
*/
getBezierNowPoint2P(
p0: Point,
p1: Point,
p2: Point,
num: number,
tick: number,
): Point {
return {
x: this.bezier2P(p0.x, p1.x, p2.x, num * tick),
y: this.bezier2P(p0.y, p1.y, p2.y, num * tick),
};
}
/**? 生成二次方貝塞爾曲線(xiàn)頂點(diǎn)數(shù)據(jù)
?
? @param {Point} p0
? @param {Point} p1
? @param {Point} p2
? @param {number} [num=100]
? @param {number} [tick=1]
? @return {*}
? @memberof Path
*/
create2PBezier(
p0: Point,
p1: Point,
p2: Point,
num: number = 100,
tick: number = 1,
) {
const t = tick / (num - 1);
const points = [];
for (let i = 0; i < num; i++) {
const point = this.getBezierNowPoint2P(p0, p1, p2, i, t);
points.push({x: point.x, y: point.y});
}
return points;
}

三階

/**
? 三次方塞爾曲線(xiàn)公式
?
? @param {number} p0
? @param {number} p1
? @param {number} p2
? @param {number} p3
? @param {number} t
? @return {*}
? @memberof Path
*/
bezier3P(p0: number, p1: number, p2: number, p3: number, t: number) {
const P0 = p0 * Math.pow(1 - t, 3);
const P1 = 3 * p1 * t * Math.pow(1 - t, 2);
const P2 = 3 * p2 * Math.pow(t, 2) * (1 - t);
const P3 = p3 * Math.pow(t, 3);
return P0 + P1 + P2 + P3;
}
/**
? 獲取坐標(biāo)
?
? @param {Point} p0
? @param {Point} p1
? @param {Point} p2
? @param {Point} p3
? @param {number} num
? @param {number} tick
? @return {*}
? @memberof Path
*/
getBezierNowPoint3P(
p0: Point,
p1: Point,
p2: Point,
p3: Point,
num: number,
tick: number,
) {
return {
x: this.bezier3P(p0.x, p1.x, p2.x, p3.x, num * tick),
y: this.bezier3P(p0.y, p1.y, p2.y, p3.y, num * tick),
};
}
/**
? 生成三次方貝塞爾曲線(xiàn)頂點(diǎn)數(shù)據(jù)
?
? @param {Point} p0 起始點(diǎn) { x : number, y : number}
? @param {Point} p1 控制點(diǎn)1 { x : number, y : number}
? @param {Point} p2 控制點(diǎn)2 { x : number, y : number}
? @param {Point} p3 終止點(diǎn) { x : number, y : number}
? @param {number} [num=100]
? @param {number} [tick=1]
? @return {Point []}
? @memberof Path
*/
create3PBezier(
p0: Point,
p1: Point,
p2: Point,
p3: Point,
num: number = 100,
tick: number = 1,
) {
const pointMum = num;
const _tick = tick;
const t = _tick / (pointMum - 1);
const points = [];
for (let i = 0; i < pointMum; i++) {
const point = this.getBezierNowPoint3P(p0, p1, p2, p3, i, t);
points.push({x: point.x, y: point.y});
}
return points;
}

擬合算法

問(wèn)題在于如何得到控制點(diǎn),我們以比較簡(jiǎn)單的方法:

  • 取p1-pt-p2的角平分線(xiàn),c1c2垂直于該條角平分線(xiàn)c2為p2的投影點(diǎn)。
  • 取短邊作為c1-pt c2-pt的長(zhǎng)度。
  • 對(duì)該長(zhǎng)度進(jìn)行縮放,這個(gè)長(zhǎng)度可以大概理解為曲線(xiàn)的彎曲程度。 

ab線(xiàn)段:這里簡(jiǎn)單處理,只使用了二階的曲線(xiàn)生成。

PS:這里可以按照個(gè)人想法處理。

bc線(xiàn)段:使用abc計(jì)算出來(lái)的控制點(diǎn)c2和bcd計(jì)算出來(lái)的控制點(diǎn)c3以此類(lèi)推。

/**
? 生成平滑曲線(xiàn)所需的控制點(diǎn)
?
? @param {Vector2D} p1
? @param {Vector2D} pt
? @param {Vector2D} p2
? @param {number} [ratio=0.3]
? @return {*}
? @memberof Path
*/
createSmoothLineControlPoint(
p1: Vector2D,
pt: Vector2D,
p2: Vector2D,
ratio: number = 0.3,
) {
const vec1T: Vector2D = vector2dMinus(p1, pt);
const vecT2: Vector2D = vector2dMinus(p1, pt);
const len1: number = vec1T.length;
const len2: number = vecT2.length;
const v: number = len1 / len2;
let delta;
if (v > 1) {
delta = vector2dMinus(
p1,
vector2dPlus(pt, vector2dMinus(p2, pt).scale(1 / v)),
);
} else {
delta = vector2dMinus(
vector2dPlus(pt, vector2dMinus(p1, pt).scale(v)),
p2,
);
}
delta = delta.scale(ratio);
const control1: Point = {
x: vector2dPlus(pt, delta).x,
y: vector2dPlus(pt, delta).y,
};
const control2: Point = {
x: vector2dMinus(pt, delta).x,
y: vector2dMinus(pt, delta).y,
};
return {control1, control2};
}
/**
? 平滑曲線(xiàn)生成
?
? @param {Point []} points
? @param {number} ratio
? @return {*}
? @memberof Path
*/
createSmoothLine(points: Point[], ratio: number = 0.3) {
const len = points.length;
let resultPoints = [];
const controlPoints = [];
if (len < 3) return;
for (let i = 0; i < len - 2; i++) {
const {control1, control2} = this.createSmoothLineControlPoint(
new Vector2D(points[i].x, points[i].y),
new Vector2D(points[i + 1].x, points[i + 1].y),
new Vector2D(points[i + 2].x, points[i + 2].y),
ratio,
);
controlPoints.push(control1);
controlPoints.push(control2);
let points1;
let points2;
// 首端控制點(diǎn)只用一個(gè)
if (i === 0) {
points1 = this.create2PBezier(points[i], control1, points[i + 1], 50);
} else {
console.log(controlPoints);
points1 = this.create3PBezier(
points[i],
controlPoints[2 * i - 1],
control1,
points[i + 1],
50,
);
}
// 尾端部分
if (i + 2 === len - 1) {
points2 = this.create2PBezier(
points[i + 1],
control2,
points[i + 2],
50,
);
}
if (i + 2 === len - 1) {
resultPoints = [...resultPoints, ...points1, ...points2];
} else {
resultPoints = [...resultPoints, ...points1];
}
}
return resultPoints;
}

案例代碼

const input = [
{ x: 0, y: 0 },
{ x: 150, y: 150 },
{ x: 300, y: 0 },
{ x: 400, y: 150 },
{ x: 500, y: 0 },
{ x: 650, y: 150 },
]
const s = path.createSmoothLine(input);
let ctx = document.getElementById('cv').getContext('2d');
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(0, 0);
for (let i = 0; i < s.length; i++) {
ctx.lineTo(s[i].x, s[i].y);
}
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, 0);
for (let i = 0; i < input.length; i++) {
ctx.lineTo(input[i].x, input[i].y);
}
ctx.strokeStyle = 'red';
ctx.stroke();
document.getElementById('btn').addEventListener('click', () => {
let app = document.getElementById('app');
let index = 0;
let move = () => {
if (index < s.length) {
app.style.left = s[index].x - 10 + 'px';
app.style.top = s[index].y - 10 + 'px';
index++;
requestAnimationFrame(move)
}
}
move()
})
責(zé)任編輯:龐桂玉 來(lái)源: 得物技術(shù)
相關(guān)推薦

2020-08-13 06:56:57

Javascript插件前端

2014-12-08 10:56:24

JavaScript

2013-04-02 13:04:07

ListView平滑滾

2021-10-19 22:23:47

CSSBeautiful按鈕

2024-08-29 08:13:58

2011-11-03 09:13:27

JavaScript

2022-02-21 07:02:16

CSSbeautiful按鈕

2020-11-04 13:55:06

CSS密室逃脫前端

2022-08-10 16:08:38

鴻蒙CSS

2013-04-08 14:07:28

CSS

2009-04-01 10:41:00

GSMWCDMA的

2012-06-28 10:21:37

JavaScript

2021-05-07 09:18:04

CSS 文字動(dòng)畫(huà)技巧

2017-09-18 16:13:59

前端圖像處理人臉識(shí)別

2022-08-29 17:39:53

應(yīng)用開(kāi)發(fā)css動(dòng)畫(huà)

2021-01-19 12:16:10

CSS前端UI

2011-03-14 13:10:43

jQueryscroll滾動(dòng)

2021-12-03 06:02:19

CSS濾鏡前端

2019-08-09 08:05:11

MQ平滑遷移架構(gòu)

2017-02-24 12:00:35

iOS代碼AutoLayout
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)