离谱,画个圆角居然还要数学知识

事情是这样的,设计要求我们搞一个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...

www.hi-id.com/?p=3445

zhuanlan.zhihu.com/p/539477988

www.ocnus.com/NURBS/nurbs...

相关推荐
深海呐3 小时前
Android AlertDialog圆角背景不生效的问题
android
ljl_jiaLiang3 小时前
android10 系统定制:增加应用使用数据埋点,应用使用时长统计
android·系统定制
花花鱼3 小时前
android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。
android
落落落sss4 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
消失的旧时光-19436 小时前
kotlin的密封类
android·开发语言·kotlin
服装学院的IT男8 小时前
【Android 13源码分析】WindowContainer窗口层级-4-Layer树
android
CCTV果冻爽9 小时前
Android 源码集成可卸载 APP
android
码农明明9 小时前
Android源码分析:从源头分析View事件的传递
android·操作系统·源码阅读
秋月霜风10 小时前
mariadb主从配置步骤
android·adb·mariadb
Python私教11 小时前
Python ORM 框架 SQLModel 快速入门教程
android·java·python