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

事情是这样的,设计要求我们搞一个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 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹4 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
GEEK零零七5 小时前
Leetcode 1103. 分糖果 II
数学·算法·leetcode·等差数列
每次的天空6 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭7 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日8 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安8 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑8 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
让我们一起加油好吗10 小时前
【基础算法】贪心 (二) :推公式
数据结构·数学·算法·贪心算法·洛谷
万少10 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端