怎么利用Javascript生成平滑曲线
发表于:2025-02-01 作者:千家信息网编辑
千家信息网最后更新 2025年02月01日,这篇文章主要讲解了"怎么利用Javascript生成平滑曲线",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么利用Javascript生成平滑曲线"吧
千家信息网最后更新 2025年02月01日怎么利用Javascript生成平滑曲线
这篇文章主要讲解了"怎么利用Javascript生成平滑曲线",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"怎么利用Javascript生成平滑曲线"吧!
前言
平滑曲线生成是一个很实用的技术
很多时候,我们都需要通过绘制一些折线,然后让计算机平滑的连接起来,
先来看下最终效果(红色为我们输入的直线,蓝色为拟合过后的曲线) 首尾可以特殊处理让图形看起来更好:)
实现思路是利用贝塞尔曲线进行拟合
贝塞尔曲线简介
贝塞尔曲线(英语:Bézier curve)是计算机图形学中相当重要的参数曲线。
二次贝塞尔曲线
二次方贝塞尔曲线的路径由给定点P0、P1、P2的函数B(t)追踪:
三次贝塞尔曲线
对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0、Q1、Q2,和由二次曲线描述的点R0、R1所建构
贝塞尔曲线计算函数
根据上面的公式我们可有得到计算函数
二阶
/** * * * @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), }; } /** * 生成二次方贝塞尔曲线顶点数据 * * @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; }
三阶
/** * 三次方塞尔曲线公式 * * @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; } /** * 获取坐标 * * @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), }; } /** * 生成三次方贝塞尔曲线顶点数据 * * @param {Point} p0 起始点 { x : number, y : number} * @param {Point} p1 控制点1 { x : number, y : number} * @param {Point} p2 控制点2 { x : number, y : number} * @param {Point} p3 终止点 { 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; }
拟合算法
问题在于如何得到控制点,我们以比较简单的方法
取 p1-pt-p2的角平分线 c1c2垂直于该条角平分线 c2为p2的投影点取短边作为c1-pt c2-pt的长度对该长度进行缩放 这个长度可以大概理解为曲线的弯曲程度
ab线段 这里简单处理 只使用了二阶的曲线生成 -> ? 这里可以按照个人想法处理
bc线段使用abc计算出来的控制点c2和bcd计算出来的控制点c3 以此类推
/** * 生成平滑曲线所需的控制点 * * @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}; } /** * 平滑曲线生成 * * @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; // 首端控制点只用一个 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() })
附录:Vector2D相关的代码
/** * * * @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 += v.x; this.y += v.y; return this; } /** * * * @param {*} v * @return {*} * @memberof Vector2D */ sub(v) { this.x -= v.x; this.y -= v.y; 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 * v.y - v.x * this.y; } /** * * * @param {*} v * @return {*} * @memberof Vector2D */ dot(v) { return this.x * v.x + v.y * this.y; } /** * 归一 * * @return {*} * @memberof Vector2D */ normalize() { return this.scale(1 / this.length); }}/** * 向量的加法 * * @param {*} vec1 * @param {*} vec2 * @return {Vector2D} */function vector2dPlus(vec1, vec2) { return new Vector2D(vec1.x + vec2.x, vec1.y + vec2.y);}/** * 向量的减法 * * @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};
感谢各位的阅读,以上就是"怎么利用Javascript生成平滑曲线"的内容了,经过本文的学习后,相信大家对怎么利用Javascript生成平滑曲线这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!
曲线
生成
贝塞
贝塞尔
平滑
控制点
控制
函数
长度
处理
学习
代码
公式
内容
向量
图形
思路
数据
线段
计算机
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
现在多少企业上了服务器虚拟化
应用软件开发概念
罗技音乐服务器winpe
静安区标准网络技术怎么样
数据库很难学吗
理正基坑数据库导不出来
漳州欧重网络技术有限公司
河南省航测集成服务器
软件开发是否属于高新
网络安全内容检测技术
光电子与网络技术
登游戏时出现数据库连接失败
yii 多个数据库查询
软件开发公司建筑结构
网络安全哪些机构有课程
mac 下数据库管理工具
上传服务器文件的软件
我的世界如何建服务器
口袋魔域服务器查询
网络安全系统测试方案
软件开发有什么资格证书
静安区标准网络技术怎么样
无锡网络软件开发哪家好
网络安全等相关产业
上海网络技术开发生产过程
深圳市找房网络技术有限公司
广东前端软件开发服务
网络安全教育主题班会板报
适合软件开发的城市
艾斯网络技术有限公司