PrefaceSmooth curve generation is a very practical technology Often, we need to draw some polylines and then let the computer connect them smoothly. Let's take a look at the final effect first (the red line is the straight line we input, and the blue line is the curve after fitting). The beginning and the end can be specially processed to make the graph look better:) The idea is to use Bezier curve for fitting Introduction to Bezier curvesThe Bézier curve is a very important parametric curve in computer graphics. Quadratic Bezier curveThe path of a quadratic Bezier curve is traced by the function B(t) given the points P0, P1, P2: Cubic Bezier curveFor a cubic curve, it can be constructed by the intermediate points Q0, Q1, Q2 described by the linear Bezier curve and the points R0 and R1 described by the quadratic curve. Bezier curve calculation functionAccording to the above formula, we can get the calculation function Second order /** * * * @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), }; } /** * Generate quadratic Bezier curve vertex data* * @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; } Third level /** * Cubic Searl Curve Formula* * @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; } /** * Get coordinates * * @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), }; } /** * Generate cubic Bezier curve vertex data* * @param {Point} p0 starting point {x: number, y: number} * @param {Point} p1 control point 1 { x : number, y : number} * @param {Point} p2 control point 2 { x : number, y : number} * @param {Point} p3 end point {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; } Fitting algorithmThe problem is how to get the control points. We use a relatively simple method Take the angle bisector c1c2 of p1-pt-p2, which is perpendicular to the angle bisector c2. Take the short side as the length of c1-pt c2-pt. Scale the length. This length can be roughly understood as the curvature of the curve. The ab line segment is simply processed here and only uses the second-order curve generation-> 🌈 You can process it according to your personal ideas The bc line segment uses the control point c2 calculated by abc and the control point c3 calculated by bcd, and so on. /** * Control points needed to generate a smooth curve * * @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}; } /** * Smooth curve generation * * @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; // The first control point only uses one 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, ); } // Tail part 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; } Example code 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() }) Appendix: Vector2D related code/** * * * @class Vector2D * @extends {Array} */ class Vector2D extends Array { /** * Creates an instance of Vector2D. * @param {number} [x=1] * @param {number} [y=0] * @memberof Vector2D * */ constructor(x: number = 1, y: number = 0) { super(); this.x = x; this.y = y; } /** * * @param {number} v * @memberof Vector2D */ set x(v) { this[0] = v; } /** * * @param {number} v * @memberof Vector2D */ set y(v) { this[1] = v; } /** * * * @readonly * @memberof Vector2D */ get x() { return this[0]; } /** * * * @readonly * @memberof Vector2D */ get y() { return this[1]; } /** * * * @readonly * @memberof Vector2D */ get length() { return Math.hypot(this.x, this.y); } /** * * * @readonly * @memberof Vector2D */ get dir() { return Math.atan2(this.y, this.x); } /** * * * @return {*} * @memberof Vector2D */ copy() { return new Vector2D(this.x, this.y); } /** * * * @param {*} v * @return {*} * @memberof Vector2D */ add(v) { this.x += vx; this.y += vy; return this; } /** * * * @param {*} v * @return {*} * @memberof Vector2D */ sub(v) { this.x -= vx; this.y -= vy; return this; } /** * * * @param {*} a * @return {Vector2D} * @memberof Vector2D */ scale(a) { this.x *= a; this.y *= a; return this; } /** * * * @param {*} rad * @return {*} * @memberof Vector2D */ rotate(rad) { const c = Math.cos(rad); const s = Math.sin(rad); const [x, y] = this; this.x = x * c + y * -s; this.y = x * s + y * c; return this; } /** * * * @param {*} v * @return {*} * @memberof Vector2D */ cross(v) { return this.x * vy - vx * this.y; } /** * * * @param {*} v * @return {*} * @memberof Vector2D */ dot(v) { return this.x * vx + vy * this.y; } /** * Normalization* * @return {*} * @memberof Vector2D */ normalize() { return this.scale(1 / this.length); } } /** * Vector addition * * @param {*} vec1 * @param {*} vec2 * @return {Vector2D} */ function vector2dPlus(vec1, vec2) { return new Vector2D(vec1.x + vec2.x, vec1.y + vec2.y); } /** * Vector subtraction * * @param {*} vec1 * @param {*} vec2 * @return {Vector2D} */ function vector2dMinus(vec1, vec2) { return new Vector2D(vec1.x - vec2.x, vec1.y - vec2.y); } export {Vector2D, vector2dPlus, vector2dMinus}; SummarizeThis is the end of this article on how to use Javascript to generate smooth curves. For more information about JS generating smooth curves, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future! |
<<: Guide to Efficient Use of MySQL Indexes
>>: DockerToolBox file mounting implementation code
Cluster Deployment Overview 172.22.12.20 172.22.1...
Overview Backup is the basis of disaster recovery...
1. Property List Copy code The code is as follows:...
Table of contents As a global variable Variable H...
1. Prerequisites JDK has been installed echo $PAT...
Preface We may have heard of the concept of rowid...
Firefox, Opera and other browsers do not support W...
Preview versions of Safari (Technology Preview 10...
introduce In a distributed system, distributed lo...
Main library configuration 1. Configure mysql vim...
Before talking about data responsiveness, we need...
MySQL DDL statements What is DDL, DML. DDL is dat...
Table of contents introduce Installation and Usag...
Query Rewrite Plugin As of MySQL 5.7.6, MySQL Ser...
Effect: First create five vue interfaces 1.home.v...