【原創】CGAL使用心得


CGAL使用心得

作者:李浩

 

首先我說說我研究CGAL的背景,由於,早一陣子,有一個需求,需要求出在一堆二維線中(包括直線和弧線),找出所有的最小區域和最大外包。如下圖所示。



在這兩個圖形中,要快速的找到每一個最小的封閉區域和一個由這些最小封閉區域組成的一個最大封裝區域。這樣的算法,有,像最常見的建構多邊形TOPO,然后用雷達掃描法,可以求出來。但是,這么多的算法開源庫,讓我下了使用開源庫來解決這個問題的決心,很快我就鎖定了CGAL這個強大的圖形算法庫。

很快的我發現,CGAL里的ARRANGEMENT能夠實現類似需求的功能,就這樣,我開始慢慢的對ARRANGEMENT進行研究。但研究CGAL的應用,不可能不學習CGAL的基本結構。CGAL是一個基於C++模版的算法庫(好像很多的C++開源算法庫,都是基於模版的)。學習其基礎對C++泛型編程的知識有一定的要求。如果你看過候捷的《STL源碼剖析》一書,你會更容易的看懂CGAL的代碼。

CGAL中的重要基本名詞包括核心、域數據類型、TRAITS特性;

核心中的數據存儲是用到了域數據類型,而核心中的數據與基本幾何數據的提取是通過TRAITS來的。

ARRANGEMENT中定義了點、面、邊的概念,每一個最小區域在它里面被描述成面,而組成最小區域的線被描述成邊,所有線的交點被描述成點。這樣,需求上的概念與ARRANGEMENT中的概念可以匹配上,就證明,可以用這個算法來實現需求。

在慢慢的學習過程中,我發現CGAL是一個對精度要求比效率要求更高的庫(這一度讓我想放棄CGAL,但后來,發現再差的效率,都比一般的算法求解出來的速度要快),而我們能夠提供的數據,精度是達不到的。就拿MF_FLOAT域數據類型來說,它里面真正保存數據是一個std::vector<unsigned int>,這樣的精度是DOUBLE型不能比的。到現在,我還沒有能夠非常完美的解決這個問題。精度不夠,特別表現在構建弧線時,CGAL preCondition就會通不過,報異常。這樣讓我很頭大。發現是,我們提供的數據,在構建CGAL的弧時,弧的終點不在其支持圓上。想了很多的辦法。最后網上有一位牛人告訴我,要重寫CGAL的一個DCEL也許能夠解決這個問題。DCEL是doubly-connected edge list data-structure,這種數據結構的最基本的觀點是,將被共邊的邊,看作是一對方向互逆的邊(注意,在這個數據結構中,每條邊都是有方向,組成的一個環就是一個面)。我又開始學習CGAL中這一部分的內容 Arr_default_dcel,並且學習着,里面對頂點、面、邊、孤點等幾何對象的定義。發現,如果真正需要解決精度問題,不僅僅是重寫DCEL能夠解決的。對核心的部分內容也需要重寫,這個工作量太大,代價也大。目前公司肯定是不允許的。更要命的是,公司的需求是,能夠在傳進去的線段上附加信息,出來后,其線段上的信息要不丟失。一開始,我想,有這個需求,不重寫DCEL是不行了。於是,我開始跟蹤CGAL構建ARRANGEMENT的過程,發現CGAL中,大部分的賦值操作,都是直接的內存拷貝。並且,不管怎么樣,CGAL中真正處理的線類型只可能是X_monotone_curve_2,所以,就算你在開始構建的線段上能夠附加上信息,當CGAL內部通過這個線段構建X_monotone_curve_2時,你的信息也會丟失。慢慢的,我發現,出來之后的線段也是X_monotone_curve_2,也就是說,只要你能在構建X_monotone_curve_2時能夠將你的信息附加上去,你的信息,就有可能在計算出來之后的結果上,還存在。CGAL將核心中定義的CURVE轉換成X_monotone_curve_2的過程是由make_x_objects完成的,所以,我在這里進行了改動,在轉換過程中,將附加的值的信息給考慮上了。最終完成了這個需求。現在,又回到了,精度的問題了,把附加信息的問題解決了之后,重寫這條路更加是不允許走了,通過CURVE構建的過程,我決定,將原來的構造方法改變,改成三點構造法。然后,在外邊,對輸入的數據的精度,進行更進一步的處理。最終應該是能完成這個功能。具體的,現在同事還在測試中。

通過對CGAL這一段時間的學習,我發覺,CGAL確實是一個很強大的圖形算法庫,對數據精度要求相當高,所以處理出來的數據正確率也是相當高的,對於那種對算法處理速度要求特別高的,不推薦用CGAL,但可以用CGAL中的算法的思想。

目前CGAL也有商業化的產品GeometryFactory,客戶還是挺多的,像國產CAD的龍頭產品(吼得最大,動作最大的產品,哈)ZWCAD,也是其客戶。

哈哈,就稍微總結在這里,對CGAL的學習,借用一句廣告詞——永不止步。

附加說明: 
1.       CGAL整體概述
CGAL是一個用C++描述的,包含三個主要部分的計算幾何算法庫.
第一部分是核心組件(Kernel),它包括基本的幾何對象以及做用在這些對象上的各種操作.這些對象被實現成使用表現類參數化的獨立的類,這樣使得核心更具有靈活性和適應性.
第二部分是一系列的基礎幾何數據結構和算法.它們被特征類參數化.而特征類定義了數據結構或者算法和它們使用的原生類型(primitives)的接口.在很多情況下CGAL中的核心類可以作為這些數據結構或算法的特征類使用.
第三部分是由一些支持設施比如為方便調試設計的迭代器,隨即數源,I/O支持以及一些可視化工具等等.這個部分主要介紹核心部分.核心由一些基礎對象組成,比如點,向量,方向,直線,射線,線段,三角形,ISO型長方形和四面體.每個部分都有一些對這些對象進行操作的函數.一般有訪問函數(比如一個點的坐標),測試點和這個對象的位置關系,得到對象的包圍盒子的函數,長度,面積等等.核心中還包含一些基本超作,比如仿射變幻,相交的檢測與計算,距離計算.

2. CGAL核心說明
我們學習的對象是d維歐幾里德仿射空間.這里我們主要考慮2維和3維得情況.空間中的對象是有點集組成.表示點的一般方法是使用笛卡兒坐標.它假定了一個參照框架(一個原點和d個正交軸).這個框架中一個點是由一個d維的向量表示的(c0,c1,...,cd-1),相應的線性空間中的向量也是如此.每個點都有唯一的笛卡兒坐標與之對應.另一種表示點的方法是齊次坐標.在這個框架中一個點是有一個d+1(h0,h1...,hd)維向量表示的.根據公式ci=hi/hd,對應的點的笛卡兒坐)標(c0,c1,...,cd-1)可以計算出來.注意齊次坐標的點的表示是不唯一的.當λ≠0時,向量(h0,h1,…hd)和向量(λh0, λh1 …, λhd)表示同一個點.如果一個點的笛卡兒坐標是(c0,c1,…,cd-1),它可以表示成(c0,c1,…,cd-1,1)這個齊次坐標.齊次坐標系可實際上可以在一個更一般的空間的中表示對象.這個空間叫射影空間.在CGAL中我們不會進行射影幾何的計算.我們使用齊次坐標是為了避免除法運算,而增加的這個坐標是作為公共分母.

2.1 通過參數化實現泛型
幾乎所有的核心對象(已經對應的函數)都是由模板來實現的.而模板參數是用用戶來選擇從而決定核心對象的表現形式.

2.2 笛卡兒坐標系核心
通過Cartesian<FieldNumberType>你可以給笛卡兒坐標系選擇表現形式.當你選擇了坐標系的表現形式以后,你必須同時指定坐標的數據類型.這種數據類型必須具有域的性質.前面提到int不是一種域類型.但是在笛卡兒坐標系中不包括除法運算,所以環類型在這種情況下是可以使用的.當指定了Cartesian<FieldNumberType>以后Cartesian<FieldNumberType>::FT和Cartesian<FieldNumberType>::RT都被映射成域類型.
Cartesian<FieldNumberType>的內部使用了引用計數來節省拷貝的開支.CGAL同時也提供了不使用引用計數的Simple_cartesian<FieldNumberType>.調試的時候使用Simple_cartesian <FieldNumberType>會比較方便,這是因為坐標被保存在類的內部,因此可以直接訪問坐標.在使用的不同算法的時候,這兩種實現方式將有不同的效率.

2.3 齊次坐標系核心(由於ARRANGMENT的例子都是基於笛卡兒坐標系核心的,所以沒有做過多的研究)
齊次坐標系中可以避免除法運算,因為引入的補充坐標可以坐標為公共分母.避免了除法運算對幾何計算的精確性相當有利.通過Homogeneous<RingNumberType>可以指定齊次坐標的表現形式.而在笛卡兒坐標系中我們還必須指定坐標的數值類型.因為齊次坐標系不使用除法,作為齊次坐標表現的數值類型必須被建立成為相對較弱的環數據類型.然而齊次核心提供的一些操作中使用到了除法,例如計算笛卡兒坐標系下的平方距離.為了不改變齊次坐標的弱類型參數,我們使用Quotient<RingNumberType>來解決需要除法計算的情況.這種類型可以認為是一種把環類型轉換成為域類型的配接器.它保存數為分子和分母的形式.使用Homegeneous <RingNumberType>后,Homogeneous<RingNumberType>::FT和Quotient<RingNumberType>等價,同時Homogeneous<RingNumberType>::RT和RingNumberType等價.
Homogeneous<RingNumberType>使用引用計數.CGAL同樣也提供了沒有引用計數技術的Simple_homogeneous<RingNumberType>.

2.4 命名約定
使用核心類不僅可以避免出問題,而且使得CGAL類具有一致性.
1.    以大寫字母開頭的名字表示幾何對象,像Point,Segment,Triangle.
2.    下划線加上對象的維度,_2,_3或者_d
3.    核心類型加上參數類型比如Cartesian<double>,Homogeneous<leda_integer>.

2.5 作為特征類的核心組件
CGAL基本庫中的算法與數據結構是由一些特征類來參數化的.這些特征類包含了一類對象和上面算法或數據結構的操作和函數的行為一致.基礎庫中的大部分的算法與數據結構都可以由核心組件作為特征類.一些算法可以根據傳入的幾何對象的類型進行自動的推導,從而不需要直接指定.有些類還需要更多的參數.有些則不能使用核心組件.

2.6 選擇一種核心組件和預定義核心組件

如果你使用整形的笛卡兒坐標,大部分的幾何計算將只使用整形數值.特別是在只使用斷言計算的時候.例如點集三角化和凸包計算.這些情況下笛卡兒坐標系是第一選擇,即使是使用RingNumberType.你可以使用精度受限的int和long,使用double來表示整形,或者任意精度的整形例如GMP整形的包裝類GMPZ,lead_integer,或者MP_Float.要注意,除非使用任意精度的環類型,溢出將會產生錯誤.

如果出現新建點的情況,比如求兩條直線的交點,笛卡兒坐標中的計算經常出現除法.因此使用笛卡兒坐標的時候需要FieldNumberType.相對的,轉換到齊次坐標的時候也一樣.double是一個不精確的FieldNumberType.你可以把任何RingNumberType傳給Quotient配接器來得到一個FieldNumberType從而可以在笛卡兒坐標系中進行運算.一般來說使用RingNumberType進行齊次坐標運算是比較合適的.其他一些FieldNumberType有leda_rational和leda_real..

如果可靠性對你來說非常重要,使用經過認定的精確計算的數據類型是比較好的選擇.Filtered_kernel提供了一種過濾機制使得核心具有既精確又有效率的斷言.仍然還有許多人喜歡使用double,因為他們需要速度,而且可以接受近似的結果,甚至可以忍受時不時由於舍入誤差而崩潰的算法.

預定義核心組件
為了使用方便,CGAL預定義3個類型
它們都是笛卡兒坐標系核心
它們都支持從double型來建立笛卡兒坐標系里的點
它們用不同的方式處理建立幾何對象的問題

---Exact_predicates_exact_constructions:精確的生成幾何對象,提供精確的幾何斷言
---Exact_predicates_exact_construtions_kernel_with_sqrt:和上面一樣,但數值類型提供了精確的開方運算
---Exact_predictates_inexact_constructions_kernel:提供精確斷言,但是生成幾何對象可能存在舍入誤差.這對於大部分的CGAL算法來說已經足夠了.而且比前面兩種情況要快很多.
這三種預定義核心組件都是基於笛卡兒坐標系核心的,只是其構造的域參數類型不同。
我個人建議,內核用基本的笛卡兒坐標系核心,域參數類型用MF_FLOAT或者DOUBLE類型,因為,這樣的效率會比其它,對精度對准確度有要求的域參數類型和內核封裝類型(其實也是從笛卡兒坐標系核心派生出來的)快。效率最高的Exact_predictates_inexact_constructions_kernel就是Filtered_kernel<Simple_cartesian <double>>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM