事情是这样的,设计要求我们搞一个G3曲线的圆角。我看到需求的时候,直接懵逼了,什么是G3曲线?圆角还有能有什么骚操作吗?经过一通google,我总算了解了一些新知识,这里分享给大家。
曲线连续性
首先我们需要了解一下什么是G3曲线,这里需要先了解一些曲线的基础知识。当许多曲线首尾相连构成一条曲线时,如何保证各曲线段在连接处具有合乎要求的连续性是一个重要问题。在数学上有两种连续性:参数连续性和几何连续性。
参数连续性
0阶参数连续性,记作C0连续性,是指曲线的几何位置连接,即第一个曲线段在t 点处的值相等
1阶参数连续性,记作C1连续性,指两个相邻曲线的交点有相同的一阶导数
2阶参数连续性,记作C2连续性,指两个相邻的曲线的交点有相同的一阶导数和二阶导数
经典的参数连续性在图形学里是不适合的,因为太苛刻了,所以引入了下面几何连续性的概念。
几何连续性
曲线段相连的另一个连续性条件是几何连续性。与参数连续性不同的是,它只需要曲线段在相交处的参数导数成比例即可
0阶几何连续性,记作G0连续性。与0阶参数连续性的定义相同
1阶几何连续性,记作G1连续性。若要求在相接处达到G1几何连续性,就是说两条曲线在满足G0的情况下,该点的导数成比例
2阶几何连续性,记作G2连续性。要求满足G1的条件下,该点二阶导数成比例
G3曲线就是在满足G2的条件下,该点的三阶导数成比例
其他的依次类推
计算机曲线
上面我们了解了G3曲线是什么东西,但是我们应该怎么在计算机上显示出来呢?这就需要你了解计算机曲线的相关知识。在之前的文章《一文理解贝塞尔曲线》和 《一文理解B样条曲线、Nurbs曲线》中,我们对计算机曲线进行了详细的介绍,感兴趣的可以看一下,不看也没关系,影响不大,这里就列一下它们的区别。
曲线 | 特点 | 缺点 |
---|---|---|
贝塞尔曲线 | 简单、计算快 | 不能局部修改;不能表示圆、椭圆等封闭曲线 |
B样条曲线 | 可以局部修改 | 不能表示圆、椭圆等封闭曲线 |
Nurbs曲线 | 可以局部修改;可以表示圆、椭圆等封闭曲线 | 计算量大 |
实践
看到可能你会说,搞这么多干嘛,直接找设计要图,mask一下不就行了吗?开发过程中确实可以这么做,不过我们这里的目的是了解它的原理,提升自己的技术水平(顺便装个B,不是)
使用Nurbs曲线画一个G3的圆角
有人看到这里就想到了Nurbs曲线的复杂公式,这里我们不需要关注。因为有很多相关的库,我们不需要重复造轮子,只需要关注构造圆角的参数即可。
首先,我们在网上找一个可以可视化绘制Nurbs 曲线的网站,这样就可以通过调参来查看目前曲线的形状了
第二步,我们找一个绘制 Nurbs 的库,这样我们只需要把参数填入就可以绘制出来所需要的曲线了。不过理想很丰满,现实很骨感。我尝试几种库,有的在Android上都无法运行,有的直接无法编译通过,具体的情况就不细说了,有时间你们可以亲自去试试。
既然直接绘制行不通,那间接呢?我们知道,线是由点构成的,如果我们有足够多的点,不就可以绘制出我们需要的曲线了吗?说干就干,先使用IDEA创建一个java工程,添加tinyspline 这个库来获取Nurbs曲线上的点,代码如下
ini
public class Spline {
public static void main(String[] args) {
double[] pointsArray = new double[]{
0.0, -4.0,
-4.0, -4.0,
-4.0, 0.0,
-4.0, 4.0,
0.0, 4.0,
4.0, 4.0,
4.0, 0.0,
4.0, -4.0,
0.0, -4.0,
-4.0, -4.0,
-4.0, 0.0,
-4.0, 4.0,
0.0, 4.0,
4.0, 4.0,
4.0, 0.0,
4.0, -4.0,
};
double[] knotArray = new double[]{
0,
0.048,
0.095,
0.143,
0.19,
0.238,
0.286,
0.333,
0.381,
0.429,
0.476,
0.524,
0.571,
0.619,
0.667,
0.714,
0.762,
0.81,
0.857,
0.905,
0.952,
1
};
ArrayList<Double> points = new ArrayList<>();
ArrayList<Double> knots = new ArrayList<>();
for (double i : pointsArray) {
points.add((double) i);
}
for (double v : knotArray) {
knots.add(v);
}
final BSpline spline = new BSpline(16,2, 5, BSpline.Type.Clamped);
final List<Double> ctrlp = spline.getControlPoints();
ctrlp.clear();
ctrlp.addAll(points);
spline.setControlPoints(ctrlp);
spline.setKnots(knots);
List<Double> result = spline.sample(100);
System.out.println(result);
}
获取到所需要的点后,我们就可以在Android中将它绘制出来。效果如下图所示,是不是看不出区别。其实在平面上人眼一般只能看出G1连续性,G2、G3曲线都是应用于3D的场景,具体的可以看油管上的视频www.youtube.com/watch?v=ot7...
使用超椭圆画一个圆角
在google各种G3曲线的资料时,我还了解到一种特殊的曲线------超椭圆。方程如下所示。需要注意如果需要让超椭圆表示圆角,需要令 a == b。具体可以看wiki的介绍超椭圆
代码实现如下:
kotlin
class SuperellipseView: View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,
attrs,
defStyleAttr)
private var bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.test)
var n = 1
private var path = Path()
private var paint = Paint()
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
path = getSuperellipsePath(w / 2, n)
bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true)
}
private fun getSuperellipsePath(radius: Int, n: Int): Path {
val radiusToPow = Math.pow(radius.toDouble(), n.toDouble())
val path = Path()
path.moveTo(-radius.toFloat(), 0f)
for (x in -radius..radius) {
path.lineTo(x.toFloat(),
Math.pow(radiusToPow - Math.abs(Math.pow(x.toDouble(), n.toDouble())), 1.0 / n).toFloat())
}
for (x in radius downTo -radius) {
path.lineTo(x.toFloat(),
-Math.pow(radiusToPow - Math.abs(Math.pow(x.toDouble(), n.toDouble())), 1.0 / n).toFloat())
}
path.close()
val matrix = Matrix()
matrix.postTranslate((radius).toFloat(), (radius).toFloat())
path.transform(matrix)
return path
}
override fun onDraw(canvas: Canvas) {
paint.style = Paint.Style.FILL
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
canvas.drawPath(path, paint)
}
}
效果如下所示
还有一个问题,我们上面提到过G1、G2、G3是相对于曲线连接点来说的。像圆、椭圆、超椭圆这种封闭曲线有几何连续性吗?它们分别是G几连续?这个问题,我求助过google没有明确的答案。下面是我问ChatGPT的回答,不确定是否正确,如果有大佬知道,麻烦评论区解惑。
在几何连续性的分类中,G1、G2和G3连续性主要描述的是两条曲线之间的平滑性和曲率连续性。对于圆、椭圆和超椭圆这类曲线,它们本身都是连续的,但无法被明确定义为G1、G2或G3连续。
圆是一个完美的连续曲线,它在每个点上都具有相同的曲率,没有任何转折或异常的地方。圆的连续性可以被认为是G∞(G-infinity)连续,因为它没有曲率的断裂点。
椭圆是由两个焦点之间距离之和等于常数的点的集合构成。椭圆在每个点上都具有连续的曲率,但曲率不是恒定的,它随着点的位置变化而变化。椭圆的连续性可以说是G1或G2连续,因为在每个点上曲率或曲率导数是连续的。
超椭圆是参数方程定义的一个曲线,参数方程中的参数取不同值可以产生不同形状的曲线。超椭圆在每个点上都具有连续的曲率,但曲率也不是恒定的,它随着参数的变化而变化。超椭圆的连续性可以说是G1或G2连续,因为在每个点上曲率或曲率导数是连续的。
综上所述,圆、椭圆和超椭圆都具有几何连续性,但不能被明确定义为G1、G2或G3连续。具体的几何连续性分类可能因曲线的定义和参数化方式而有所不同。
参考
● 常用参数化曲线
● 【软件理论】(一)从贝塞尔(Bézier)曲线到NURBS曲线(非均匀有理B样条)的浅谈
● applypixels.com/blog/the-hu...
● www.sciencedirect.com/science/art...
● en.wikipedia.org/wiki/Supere...
● en.wikipedia.org/wiki/Smooth...
● en.wikipedia.org/wiki/Non-un...
● en.wikipedia.org/wiki/Squirc...
● www.bilibili.com/video/BV1Dt...