精华内容
下载资源
问答
  • 【笔记】Loop曲面细分算法c++实现

    千次阅读 2019-01-12 20:03:30
    基于半边数据结构实现了Loop曲面细分算法。   二、思路 1、Loop算法简述 Loop细分是一种三角网络的细分法则。算法本身挺简单的,主要就是把1个旧三角面片分成4个新三角面片,涉及到半边数据结构操作的部...

    一、概述
    二、思路
           1、Loop算法简述
           2、更新策略
           3、三角面细分操作
           4、操作流程
    三、数据结构
    四、参考资料


    一、概述

    上课作业。基于半边数据结构实现了Loop曲面细分算法。

     

    二、思路

    1、Loop算法简述

    Loop细分是一种三角网络的细分法则。算法本身挺简单的,主要就是把1个旧三角面片分成4个新三角面片,涉及到半边数据结构操作的部分有点麻烦。具体:每次细分,每条边上计算插入一个新顶点,同时每个原始顶点更新位置。边界边/点和非边界边/点按不同策略插入/更新坐标。

     

    2、更新策略

    每条边上新插入的点计算坐标后暂时存储在边结构的newPos变量中,每个旧顶点计算完更新后顶点位置后暂时存储在顶点结构的newPos变量中。

    更新策略如下图所示,图上为非边界情况的更新策略,图下为边界情况的更新策略。

    顶点更新策略

     

    3、三角面细分操作

    采用如下策略进行三角面的划分:(更详细说明见参考资料链接第二条)

    • Split:对每一条旧边执行split操作,可按任意顺序执行。具体操作为:按照该边中存储的前一步已计算好的新顶点的坐标将该边断成两条边,并插入2条边连接新顶点与所处三角形的对角顶点(如果该边是边界,则新插入的边为1条)。
    • Flip:对连接新旧顶点的新插入的边(新插入——不是由断裂原有边形成)进行flip操作。

     

    4、操作流程

    • 标记原始mesh的所有顶点和边。
    • 按策略计算每条边上新插入的顶点坐标,数据暂存在边结构中。
    • 按策略计算每个旧顶点更新后的位置,数据暂存在顶点结构中。
    • 对每条旧边执行split操作。PS:要避免split新插入的边
    • 对特定边执行flip操作。PS:注意特定边的要求
    • 将暂存在顶点结构中的顶点更新后坐标,替换到顶点坐标中去。

     

    三、数据结构

    这部分主要是半边数据结构。之前实现欧拉操作的时候已经写过一遍,这次写起来还算顺手。区别于实现miniCAD的欧拉操作中用到的半边数据结构,本次用到的半边在层级结构上去掉了Loop那一层(此Loop非彼Loop,欧拉操作中为Solid--Face--Loop--Halfedge--Vertex + Edge--Halfedge)。

    因为半边数据结构中各种面、边、点结构指来指去,为了防止内存泄漏用了智能指针。但基本都是自己瞎瘠薄用,总觉得自己用得不伦不类的,所以仅供参考。

    • 顶点结构
    class Vertex
    {
    public:
    	Point3d pos;			// 顶点坐标
    	Point3d newPos;			// 更新后顶点坐标
    	bool ifCalNewPos = false;		// 用于区分新旧顶点
    	bool isOnBoundary = false;	// 用于判断是否位于边界
    	Index idxForSave;		// 用于新mesh写出
    
    public:
    	Vertex(Point3d& p) : pos(p) {}
    };
    • 半边结构
    class Halfedge
    {
    public:
    	shared_ptr<Vertex> v;
    	weak_ptr<Halfedge> twin;
    	weak_ptr<Halfedge> next;
    	weak_ptr<Halfedge> prev;
    	weak_ptr<Edge> e;
    	weak_ptr<Face> f;
    	
    public:
    	Halfedge() {}
    	Halfedge(shared_ptr<Vertex>&  _v) {
    		v = _v;
    	}
    };
    • 边结构
    class Edge
    {
    public:
    	Index ID;
    	shared_ptr<Halfedge> he1;
    	shared_ptr<Halfedge> he2;
    	Point3d newPos;			// 用于存储新顶点的坐标
    	bool ifCalNewPos = false;		// 用于判断是否计算了新顶点
    	bool ifNew = false;		// 用于判断是否为新插入的边
    	bool isBoundary = false;		// 用于判断是否为边界
    
    public:
    	Edge() {
    		static Index id = 0;
    		ID = id++;
    	}
    };
    • 面结构
    class Face
    {
    public:
    	shared_ptr<Halfedge> he;
    	Point3d normal;
    	bool ifNeedDelete = false;
    
    public:
    	Face(){}
    	Face(shared_ptr<Halfedge>& _he) {
    		he = _he;
    	}
    };

     

    四、参考资料

    强烈推荐链接2里的Scotty3D项目,虽然Scotty3D本身是个空项目,但是整个系统都搭建起来了,代码里有大量的TODO注释,还有wiki里各种生动形象且详细到令人发指的知识点介绍。半边数据结构也有介绍(虽然我是按照自己的理解瞎瘠薄写的)。看着cmu462/Scotty3D这个项目,心里满满都是羡慕qaq

    展开全文
  • loop细分曲面算法

    2020-10-21 05:01:05
    loop细分曲面算法
  • OpenGL-PN算法曲面细分

    2021-04-16 17:18:30
    原理 原理是通过一个几何上的贝塞尔表面来替换原始的网格数据进而对精细度较低的网格进行平滑处理。

    原理

    原理是通过一个几何上的贝塞尔表面来替换原始的网格数据进而对精细度较低的网格进行平滑处理。

    展开全文
  • Geometry 曲面细分和曲面简化1 曲面细分a) Loop细分b) Catmull-Clark细分(Catmull-Clark Subdivision)曲面简化边坍缩总结: 1 曲面细分 曲面细分是指将一个模型的面合理的分成更多小的面,从而提升模型精度,提高...



    1 曲面细分

    曲面细分是指将一个模型的面合理的分成更多小的面,从而提升模型精度,提高渲染效果,让物体更加趋近于真实世界.
    在这里插入图片描述
    三角形越多,模型越精确,产生的视觉效果更直观更好.

    a) Loop细分

    首先要介绍清楚,这个Loop细分并不是循环细分的意思,而是发明这个算法的人叫Loop.

    Shout out to every bald man!

    总而言之,Loop细分总体只做了两件事:

    • 先细分(将一个三角形变成四个小三角形)

    • 再调整个顶点的位置.

    具体步骤如下:

    1 细分:

    通过连接三角形各边中心点,我们可以在一个大的三角形基础上,得到四个新的小三角形.

    在这里插入图片描述

    2 调整:

    细分了之后我们要对现如今的各个三角形的顶点位置进行调整,从而产生平滑的过渡效果,显得不会突兀.

    对于新老顶点我们要分别用不同的规则来调整:

    • 对新顶点:

    在这里插入图片描述
    如图中的白点是通过细分后产生的一个新顶点,它一定在三角形的一条边上,只要这条边不是表示的边界,那么它一定会被不同的三角形所共享,比如图中白点所在的边就被上下两个大三角形所共享.

    我们把这条共享边上的两个顶点叫做A 和 B,剩下的两个顶点叫做C 和 D,也就是四个黑点.

    找到黑点之后,白点现在的位置就是3/8的(A和B的平均位置) + 1/8的(C+D的平均位置),也就是其位置为周围4个顶点的权重之和:
    在这里插入图片描述
    因为A,B两点离白点近,权值大,C,D两点离得远,权值小.至此我们得到了新顶点的位置.

    • 对老顶点:
      在这里插入图片描述
      如图我们可以看到,白点是一个老顶点,他一共被六个大三角形共享,此时白点的位置一部分取决于其他相邻的老顶点,剩下的取决于自己的位置.

      在此我们要引入两个概念:n和u
      n: 顶点的度,连接几条边就是几度,如图白点为6度
      u:与度相关的一个数,如果是n = 3,那么y = 3 /16;如果n != 3,则u = 3 / 8n.(如图中白点的u应该是1/16).

      引入n和u是为了求出老顶点现在的位置:

      一方面相信自己原本的位置: (1 - n * u) * 原本的位置

      一方面想引自己周围老顶点的平均: u * 老顶点们的位置平均.

      将他们一相加就得到老顶点现在的位置.

      如果一个顶点连接了很多个三角形,例如连接了20个三角形,那么这时就可以忽略他自己的位置,只需要由周围20个三角形来决定自己的信息.

    在这里插入图片描述
    这是一个不断进行Loop细分的例子效果.

    b) Catmull-Clark细分(Catmull-Clark Subdivision)

    Loop细分针对是所有三角形面,但是现实中常用的网格不仅仅只有三角形网格,因此就有了Catmull-Clark细分,这里以四边形面和三角面的混合为例:在这里插入图片描述
    首先做一些定义:

    1 我们定义四边形的面为quad face,对于所有不是四边形的面,称之为Non-quad face
    在这里插入图片描述

    2 所有度不为4的顶点称之为奇异点
    在这里插入图片描述
    (如图中紫色的点即为奇异点,因为这两个点的n为5)

    定义完这两个概念之后我们来介绍如何细分的:

    1.取non-quad 面中间附近的一个点

    2.取non-quad 面上的每条边的中点处

    3.将non-quad 面中间的点与边上的中点连接,生成了若干个新的quad face,从而达到了曲面细分的效果.

    结果如下图所示:**

    在这里插入图片描述
    我们在经过一次细分之后:

    1 有几个非四边形面,就会多出几个奇异点,所以现在一共有2+2 = 4个

    2 新多出来的奇异点的度数与原来non-quad face的边数相等,如这里原non-quad face是三角形,新产生的奇异点就是3度

    3 如果在第一次细分之后所有面都会变成四边形,不存在non-quad face,那么往后奇异点数目不再增加

    如果此时我们再做一次细分,仍然不会增加奇异点:
    在这里插入图片描述
    可以看到奇异点依然是4个,不再改变。

    以上我们明白了如何增加新顶点,与Loop细分类似,我们同样需要去调整各类顶点的位置,这里将所有的顶点分为三类:

    1. face point

    2. edge point

    3. Vertex point

    对于各类顶点位置调整如下图所示:
    在这里插入图片描述

    Loop细分 V.S Catmull-Clark细分:

    在这里插入图片描述

    曲面简化

    曲面简化是指将一个模型的面合理的合成更少的面,从而降低模型精度,为特定情形下提供使用(如LOD技术)

    在这里插入图片描述

    边坍缩

    在这里插入图片描述
    其实曲面简化所利用的一个方法叫做边坍缩,如上图所示就是将一条边的两个顶点合成为一个顶点。

    但随之而来的问题就是,曲面简化需要尽量保持原本模型的形状,图中我们坍缩一条边,与之相连的边的也会发生改变,比如原先趋近于竖直的边现在趋近于平行,这些会导致模型的样貌变化.

    因此坍缩哪一条边能够使得原模型样貌被改变的程度最小,这是曲面简化的关键所在。

    为此我们引入一个度量,即二次误差度量(Quadric Error Metrics)

    在这里插入图片描述
    即坍缩之后蓝色新顶点所在的位置与原来各个平面的垂直距离之和,在此我们忽略如何求新顶点的位置,我们认为新顶点的位置是可求得.

    如果能够使得这个二次误差度量值处于最小,那么在进行边坍缩之后,对整个模型样貌修改一定程度上也会较小,因此我们应该最先从这条影响最小的边进行坍缩.

    那么其实到这整个曲面简化的算法流程已经比较清晰了:

    1.我们对模型上的每条边进行赋值,其值为坍缩这条边之后,代替两个老顶点的新顶点所能得到的最小二次误差度量

    2.找到在所有边中二次误差度量最小的那条边做坍缩,新顶点位置为原来计算得出使得二次误差度量最小的位置

    3.由于坍缩之后会引起与之相连的一些边的变化,因此要更新这些边的二次误差度量值,在坍缩之后重新计算这些边的二次误差度量并更新.

    4.重复2 , 3 步步骤,直到到达终止条件.

    这其实是一个标准的贪心算法,可能到不了全局最优解,但事实证明最终的结果依然相当不错
    在这里插入图片描述

    总结:

    到此,几何部分就全部结束了,我们了解了显式和隐式几何,明白了贝塞尔曲线和贝塞尔曲面,也知道了曲面细分和简化的做法,下一章我们将进行关于闫老师的Ray Tracing部分,拖延症真的没救了…在好久就将几何部分的笔记写在了本上却一直没有更新电子档,加油,打工人,大家都在进步本来就已经落后于他人了更要勤奋.

    “勤奋的人不一定是天才,但天才一定勤奋.”

    展开全文
  •  2、当前线条的笔触大小比例值和上个线条的笔触大小比例值之间的差值,也按照分割量细分,算出每份差值有多大。  3、线条按照分割量分成一个个小单元,计算两次小单元坐标之间的旋转程度,然后用一个个实心菱形...

    本文用于在无法获取压感的设备上实现书法效果,因此所有的书写效果的笔触粗细变化,均是通过速率进行确认。最后实现效果如下(不会书法,只能让大家体会一下效果),代码基于之前的文章https://blog.csdn.net/cjzjolly/article/details/88661286 上进行修改而成:

    一、推导

          在正常的情况下,使用Canvas和Path分段绘制用户的书写结果时,一般不会去改变Paint的大小,因此无论两次触摸事件点到点之间的长度多大——也就是速率多大,笔画都不会产生粗细变化。

           那么如果我希望书写的时候,笔划的粗细可以随着速率的变化而变化的话,观察我们平常书写时笔划的粗细变化,那么我们可以定义如下规则:

            1、速率快到一定程度,笔触成比例持续缩小,但设定一个最小比例值

            2、速率慢到一定程度,笔触成比例持续增大,但设定一个最大比例值

            3、速率介于上面两个阈值之间,笔触成比例增大或减小,逐渐重新接近比例值100%,即笔触原来大小。

    这样,我们可以得到如下效果的线条:

           示意图:

            

            实际效果:

            这样虽然有点接近,但是粗线条和细线条之间的粗细没有一个平滑的过渡,导致阶梯感非常严重。因此为了解决这个问题,还需要做一个细分功能。规则如下:

            1、 设定一个分割量,例如30,即把两点之间的连线分成30份。

            2、当前线条的笔触大小比例值和上个线条的笔触大小比例值之间的差值,也按照分割量细分,算出每份差值有多大。

            3、线条按照分割量分成一个个小单元,计算两次小单元坐标之间的旋转程度,然后用一个个实心菱形或者其他笔触形状,按照旋转程度旋转,并按照差值累加比例值控制笔触大小渐变着地填充笔触形状到画布上,使得过度变得平滑。

           该规则灵感来源于文章《基于iOS平台的实时手写美化技术及应用》4.2.2.3章节:

           http://www.doc88.com/p-1012869503723.html

           而关于Path如何根据Path某个微分段落的tan值进行旋转,可以参考这篇文章的“5.getPosTan”部分:

            https://blog.csdn.net/u013831257/article/details/51565591

     

    实际效果:

    可以看到笔尖形态和粗细过度效果好看很多了。

     

    二、实际代码:

    1、上次触摸点和本次触摸点之间绘制贝塞尔线段:

      /**
         * 落闸放点(狗),贝塞尔曲线化
         *
         * @param x
         * @param y
         * @param action
         */
        public void setCurrent(float x, float y, int action) {
            if(!isStart()) {
                setCurrentRaw(x, y, action);
    
                totalPath.moveTo(x, y);
    //            if(!isBuildPathAllDoing)
                touchPointList.add(new PointF(x, y));
                segPathList.add(new Path());
            } else {
                if (action == MotionEvent.ACTION_UP)
                    System.out.println("setCurrent end " + x + " , " + y);
                touchPointList.add(new PointF(x, y));
                drawPath = new Path();
                segPathList.add(drawPath);
                setCurrentRaw(x, y, action);
    
                double distance = Math.sqrt(Math.pow(Math.abs(x - last.x), 2) + Math.pow(Math.abs(y - last.y), 2));
                /**如果两次点击之间的距离过大,就判断为该点报废,Current点回退到last点**/
                if (distance > 400) {  //如果距离突变过长,判断为无效点,直接current回退到上一次纪录的last的点,并且用UP时间结束这次path draw
                    Log.i("NewCurv.SetCurrent", "超长" + distance);
    //                super.setCurrent(getLast().x, getLast().y, MotionEvent.ACTION_UP);
                    System.out.println("超长?");
                    return;
                }
                cx = last.x;
                cy = last.y;
    
                midX = (x + cx) / 2;
                midY = (y + cy) / 2;
    
                startX = mid.x;
                startY = mid.y;
    
                mid.x = midX;
                mid.y = midY;
    
                drawPath.moveTo(startX, startY);
    
                double s = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2));
                if (action == MotionEvent.ACTION_UP){
                    drawPath.lineTo(x,y);
                    totalPath.lineTo(x, y);
                } else {
                    if (s < 200) {
                        if (s < 10) {//1.10 //2.12 //3.15
                            drawPath.cubicTo(cx, cy, midX, midY, x, y);
                            totalPath.cubicTo(cx, cy, midX, midY, x, y);
                            System.out.println("cubicTo");
                        } else {
                            drawPath.quadTo(cx, cy, midX, midY);
                            totalPath.quadTo(cx, cy, midX, midY);
    //                    System.out.println("quadTo");
                        }
                    } else {
                        drawPath.quadTo(cx, cy, midX, midY);
                        totalPath.quadTo(cx, cy, midX, midY);
                    }
                }
            }
            //抬起时把画好的线段生成OpenGL线段
    //        if(action == MotionEvent.ACTION_UP) {
    //            //OpenGL此时DPI和Canvas不一样,要放大再对景区
    //            Path path = new Path();
    //            Matrix matrix = new Matrix();
    //            matrix.postScale(UITrees.openGLRenderer.scale / 2, UITrees.openGLRenderer.scale / 2, UITrees.panelView.scaleCenterPoint.x, UITrees.panelView.scaleCenterPoint.y);
    //            totalPath.transform(matrix, path);
    //
    //            PathMeasure pathMeasure = new PathMeasure();
    //            pathMeasure.setPath(path, false);
    //            float step = 10f / paint.getStrokeWidth() > 1 ? 10f / paint.getStrokeWidth() : 1; //粗线条的点密度设置大一些咯
    //
    //            float[] point = new float[2];
    //            for(float i = 0; i < pathMeasure.getLength(); i += step) {
    //                pathMeasure.getPosTan(i, point, null);
    //                //todo 缩放之后,Canvas再加Path的时候还是采用实际点,但OpenGL用了这个点就和Canvas的不对齐了,因为OpenGL缩放是把画布前后推,要做做换算,例如缩放小了,左上角的坐标是画布外的坐标
    //
    //                float realtiveX = point[0] / 1080 * 4f - UITrees.openGLRenderer.dx;  //4个象限
    //                float realtiveY = -point[1] / 1080 * 4f + UITrees.openGLRenderer.dy ;
    //
    //                glLine.drawLine(realtiveX, realtiveY);
    //            }
    //        }
            if(action == MotionEvent.ACTION_UP) {
                //OpenGL此时DPI和Canvas不一样,要放大再对景区
                Path path = new Path();
                Matrix matrix = new Matrix();
                //即使缩小过后,OpenGL画布实际上还是原来的大小,只是因为透视原理推远了看起来才小了,所以必须以屏幕中心为缩放中心放大图像之后再贴在OpenGL画布,并根据OpenGL的偏移值偏移图像顶点,看起来才会和Canvas的一样大:
                matrix.postScale(1 / UITrees.panelView.totalScale, 1 / UITrees.panelView.totalScale, 1920 / 2, 1080 / 2);
                totalPath.transform(matrix, path);
                //缩放过程中缩放中心不定,因此如何很好地进行贴在OpenGL呢?
                PathMeasure pathMeasure = new PathMeasure();
                pathMeasure.setPath(path, false);
                float step = 10f / paint.getStrokeWidth() > 1 ? 10f / paint.getStrokeWidth() : 1; //粗线条的点密度设置大一些咯
    
                float[] point = new float[2];
                for(float i = 0; i < pathMeasure.getLength(); i += step) {
                    pathMeasure.getPosTan(i, point, null);
                    //写进OpenGL的点还要做点偏移才能对正
                    float realtiveX = point[0] / 1080 * 4f - UITrees.openGLRenderer.dx;  //
                    float realtiveY = -point[1] / 1080 * 4f + UITrees.openGLRenderer.dy ;
                    glLine.drawLine(realtiveX, realtiveY);
                }
            }
        }

            在生成了本次的贝塞尔曲线之后,便可以根据这次曲线的长度为依据来判断速率的快慢了,从而确定笔触应该按照多大的比例进行放大或者缩小:

        public void drawTo(Canvas canvas) {
            Paint changedPaint = new Paint(paint);
            changedPaint.setStrokeCap(Paint.Cap.ROUND);//结束的笔画为圆心
            changedPaint.setStrokeJoin(Paint.Join.ROUND);//连接处元
            changedPaint.setStrokeMiter(1.0f);
    
            if(segPathList.size() >= 2) {
                Path prevPath = segPathList.get(segPathList.size() - 2);
                PathMeasure pathMeasure = new PathMeasure(prevPath, false);
                float beforeRatio = ratio; //从之前的曲率渐变到现在的曲率,产生细分效果
    
                if (paint.getStrokeWidth() > 5) { //粗线条的粗细率变化
                    if (pathMeasure.getLength() > 15f) { //速度快
                        if (ratio > 0.3f) {
                            ratio *= 0.8f;
                        }
                    } else if (pathMeasure.getLength() < 5f) { //速度慢
                        if (ratio < 1.5f) {
                            ratio *= 1.15f;
                        }
                    } else { //不快不慢,回到原大小
                        if (ratio > 1f) {
                            ratio *= 0.95f;
                        } else {
                            ratio *= 1.15f;
                        }
                    }
                } else { //细线条的粗细率变化
                    if (pathMeasure.getLength() > 5f) { //速度快
                        if (ratio > 0.3f) {
                            ratio *= 0.5f;
                        }
                    } else if (pathMeasure.getLength() < 2f) { //速度慢
                        if (ratio < 1.5f) {
                            ratio *= 1.4f;
                        }
                    } else { //不快不慢,回到原大小
                        if (ratio > 1f) {
                            ratio *= 0.8f;
                        } else {
                            ratio *= 1.2f;
                        }
                    }
                }
                changedPaint.setStrokeWidth(changedPaint.getStrokeWidth() * ratio);
                //分母,将当成小线段drawPath细分成多少份,份数越大曲面细分越细腻
                float denominator = 60f;
                Paint smallPaint = new Paint(paint);
                smallPaint.setStrokeWidth(2f);
                smallPaint.setStyle(Paint.Style.FILL);
                smallPaint.setMaskFilter(new BlurMaskFilter(0.8f, BlurMaskFilter.Blur.SOLID));
                //todo j起始值太小会有毛刺,太大会显得不连贯
                /** 按百分比遍历的循环
                 *  i ---> 粗细比率的遍历用变量
                 *  j ---> 遍历细分的PathMeasure的分子的存储变量
                 *  denominator   ----->   分母,将当成小线段drawPath细分成多少份
                 * (ratio - beforeRatio) / denominator ----> 将百分率的渐变细分成像线条那么多份,然后每份有多大
                 *
                 * **/
                if(beforeRatio < ratio){
                    for(float i = beforeRatio, j = 0.4f; i < ratio && j < denominator; i += (ratio - beforeRatio) / denominator, j++){
                        drawCurvSubDivision(paint, smallPaint, pathMeasure, canvas, i, j, denominator);
                    }
                } else {
                    for(float i = beforeRatio, j = 0.4f; i >= ratio && j < denominator; i += (ratio - beforeRatio) / denominator, j++){
                        drawCurvSubDivision(paint, smallPaint, pathMeasure, canvas, i, j, denominator);
                    }
                }
                DrawPathAndPaint drawPathAndPaint = new DrawPathAndPaint();
                drawPathAndPaint.inLength = new PathMeasure(totalPath, false).getLength() - new PathMeasure(drawPath, false).getLength();
                drawPathAndPaint.paint = changedPaint;
                drawPathAndPaint.ratio = ratio;
                drawPathAndPaintList.add(drawPathAndPaint);
            }
        }

       然后,就是细分贝塞尔曲线Path,并使用PathMeasure工具沿着Path轨迹,按照之前提到的细分规则绘制菱形:

        其中

            pathArrow.moveTo(0, w / 2);
            pathArrow.lineTo(w, 0 );
            pathArrow.lineTo(2 * w, w / 2);
            pathArrow.lineTo(w, w);
            pathArrow.lineTo(0, w / 2);

    是用来绘制长宽比例2:1的扁菱形的,参数图示如下,w是画笔的宽度,可以根据传入画笔的宽度调整菱形单元的大小,无数细密的菱形即可组成类似钢笔效果的线条:

     

        /** 传入当前绘制线段的PathMeasure,用菱形曲面细分使得笔划细腻而好看,避免粗细的突变感
         *  @param  paint  传入原始画笔,用于通过画笔宽度确定菱形长度和高度
         *  @param  borderPaint 传入菱形的边界画笔,用于确定每个菱形单元用多粗的线条进行绘制
         *  @param  drawPathMeasure 需要被曲面细分的线条的PathMeasure,用于遍历该线条
         *  @param  divisionRatio 传入PathMeausre指定目标刻度应该用多少原本画笔粗细的比率来绘制一个菱形,来形成渐变过度
         *  @param  pathMeausreNumerator 传入要进行细分绘制的PathMeasure的第几个刻度(用分子表示)
         *  @param  pathMeasureDenominator  传入要进行细分绘制的PathMeasure分成了几份,即分母,分母越大,则细分分数越多,线条则越细腻
         * **/
        private void drawCurvSubDivision(Paint paint, Paint borderPaint, PathMeasure drawPathMeasure, Canvas canvas, float divisionRatio, float pathMeausreNumerator, float pathMeasureDenominator){
            Path pathArrow = new Path();
            float w = paint.getStrokeWidth() * divisionRatio > 1f ? paint.getStrokeWidth() * divisionRatio : 1f;
            pathArrow.moveTo(0, w / 2);
            pathArrow.lineTo(w, 0 );
            pathArrow.lineTo(2 * w, w / 2);
            pathArrow.lineTo(w, w);
            pathArrow.lineTo(0, w / 2);
            float[] pos = new float[2];
            float[] tan = new float[2];
            drawPathMeasure.getPosTan(pathMeausreNumerator / pathMeasureDenominator * drawPathMeasure.getLength(), pos, tan);
    //                        canvas.drawCircle(pos[0], pos[1], paint.getStrokeWidth() / 2f * smallRatio, changedPaint);
            Matrix matrix = new Matrix();
            //计算方位角
            float degrees = (float)(Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
            RectF rect = new RectF();
            pathArrow.computeBounds(rect, false);
            matrix.postRotate(degrees, rect.width() / 2, rect.height() / 2);   // 旋转图片
            matrix.postTranslate(pos[0] - rect.width() / 2, pos[1] - rect.height() / 2);   // 将图片绘制中心调整到与当前点重合
            pathArrow.transform(matrix);
            if(canvas != null){
                canvas.drawPath(pathArrow, borderPaint);
            }
        }

    至此,就讲完了这个功能大致是怎么实现的了。

    展开全文
  • Doo-Sabin细分算法

    千次阅读 2017-08-11 21:35:46
    Doo-Sabin细分算法是二次均匀B样条曲面二分技术的推广。不同于Loop细分算法,Doo一Sabin细分算法是一种基于四边形控制网格的细分算法
  • 以{positions: subdividedPositions, cells: subdividedCells}的形式返回对象中的细分网格。 positions [ [1.0,2.0,3.0], [3.4,1.3,4.2],...]上的输入网格的顶点位置 cells输入网cells的索引。 这是一个四边形索引...
  • 三角网格细分算法 —— Loop细分 主要参考文章: https://zhuanlan.zhihu.com/p/144400261 https://blog.csdn.net/McQueen_LT/article/details/106136324 Loop细分: 是一种专门针对三角形面的细分方法——...
  • 提出的细分曲面水印算法是基于图像水印算法和Fourier加法性质。首先将水印信息嵌入一幅图像,通过嵌入水印的图像和原始图像在空域作比较得到含有水印信息的矩阵,然后将细分曲面初始网格迭代三次以使网格的顶点数...
  • Loop细分曲面不同细分层次的网格面可作为不同加工工序的加工模型.现有等距面生成算法因未考虑折痕和边界的特殊情况,当折痕或边界存在时将会生成与预期结果有较大差别的等距面.给出了折痕等尖锐特征处极限等距位置的...
  • 但是曲面细分的目的是为了自动平滑LowPoly模型,而上一个案例中,我们是使用了波动函数“凹”出了一个特定的形状,这明显是不符合我们想要自动平滑的预期,而贝塞尔曲线很好的解决了这个问题,所以我们这篇来学习...
  • 细分曲面Catmull-Clark Subdivision算法

    千次阅读 2010-10-13 17:00:00
    细分曲面也有很简单的算法实现, 比如Catmull-Clark Subdivision算法,其可以对任意拓扑结构的多边形进行细分。下面简要介绍下。 细分新的曲面,先求出新的曲面的顶点: Face point(位于原来多边形面里的新顶点)...
  • 不同层次的Loop细分网格曲面可作为不同工序的...本文考虑了尖锐特征的存在,对现有给定误差估计所需细分次数的算法进行了改进,并针对粗加工和精加工的不同特点,给出了各工序等距面的生成及调整算法。实践验证方法可行。
  • 曲面造型方法由于其局部性好、计算量小、算法简单、响应速度高等优点,已经广泛应用于计算机图形学、...介绍了近年来提出的一些细分算法,对其中几种比较经典的算法进行了简单的分类和比较,并论述了各自的适用范围。
  • 匹配方法ICP算法,布尔莎模型,筛选有效数据点的算法ransac。
  • 递归分割方法的科技文献,从表面三角网格开始进行细分,得到光滑曲面的经典算法
  • 下图描述了细分的基本思想,每次细分都是在每条边上...曲面细分需要有几何规则和拓扑规则,几何规则用于计算新顶点的位置,拓扑规则用于确定新顶点的连接关系。下面介绍两种网格细分方法:Catmull-Clark细分和Loop细分
  • NURBS 曲面曲线例子

    热门讨论 2009-05-25 17:57:17
    基于NURBS 的曲面曲线。BSPLINE 曲面曲线。 KNOT 插入算法。曲面细分算法。曲线基函数调整。
  • 曲面细分需要有几何规则和拓扑规则,几何规则用于计算新顶点的位置,拓扑规则用于确定新顶点的连接关系。下面介绍两种网格细分方法:Catmull-Clark细分和Loop细分。 Catmull-Clark subdivision:  Catmull-...
  • ♥,.*,.♥,.*,.♥,.*,.♥,.*♥,.*,.♥,.*,.♥,.*,.♥,.*,.♥,.*,.♥,.*,.♥,.*,.♥♥,.*,....1.细分曲面的作用 ...2.细分曲线、细分曲面原理 ...3.细分曲面算法——Catmull-Clark细分 4.细分曲面算法——...
  • LOOP细分模型的边界拼接算法,可以解决细分曲面的拼接问题
  • DOO-SABIN 细分曲面(编辑中)

    千次阅读 2016-05-04 19:38:02
    之前实现了简单的细分曲线,即Chaikin细分算法,现在准备实现DooSabine细分曲面 我这里借助http://www.idav.ucdavis.edu/education/CAGDNotes/CAGDNotes/Doo-Sabin/Doo-Sabin.html 这篇文章中的例子,首先需要先绘制...
  • 高级计算机三维建模课程最后需要提交一个大作业,比较Catmull-Clark细分曲面与Loop细分曲面算法。先把Catmull-Clark细分曲面弄懂了,这里做个记录。昨天一直在网上找Catmull-Clark细分曲面相关的解释教程,但是并...
  • 渲染地球其中的重中之重是椭球体的曲面细分算法和着色算法。流行的有三种细分算法,用于产生近似地球表面的三角形: 1 基于单位球的简单的表面细分算法,这种方法经常会在计算机图形学课程看到。 2 基于立方体的椭球...
  • 为了生成通过有限个离散点的光滑曲面及其等距曲面,构造了含参数三次有理插值样条模型,该模型可...并通过适当调整插值函数中的参数进行交互式的修改,以得到满意的曲面及其等距曲面,再结合细分算法达到要求的逼近精度。
  • PN-Triangles (也称作N-patches)是比较流行的处理粗糙模型细分算法技术,PN-Triangles算法能够将低分辨率模型转化为弯曲表面,该表面然后可以被重新绘制成由“高精曲面细分”的三角形所组成的网格,经常借助于...
  • 在改进任意拓扑网构造光滑表面时,初始控制网格确定的情况下,生成的曲面形状惟一确定,最终的物体造型也随之确定,不具有可调性,因而在曲面细分过程中引入了控制参数和摄动。通过引入控制参数,调节一个参数值,...
  • 提出了一种新的六角形网格的砍边细分算法。该算法通过面收缩和砍边两个过程,使细分网格的数目以4为倍数增长,并选择适当的几何定位使细分曲面保持C1连续性。该算法只适用于顶点的价为3的半正则网格,而对于任意的初始...

空空如也

空空如也

1 2 3 4
收藏数 67
精华内容 26
关键字:

曲面细分算法