精华内容
下载资源
问答
  • 该方法使用光电式图像瞄准系统采集被测物体的目标图像并构造该图像的高斯金字塔,并采用了多分辨率动态轮廓线且使其由初始位置向目标轮廓边缘收敛;根据基于B样条封闭曲线面积与形心公式,准确计算出图像的目标面积与...
  • 浅析基于二维轮廓线重构表面算法

    千次阅读 2014-02-13 17:04:10
    轮廓线重构算法  由一组二维轮廓线重建出物体的三维表面是三维数据场可视化中的一种表面绘制方法。在医学图像可视化以及其他可视化领域中有着广泛的应用。三维表面重建实际上是对物体表面进行三角形划分,从轮廓...

    轮廓线重构算法

      由一组二维轮廓线重建出物体的三维表面是三维数据场可视化中的一种表面绘制方法。在医学图像可视化以及其他可视化领域中有着广泛的应用。三维表面重建实际上是对物体表面进行三角形划分,从轮廓线的角度出发就是将轮廓线上的顶点按照一定规则进行三角形拼接,从而构成可视的三维物体表面,再利用三维显示技术将其显示出来。本文讨论了一种实现轮廓线重构的简易方法,其关键的步骤体现在相邻轮廓线的编织方法以及顶底层轮廓线的三角化上。并且用c++代码来实现之。

    轮廓线数据结构表示

      首先为了表示输入的轮廓线,需要使用一种数据结构来表示每一层的轮廓线。通常重建的输入会由N个轮廓线组成,每个轮廓线位于不同的高度位置,我们可以把轮廓线认为是以条在X-Y平面上闭合的曲线,而每条轮廓线有着不同的Z值。而二维曲线通常都是用一系列二维点组成的折线近似表示的,这样轮廓线的数据结构可以表示为如下的形式:

    复制代码
    struct FloatDouble
    {
        float X;
        float Y;
        FloatDouble(float x,float y)
        {
            X=x;Y=y;
        }
        FloatDouble()
        {
            X=0;
            Y=0;
        }
    };
    复制代码
    复制代码
    struct Box3Float
    {
    public:
        float Min3[3];
        float Max3[3];
        Box3Float()
        {
            Min3[0]=99999;
            Min3[1]=99999;
            Min3[2]=99999;
            Max3[0]=-99999;
            Max3[1]=-99999;
            Max3[2]=-99999;
        }
        Box3Float(float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
        {
            Min3[0] = minX;
            Min3[1] = minY;
            Min3[2] = minZ;
            Max3[0] = maxX;
            Max3[1] = maxY;
            Max3[2] = maxZ;
        }
        void UpdateRange(float x, float y, float z)
        {
            if (x < Min3[0])
                Min3[0] = x;
            if (y < Min3[1])
                Min3[1] = y;
            if (z < Min3[2])
                Min3[2] = z;
            if (x > Max3[0])
                Max3[0] = x;
            if (y > Max3[1])
                Max3[1] = y;
            if (z > Max3[2])
                Max3[2] = z;
        }
        float GetXLength()
        {
            return Max3[0]-Min3[0];
        }
        float GetYLength()
        {
            return Max3[1]-Min3[1];
        }
        float GetZLength()
        {
            return Max3[2]-Min3[2];
        }
    };
    复制代码
    复制代码
    class ContourLine
    {
    private:
        std::vector<FloatDouble> points;
        float z;
    public:
        ContourLine():z(0) { }
        void AddPoint2d(float x,float y)
        {
            FloatDouble t(x,y);
            points.push_back(t);
        }
        inline std::vector<FloatDouble>& GetPointList()
        {
            return points;
        }
        inline int GetLinePointCount() const
        {
            return points.size();
        }
        inline void SetZ(float z)
        {
            this->z=z;
        }
        inline float GetZ() const
        {
            return z;
        }
        Box3Float GetBox()
        {
            Box3Float box;
            for (size_t i = 0; i < points.size(); i++)
            {
                box.UpdateRange(points[i].X, points[i].Y, 0);
            }
            return box;
        }    
    };
    复制代码

      这样std::vector<ContourLine>即可以作为轮廓线重构算法的输入类型,重构算法最终输出代表结果表面的Mesh。Mesh结构在之前的文章中多次出现,就不详细描述了。在介绍轮廓线重构算法中,需要首先解决下面的几个子算法问题,所以为了正确理解算法逻辑,首先需要介绍一下这些子算法。

     

    子算法一:判断折线圈是否为逆时针

      上面说了一般用来表示平面上一条轮廓线的方法是使用一系列点。按这些点练成折线,最后再将最后一个点与第一个点连起来,便是一条闭合的折线,假设折线是不自交的,则这个线圈一定能够判断它是逆时针还是顺时针。在轮廓线重构算法中,需要保证每一层的轮廓线为是由逆时针的折现圈表示的。所以,这里介绍一种判断折线圈是否为逆时针的方法。

      首先在计算几何中,利用解析几何的原理我们知道向量的叉积可以用来判断向量拐弯的方向,例如下图中向量A×B若为正数,则意味着B相对A是向逆时针方向旋转的;为0则A,B共线;否则B是相对A顺时针旋转的。而1/2*abs(A×B)则为AB向量构成三角形的面积。

    AB×BC为正 AB×BC为负 逆时针的轮廓线 顺时针的轮廓线

      因此一种直观的判断由点A1、A2、......An构成的折线圈是否为逆时针的方法是计算三角形(A1,A2,A3),(A2,A3,A4),(A3,A4,A5).....(An,A1,A2)的面积之和是否为正值。这里不做原理性证明为什么可以这样,详细的原理可以从如下网址查看:

      原网址:http://www.mathopenref.com/coordpolygonarea2.html ,打不开的话可见CSDN的备份:http://blog.csdn.net/swfa1/article/details/18146581 

      这里只附上相应的代码:

    复制代码
    static float Area(std::vector<FloatDouble>& contour)
    {
        int n = contour.size();
        float A = 0.0f;
        for (int p = n - 1, q = 0; q < n; p = q++)
        {
            A += contour[p].X * contour[q].Y - contour[q].X * contour[p].Y;
        }
        return A * 0.5f;
    }
    复制代码

      上述函数返回true,则折线圈为逆时针。

     

    子算法二: 两层轮廓线编织算法

      所谓轮廓线编织,就是把相邻两层的轮廓线上的点采用合适的方式连接起来,形成三角网,但这种连接的方式显然是不唯一的。下图显示了两种不同的编织方式形成的三角网,显然,两者存在形态上的差别。

    一种好的编织方式 一种看上去不太好的编织方式

      从直观感觉上看,显然前一个三角网中三角形更为规则(较少的钝角三角形)。所以究竟如何生成好的三角网,需要采用合适的策略。这样的策略在相关领域有很多研究成果,提出了不少的方法,这里介绍一个容易理解的法则,叫做最短对角线原则。下图为了说明这个原则,将轮廓线剪开并展平成直线,轮廓线的点则分布在这条直线上,相隔不等的距离。

      将轮廓线编织模拟成在两条直线上连接相应的点的连接,采用最短对角线原则连接方式为:若当前Ai已经与Bj相连接,检查由四个点(Ai,Ai+1,Bj+1,Bj)组成的四边形,检测对角线AiBj+1与Ai+1Bj的长度,选择较短的一条作为新三角形的边。

    最短对角线连接方式 非最短对角线的连接方式

      当创建第一条连接线的时候,对于点A0,需要遍历一次B点的数组,寻找到离A0最近的B点,假设为Bk,则需要将B数组调整为以Bk为首元素,与A方向相同(顺逆时针)的新数组。所以这就解释了为何需要先清楚子算法一的内容。这样,在编织活动开始的时候,A0与B0是最近的点对,A0B0是连接的第一条线,之后的循环过程按最短对角线原则进行。在实现过程中需要使用两个指示变量分别标记A数组和B数组中行进到的位置,有点算法基础的同学会发现这与归并排序中的Merge过程有点相似。循环到A、B数组中有一个访问完为止,这时另一个数组余下的点可以与访问完的数组的最后一个点连接成若干三角形。最后还需要注意要将最后的An-1Bm-1与A0B0组成的四边形用最短对角线法再三角化一次。

      在应用这个原则时,当两层轮廓线投影在平面上的位置偏差较大时可能出现的问题,例如下图的情况,在投影偏差较大的时候,可能会导致所有三角形的顶点都是同一点,如下图所示,这样显然没有达到编织轮廓线的目的,解决的方案是将两层轮廓线的几何中心移在一起,这样就会避免这种现象。

      综上,编织由点数组A、B表示的轮廓线的过程用文字表述如下:

    1. 检查A、B数组的方向,将A,B数组调整为逆时针。
    2. 获取A、B的Box范围并保存,然后将A,B平移到使几何中心到原点(0,0)。
    3. 为A0寻找B中的最近点,然后以此最近点为B0,将B数组按顺序重新编号。
    4. 设置A、B数组的位置指示变量i,j,初始化为0
    5. 当i与j均未达到A和B的长度
      1. 检查(Ai,Ai+1,Bj+1,Bj)组成的四边形,若AiBj+1短于Ai+1Bj,则创建三角片(Ai,Bj,Bj+1)否则创建三角片(Ai,Bj,Ai+1)
    6. 若i达到A的长度,则将B中余下的点连A的最后一点构成三角片
    7. 若j达到B的长度,则将A中余下的点连B的最后一点构成三角片
    8. 为四边形(An-1,Bm-1,A0,B0)构建按最短对角线原则创建三角片。

      下列图片序列简单的反映了这一过程:

    初始四边形
    第二次迭代
    第三次迭代
      迭代直到有一个点数组访问完毕
    迭代完成

      相应的实现代码,写成一个类形式如下,其中QuadUnit类用来表示一个用来使用最短对角线原则的四边形:

    复制代码
    class ContourStitcher
    {
    private:
        struct QuadUnit
        {
        public:
            int UpIndex0;
            int UpIndex1;
            int DownIndex0;
            int DownIndex1;
            double DiaU0D1Len;
            double DiaU1D0Len;
            std::vector<FloatDouble>* lineUp;
            std::vector<FloatDouble>* lineDown;
            void Init(int upIndex, int downIndex)
            {
                UpIndex0 = upIndex;
                DownIndex0 = downIndex;
                UpIndex1 = (upIndex + 1); //%lineUp.Count;
                DownIndex1 = (downIndex + 1); //%lineDown.Count;
                DiaU0D1Len = GetDLen(UpIndex0, DownIndex1);
                DiaU1D0Len = GetDLen(UpIndex1, DownIndex0);
            }
            void InitLast()
            {
                UpIndex0 = lineUp->size() - 1;
                DownIndex0 = lineDown->size() - 1;
                UpIndex1 = 0; //%lineUp.Count;
                DownIndex1 = 0; //%lineDown.Count;
                DiaU0D1Len = GetDLen(UpIndex0, DownIndex1);
                DiaU1D0Len = GetDLen(UpIndex1, DownIndex0);
            }
            double GetDLen(int index1, int index2)
            {
                float x0 = (*lineUp)[index1].X;
                float y0 = (*lineUp)[index1].Y;
                float x1 = (*lineDown)[index2].X;
                float y1 = (*lineDown)[index2].Y;
                return sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1));
            }
        };
        void Transform(std::vector<FloatDouble>& list, float dx, float dy)
        {
            for (size_t i = 0; i < list.size(); i++)
            {
                list[i].X += dx;
                list[i].Y += dy;
            }
        }
        Point3d GetCenter(Box3Float& box3)
        {
            return Point3d((box3.Max3[0] + box3.Min3[0]) / 2.0f, (box3.Max3[1] + box3.Min3[1]) / 2.0f, (box3.Max3[2] + box3.Min3[2]) / 2.0f);
        }
    public:
        ContourLine* lineUp;
        ContourLine* lineDown;
        std::vector<FloatDouble> lineUpProcessed;
        std::vector<FloatDouble> lineDownProcessed;
        Box3Float boxUp;
        Box3Float boxDown;
        ContourStitcher(ContourLine* line1, ContourLine* line2)
        {
            if (line1->GetZ() > line2->GetZ())
            {
                this->lineUp = line1;
                this->lineDown = line2;
            }
            else
            {
                this->lineUp = line2;
                this->lineDown = line1;
            }
            lineUpProcessed.reserve(lineUp->GetLinePointCount());
            lineDownProcessed.reserve(lineDown->GetLinePointCount());
            CopyArray(lineUp->GetPointList(), lineUpProcessed);
            CopyArray(lineDown->GetPointList(), lineDownProcessed);
            boxUp = lineUp->GetBox();
            boxDown = lineDown->GetBox();
            Point3d cU = GetCenter(boxUp);
            Point3d cD = GetCenter(boxDown);
            Transform(lineDownProcessed, -cD.X, -cD.Y);
            Transform(lineUpProcessed, -cU.X, -cU.Y);
            int indexDown = GetNearIndex();
            AdjustDownArray(indexDown);
        }
        ~ContourStitcher()
        {
            lineUp=NULL;
            lineDown=NULL;
        }
    private:
        void CopyArray(std::vector<FloatDouble> &olist, std::vector<FloatDouble> &tarlist)
        {
            for (size_t i = 0; i < olist.size(); i++)
            {
                tarlist.push_back(FloatDouble(olist[i].X, olist[i].Y));
            }
        }
        int ContourStitcher::GetNearIndex()
        {
            int index = -1;
            double distense = DBL_MAX;
            FloatDouble& p = lineUpProcessed[0];
            float x0 = p.X;
            float y0 = p.Y;
            for (size_t i = 0; i < lineDownProcessed.size(); i++)
            {
                float x1 = lineDownProcessed[i].X;
                float y1 = lineDownProcessed[i].Y;
                double dis = sqrt((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1));
                if (dis < distense)
                {
                    distense = dis;
                    index = i;
                }
            }
            return index;
        }
        void AdjustDownArray(int indexDown)
        {
            std::vector<FloatDouble> list;
            list.reserve(lineDownProcessed.size());
            for (size_t i = 0; i < lineDownProcessed.size(); i++)
            {
                list.push_back(lineDownProcessed[(indexDown + i) % lineDownProcessed.size()]);
            }
            lineDownProcessed.swap(list);
        }
    public:
        Mesh* DoStitching()
        {
            Mesh *m = new Mesh();
            int* upMap=new int[lineUpProcessed.size()];
            float dx1 = GetCenter(boxUp).X;
            float dy1 = GetCenter(boxUp).Y;
            float dx2 = GetCenter(boxDown).X;
            float dy2 =GetCenter( boxDown).Y;
            int* downMap=new int[lineDownProcessed.size()];
            for (size_t i = 0; i < lineDownProcessed.size(); i++)
            {
                Point3d p(lineDownProcessed[i].X + dx2, lineDownProcessed[i].Y + dy2, lineDown->GetZ());
                downMap[i] = m->AddVertex(p);
            }
            for (size_t i = 0; i < lineUpProcessed.size(); i++)
            {
                Point3d p(lineUpProcessed[i].X + dx1, lineUpProcessed[i].Y + dy1, lineUp->GetZ());
                upMap[i] = m->AddVertex(p);
            }
            size_t upIndex = 0;
            size_t downIndex = 0;
            QuadUnit quad;
            quad.lineDown = &lineDownProcessed;
            quad.lineUp = &lineUpProcessed;
            while (true)
            {
                if (upIndex == lineUpProcessed.size() - 1 || downIndex == lineDownProcessed.size() - 1)
                {
                    break;
                }
                quad.Init(upIndex, downIndex);
                if (quad.DiaU0D1Len < quad.DiaU1D0Len)
                {
                    Triangle t(upMap[quad.UpIndex0], downMap[quad.DownIndex0], downMap[quad.DownIndex1]);
                    m->AddFace(t);
                    downIndex++;
                }
                else
                {
                    Triangle t(upMap[quad.UpIndex0], downMap[quad.DownIndex0], upMap[quad.UpIndex1]);
                    m->AddFace(t);
                    upIndex++;
                }
            }
            if (upIndex == lineUpProcessed.size() - 1 || downIndex == lineDownProcessed.size() - 1)
            {
                if (downIndex == lineDownProcessed.size() - 1)
                {
                    int last = lineDownProcessed.size() - 1;
                    while (upIndex != lineUpProcessed.size() - 1)
                    {
                        Triangle t(downMap[last], upMap[upIndex + 1], upMap[upIndex]);
                        m->AddFace(t);
                        upIndex++;
                    }
                }
                else
                {
                    int last = lineUpProcessed.size() - 1;
                    while (downIndex != lineDownProcessed.size() - 1)
                    {
                        Triangle t(upMap[last], downMap[downIndex], downMap[downIndex + 1]);
                        m->AddFace(t);
                        downIndex++;
                    }
                }
            }
            quad.InitLast();
            if (quad.DiaU0D1Len < quad.DiaU1D0Len)
            {
                Triangle t(upMap[quad.UpIndex0], downMap[quad.DownIndex0], downMap[quad.DownIndex1]);
                Triangle t2(upMap[quad.UpIndex0], downMap[quad.DownIndex1], upMap[quad.UpIndex1]);
                m->AddFace(t);
                m->AddFace(t2);
            }
            else
            {
                Triangle t(upMap[quad.UpIndex0], downMap[quad.DownIndex0], upMap[quad.UpIndex1]);
                Triangle t2(upMap[quad.UpIndex1], downMap[quad.DownIndex0], downMap[quad.DownIndex1]);
                m->AddFace(t);
                m->AddFace(t2);
            }
            delete[] upMap;
            delete[] downMap;
            return m;
        }
    };
    复制代码

      下图是按此原则编织的两层轮廓线的例图:

     

    子算法三:Mesh合并方法简述

      在实现了相邻两层轮廓线的三角网格生成操作后,对于已经按Z高度排序的轮廓线C1到Cn,可以使用子算法二对<C1,C2>...<Cn-1,Cn>生成对应的三角网格,其编号为M1,M2,...Mn-1。

      考虑到每一个Mesh都是由两层顶点组成,所以这些网格合并成一个大的M网格M需要考虑顶点去重的问题,一个简单的方式是为每层顶点建立一个映射数组f,数组存储着每层顶点在M中的索引位置,这样每当需要添加Mi的顶点和三角片进入M的时候,Mi中低一层的顶点由于肯定在Mi-1加入M时已经加入M,则只添加Mi中高一层的顶点即可,在顶点加入时更新其三角片的顶点新索引。这样在所有三角片加入M后,Mesh便生成完成。由于这个子算法属于算法细节,这里就不单独描述伪代码。包含这部分的代码会在总的实现中贴出来。

    合并前独立的两个Mesh 合并后,一些顶点共用

     

    子算法四:任意多边形三角化方法

      在轮廓线层之间的三角网格建立起来后,往往还需要把顶面和底面的轮廓线三角化,使得整个三角网形成封闭的拓扑结构,这部分算法摘抄自John W. Ratcliff在http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml上贴出的代码,其输入为点集表示的折线,输出为三角片集合,三角片顶点索引指向点集,这份代码中的Area函数,即是子算法一中判断折线圈方向的函数,该函数计算一个有符号的面积,返回正则说明折线圈为逆时针方向。

      下面简要说明一下该算法的和主要思想:先将点顺序调整为逆时针,然后沿着折线圈,迭代着创建连续三个顶点(Ai,Ai+1,Ai+2)组成的三角片,若该三角片的旋转方向也为顺时针,则将该三角片删去,直到最后剩下一个三角片为止。该算法的代码整合为一个静态类如下:

    复制代码
    class PolyTriangulator
    {
    public:
        static bool Process(std::vector<FloatDouble>& contour, std::vector<Triangle>& result)
        {
            int n = contour.size();
            if (n < 3) return false;
            int* V = new int[n];
            if (0.0f < Area(contour))
                for (int v = 0; v < n; v++) V[v] = v;
            else
                for (int v = 0; v < n; v++) V[v] = (n - 1) - v;
            int nv = n;
            int count = 2 * nv;   /* error detection */
            for (int m = 0, v = nv - 1; nv > 2; )
            {
                if (0 >= (count--))
                {
                    return false;
                }
                int u = v; if (nv <= u) u = 0;     /* previous */
                v = u + 1; if (nv <= v) v = 0;     /* new v    */
                int w = v + 1; if (nv <= w) w = 0;     /* next     */
                if (Snip(contour, u, v, w, nv, V))
                {
                    int a, b, c, s, t;
                    a = V[u]; b = V[v]; c = V[w];
                    Triangle tri(a, b, c);
                    result.push_back(tri);
                    m++;
                    for (s = v, t = v + 1; t < nv; s++, t++) V[s] = V[t]; nv--;
                    count = 2 * nv;
                }
            }
            delete []V;
            return true;
        }
        static float Area(std::vector<FloatDouble>& contour)
    {
        int n = contour.size();
        float A = 0.0f;
        for (int p = n - 1, q = 0; q < n; p = q++)
        {
            A += contour[p].X * contour[q].Y - contour[q].X * contour[p].Y;
        }
        return A * 0.5f;
    }
    private:
        static bool InsideTriangle(float Ax, float Ay, float Bx, float By, float Cx, float Cy, float Px, float Py)
        {
            float ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
            float cCROSSap, bCROSScp, aCROSSbp;
    
            ax = Cx - Bx; ay = Cy - By;
            bx = Ax - Cx; by = Ay - Cy;
            cx = Bx - Ax; cy = By - Ay;
            apx = Px - Ax; apy = Py - Ay;
            bpx = Px - Bx; bpy = Py - By;
            cpx = Px - Cx; cpy = Py - Cy;
    
            aCROSSbp = ax * bpy - ay * bpx;
            cCROSSap = cx * apy - cy * apx;
            bCROSScp = bx * cpy - by * cpx;
    
            return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
        }
        static bool Snip(std::vector<FloatDouble>& contour, int u, int v, int w, int n, int* V)
        {
            int p;
            float Ax, Ay, Bx, By, Cx, Cy, Px, Py;
            Ax = contour[V[u]].X;
            Ay = contour[V[u]].Y;
            Bx = contour[V[v]].X;
            By = contour[V[v]].Y;
            Cx = contour[V[w]].X;
            Cy = contour[V[w]].Y;
            if (0.00001f > (((Bx - Ax) * (Cy - Ay)) - ((By - Ay) * (Cx - Ax)))) return false;
            for (p = 0; p < n; p++)
            {
                if ((p == u) || (p == v) || (p == w)) continue;
                Px = contour[V[p]].X;
                Py = contour[V[p]].Y;
                if (InsideTriangle(Ax, Ay, Bx, By, Cx, Cy, Px, Py)) return false;
            }
            return true;
        }
    };
    复制代码

     

    综合算法

      在了解了这些子算法的过程之后,可以总结出轮廓线算法的总体步骤如下:

    1. 为输入轮廓线排序,确保按Z升序排列
    2. 调整输入轮廓线,确保为均逆时针
    3. 平移所有轮廓线,使其几何中心重合
    4. 对所有连续两相邻轮廓线,执行编织算法(子算法二)
    5. 合并子算法二生成的Mesh
    6. 使用子算法四三角化顶面和地面,并将三角片与上一步的Mesh合并
    7. 将轮廓线平移回原来位置。

       这样一个集成子算法完成轮廓线重建的类代码如下:

    复制代码
    class ContourLineSurfaceGenerator
    {
    private:
        static bool CompareContourline(ContourLine* r1,ContourLine* r2)
        {
            return r1->GetZ() < r2->GetZ();
        }
        void Transform(std::vector<FloatDouble>& list, float dx, float dy)
        {
            for (size_t i = 0; i < list.size(); i++)
            {
                list[i].X += dx;
                list[i].Y += dy;
            }
        }
        Point3d GetCenter(Box3Float& box3)
        {
            return Point3d((box3.Max3[0] + box3.Min3[0]) / 2.0f, (box3.Max3[1] + box3.Min3[1]) / 2.0f, (box3.Max3[2] + box3.Min3[2]) / 2.0f);
        }
    private:
        std::vector<ContourLine*> lines;
    public:
        ContourLineSurfaceGenerator(std::vector<ContourLine*> lines)
        {
            this->lines = lines;
        }private:
        void ReverseNormal(std::vector<Triangle> &list)
        {
            for (size_t i = 0; i < list.size(); i++)
            {
                int temp = list[i].P0Index;
                list[i].P0Index = list[i].P2Index;
                list[i].P2Index = temp;
            }
        }
        bool IsNormalZ2(std::vector<FloatDouble> &vertices, std::vector<Triangle> &faces)
        {
            if (faces.empty())
            {
                return true;
            }
            else
            {
                Triangle &t = faces[0];
                Point3d p0(vertices[t.P0Index].X, vertices[t.P0Index].Y, 0.0f);
                Point3d p1(vertices[t.P1Index].X, vertices[t.P1Index].Y, 0.0f);
                Point3d p2(vertices[t.P2Index].X, vertices[t.P2Index].Y, 0.0f);
    
                Vector v = Triangle::CaculateNormal(p0,p1,p2);
                if (v.Z > 0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
        void FillDownHole(Mesh *m, std::vector<int>* maps, std::vector<FloatDouble> &downContourPrcessed, Box3Float& boxD)
        {
            Transform(downContourPrcessed, GetCenter(boxD).X, GetCenter(boxD).Y);
            std::vector<Triangle> down;
            PolyTriangulator::Process(downContourPrcessed, down);
            if (IsNormalZ2(downContourPrcessed, down))
            {
                ReverseNormal(down);
            }
            for (size_t i = 0; i < down.size(); i++)
            {
                Triangle t = down[i];
                t.P0Index = maps[0][t.P0Index];
                t.P1Index = maps[0][t.P1Index];
                t.P2Index = maps[0][t.P2Index];
                m->AddFace(t);
            }
        }
        void FillUpHole(Mesh *m, std::vector<int>* maps, std::vector<FloatDouble> &upContourPrcessed, Box3Float& boxU)
        {
            Transform(upContourPrcessed,GetCenter(boxU).X,GetCenter(boxU).Y);
            std::vector<Triangle> up;
            PolyTriangulator::Process(upContourPrcessed, up);
            if (!IsNormalZ2(upContourPrcessed, up))
            {
                ReverseNormal(up);
            }
            for (size_t i = 0; i < up.size(); i++)
            {
                Triangle t = up[i];
                t.P0Index = maps[lines.size() - 1][t.P0Index];
                t.P1Index = maps[lines.size() - 1][t.P1Index];
                t.P2Index = maps[lines.size() - 1][t.P2Index];
                m->AddFace(t);
            }
        }
        void ReverseArray(std::vector<FloatDouble> &list)
        {
            size_t count = list.size();
            for (size_t i = 0; i < count / 2; ++i)
            {
                FloatDouble temp = list[count - i - 1];
                list[count - i - 1] = list[i];
                list[i] = temp;
            }
        }
    public:
        Mesh *GenerateSurface()
        {
            if (lines.size() <= 1)
            {
                return NULL;
            }
            std::sort(lines.begin(), lines.end(), CompareContourline);
            for (size_t i = 0; i < lines.size(); i++)
            {
                std::vector<FloatDouble>& linepoints = lines[i]->GetPointList();
                if (0.0f > PolyTriangulator::Area(linepoints))
                {
                    ReverseArray(linepoints);
                }
            }
            Mesh *m = new Mesh();
            std::vector<int>* maps=new std::vector<int>[lines.size()];
            for (size_t i = 0; i < lines.size(); i++)
            {
                maps[i].resize(lines[i]->GetLinePointCount(),-1);
            }
            std::vector<FloatDouble> upContourPrcessed;
            Box3Float boxU = lines[lines.size() - 1]->GetBox();
            std::vector<FloatDouble> downContourPrcessed;
            Box3Float boxD = lines[0]->GetBox();
            for (size_t i = 0; i < lines.size() - 1; i++)
            {
                ContourStitcher cs(lines[i],lines[i + 1]);
                if (i == 0)
                {
                    downContourPrcessed.insert(downContourPrcessed.end(),cs.lineDownProcessed.begin(),cs.lineDownProcessed.end());
                }
                if (i == lines.size() - 2)
                {
                    upContourPrcessed.insert(upContourPrcessed.end(),cs.lineUpProcessed.begin(),cs.lineUpProcessed.end());
                }
                Mesh *submesh = cs.DoStitching();
                size_t Z0Count = lines[i]->GetLinePointCount();
                size_t Z2Count = lines[i + 1]->GetLinePointCount();
                if (submesh->Vertices.size() != Z0Count + Z2Count)
                    throw std::exception();
                for (size_t j = 0; j < Z0Count; j++)
                {
                    if (maps[i][j] == -1)
                    {
                        maps[i][j] = m->AddVertex(submesh->Vertices[j]);
                    }
                }
                for (size_t j = 0; j < Z2Count; j++)
                {
                    maps[i + 1][j] = m->AddVertex(submesh->Vertices[j + Z0Count]);
                }
                for (size_t j = 0; j < submesh->Faces.size(); j++)
                {
                    Triangle t = submesh->Faces[j];
                    if (t.P0Index < (int)Z0Count)
                    {
                        t.P0Index = maps[i][t.P0Index];
                    }
                    else
                    {
                        t.P0Index = maps[i + 1][t.P0Index - Z0Count];
                    }
    
                    if (t.P1Index < (int)Z0Count)
                    {
                        t.P1Index = maps[i][t.P1Index];
                    }
                    else
                    {
                        t.P1Index = maps[i + 1][t.P1Index - Z0Count];
                    }
    
                    if (t.P2Index <(int)Z0Count)
                    {
                        t.P2Index = maps[i][t.P2Index];
                    }
                    else
                    {
                        t.P2Index = maps[i + 1][t.P2Index - Z0Count];
                    }
                    m->AddFace(t);
                }
                delete submesh;
            }
            FillUpHole(m,maps,upContourPrcessed, boxU);
            FillDownHole(m, maps, downContourPrcessed, boxD);
            delete[] maps;
            return m;
        }
    
    };
    复制代码

     

    实验效果

      本实验采用的轮廓线一共有四层,每一层轮廓线由如下图四条在取自X-Y平面上格点的点组成。数据的预览图如下:

         
     最下层轮廓  第2层轮廓  第3层轮廓  第4层轮廓

      生成的结果预览如下:

      本文的项目工程的下载:http://files.cnblogs.com/chnhideyoshi/BlogAlgCp.rar

    展开全文
  • 按照正向设计的思路,桥梁上部结构轮廓一般可解析为两部分——参数化截面和参数沿纵桥向的函数变化。 1. 截面参数化 截面参数化就是将截面上所有变化均定义为可在外部驱动的参数,或通过外部参数集进行描述。...

     

     

    按照正向设计的思路,桥梁上部结构轮廓一般可解析为两部分——参数化截面和参数沿纵桥向的函数变化。

    1. 1.       截面参数化

    截面参数化就是将截面上所有变化均定义为可在外部驱动的参数,或通过外部参数集进行描述。如下图所示:

                           

    定义参数化截面

    截面参数的选择有多种可能,可用完备参数集的概念来评判起参数集选择是否合理。所谓完备参数集,是参数间既相互独立,又能描述截面所有的变化情况。

    按照道路描述的习惯,一般从平、纵、横三个角度来进行参数化解析桥梁截面。所有参数均以道路中心线为基准。常见的参数组合为:

    平面类:结构中线与道路中线偏心值、顶板左(右)边线与道路中心线距离、左(右)悬臂长度、底板左(右)边线与道路中线距离、腹板根部与道路中心线间距、加劲肋布置位置距离道路中心线距离集合;

    竖向类:铺装厚度(道路中心线处)、梁高;

    横向类:顶板横坡(单坡、双坡或复合坡)、底板横坡(单坡、双坡或复合坡)。

    构造类:加劲肋尺寸、加劲肋间距等

    满足完备性要求是评价参数集合是否合理最重要指标,但现实中允许一定的信息冗余,这可以通过定义冗余属性(参数是设计人员通过某种方式直接赋值;与参数不同,属性则不可以自动赋值,可通过参数集合进行间接计算得到,它是反映结构某种状态的数据)来实现。如桥梁左、右边界与道路中心线的间距为相互独立参数,而桥面总宽度为两者之和,即为冗余信息。

    参数的层次结构,是评价参数集合优劣的第二个重要指标。一般而言,根据参数的作用,可以分为定位参数和构造参数两类,其中构造参数又可以分为总体参数和局部参数,局部参数中还可进一步细分。定位数据和构造数据不可混用,否则将导致设计修改十分困难。

    道路总体设计成果中,道路中心线的平、纵、横成果数据均为定位数据。截面参数中,结构中心与道路中心的偏移值也是定位参数(虽然有时结构中心与道路中心线重合,但这应为两个独立的概念)。

    构造参数是用于描述截面上各构件基于结构中心点变化规律的参数。构造参数应具有合理的逻辑层次,如描述截面宽度、梁高、横坡的参数一般为总体参数,加劲肋间距、加劲肋构造参数等一般为次一层级的参数。

    1. 参数函数化

    所谓参数函数化,其实是各参数沿道路中心线变化规律的描述。以某跨径布置为30+50+30=110m钢结构连续梁的梁高H沿跨径方向(x)的规律为例,可描述如下:

    序号

    参数

    X坐标

    (m)

    取值

    (m)

    变化方式

    备注

    1

    H

    0

    2

     

    中支点梁高2.5m,边支点和跨中梁高2m

    2

    10

    2

    线性

    3

    29

    2.5

    二次抛物线

    4

    31

    2.5

    线性

    5

    50

    2

    二次抛物线

    6

    60

    2

    线性

    7

    79

    2.5

    二次抛物线

    8

    81

    2.5

    线性

    9

    100

    2

    二次抛物线

    10

    110

    2

    线性

    梁高参数函数化

    在CATIA软件中,截面参数的函数规律也可以用law曲线进行定义,也可以利用law法则定义。无论哪种形式,为保证与工程习惯相吻合,参数函数化均具有如下特点:

    1)       上述各参数的变化,是以道路中心线为基准进行描述的。

    2)       是以平、纵、横三个角度分别独立描述的。

    1. 3.       生成引导线

    生成引导线的过程,其实就是将截面中各参数变化规律重新整合的过程,这是前述解析过程的逆过程。

    实际项目中将产生各种形式的空间曲线,其中最简单的是生成道路中心线的过程。道路中心线的曲线方程可以表述为:

     

    上述坐标(X,Y,Z)是基于流动坐标系的坐标表达。其中,X是道路平曲线;x是自变量符号,它只在一组确定的值域内取值(道路平曲线),是道路里程桩号的线性函数;Y是横桥向数值,对道路中心线而言, ,曲线与道路中心线重合;Z是高程方向函数。由上述表达式可见,道路中心线是由一组显函数表示的。在CATIA软件中,利用平行曲线的功能可以生成道路中心线,过程如下:

    1)       生成道路平曲线(即Y=0曲线);

    2)       生成道路竖向曲面(即Y=0曲面);

    3)       根据竖曲线(即Z=Z(x)曲线)生成法则曲线;

    4)       生成道路中心线;

     

    道路中心线生成过程

    对于截面上不在道路中心线上各变化点,理论上也可通过这种利用平曲线和竖曲线结合的方式生成三维空间曲线,但事实上在CATIA中却很难实现。原因如下:

    非道路中心线上点的函数表达式为:

     

    对照道路中心线的生成过程,生成曲线 是容易实现的,但竖向函数 是以道路里程桩号为自变量的,没有办法将函数形式改写为 (该函数不存在单值逆函数),因此无法直接应用于 上。

    如采用分段函数的形式,也可间接实现上述过程。分段原则为在每一个区段内,函数 或  具有单值逆函数(即 )。但事实上,由于道路平曲线是由多段曲线组合而成,上述分段不仅工作量大,而且工程意义不明确。综上所述,通过平曲线的方式无法得到想要的空间曲线,需要通过其他的方式来生成。

    经过多种尝试,最终发现在在CATIA中,可通过投影曲线的方式来得到该曲线。过程如下:

    1)       生成道路平曲线( );

    2)       生成平面曲线 ,曲线宽度取一个合理的较大值,至少应包络桥面;

    3)       生成竖直曲面 ;

    4)       在道路中心线上生成曲线 ;

    5)       按照法向方式,将空间曲线 向数值曲面 内投影,生成投影曲线 ;

    6)       利用曲线 及 生成投影曲面 ;

    7)       生成平面曲线 ;

    8)       将曲线 向投影曲面 投影,其投影曲线即为所求空间曲线。

    上述过程的关键就是生成准确的投影曲面,示意如图下:

     

    生成三维空间曲线过程示意

    上述过程虽然复杂,但每一步相对简单,不容易出错,而且将空间曲线的处理方式均能统一到该模式下,具有重要的普适意义。

    目前在生成路面时,采用了多截面轮廓的方式。也可用这个方法来生成曲面 ,但必须按照横坡变化进行分段。在每一曲线段内,横坡应为同一线性规律变化。显然,这是非常麻烦的。

    需要进一步说明的有如下几点:

    1)       曲线 以法向的方式往竖曲面 上作投影,生成投影曲线 ,法向应为竖曲面的在对应位置的法向。验证方式为:在曲线上任选一点,向竖曲面内以法向的方式作投影,投影点均在曲线 上。如下图所示:

     

    点向竖曲面内投影

    因为点没有法线的概念,因此法向投影的方式,只能是按照曲面的法向投影。

    2)       竖曲线 是非道路中心线上点的真实竖向信息,而不是道路中心线的竖向设计成果。 包含了定位数据和构造数据两类,在程序编写时应分别计算,然后相加,不可混用。

    3)       生成的投影曲面 时,宜采用两条引导线扫掠的方式。这样得到的曲面,其上任一点向道路中心线所在的竖曲面( )做法线,法线均位于曲面 内。这能保证所求的结果与工程实际一致。

     

    曲面 生成方式

    4)       上述方式将结构各点的平面信息独立于其它信息(主要是横坡)之外,这对于优化设计具有积极意义。因为我们在设计过程中,常常需要调整结构平面布置,按照上述过程,我们就可以随意修改结构边线了。

    1. 生成桥梁轮廓

    按照前述方式,能得到截面上所有特征点的引导线(变化曲线),进而生成结构轮廓。

     

    典型截面

    桥梁截面上所有特征点中,有的点是独立的,需要用独立参数进行描述,如结构中心点和顶(底)板两侧边缘点,这些点显然需要求出引导线。但对于腹板顶点,由于其位于顶板面内,可由顶板边缘点或结构中心点进行定位。对于这一类特征点,能否通过平行曲线的方式由其它曲线生成呢?要讨论这个问题,首先应弄清楚在曲面上生成平行曲线时,基准点和生成点间的连线,是基准曲线的法线还是曲面的梯度方向。验证方式如下:

    1)       利用基准曲线在曲面上做平行曲线(目标曲线,非等距离的方式);

    2)       基准曲线上选择一点(点1),向竖曲面 上作投影,得到投影点(点2);

    3)       通过投影点2、点1作直线,并延伸与目标曲线相交(点3);

    4)       由点3向竖曲面 上作投影,投影点如与点2重合,则说明点3是目标曲线上与基准曲线相应点1对应的点。

     

    平行曲线方向确认

    上述验证结果表明,利用基准曲线生成平行曲线时,平行方向为基准曲线上点处曲面的梯度方向。因此,利用顶板边缘线生成腹板顶面线的方式是可行的。

    综上所述,在生成截面轮廓点的引导曲线时,只需生成结构中心点,以及顶板边缘、底板边缘等少量点的引导线,然后利用引导线生成必要的曲面(如顶面、地面等)。其它引导线可通过曲面上作平行曲线的方式来生成。这有利于与设计过程紧密结合。

    转载于:https://www.cnblogs.com/DSBIMbyLincoln/p/10527454.html

    展开全文
  • 1.元素的显示和隐藏 1.1 display 显示 display 设置或检索对象是否及如何显示 display: none; : 隐藏对象 display:block;: 除了转换为块级元素之外,同时还有显示元素的意思。...1.2 visibility 可见性 ...

    1.元素的显示和隐藏

    1.1 display 显示

      display 设置或检索对象是否及如何显示
    

    display: none; : 隐藏对象
    display:block;: 除了转换为块级元素之外,同时还有显示元素的意思。

    特点: 隐藏之后,不再保留位置。
    应用: 配合后面js做特效,比如下拉菜单,原先没有,鼠标经过,显示下拉菜单, 应用极为广泛

    1.2 visibility 可见性

           visibility:设置或检索是否显示对象:
    

    visibility:visible;  对象可视
    visibility:hidden;   对象隐藏

    特点: 隐藏之后,继续保留原有位置。

    1.3 overflow 溢出

    		检索或设置当对象的内容超过其指定高度及宽度时如何管理内容。
    

    overflow:visible; 不剪切内容也不添加滚动条
    overflow:hidden; 不显示超过对象尺寸的内容,超出的部分隐藏掉
    overflow:scroll; 不管超出内容否,总是显示滚动条
    overflow:auto; 超出自动显示滚动条,不超出不显示滚动条

    应用场景:

    1. 清除浮动
    2. 隐藏超出内容,隐藏掉, 不允许内容超过父盒子。

    1.4 显示和隐藏总结

    属性 区别 用途
    display 隐藏对象,不保留位置 配合后面js做特效,比如下拉菜单,原先没有,鼠标经过,显示下拉菜单, 应用极为广泛
    visibility 隐藏对象,保留位置 使用较少
    overflow 只是隐藏超出大小的部分 1. 可以清除浮动 2. 保证盒子里面的内容不会超出该盒子范围

    2.CSS用户界面样式

    2.1鼠标样式cursor

     设置或检索在对象上移动的鼠标指针采用何种系统预定义的光标形状。   例如:cursor = “pointer”;
    

    cursor:default; :小白 默认
    cursor:pointer; :小手
    cursor:move; :移动
    cursor:text; :文本
    cursor:not-allowed; : 禁止

    2.2轮廓线 outline

     是绘制于元素周围的一条线,位于边框边缘的外围,可起到突出元素的作用。 
         outline 包含: outline-color、outline-style、outline-width 
    

    去掉,最直接的写法是 : outline: 0; 或者 outline: none;

    2.3防止拖拽文本域resize

    实际应用场景中:文本域右下角是不可以拖拽:
       <textarea  style="resize: none;"></textarea>
    

    2.4用户样式界面总结

    属性 用途 描述
    鼠标样式 更改鼠标样式cursor 样式很多,重点记住 pointer
    轮廓线 表单默认outline outline 轮廓线,我们一般直接去掉,border是边框,我们会经常用
    防止拖拽 主要针对文本域resize 防止用户随意拖拽文本域,造成页面布局混乱,我们resize:none

    3.vertical-align 垂直对齐

           有宽度的块级元素居中对齐,是margin: 0 auto;
          让文字居中对齐,是 text-align: center;
    

    vertical-align 垂直对齐,它只针对于行内元素或者行内块元素
    语法:vertical-align : baseline |top |middle |bottom

    在这里插入图片描述
    设置或检索对象内容的垂直对其方式。
    注意:
    vertical-align 不影响块级元素中的内容对齐,它只针对于行内元素或者行内块元素,特别是行内块元素, 通常用来控制图片/表单与文字的对齐

    3.1 图片表单和文字对齐

    在这里插入图片描述

    3.2 去除图片底侧空白缝隙

    原因:
    图片或者表单等行内块元素,他的底线会和父级盒子的基线对齐。
    就是图片底侧会有一个空白缝隙。

    解决方法:
    1).给img vertical-align:middle | top| bottom等等。 让图片不要和基线对齐。
    2).给img 添加 display:block; 转换为块级元素就不会存在问题了。

    4.溢出的文字省略号表示

    4.1white-space

    white-space设置或检索对象内文本显示方式。通常我们使用于强制一行****显示内容
    white-space:normal ; 默认处理方式
    white-space:nowrap ; 强制在同一行内显示所有文本,直到文本结束或者遭遇br标签对象才换行。

    4.2text-overflow 文字溢出

    设置或检索是否使用一个省略标记(...)标示对象内文本的溢出
    

    text-overflow : clip ; 不显示省略标记(…),而是简单的裁切
    text-overflow:ellipsis ; 当对象内文本溢出时显示省略标记(…)
    注意: 一定要首先强制一行内显示,再次和overflow属性 搭配使用

    4.3文字省略号三步骤

      /*1. 先强制一行内显示文本*/
      white-space: nowrap;
      /*2. 超出的部分隐藏*/
      overflow: hidden;
      /*3. 文字用省略号替代超出的部分*/
      text-overflow: ellipsis;
    

    5.CSS精灵技术

    5.1为什么要使用精灵图

    在这里插入图片描述

    图所示为网页的请求原理图,当用户访问一个网站时,需要向服务器发送请求,网页上的每张图像都要经过一次请求才能展现给用户。
    然而,一个网页中往往会应用很多小的背景图像作为修饰,当网页中的图像过多时,服务器就会频繁地接受和发送请求,这将大大降低页面的加载速度。
    为了有效地减少服务器接受和发送请求的次数,提高页面的加载速度。

    5.2精灵技术

    CSS 精灵其实是将网页中的一些背景图像整合到一张大图中(精灵图),然而,各个网页元素通常只需要精灵图中不同位置的某个小图,要想精确定位到精灵图中的某个小图。
    当用户访问该页面时,只需向服务发送一次请求,网页中的背景图像即可全部展示出来。
    我们需要使用CSS的

    • background-image、
    • background-repeat
    • background-position 属性进行背景定位,
    • 其中最关键的是使用background-position 属性精确地定位。

    5.3使用

    首先我们知道,css精灵技术主要针对于背景图片,插入的图片img 是不需要这个技术的。

    1. 精确测量,每个小背景图片的大小和 位置。
    2. 给盒子指定小背景图片时, 背景定位基本都是 负值。

    6.滑动门

    经典布局:
    <li>
    	 <a href="#">
    		 <span>导航栏内容</span>
    	 </a>
    </li>
    
    CSS样式:
    * {
      padding:0;
      margin:0;
    
    }
    body{
      background: url(images/wx.jpg) repeat-x;
    }
    .father {
      padding-top:20px;
    }
    li {
      padding-left: 16px;
      height: 33px;
      float: left;
      line-height: 33px;
      margin:0  10px;
      background: url(./images/to.png) no-repeat left ;
    }
    a {
      padding-right: 16px;
      height: 33px;
      display: inline-block;
      color:#fff;
      background: url(./images/to.png) no-repeat right ;
      text-decoration: none;
    }
    li:hover,
     li:hover a {
      background-image:url(./images/ao.png);
    }
    

    总结:

    1. a 设置 背景左侧,padding撑开合适宽度。
    2. span 设置背景右侧, padding撑开合适宽度 剩下由文字继续撑开宽度。
    3. 之所以a包含span就是因为 整个导航都是可以点击的。

    7.拓展

    7.1 margin负值之美

    1)负边距+定位:水平垂直居中
    一个绝对定位的盒子, 利用 父级盒子的 50%, 然后 往左(上) 走 自己宽度的一半 ,可以实现盒子水平垂直居中。
    2)压住盒子相邻的边框

    7.2CSS三角形之美

    div {
    width: 0; 
    
    height: 0;
    line-height:0;
    font-size: 0;
    border-top: 10px solid red;
    
    border-right: 10px solid green;
    
    border-bottom: 10px solid blue;
    
    border-left: 10px solid #000;
    }
    
    1. 我们用css 边框可以模拟三角效果
    2. 宽度高度为0
    3. 我们4个边框都要写, 只保留需要的边框颜色,其余的不能省略,都改为 transparent 透明就好了
    4. 为了照顾兼容性 低版本的浏览器,加上 font-size: 0; line-height: 0;
    展开全文
  • CSS轮廓outline

    2016-03-15 00:50:00
     轮廓outline处在边框边界的外面,它像边框那样参与到文档流中,因此轮廓出现或消失时不会影响文档流,即不会导致文档的重新显示。利用轮廓,浏览器可以合并部分轮廓,创建一个连续但非矩形的形状。默认地,轮廓...

    前面的话

      轮廓outline处在边框边界的外面,它不像边框那样参与到文档流中,因此轮廓出现或消失时不会影响文档流,即不会导致文档的重新显示。利用轮廓,浏览器可以合并部分轮廓,创建一个连续但非矩形的形状。默认地,轮廓是一个动态样式,只有元素获取到焦点或被激活时呈现

      [注意]IE7-浏览器不支持

    轮廓样式

      与边框类似,轮廓最基本的方面是样式,如果一个轮廓没有样式,轮廓将根本不会存在。与边框不同的是,值少了一个hidden

    outline-style

      值: none | dotted | dashed | solid | double | groove | ridge | inset | outset | inherit

      初始值: none

      应用于: 所有元素

      继承性: 无

    轮廓宽度

      与边框类似,轮廓宽度不能为负数,也不能指定为百分比值

    outline-width

      值: thin | medium | thick | <length> | inherit

      初始值: medium

      应用于: 所有元素

      继承性: 无

      [注意]如果轮廓的样式是none,则轮廓宽度计算值为0

    轮廓颜色

      与边框不同,轮廓颜色有关键字invert反色轮廓,代表对轮廓所在的像素完全反色转换,使轮廓在不同的背景颜色中都可见。但实际上invert关键字只有IE浏览器支持,其他浏览器的轮廓颜色是元素本身的前景色

    outline-color

      值: <color> | invert | inherit

      初始值: invert(IE)、前景色(其他浏览器)

      应用于: 所有元素

      继承性: 无

    轮廓偏移

      轮廓偏移用来定义轮廓的偏移位置的数值。当参数值为正数时,表示轮廓向外偏移;当参数值为负值时,表示轮廓向内偏移

      [注意]IE浏览器不支持

    outline-offset

      值: length | inherit

      初始值: 0

      应用于: 所有元素

      继承性: 无

    轮廓

      轮廓outline类似于边框样式的border属性,允许一次完成轮廓样式、宽度和颜色的设置。由于给定轮廓必须采用某种统一的样式、宽度和颜色,所以outline是关于轮廓的唯一简写属性。对于轮廓没有诸如outline-top或outline-right之类的属性

      [注意]outline中并没有包括outline-offset,需要对outline-offset进行单独设置

    outline

      值: [<outline-color> || <outline-style> || <outline-width>] | inherit

      初始值: 无

      应用于: 所有元素

      继承性: 无

     

    应用

      由于轮廓outline不影响元素的盒模型大小,不影响页面布局,所以可以用outline模仿border边框效果。但如果是圆角边框就不是那么好办了。

      firefox浏览器支持私有属性-moz-outline-radius来设置轮廓圆角。该属性对应的js写法是MozOutlineRadius

      对于其他浏览器,我们可以使用其他属性实现类似效果。box-shadow与border-radius属性一脉相承,也就是说如果border-radius是圆角,则box-shadow的投影也是圆角

    <div class="show">测试内容</div>
    .show{
        margin: 50px;
        width: 100px;
        height: 100px;
        background-color: pink;
        border-radius : 1px;
        box-shadow: 0 0 0 30px lightblue;
    }

    展开全文
  • 对角点轮廓遮挡, 利用过角点的两条曲线的可见部分构造两条Euler spiral, 根据Euler spiral的扩展性对构造的两条Euler spiral进行延拓并求交, 从而修复角点遮挡轮廓。方法能自动确定遮挡发生的位置, 能够对T型节点...
  • 之前做获取边界点的时候,主要采用的是在线地图的方式,因为在线地图中直接内置了函数可以根据行政区域的名称来自动获取边界,其实这些边界就是一些点坐标集合连接起来的平滑线,然后形成的轮廓图,这种方式有个弊端...
  • CSS(9)轮廓

    2012-08-31 23:59:00
    在CSS中,outline属性用于设置元素的轮廓轮廓是绘制于元素周围的一条线,位于边框(border)边缘的外围,可起到突出元素的作用。CSS outline 属性规定元素轮廓的样式(outline-style)、颜色(outline-color)和...
  • Java AWT/Swing实现规则窗体和控件

    千次阅读 多人点赞 2019-05-09 05:27:28
    终于重写这个话题了。 缘由 2003年是我写Java的第一年,2004年是我写Java的第二年。 由于是自学,又是大专,没有科班的基础,所以不是很care算法和数据结构,因为Java可以快速作出一...如何用Java实现一个规则...
  • 利用到路面提取道路中心线的方法

    万次阅读 多人点赞 2016-10-11 08:23:23
    利用到路面提取道路中心线的方法   在利用GIS制图时,需要经常跟数据打交道。很多初级的制图人员都存在一种惯性思路,以为数据精度越高,出图的效果就越好。...由于其精度高,有些数据甚至是线道路图层的,而在1
  • 采用红外和可见光视频融合方法增强检测效果。这些措施使得夜间运动行人检测结果更加可靠有效,行人及其所处环境信息更加清晰直观。  夜间行人检测是视频监控及运动目标检测中的重要研究内容之一。夜间光线较弱,...
  • CSS中的盒模型,边框和背景,表格与列表,颜色和透明度,盒子阴影和轮廓,光标样式,CSS3 前缀,以及长度单位 rem。
  • 一篇关于红外图像和可见光图像融合的摘要

    千次阅读 多人点赞 2020-07-15 15:44:14
    相反,可见图像与人类视觉系统一致的方式可以提供具有高空间分辨率和清晰度的纹理细节。因此,期望融合这两种类型的图像,这可以结合红外图像中的热辐射信息和可见图像中的详细纹理信息的优点。在这项工作中,我们...
  • 1.cpu中的寄存器大致分为两类,第一类是内部使用,对程序员不可见,只能给cpu内部的数据提供存储空间,如全局描述符表寄存器GDTR、中断描述符表寄存器IDTR、指令指针寄存器IP、控制寄存器CR0-3等,虽然有些寄存器是...
  • 参考源 参考论文:UnderwaterHazeLines_BMVC2017 ...现有的单一水下图像增强技术要么忽略衰减的波长依赖性,要么采用特定的光谱轮廓。 我们提出了一种新的方法,该方法考虑了不同水类型的多个光谱剖面,并从单个图像
  • 此外,数字电视显示过程中的一些视频增强处理,同样会让伪轮廓变得清晰可见,例如直方图均衡化、对比度增强以及清晰度增强等。位深的减少可能来自于多方面的限制,例如视频存储器限制、显示器的物理特性、显示驱动和...
  • 基于3DSOM的侧影轮廓方法空间三维模型重建1 背景知识1.1 三维信息获取方法1.2 侧影轮廓方法原理及其流程2 三维模型制作2.1 马铃薯三维模型制作2.1.1 多角度图像导入2.1.2 图像掩饰2.1.3 表面生成与优化2.1.4 纹理...
  • Recent Advances in the Applications of Convolutional Neural Networks to Medical Image Contour Detection 是一篇关于神经网络用于医学轮廓检测的综述。 摘要: 深度学习技术,由于其快速发展,已经成为医学...
  • B/S下曲线的画法

    千次阅读 2006-08-12 12:45:00
    人们看一些图的大概轮廓就可以了解事物的一般情况了。图的应用生活中可以说无处不在,比如交通图、股市图、地图等各种各样的图,给人一目了然的感觉。现在人们越来越多的使用互联网。人们学会了通过网络了解世界。...
  • unity 渲染流水线

    2021-01-20 12:27:46
    渲染流水线 主要分为应用阶段、几何阶段、光栅化阶段。 应用阶段:这一阶段最重要的输出是渲染所需的几何信息,即渲染图元。 几何阶段:重要任务就是把顶点坐标变换到屏幕空间。通过对输入的渲染图元进行多次处理后...
  • 《3D Face Profilometry Based on Galvanometer Scannerith Infrared Fringe Projection in High ...该系统具有投影速度快、成本低、体积小、红外或可见光照明、无接触等特点。可用于三维形状测量和机器视觉检测。特...
  • 一、A级曲面的定义A级曲面是指由造型部门或者Class A Engineering过程相关人员发布,满足产品整体审美要求和性能制造、装配及模具工艺等要求的视觉可见表面的曲面数据。(基本要求:满足造型、功能和生产)二、A级曲面...
  • 精彩不容错过尺寸链计算及公差分析案例免费咨询,点击下方链接报名!...https://mp.weixin.qq.com/s/uRcbGOq9ADLjIjH4p4Y_3作者:张露引言以往,在无基准轮廓度的检测与评价中一般都采...
  • 一、前言 之前做获取边界点的时候,主要采用的是在线地图的方式,因为在线地图中直接内置了函数可以根据行政区域的名称来自动获取边界,其实这些边界就是一些点坐标集合连接起来的平滑线,然后形成的轮廓图,这种...
  • 3.图样中,机件的可见轮廓线用粗实线画出,不可见轮廓线用虚线画出,尺寸线和尺寸界线用细实线画出来,对称中心线和轴线用细点划线画出。虚线、细实线和细点划线的图线宽度约为粗实线的1/3。 4.比例是指图中图形尺寸...
  • 基于Arcgis 利用道路面要素提取道路中心线的方法

    万次阅读 多人点赞 2018-03-22 10:49:21
    路网复杂的时候,arcgis制图综合工具箱里提取中心线的方法效果很难让人满意,以下方法亲测有效,效果很好,结果直接输出到模板,很实用,感谢大神。 但是Arcscan在进行栅格单元捕捉时最大能识别100个像素,所以如果...
  • 1、首先说一下canvas类:  Class Overview  The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap ...由于Android 画图API为提供直接画饼图的方法,采用了比较原始...
  • Unity Shader概述:渲染流水线 前言 渲染流水线 CPU应用阶段 GPU流水线 顶点着色器 裁剪 屏幕映射 三角形设置 三角形遍历 片元着色器 逐片元操作 模板测试 深度测试 合并混合 各种测试总结 附加知识
  • drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) //画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置,参数四y轴垂直位置,最后一个参数为Paint 画刷...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,676
精华内容 2,270
关键字:

不可见轮廓线采用