两条平滑曲线相乘_对三次贝塞尔曲线过点平滑中尖角和交叉现象的优化
目前在做等值线等值面相关的功能,用户可拖拽控制点修改等值线,再用等值线生成等值面。因为初始的等值线点数据太多,不利于用户操作,所以先使用道格拉斯-普克算法(Douglas–Peucker)进行等值线抽稀,再将抽稀后的控制点使用贝塞尔曲线算法进行平滑。
对于贝塞尔曲线算法的平滑过程,有人做了很详细的示意图,推荐大家看下贝塞尔曲线算法之JS获取点
可以了解到贝赛尔曲线算法平滑得到的曲线是经过起始点的,同时二阶算法需要三个点,三阶算法需要四个点,四阶算法需要五个点,以此类推。
一般的来说,三阶贝塞尔曲线就已经够用了,而且效果还不错,所以我选择了三次贝塞尔曲线平滑算法来进行控制点的平滑处理。
贝塞尔曲线平滑后的等值线是基本不经过控制点的,考虑到用户操作逻辑,以及点线关系(我的控制点是等值线抽稀得到的,所以等值线是经过控制点的),所以采用三次贝塞尔曲线过点平滑算法来进行控制点的平滑处理。
过点平滑的原理就是以相邻两个控制点为起始点,然后往起始点中间插入其他过程点(不是在起始点直线上选择点),这样平滑得到的曲线是经过起始点的,而曲线如何平滑是由插入的点来控制的,三次贝塞尔曲线需要四个点,那就需要在起始点中间插入两个点。
大致思路就是,先算出相邻原始点的中点,在把相邻中点连成的线段平移到对应的原始点,以平移后的中点作为控制点,相邻原始点为起始点画贝塞尔曲线,这样就保证了连接处的光滑。而贝塞尔曲线本身是光滑的,所以就把这些原始点用光滑曲线连起来了。具体代码及示意图如下:
代码:function createCurve(originPoint, option){
//控制点收缩系数 ,经调试0.6较好
let scale = option.tension || 0.6;
//平滑插值插入的最大点数
let maxpoints = option.pointsPerSeg
let originCount = originPoint.length
let curvePoint = []
let midpoints = []
//生成中点
for(let i = 0 ;i < originCount - 1 ; i++){
midpoints.push([
(originPoint[i][0] + originPoint[i + 1][0])/2.0,
(originPoint[i][1] + originPoint[i + 1][1])/2.0
])
}
//平移中点
let extrapoints = []
for(let i = 1 ;i < originCount - 1 ; i++){
let backi = i - 1;
let midinmid = [
(midpoints[i][0] + midpoints[backi][0])/2.0,
(midpoints[i][1] + midpoints[backi][1])/2.0
]
let offsetx = originPoint[i][0] - midinmid[0];
let offsety = originPoint[i][1] - midinmid[1];
let extraindex = 2 * i;
extrapoints[extraindex] = [
midpoints[backi][0] + offsetx,
midpoints[backi][1] + offsety
]
//朝 originPoint[i]方向收缩
let addx = (extrapoints[extraindex][0] - originPoint[i][0]) * scale;
let addy = (extrapoints[extraindex][1] - originPoint[i][1]) * scale;
extrapoints[extraindex] = [
originPoint[i][0] + addx,
originPoint[i][1] + addy
]
let extranexti = extraindex + 1;
extrapoints[extranexti] = [
midpoints[i][0] + offsetx,
midpoints[i][1] + offsety
]
//朝 originPoint[i]方向收缩
addx = (extrapoints[extranexti][0] - originPoint[i][0]) * scale;
addy = (extrapoints[extranexti][1] - originPoint[i][1]) * scale;
extrapoints[extranexti] = [
originPoint[i][0] + addx,
originPoint[i][1] + addy
]
}
let controlPoint = []
//生成4控制点,产生贝塞尔曲线
for(let i = 1 ;i < originCount - 2 ; i++){
controlPoint[0] = originPoint[i];
let extraindex = 2 * i;
controlPoint[1] = extrapoints[extraindex + 1];
let extranexti = extraindex + 2;
controlPoint[2] = extrapoints[extranexti];
let nexti = i + 1;
controlPoint[3] = originPoint[nexti];
for(let n = maxpoints; n >= 0; n--){
//存入曲线点
curvePoint.push( bezier3func(n / maxpoints, controlPoint) );
}
}
return curvePoint
}
//三次贝塞尔曲线
function bezier3func(uu, controlP){
let partX0 = controlP[0][0] * uu * uu * uu;
let partX1 = 3 * controlP[1][0] * uu * uu * (1 - uu);
let partX2 = 3 * controlP[2][0] * uu * (1 - uu) * (1 - uu);
let partX3 = controlP[3][0] * (1 - uu) * (1 - uu) * (1 - uu);
let partX = partX0 + partX1 + partX2 + partX3;
let partY0 = controlP[0][1] * uu * uu * uu;
let partY1 = 3 * controlP[1][1] * uu * uu * (1 - uu);
let partY2 = 3 * controlP[2][1] * uu * (1 - uu) * (1 - uu);
let partY3 = controlP[3][1] * (1 - uu) * (1 - uu) * (1 - uu);
let partY = partY0 + partY1 + partY2 + partY3;
return [partX, partY]
}
然而事情到这里并没有结束,还有坑需要填,直接使用该算法进行平滑,在实际应用中发现控制点距离近的话,平滑的曲线会有尖角或交叉现象。
比如这样的
还有这样的
这是因为插入的两个新控制点和起始控制点位置特殊,平滑后产生尖角或交叉,如下所示:
优化思路是判断四个点的关系,获取直线控制点1-新控制点1与直线控制点2-新控制点2之间的交点(如果有的话),如果交点在线段控制点1-点1中时,用交点替换点1,线段控制点2-点2同理。或者以控制点1、交点、控制点2三点,然后使用二次贝赛尔曲线算法进行平滑。新的效果示意图如下所示:
优化前与优化后的效果对比如下:左边为优化前的平滑效果,右边是优化后的平滑效果:
优化后的代码:function createCurve(originPoint, option){
//控制点收缩系数 ,经调试0.6较好
let scale = option.tension || 0.6;
//平滑插值插入的最大点数
let maxpoints = option.pointsPerSeg
let originCount = originPoint.length
let curvePoint = []
let midpoints = []
//生成中点
for(let i = 0 ;i < originCount - 1 ; i++){
midpoints.push([
(originPoint[i][0] + originPoint[i + 1][0])/2.0,
(originPoint[i][1] + originPoint[i + 1][1])/2.0
])
}
//平移中点
let extrapoints = []
for(let i = 1 ;i < originCount - 1 ; i++){
let backi = i - 1;
let midinmid = [
(midpoints[i][0] + midpoints[backi][0])/2.0,
(midpoints[i][1] + midpoints[backi][1])/2.0
]
let offsetx = originPoint[i][0] - midinmid[0];
let offsety = originPoint[i][1] - midinmid[1];
let extraindex = 2 * i;
extrapoints[extraindex] = [
midpoints[backi][0] + offsetx,
midpoints[backi][1] + offsety
]
//朝 originPoint[i]方向收缩
let addx = (extrapoints[extraindex][0] - originPoint[i][0]) * scale;
let addy = (extrapoints[extraindex][1] - originPoint[i][1]) * scale;
extrapoints[extraindex] = [
originPoint[i][0] + addx,
originPoint[i][1] + addy
]
let extranexti = extraindex + 1;
extrapoints[extranexti] = [
midpoints[i][0] + offsetx,
midpoints[i][1] + offsety
]
//朝 originPoint[i]方向收缩
addx = (extrapoints[extranexti][0] - originPoint[i][0]) * scale;
addy = (extrapoints[extranexti][1] - originPoint[i][1]) * scale;
extrapoints[extranexti] = [
originPoint[i][0] + addx,
originPoint[i][1] + addy
]
}
let controlPoint = []
//生成4控制点,产生贝塞尔曲线
for(let i = 1 ;i < originCount - 2 ; i++){
controlPoint[0] = originPoint[i];
let extraindex = 2 * i;
controlPoint[1] = extrapoints[extraindex + 1];
let extranexti = extraindex + 2;
controlPoint[2] = extrapoints[extranexti];
let nexti = i + 1;
controlPoint[3] = originPoint[nexti];
let fn = bezier3func;
let cp = intersects(controlPoint.slice(0, 2), controlPoint.slice(-2))
if(cp && isContains(controlPoint[0], controlPoint[1], cp)){
controlPoint[1] = cp
}
if(cp && isContains(controlPoint[2], controlPoint[3], cp)){
controlPoint[2] = cp
}
if(controlPoint[1][0] == controlPoint[2][0] && controlPoint[1][1] == controlPoint[2][1]){
fn = bezier2func
controlPoint.splice(1, 1)
}
for(var n = maxpoints; n >= 0; n--){
//存入曲线点
curvePoint.push( fn(n / maxpoints, controlPoint) );
}
}
return curvePoint
}
//三次贝塞尔曲线
function bezier3func(uu, controlP){
let partX0 = controlP[0][0] * uu * uu * uu;
let partX1 = 3 * controlP[1][0] * uu * uu * (1 - uu);
let partX2 = 3 * controlP[2][0] * uu * (1 - uu) * (1 - uu);
let partX3 = controlP[3][0] * (1 - uu) * (1 - uu) * (1 - uu);
let partX = partX0 + partX1 + partX2 + partX3;
let partY0 = controlP[0][1] * uu * uu * uu;
let partY1 = 3 * controlP[1][1] * uu * uu * (1 - uu);
let partY2 = 3 * controlP[2][1] * uu * (1 - uu) * (1 - uu);
let partY3 = controlP[3][1] * (1 - uu) * (1 - uu) * (1 - uu);
let partY = partY0 + partY1 + partY2 + partY3;
return [partX, partY]
}
//二次贝塞尔曲线
function bezier2func(uu, controlP){
let partX0 = controlP[0][0] * uu * uu;
let partX1 = 2 * controlP[1][0] * uu * (1 - uu);
let partX2 = controlP[2][0] * (1 - uu) * (1 - uu);
let partX = partX0 + partX1 + partX2;
let partY0 = controlP[0][1] * uu * uu;
let partY1 = 2 * controlP[1][1] * uu * (1 - uu);
let partY2 = controlP[2][1] * (1 - uu) * (1 - uu);
let partY = partY0 + partY1 + partY2;
return [partX, partY]
}
/**
* Find a point that intersects LineStrings with two coordinates each
* 找到一个点,该点与每个线串有两个坐标相交
*/
function intersects(coords1, coords2) {
if (coords1.length !== 2) {
throw new Error(" line1 must only contain 2 coordinates");
}
if (coords2.length !== 2) {
throw new Error(" line2 must only contain 2 coordinates");
}
const x1 = coords1[0][0];
const y1 = coords1[0][1];
const x2 = coords1[1][0];
const y2 = coords1[1][1];
const x3 = coords2[0][0];
const y3 = coords2[0][1];
const x4 = coords2[1][0];
const y4 = coords2[1][1];
//斜率交叉相乘 k1 = (y4 - y3) / (x4 - x3) ... k2 = (y2 - y1) / (x2 - x1)
//k1 k2 同乘 (x4 - x3) * (x2 - x1) 得到denom
const denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
const numeA = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
const numeB = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));
if (denom === 0) { //斜率一样,平行线
return null;
}
const uA = numeA / denom;
const uB = numeB / denom;
const x = x1 + (uA * (x2 - x1));
const y = y1 + (uA * (y2 - y1));
return [x, y];
}
function isContains(sp, ep, p) {
return (
(p[0] > ep[0] && p[0] < sp[0]) || (p[0] > sp[0] && p[0] < ep[0])
) && (
(p[1] > ep[1] && p[1] < sp[1]) || (p[1] > sp[1] && p[1] < ep[1])
)
}
两条平滑曲线相乘_对三次贝塞尔曲线过点平滑中尖角和交叉现象的优化相关推荐
- 【HTML 中的二次贝塞尔曲线 和三次贝塞尔曲线】(使用说明详解)
二次 贝塞尔曲线 和三次 贝塞尔曲线 使用说明 1. 二次 贝塞尔曲线 和三次 贝塞尔曲线 1.1 贝塞尔曲线的 基本知识 1.2 贝塞尔曲线 生成动图 1.3 HTML 中 画贝塞尔曲线的 2 种方 ...
- Python绘制三次贝塞尔曲线
对于贝塞尔曲线而言,其特点在于第一个控制点恰好是曲线的起点,最后一个控制点是曲线的终点,其他控制点并不在曲线上,而是起到控制曲线形状的作用.另外,曲线的起点处与前两个控制点构成的线段相切,而曲线的终点 ...
- 书小宅之网页设计——二次贝塞尔曲线和三次贝塞尔曲线
贝塞尔曲线起始点和终止点在曲线上,方向控制点不再曲线上.二次贝塞尔曲线有一个控制点,三次贝塞尔曲线有两个控制点. 二次贝塞尔曲线 定义和用法 quadraticCurveTo() 方法通过使用表示二次 ...
- html贝塞尔曲线在线,【HTML+js+纯前端】三次方贝塞尔曲线手工拟合小工具
本帖最后由 南郊居士 于 2020-2-18 23:38 编辑 2020.2.18 重要修正: 1. 修正了当图样长宽不同时不能正确显示各控制点的问题. (一整天了都没人吐槽这个问题,看来这个工具还真 ...
- 二次贝塞尔曲线转换为三次贝塞尔曲线
二次贝塞尔曲线转换为三次贝塞尔曲线 在使用cairo绘图的时候,发现cairo不支持二次贝塞尔曲线的绘制,为了与QT实现的canvas的行为一致,cairo必须同样实现二次贝塞尔曲线的绘制.思路是将二 ...
- Python+Matplotlib可视化三次贝塞尔曲线的4个调和函数
开学第一课:一定不要这样问老师Python问题 中国大学MOOC"Python程序设计基础"第6次开课时间 董付国老师Python系列教材推荐与选用参考 ============= ...
- 一张图看懂三次贝塞尔曲线(Cubic Bézier Curve)
用权重的概念理解三次贝塞尔曲线,三次贝塞尔曲线综合了插值(Interpolation)和近似(Approximation),各点前面的多项式是点的权重(也可以把点看成权重,多项式看成是基,该被称为Be ...
- 求二次、三次贝塞尔曲线的某个时间的位置及切线方向
Public Module BezierHelper ''' <summary> ''' 获得二次贝塞尔曲线在某个时刻的位置 ''' B(t) = PA(1-t)^ ...
- OpenGL实现可交互的三维三次贝塞尔曲线(实现鼠标控制)
系列文章目录 前文:OpenGL实现可交互的三次多项式曲线(控制鼠标可拖拽)https://editor.csdn.net/md/?articleId=109129704 OpenGL实现可交互的三维 ...
- OpenGL Cubic Bezier三次贝塞尔曲线修补实例
OpenGL Cubic Bezier三次贝塞尔曲线修补 先上图,再解答. 正常显示 按下C键 按下W键 按下X键 完整主要的源代码 源代码剖析 先上图,再解答. 正常显示 按下C键 按下W键
最新文章
- python的socket编程_Python Socket编程详细介绍
- python 字符匹配_python 中如何匹配字符串
- python 装饰器是啥?
- 量子计算机有哪些战略意义,世界性颠覆!量子计算机在中国诞生,对我国有五层重大战略意义!...
- java 获取本机的IP和hostname
- Iphone 铃声制作及同步
- QT应用编程: 开发TCP网络调试助手
- 语音系统智能AI机器人AI源码营销机器人电销机器人智能电话机器人拨号机器人语音机器人空号识别FreeSWITCH呼叫中心中间ipbxIPBX科大识别阿里识别语音识别语音翻译
- 2017软件测试考试分值,2017年英语四级分值分配「最新」
- 谷粒商城基础篇-1.分布式基础概念架构图与功能模块图
- 一行代码去除序列中含有n或者其他简并碱基的低质量序列
- TCP和 UDP的区别
- 获取淘宝商品历史价格信息API(PHP,JAVA都可对接)
- Looop运行日本首座支持直流1500V电压的百万光伏电站
- Windows Nginx 服务器 SSL 证书安装部署
- 两个字符串拼接(不用strcat函数)
- 出现未知错误,错误代码:0x80070057
- 孤陋寡闻的我刚知道怎么debug远程服务
- python3导入docx报错ImportError: No module named ‘exceptions‘
- FICO 成本差异分析表