From 6be76c00d1d3d6e14d8dfb35ffc73abefff39169 Mon Sep 17 00:00:00 2001 From: Alec Jacobson Date: Wed, 16 Apr 2014 15:08:24 -0400 Subject: [PATCH] tetgen 1.5 --- examples/skeleton/example.cpp | 5 +- external/medit/Makefile.igl | 8 +- external/medit/medit | Bin 1252820 -> 1254204 bytes external/tetgen/LICENSE | 699 +- external/tetgen/README | 2 +- external/tetgen/example.poly | 223 +- external/tetgen/header.txt | 17 + external/tetgen/makefile | 16 +- external/tetgen/predicates.cxx | 518 +- external/tetgen/src/behavior.cxx | 210 +- external/tetgen/src/constrained.cxx | 3098 +++-- external/tetgen/src/delaunay.cxx | 596 +- external/tetgen/src/flip.cxx | 3108 ++--- external/tetgen/src/geom.cxx | 529 +- external/tetgen/src/io.cxx | 46 +- external/tetgen/src/main.cxx | 111 +- external/tetgen/src/mempool.cxx | 320 +- external/tetgen/src/meshstat.cxx | 263 +- external/tetgen/src/optimize.cxx | 819 +- external/tetgen/src/output.cxx | 308 +- external/tetgen/src/reconstruct.cxx | 1752 +-- external/tetgen/src/refine.cxx | 1835 ++- external/tetgen/src/steiner.cxx | 1749 +-- external/tetgen/src/surface.cxx | 1118 +- external/tetgen/tetgen.cxx | 15873 ++++++++++++-------------- external/tetgen/tetgen.h | 1793 +-- 26 files changed, 16869 insertions(+), 18147 deletions(-) create mode 100644 external/tetgen/header.txt diff --git a/examples/skeleton/example.cpp b/examples/skeleton/example.cpp index f030ea0ab..276e6504b 100644 --- a/examples/skeleton/example.cpp +++ b/examples/skeleton/example.cpp @@ -252,7 +252,10 @@ void display() glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, SILVER_SPECULAR); glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, 128); - vector dQ(BE.rows(),Quaterniond::Identity()),vQ; + typedef std::vector< + Eigen::Quaterniond,Eigen::aligned_allocator > + RotationList; + RotationList dQ(BE.rows(),Quaterniond::Identity()),vQ; vector vT; Matrix3d A = Matrix3d::Identity(); for(int e = 0;emvncl7yD_$yMASqS3#hnayPDC&8ckwi zj2e<)7d3WG?7i2!8hbY?@8`_iMUvn5zt`ts_s;2a=FH5Qa@P}^Gq&u>sQB0Gd+*8^ z49vk`n4lO827mnZ_MB63Q~8)G0Hd_%N?Cjw3^7&aTx(SR(7Un%I*(BJcj%p3i#00W z+u$m|f0GRS>updc0o>X_f|o!A{cG)@9s~G(SqYADli>Sh)v8LP@Qlat@8XgU-1NJ9;>I8=%?+Fz> z1bR?{G@r^25`DzMk&RJLIy7KDY9Ggta)#C>K{`#Zs6k!m*tp!ZHWKQon=(zc0lGkk zCh6b>YJ#z`_nAJDHc6+YvI8G(araYK8H2oi`%1L4j;6A1>Pur#xgZ@qMn?y!VNQ)J zEYqQOnJDvh=UfBS1x`Wbujt4io#jej^?*|&Z$m$+C^T4yqimFwu2&|gLQIZCd+At| zrN|hE^6FP*JiIMBfs4DOptA0?D@u8Fy0gh^jucTOBgN1gWnDw4bC7q?B8d*tHBea> zu+KS2Nm``dac-%MSgh7}sWaEr*X8A6N$;#qEc=0rVTmmDRUu5)l)QHJvG?%1DA(->8ZNhIZ_?x?C0{(P2v}* zpSW7e%|`IkzhyG+8ctRZxTe(JYcNn``Qoo$nQ{gf#OjGN6d}Icbh!Yoh3xDDE zGD*X~Q1nDnioT-`asN}P@JMaoG0W%&ke~XOhmT{O=-4{y9gjdkq!)}0EX$O&KjH5*=|H!X&r77$~Y9L3H8v1&w;>iM>hI)1sW}Rj%Ka1 z+90$_`E*I;Tf#j<9TM6y{sFkQMT=@}F?+&Z?I<8<*w2dbm3Iw3OV9fQYF7blbu+{K z)KTjF&~U$Ri6B4A?5TAW9KQ;7a{nkbF3jxGt+8m;;aAn(VJjFKEBs66V{gk+6<(>| zgtrOXo@lZM^Cc*@DCU0Z8S|-@@{L_)zHW)%@2T4V|Jz= z1rey16L)_6kxxyq)ROPM3lDI|22hi0_EqV9zE^9qiH9C3N z2`EN=^Ip@}7V{BObcbLiU2L8JiqYjGovJla4VIkUO2b}58#pa_yG%K5Msfx^kR|W3 z;P#GElWSIC=hfady(-QIr7$-+?~lxAOTQ!V&QHPW^qSFZiMp|-f0`e}r{&(u?3=tD z+JvMSyQF@Ru7Ar260syXPc*%UZI0IkO5 zU`x%jLE>dhKT=`?(4oO5{?QrX+^t7)_v9YQJ(GLwnhi513N_MJ}Uc5uk|abngO8H6eg3&0pr0WPtR5yOS?&YJ&KwN`vPr|Kwb>r`v} zDW^3(;j9zthjnTxm1?P9*Xff{5$)fCy-D8dXr>m{qA!;cdiG`KN*-4>;$P-R7>6T*l|E;D=#6|6K;;XYKz1G^Y5ri0uGLh`G*{7d>o_h ztec&7lv?ZQMXCEJ9-d#pKKR;Sb(TgDv-t@cIL zIiY&m?h8WIZ?8yIq8Sst_8dBFJ>Cl41Ch6&-v}p0R{D<Mpgv6S%PvFD;ZO+gnQ~5wfnA0@%*?wClW(FOHD^1;fd4_UizsfL z*JV5tJ@?--mJ`Jvpm=QZoM(QC78B~SSi$uAb(vF(E~xxSb#H^Jv0qn2g({VbiX${J zqe9KjA(!H?=QA-=>n{HJyt*vW%XFnPk{Nr8wmPpSHuN`DFHP55(#_D^2!?+9uIE;d!T0S_i2{Qe15N10UKj)wfYQ|B%zvYF&&QKSpII z|KYo9peb~hB`*sp=u35KqYVG;rzAm}QUp6C!EE(GqcGN9t<*T4-BMdMZsv6QFaqII zb!lVoYG;PK8cd-VOreLh+kjH;IRQp){|>7z&4K7X7DqLg?@}gXAcYsDhL<;7jtMWNxPi2t7e1OxcM(T^Vi{x&x;yV5MShTO z8h(;K4gpt;DSM#BG<>&kulA>UI5n}i^+9Sx4d)|~=Nz5|dAKX%_qy<%s z28s3rc#+s>OOK!!c}#7e_K>wMNNd_cQOXW1m}d!PY^S<4eYDc-uv(|tFYHUz*6c6# zreI_9vWz_{_#-3S+2@Z6`f6i7T3+j&<%o#z-v66w>@d?Q5zVhHDOlK{fVnM0uP}w) zLH*qasGU2th2+(p`o#AC4J5_CQXFmmn^C*B zv!8OpRqfh2r0fHL((}hhYBN-8=c?ZEV4+1?ZyBg&VpyT>>>TGcRw$rFjgMp*s?`8X zaBxO`zeueBunc6{FAC`UmgHW9g68+D+PsUOvZt&%s7sxO#|ShGAJnAOSl!LO_-t>c zHp{?D<+c#;Lirxq;ThF@8W7pN`T{|g9-PP+|YbdMSRo8AgWv{N+ zYx1SKuv>taM>`5OucCp}l|ql0LicGl^*}ds_NBJ)Vy9!K(4AVRby!+4>`82Q#!^gc z1_?03xDOQCGvKykV*x7=JOS+en_z^n1KJfy>HUoa0qZ`MnD*)LFW!^>rh`7zNwYya zlxP5zGmIe1Vg7g@fQj6g0*V?0OG@i(m2tl#m6uLv}sLOYL4@!|BcbE5a z?zDlNkBGZ2^xs2CcY&`?d*D6DNQTV-?KY*vHL>dos`UtBP77${y051945(A%=YLUL zMA2d$8)})nH%Pm+2DnAYGB);y2RG3N;!%N2KH)2+8v9l)>giSOO)D~^h`jdZCCEyj4wOy$d<)5~6>jOjoxJ5u@ld;HU%P~Q5!e1pmd ziGDj-rZs|Hw&PSL-}(xx;M<+r93qTYclR2u*w(1=y&LygwZ>!^S#OW=vKiZX-7s6w z?u|VW&-cDt&egCj1~TQKa4x|KDbjgMo^b^DGG}f_dVxF$easY?giL8dB8MD9rt!%x zS^!y%>1))Jy_4LV5_DHmp_1H2JE&Ie^9lP%o!6&owNZ)*G2Q9FKvRX5&I3(8Ej`J1 z%QDD~7*)#|J1FYeKC>~74(=Py?x^$ohOr`bdtbG~rskmW!kmKs&}jz->XaZ%ch22< z?7{-tWO6lZ%8^TwkM_|3kXRM;+ExSt&^-X!G)1DDNwfo@m#E+Oi?CdA)CVsw1dEn6 z%u&{z)^7R6i{D{~!MgGYZDY2;8o*iGu?mr4Kw_M?H!nw|0b0-xh^ZZF<^KJ#Xw2@P zT6x#^pmD%7xZM#ST2~aeU!!?*i+ZttK&McM$jE!W%~0OnaVr9np5G~En^FRDkRZ=( zhKhEohmZrRq;0GObdf~2CeexCt8E5UD!+kftXs@6ln74V8;VRbYrt_O%A&Rz_&v)} zUk&_<+0=O-j$tDT+y}L1N?e-S@1w0I!#CurEg~z@+*hbI2gk8>YQMp;%J>!Pgi$q> z7Aw@fgX5fhSD>y-e;G2IDIQG6wITxR=Xh-|t@?K+~FVpvr0-H0v<8#ni}uh~^~(P!(F znEC}RM=DH7O;E3l9jtE2w3z(AEG09H3z>&F$z=1?>`^gJ{WwJ4QGXj1>H6(_yYrW; ze~(%fYpCmLuzmJW^ii_%VI7p)J&@&8lV|nI1h@{sz}dhj9x2#8x(#FFRiDon!J=P3 zU!JLd zI~#`8nWYXM=f_^CQ^rNH{pzpdsw3dfjjP78)pz4wmT^Mw*BUHP-;P(Y<1;6lvyN&Q zMxD)Hs$C{FQ%w^sN(epX`5d|BA$R%Oz~$P|R$|^gIZb^vv9{axNOYL?=qaA=f2#?T z;w!bEUOI3mAfUAZfVB4N)JfH_4Y_(!HN|(jdSsGWiOef_KB)^+qQccSIoFi*Ff~4R zJ!byfxw90f(1I~jnlN^*VDr?EjjU}!ME*@hF;`K`&#umT6x5sDRq=BBqO4)U7V}c* zz!u~@AE+u;znk0E;i;c1UpIsWa*wGFo)*PlWN$l6yvSxEhs$k>{KT^hVAuW07`v9- zFD{l{C;4-GpHiq#?HPk+ErTG}JU>@IRevczqB5H8(4+V5*h{T54~JO<-uAw^Ju4SX znb+FLzEe*xJkJiO1z+t}94i!j@ikgfD_FD0swk-@wf@p0@QCZO?d%uz;IjFOzk5O7 z<((KiU$E($b&e6?*eJIheoOTywlin@xJNen5?Muc{+P$Nh3V zYTezWX8&B<^%HEhV(1KrHMpwVey&sL&mN^4*Pr;Jdcui+s_tvMvRrl0+QiVQ*iA(b z-AV4Qim45wWKI@nKK~GoUWM67j`{7?!)xoYS*p{z80=ZqUDsb(UZ5^s=Tl)?fd@h+ z&M3wS+V9qK3ihsBjXd!mb!5Q_rS@yJ*{>&A4>jnwCG3>C{Wn!<@=|U8`+oLJty1_k z)^nQ*=dnN3RvY>{-D^iJtQKwdolV==H1*l0Z`3Q*eG@JHY*#dhHcMh-POYFS*(v*yC|QvDcD)$3?Uinp&fBbg$$ML zoXsYxb9dHJnl&#tu=5Kgt~G6hwta5z`QAxXK8K|grY>s87%Knuv?PKO18wT)-Fw-I zg2sES5ZzQgwD%FqF4(p&m?`cz)vNns6u+Blg#!)U7GqmY?`#LuZU_AOG+d%L;W%tR zO1~yFDQTkQyxrO&6tzTrU{f@AFT9WDH135Q?JEFjxpf7-=Si~3a|$&l8f)%C56bB= z_Zg=yEI56jFJs5m`iByf;A;h$hhiD~S^fTS3{sObhnFe=SJcn{e9acBBmYY3=!>0> zw7kRG>_mva-jH?-i%mJ-($dmuESTg0>G6Mb#}--MYwHa2D(ID4aCMr(BjZ6PI%Y}t z>;h6P`UAD>kqPWrf%S;O*lBg<(I)JWdh}?8^7F760wcSdJ&~eOtxl`Yk47rd^;Pq+ zmWp#%b>y+SL754q#>=^SVI4GU@eLsTgnIDUN5KoQC81TQkB@<%utlQnAUMO^8;dYw z1dwXb(Llfc<|NH2RV?a&!eY!=QEp|#Us7isPw-hMO65kHTkE;n6RhU3%DsF%f(=$( zPh_!I>a-Ifio-m0{RxZf^iEj9iadv_jwgF5?3_B_WH@`E&OTX%{iUuw>0POyBbs~4 zVXXdb$!IxKZVh~*h32alPsXwls_9f;ujIKR0x}0`S6@58!QRbko7CJ>JxEutYmh$b?BWG%u7NNAk5luy)z(mz}wz|il_pl=Eu3GPGj#90iy6tQ=CApz` z{p@HqU+s3zY+BKPvXeA25IR0lXPygpAFRUwXxdiYeJ;YK79doG*XpZttJz5P+w);c zvo`9H^Ssjf)=1?(rvS~5{}In{x^f(K+pSKxP&@LS)x(gU_eLCi4aGsR?07G0mUKKE z%>`*)W5Do_dga1L=s(RawlKE>ft+MsME`4&$wE<1GJDj`7lS)B1)(LsUFkF}PJ&9O zX+aWHI!$wzprCE6B)@x2vdN1W2IDTpDJP=U?vJau&JdJY2PN2|d% zf|aUsb5^_u2lR>eQ>LnTK8%dh5xAzzuiS$cQrr>{8Rnw>VP<> z2r@d$+*&)QKP738!zHY@wx780wTcbEla_nKl-r%sPbf{&T8EY8M@KmZdOHamjuKvo z;f!#8VN@k{q#)%+EK`E^tDaZ7sYh>iQp$&@;kTmMHMPsFDE6y5_0~XEtlqyho=s4P z-5#wt??cV{tIagG#2aRN?KV^wx-&;hy$5wfZ*8eSJkM+-5x?k&5!w`0)zVmB^{f`C zG%>5MwGiJ*K-yw3IhG7|k5UjV@6-faG}5y{wknlhM!_#}*gV>2fqOi0l;LbUhP4;= ziqT=oQ4yFbMF@o;+Q!Ia{m*N?h;fwB40xes7goVZ3)sN(o<|7oB&KNTlcA5 z?nWuU22=C6erY8qS^cTEcBusR++Wg-&<>Qq zt_j!{^91Tj1?s?u)f8`k)%q}s;auzCOT}-iTJR{6byKfAic*aJs_Ah>2X8+alU@h? zC}!@NaUtF~94c+MyfzgSgT)Y5{cKpp(LknvZp2 zxk{9a*~pN%`mK`u^_>L2=_jXB{Ckbk`ue4T9gHNv zYo8H^RsBYJ;C7rcx;WZn$`wxutM?&fO|vG6wHS3vY!>Cv7tReF z`3h&2;?)pzHebk?jS2iCjJaAP5B}Pj`B$FgU>^a+GOrAZat3b0V_aB8rJe`3xG=L) z-h&UvH~X8bF03y5l^=9reZr>n)YI;^&i0XavQj#_x@$L4)~4_dWmyEv;uFiVe8poW z_jF~U-gg*Wfj(33eMD)ecneqNQ)%0KR7pTh-s!1*$HxKfIumF)a!%q)U0F{ytMHvG z3sZtGE~hn@*M4l`;?$#y`(ar=hB8v)F5X)UEgozrV^{cZCN@N={FYbsWIbKyzQswl z7zfsIt0(L25mEw3d*fo@p7R`ixMw-m+_exzNqLeFEXVq~G!p=0z!HA69IL|C@`vSE zWu^RL?pB_~vwpluc@~@P($>RJDi>My+QndV7bi`Le^!QnR#5va#sU{}((P9+rRSva zD9Qok3Z885N0#j+h|PGiV0^P!XHzKHnqodh-tKSKRLDgK=8wy>?X0lymkKOeK`UJK zW*>UD$D$uY#{rxU?8CR%x!A{hRAhscRttHOKkKSEEab}rSvzIh0{+OKB`DSfJT{00 zMtllZ3wGl+P>ij4>VnQ+p7LS7EKqsan$PrQvFwM!B44&rVK4arKi1pj z^juLx_fGszKQ@A$;*I@TH)Xmi4Np_0;pJ6fc=K1FE-}3J11PHSkPLsIIiLW8+nYmS z`Ytj(53~pwY?F35ioXnVq-_eu)`93kMq6_azRZ{T6s`$il~~17v;MCuo6Y7Is<020 zqqBuCSuS4|$l5DOv!zAuvxP-5Pr+JZQO&<$k;gq3L%M1EZcDzGxcO_@amCpZb|gJN z%P~l^@d-h!hV!LZv4%X&e-2_Ht}V#B)&@w?5ArKP%kM-}kj})ICr+>c!L=BCrXuIu!+Qj?ejas8A>H@#KZ9A2Qu}G)!(cYiL2-Y~Kda7S z*m=IPIy&`Qexy1}V#9g48mzivdBp40z<5#h5$}m_|2Ob2WP9N(kdA9?A3mel6uPeR zB{f)Owuf&7BU{EV*Idh22|x%bqV$qn^jU4 zqMbC*8d&p}67VSW+EUSIf~uaN(%zt@1qQ;;*ecR=Y2#mppzj{wzlE?s_9H(@U(@-s z5ZKk9`-QRy&vVx)uyG0`j~_N{C<}J!epARa{=}DuvKs6(-xtc7D4!;A zw=lMzZRgvhg3S114toD}KxDS?;41YK$2I5&2Fs3{Xm^A` z_lO|6y5e$;w+d%|E~~&wO1g!kUv#xematfUc9pI9f!9VOq#FJ~4t-sO}4TXx23}}8{olM z781m`-i5}ofUi-spKEG^c;lL^;s=d|5>NNK;=AW=%mIhACJ=;^-5~Ay1$)!3z!Nns zDvD_u!o9anpfD+-wyu@U7u958l^!AZMO(KPZB3u4=JuNJC4LF$5Dpp1llQXp$-8~E z))!q2sR?8IoAQfrye1oZ1PHXzh0!sr88clN5BcKUiGuP^J~fsNV)^`GEc0WZaJO14 zK>6T2|0|k#V<ftO;2y@n)FlgAiBWswAe>}G~8)@Eu+66nMdAmn$*MnCJBeM<&aORJg19=<| zh-X30Ccu%lT;OT(Y^>9vXlz=~;d|m)9i`$K{w|)?>iGB+&9T=?WqDTsNyp{M(mOkc zbXe(y^adSPdL_LQFzEZ5PpQLpE9s+oi@M0@Ch|>nnc1^8kZB3S5nsPjBdq85v3!o2 zb^?A;aAvc;Eq1B2<0zs6Gh+1Qbr_a8Xxz!a(rET4PprrM%Rd3lc27F!i^mi6{mzHf zV=+$UB2mBJ`SN<~6UA_ndnF*pzsTz(uuj?gkHL!`w3iWGl7PPjY%GGK!9+QbF+R??$<^>1W7?EpdR2q_rIO4go# zKY_tB(Oqqm(0j2<0&mi4V*-6?7r{hza~O%bjVC**f(p!XF@m9cV;F27PO{ISV#F6E z_)7>zlY(ec-~$2+w!u8L#zemvMNC^&8?aj04S~m~pAK5eDT8RoAa9rUA%W?Vc?YaT zfN#|YnU#*$=AjGNZz%7>5dkif+wc+;$jU+tT|t~D1ZCu}6j%PmE%O1yL|Z`E3!?qe zL?MJ9Z@{{C7LDbm?q1-`bGHRqG!JpUo%vFog8U%Z4og#!YG5Di1r$DCD|)M13>cRH!h~!SD4_@ zQ=B6_u(>gy|Acylzb3Lcri66lwnoVIB2RPA#tgeOymn(&TZ#O*aByRGn`JLYpAm0L z*!p3Vn~jU2-9$EL4+Xr9QVtHO4&`=-3_{vV4k@DUR%DC#5B*V3(3ieKKT4;c4SITw zWQ62?@v^ipB|s)E51Uw2why zE5;rWoUGMC0Tp_I)g1!iJNN(8qdBq%s`mhpS*%-^q3yL~dPbcRB*fN6=vfqm99LGRDj9w%Hh5Ky}Xvl1ty4{y<1})fVX- zP4UgQG-tsHvA}GKASL zir_~+HUj}Qn*W@^)+sIf74~ky`YFl-U%tB)OK|y(Ibqp(pd5?O3w1){7h4vyk!%P}`^#wWdw! z*xLJ1VWakJAydwm=Vv>x&g^bst&XfCQ^L#f>77_`_Sx6&hD{j1OTr?HMkrKp+BO(o z1z8o&ij(t>B=2~sBv00uZ7x6-Yt(Y$D_!w_keA3;hQKIa8QKa74CEQs>YsvKL{hr7 zwpnuqNxHS6Sv%PjG}e^%wzU*W`R>;tWK$RozIa)P1m-91=!{xE-9pdqg@g{3==Bo) z4WUo+5uK5~Ju~s`omnlHxQ^nmFyIM)-I-Ob_+3Hi{69-`{uP7l54=_v_5u5h<629d zu&fT0p+}2*8Euh2K=0?sW3=H^g*TXm<>D~?7k;)2M&D`Ny({AH8BglUK8mbR)&ogp zIioASWAACd=Fz1TeRDK-J$06R+7!aD7WK05WLMUMHoxM!v+vjwey%&)#WMMl9;^`I z+_5K0uXyENtTkK3hxB4E%QW~2W;FbX5AMyTI8+PU$D16eTN zGzf0~jr)GYzG0pD=8xEqY$_i(7(UmyJFerfeLRZ3R`C|}HH{A&!lJ$Q{{d}Zk`q^8 zz(<1xG)}>#hampf5az8|QuwVQ*xLH!4Nn=$>bZT=gjRWUuNhm9Q-`uRALnJIT{Kb~ zjI!psj2|7!>a(ldZ5Z=~;_Aa#-DXFUMZ}$>t0~clIJ#YksH=ogLE8qW+Ky1vi7RyF zmO`qyTPHS%zg&XZ3_B+HLSo?Y5C3i$%TS`1@TbFAP37!!UVS(VtNinGcY{`Glp`&7 zjE|r!KZW*S+@q8Dpy8}aoJSHZ(|YV9^d_Jmn>y#1HH0aeW`>x#h+1nbsE!cTJic)_ z3$NaVYF)BCOhE~(#&$vk-Z7w8DYS0f`D3g;sw5RAeat>JD%TQt=do;xVpzbhjAh=5 zI~IsMwY_;Le2k0kV#e>U{b&dG7U1pz+*wlrrVd4;pY}5sB78so-?V7!OjgOIkzg^N ze#|dsvKs*x=VK{>2sLP%=}CCC5Kr*msYm?MDC}p~+62chgQvA`e;>^{Ix3Cggw3ow zzdV*ztQ#;_H~HVTIoZMguE~QrLin`5Nxu(I$zl_g=D+eiS=g~kT*z0CXCdwk!;pT% z-DM&78^=Q24r8R!K__|3aV*+Jl@M(S&mG6A*Zc%Uyx!%gQT0=C7@L%qcg14eVqS*I z;Vuj13-K8sO3mA^HDAEbj$>olX5L{ub`RHZspOMiN|b!ge;m(3-4owK(m207o&}Z( z07whu+?dUlv)}pm*{nu(^)FE=U6I9TQ@H-JCkd*xRwvy*0fu5r&Hqk;x^58;_&aewu;$H@wrWn5DEhk|6T;Y8D1Qy9o z@NpBEFW)r*;lJWGFE^3Z@%a`<)##I^utnm@`G>_;}Qw zBuB%91_o1ZS)9qinvE~XDE$?l?tI5YmZ{W;;`Ju68XkUYspYYkPcJ?e@}ZO1RHv0c z!|e9_74YnMVbElD%7LxqFQ%{tO66I+_EeUxg#E~~r?Oz@Ni#)#i}|Xltg+HyCbvz+ z=$^&vPh%nJJMw8=epO7sa8L*hu_8{7=?E-;$#dmO5F3F&l{zGe4HrDMpsnP${KskN z{OS4p{50gtK!JWFm5ic_G#{hqh&EEE5>V1=n2hcvMsrMwpen_X$A|AA(N z0IXN(4UwGsNS3JOdBhy%&W`bfIV{fK8LBtoZZxKIINu7ndSp6lyZN*^tPcB$Z=b`0 z18OaX(H(L`Non<$YNj!ROD# zg#R<&I+w*%`;cfXps`rfoUM2fxXCoHdg<5zOiOr|eUfau&OKGuqk+$M)U))Gt^x)? zO8)jt!X{4c^YI@dYO2TLWZHQP3w8sH| z-dX9ZyuI4niKt$}9#hU?%w=fNxNT5Z8E%yn`H7Fgh&~kwWIWq_W)VcUOd^n zg;b*}_|SZpK#e!fWf06KJfAy{B?jz(u|Pj9gZFOHyGINY2?cd=t=2^T_dHfPd-&G> zXpYo1N0lgaC4pT;a%*f%tHOJo#yb`SLv;+%1OyLUb_M}AA@=EooVn#{P3 z6{U8PAhhR#UpOFCFIqNXr=-jT?(-#%h*opUmn<|p339a4*~IN$56|K{n)l=Dv@ix& zJfUX-ZEfy|VoMaQ6+nY(i~*F!39&THPpl7=Fiy)OatG7e6vy16@!5D`j9w=VlCrnb zAeJmXO}~4_7CC6xs($<>s}^=0rb95jyKVa2D-;U;8BcTC+j5zI(OXFLLoR7C_q4mb z5yyt}U%V@4HQL|DMzFRQgfKr;O2FFY8lb}b>HuluftKbsLeXyiAV5o*9}bi>|8stV zv*?IsBs5{SX-aJwqkT91CsM9`1p>rq@OU1z0PE;AD5sg$-q)Ut6Ez_N+*=V*zPB3; zf)m=Hr2Pmq%t2kP6#yFm7%DqYFqL?-BcRfpZ-KzOIL_()PQqLXn**3F8-1YE1|yOAE=UEOQWr%VdI4xZHrB!G0Sz zPXtmc;DZ;k$-cd^TnzbM-CzU#{6@cH(vtKnZv2Y139hn%dLoTq`SE*7H2*%d9+wivIWXY_jjmt?T}+ULMhr>A$*={=JS_COoJ zeZOYm>@iRInl)m(`J}Hg6OG`WicsfXm4m`jT(ZS)ALm2rd}xB|fKjtEjM{uB6= zqIJ6wzZ<080AcL&?Fv1gkw}Gw3^+aEz6#WLBegwa`gw=QcoKzx7 zBLkaYr&U>Kfy>b+2=qD$3n{w)*zoRvR_w)Rv%qMi+7C3Sa@N}*p zG4mag#CG_V2K<~${CuEjF2gZD&f5+xWiy>#ZwB>w-g_B~i+HpihT+C&Nku+X^@?Ol zX^rqrb9kLhzHJ#BpM4Dpxb|u!&4-Sr+`eM%9v_uQ=4Y8lXhWot^bH=w7vYy zau(|O7$|GX2#k`M7$f}Wa?fwD@shGmOlt42Qn0ogv6%);*^keVB-C&jzHJ|(;p96a zR%0+Y#193;e@x2@(dkzckuOb?;P?Y>uwcFqm9E~gQ4BdGhp_dn?}~9!wl}3cR2lfX zZ&~KMD(ykb>=1Xp+u;TtcylNdpZK@sk z$xe_g3EXvp_8?ePSo|F;a%CqAi+;hnfX)B%H}*uy$mY*}WA|D2!W+Lc4_0yRS9ZUA zBjyJhb^LpB=M5M_Zt(OCSPfO{$v@wKYjP|3q7Cc?`>Al=M(i>$m9N>vo>#v5CAm#* zvZHRVKSflK{Oc_^^C^^2iw+&p z1(kiE6=sSe2CDErO`tTD3 z=>~~DPw2)HT`18%6MCUUUm!H~Vc5PvqGu8M3x04bYgYc_d7{$_M&ZUcg(2IRV;N=Q z3LdqaP4V6_Ll77!yaYkd*4X==^SjX(8Io~1u&H9&E=GJvf(Q_x&-z1QdBJ5IOELtr zN#eYKt0Hl|N^nnF(ZJCis0fKlEkT_D%63x@`Kk74sj$}`mZVg40S-LddefIYy3+z% z@!$5b?D#QD_3);bUU4!*8!n(-jG#$Sb5OwVaS+IAte9svaTEV&KMOZKA6iOYt(N?o z{p_|9wuD;`up4CpAwa9qoR2!l^8F@GArHy3J_!G+uiX(&&3KhVY+_i0!JvMgjw;;6 zxCYlmg6zsiE{3q3&|H{n*W8WoJH&=HodHbVPMaSRZJHbr4F!P27B#WX`}GJKQcERK zz2S}YetR&7r{+Dk&dVVTNW~{oSQvqV)D${G68gS0jE=8 z>JhfCj7#TUw0TmbEh&6^8q4_lcYDCt8;Q_xojL(l>OBECP5?g<&WS6CVkZH5(2G`~ z*bt$YYTxmxXIZ__tz%Ffyc%Nl)RG#Cmgz$cgaqE$2;cZEsH*s}Fl`&ZeHMEO5355v zHVXN&vn(*Q#e3c(4FvC_mXdcq)RpkI6ufK^|NI<_2t6}eS6D&oCYiEyrXZauffSzQ zht9F2=y&yn+(4aV7nzt_l(_;GUEG~U;9J@dNRer00k40anFFej228%5RH>yx04gjX zT2DUqJd5#-f*kBKI#?#}a<(Mg&Aef;($9BXVZ($bnmHG~k;qu<*2^dZK0@032JKN0vBhw_t2W z!gUP#nHOlrTm+hAtjqC>OwNb{`HM_=`#NzKhAf25r#$Q;3-ODT%F?YBwY5^}k>*m_ zNIv8u3#;xap&ZZ>U3LDW>u#mW*Y02M6qIUxwTTHdW8)ihgS2Ih@Y8``QCPT-Rt;-i(Vs)K6 zb`kY0;^#@{kk53T9q_7V9ih}iSNaQ?PxkH_Ifj%DlIREMf~3?*n?nT9*-9c#=?GXe zTF1oK;h$V)8KEsC#c)tiyOl4Ij8|=>CkAvz&uYtWUS?r_!#^$Y!=rd%QhA;6cOG_y zMYL@#p{FgS+F%qaUeeyV_ePdO(`ub4tnZ_By~u$7w*3AQ^fUh56&CLQ(f8sCVLWLRt(|FwDegkoa~hxG`Pi#0 zI_~%gD3e{Qg4SL#XA(2PG#|-6vU92a@|It?igm_e?sg69suR5KHC8pC+Iumc+R8ZM zFX>?Y_^4|vUWtn5YpyX%^3;z@{90BJ#z!=j#{Na>g|Qnxospq}qQW*a41*WC@!P4o^B_`L88 zhpa!02G8L;uCpj*=5YS-I_saXGFBcy6=91HJ&-)nTU-~>76Aj^t*P`yFPQYA3SdOt zXUUJsXwUO+Fkin9+PfGw(yvfj^7fz>Prftd{2?|AquTSL8>~|NMKr>H2+m_~5h5lM ztH-}se--y$qDFI%o2-)Wyi&4BFn;rpYO#(tzR4>3Ehj8(dcW6P4j1hqcmW>)_V{%~ zK)+`4o*)~0bs$(w7_3`MVY&wh%pATAtk?%UbCWd;dtOs`z$blW)b_>9E7N|s_5#|SI18DYxVSXEx@X(dTcfkYQOny4T)UShV`F=nlYR$0JT8NA+NakR>cyA5w4}1;=;J!*KB{jh=HAt6wKN9J~WXhB#YYJ`9 zPRWs3K?v+aeE$I;q-Ny*By7{~Ha*aMq=tIXK&^FwszE023Wdpmuz3!oV*$A! z+ueXg27Uq6lsg3H0{}U+<~41svD+e|$h%MMz4#Y^R0@O?T8@^%_OEk`gxi@nX{zn4 z9p}evtgqXth!O_8aBznuRPF^U{ue`q2(I2?84V+d17}RgPDsr|(nwaG#%=;R_SJFBrt63xEmB9^2}6^lnp;;d&c-#mytGh$@GkoyBL-kpqY#M*X)tJA zgeUfDJ`%A+M>uLpI^tcZ;KRG{fG!{5-bs`bcDawOh-6N#4H^Vq{Zrp2Q@@xN9hQ&=HjbkPVv9) zvy%f1l3*$l1?j&qsupzJMv4l@4%ZQmnx~HVQzAO(2;86{<>2^DA{y!lH|>&+Sj1f( zvXilq65j#|0M+~vG-t4z@R=oVrzw}-&%*$4m8Kg&;O80K^oYf!?lTKRY;`4DJ{e4Y z9vLM4+yr=BuTAN0%5jyws1XuZ5wLwRwjyAwSooAjaD6)|X{0X6S4tADZ>hWfKsBMP z4M@lxLD~~C8kJiw5%qM0Sv#jA7Dz;(jtJFu=!k5IaMBTxn#(F3Gmtla%uWuRT~+9! z*pxBb>FVIwDOX4(SLtZMcqztjfQ5!j9A`3KqJq z)|O7pg>9vLzf$@>Go{4$K~;IyQ|8}4Cl$SJ4@SIK5MWK|ZySfT&;J_q(xrmIx&jZX zJhf6N&1b07t{^hN`v6kTyp-Oeb?|;1fiCbjPq8g=K@h~h#oSKOv7Bm@TO|7hHfgYh zs=j;N4=pQ5uqb8w8J{W3PkC#th1$GVdQgc)n$8D z5!yK>V;K_(WGIxp2oQ+zI>J#4(h-*=Vt|ft*2?IJ-4cQ_xd{ zF=P^j-fjGN57jpcMBo%0B7w|H8KjMuoJ$2KC6&p<2I}IHpX`RBIwR`w336(WtB6#jpLtKJwcRCP=UtaU7I z^m<1o7QTFgy~_^Ae1wQ6n6^m;Cl2bidq?)n$Sc-bNc0Jy5g+tgTqaRKcbvp*vSX68 zZaSut56^$cLYy-bC@8ece9Jo)UiF9ix?fj=(VEiTHU;nClE3Y6l{*)+zN|MNQp_5K z_NgfBnuOVo?5aUErK<1L9e&=M@ZKNuqs1&G`?)B^Q_euto5?n@Br@gP625w%4U;5K zF$-cjw2&xJi%t@HNkS*<&}0eSC!xcnG?D1GfVCpgy;K*8t_en3hngejtBNgqrWaWh zzZR1w+0c$EAUtWxc?-XjH;s~H21sB-EK$IQ2NeY4C<*n`p=Twuw}cv{G-1O{Ou)j1 zQFUO$_mD{!koZ4Lsn_AO>4{!VgilT%i*ne zr3PETS2-%tt!hj14m$ZOvX=}T>k0Dwq2|sIf@Ohrju4doqkzpbaYXfBkf-)5uWnR^ zxhg;4+7m8}rkWjjQm&&HfBOro#!ncPddjp_e8(@W7a!)N)QlT$f>62Uh$TDzEx*ct zL7tjxIsTWEGD2zW$&MC zy`p#r*!TK47mBi`sg5V}3*=pfT0X?*89N$$&X%OZnrAO(~kCL_Io6A`AlPjmV zlXmaQQ9Q(1N$a&l6yvvJG9+XANpi6Uo=%H# z7eyOv#cYB$7)qd}GHgp$u6tcY-8Cd3q;v*3$lii?1ou+O9qq=4msR|HNzF#M6ZPIh zJ0oStwYYN4mzPx5^$ph46;#YLwsPD^?I`0PI+{qO`x4BCc<8NRhChgNC zxoEP%v{M6EeMzzdBw{)V)%p?@WCcmg_jZg~v$*jEuFA;lcP^wQK2R6Ci;R+$%CX|) zj}psFN%98Mt1fmiQ9*1siMeLSn6=50U$opWaIR>%rxEaSb(oNfUK2_`Q8p_btwdP? zx4J2w*){EyMMU`sQf)S5(l`bC0h7e0qhhNnzDy*Unz&&lXhu8Jv-^*PZVWq71r5AF zFC=tlfljO;+Wv_%p)2DVXab>u^Pxj-=bvSoDpXRPK&2A{Ed+5|mQj5I&K6YfteFbGmaeRG{2=_&5=gDmlwNPS$ z>=?7QL&s!GjFTM`s(lX(O@_Jm#@EJMDUk^&{j(xcc?%Ea&+G?ILW3s~A~N|`vPo~l zsfa!@VxT0sha^HbVkS|+hzyB2ZpWClkvgW9#B8)3~B(g%1mtl3xsGUeVqPC;dC)>%IQTzy(}TSsBE z=I6vGdMfp@dxz^a`z8e9k3+n8sjPGzqJ_@*R>+&E4o|2`G9$_zLZ|fk@qTt8nIvQ; z326gNX(2X8GCaPlF63ujNH%Vo`4asq8?a41CA*Wv7ZQFH;ZfPD z_o+X*Bo7gEgS8bZr#B5NhYB;JM=?ZrJJO)P*E?XGxs|BIx5Ev2uLY+Z=W7i^o5 zMj5sZLBBCeKhgto%u_|K39QfvB9<5HKePiCTv9D)>U^Q^qd6JS~H zep9Zuu-|(ie^g$ntN2vq;T4pwis#odU$|L8iL3RPL5R#3ek1Xv^94`soBx7*wOPEG zmohASFLa65`?rW)2lo^*JxE6VvuaeE#P1ft^Z^EXb{|(AXn%=5CDBs}-IUPQ)f(z- z^N{-&bgHUdyW?CsKh$mrG4Yf-Q`LXSCWq;f-1(@8+Xrh zeu=~e?$q{xP6oS&;7Du_1zI?21;@0a=PKgaeIO~$k&3rhC0`|uAjLJI7@h8Cphcw` z1E-B3iw!h~^V$`a3Te9}(G6scWXv>bOA3gA2ufS!zeUZ?W8U8Mi&W4?Do{wlj(5&r zx~&Z>oKaDUV9GzO`9>e5it=rJe%41B8~XcO7=UP?Yoy1J1Ztm?GLn8C7ilC0745;Z ze3e8cu()uuukr;`KB&j*R#sy0D%7CLN~>D!{t?v}=|L90(0oZKPORsoTYdC)c%jt< zjV+7%QNHnD3vR2dv{q8;@& zy|A*s67EoD&vOW!^M=<9RJ_~$NCg+X^%r~I_Ez2l*p20!w(SK;$J^+s))8K|`t*Kk ze&RL4to#>--oM1PJ-Yu)IXL#GkMV*)rM5D^7Pkc|{@tHEbH;XqDfbvUo`wD!E`#XA z@nI_GA9risbpy_1y$gTJBr_X$!e;m23T z1}U*#&SUhM0*9jd@-nT&W;Oa#vsME{F|)a0<2FPn~J+E7InMcB7ZO zv9`rsh#Y(x@C!RiRQ6*edqn98$^>x;lbp8)CqAN+$2WrLJi0)#BgVc>F#v?HVO1wb z*lde{T)OJHp76MzvJ8Teh=rDgBR646mRlRK;Q5!ogWz|~0niUXXD%ZhE`YnJ5h#2! zMHaeO8-sky+PpvLE?sRr$bqHsuyYbY@Uh>7cZ%#9vlb zsw;IX@j(1!b+;BZ|7{~K+r@W8gGw6@KmK1fE+uGwd=ZV|hzVh0FRBfhSdIot4Cr2u z`P^zsr2Cf;rnzhvJ!CpRSWW4pH1y@sW~F+3bqX9h_C!F)z{zbaF1Z_hqTgEuP1R}t zLhJ`EqF*9XD%JStkuYJpV<{8DCT9Q9s4 zwG7p|8EsGqB|9EMQ0bl^YN!%4KVHu8dac`$CrG7uxdOFze#n>LP5Ov0AWVC^1*=N@ zUJkehqm27UNr=o;Jfb~*7UGO;D)K|s6hH1+Ly4=L9QocXFtwtM3}F#|$*G1?IYfOR zO`w6>?)Y}(31I?tTQcDReu_zn^jHFJ?JXvG3{7*rrIsf(l%(u*OhhoCH-y=iiQg@Y zZ;3@_YTkj=FbeD72v{N1?nWAdc%CbXItrp|cQ25$@QkZc(IOE`w1}zg7KFM&sGK|6 zftSYUPG`vGM8^^mYy;bAV(#;8AxcGMRX9HtqC_j<_qkK367KbmG7kKIJoalc)~Nd7 zJT+7aQhdYtz)%d)3pfu|%JKs{FMb^{1#F83JRAee+!EGVLUZV ziK^xbN^#rLVs+Kl37jje+yviAIDU#`UYJtDdn9lJXXh4=Z=BqI@&ncZTGod0zrvId zrQYBCWtdW{Qrl2>kkE_D@7d|H#@{?8T&b@Vhw$uhB?K>oejlzRdM*f|w#_oTrQ?5T z)EeI9Ps5d1#pNy!iTHnXU3p+l$M?^h>k{NiLLRY%Ady&tAjA?9f*^|868l~|t$lqo z3DPH)7SpPwT1&NBwAxrIh$SeswzU=2sEMt%Qfm2q&dl7r#J9ivk$2~F&iTxl_0F9; zcZO9fJrFmCo&Dhi^0oy{bfiT^&O!NIfGcnt7Es!VssaF z6_t?_b4T^H_7fgigH??DIY&y{r0t=O&F!K10fHFUTtd~W<%z`*+h{z+Qu*EFi)*mJ zBCVl>N|iF&#uwLQC4xJOK0t80>6j!6B>mHHVjyg$(5>|ic%e%CS^C2o%->e5vOvsf zQ4LW=M1>q)x+ZJps1anXK5arBo2*?}m0OcFWb+H^$1%*5gJ?C}nu!^=@ylWjcGrZ>?|KU(81^$OaHU8iQ>ay{y+%)CU`MM~7 z-)qt%e12TM9xGqe?a&@RwjL^8^6x4`qs3dlQ--#}Djvruex@E97bdQ{Gc?YHr9P_8 zoXP{M=7D_rfscz|eOT|SlFqw{h=vyE@PVA8)hqsQ1iM%(;mUvV#pHvV1gTi6U*Tp0 zHh^VaRt`prFD~bn_!hpX0h?8-JP@l==azWk%RD5KwZ;POF_EmC*Jr;;KjJsS@q>78?p|}`x39%h*c}~$3;tNWF93{ zzecP#R;}-C#H#zZP&(38uuYbVBJ3hJ8nbwI<$_}R_JYMUG!N5C{(fWDn>D+@_aVAE zC32PeXxUuUFAr+wqW%gkD4Wh-JUWUw{LfQwZ}my2U!JPuGt);!u?aS>^E4#J z*vm7g_Fv0dFh`^6ze;^8`+7-A;Q$)rn%E1Qr6&K1bxTP99CX|={U50}>_d1|Gd82t z_+S1j?S1&EX3P;5mn+Ph*g6VzCyCx`V6%=A(d?LQ9lsjQ0xQ(~S<<3F6Wn^jpU^aO zX$Y?n!^&V6fJQMau+G`DmTs3kx*3F~nT^rwOSdmzCt;GiT&I;CU&{oxOgPP7I-ns?5 z6jt=Ko0|Hy+N#7JopQqxp%jHQLOq&Znk2bi|?W_l&ljwM&OpR{3p6IgY`4M4SJz-N$)!v{9yAy8Yrhl9Ul(8j?i;&S7pxT z1zNMZ%@-gx+~<0oz~JX4=0D*%G9&6H3<0+!m_DEcjG~JA6N4+jH3{~XV;#p@KBYB# zC!qz{|F_YF<)V2@uT(CYw;tLQ>MkrQQ;Z$r*m2ty?%Rg>m;D_{L-fOsBl`R5+3zX_ zSb)0o*fy+r*yqRGrIe>TUz`Wlg?B?t=ijzr-_|_*j&S&aYAyE4)Y z=uorF=_n_T&7=HITUN<;LLnO2L=}|75l>O`M;_RY`8hv9-ia45QKN&6N2zEW6M@nv zTb?fCH{%T(aPGX#7jtAip0(|dvRhvujKudd;-b;ISe@-b1^*;6|1tgF>*)cQqg7^Ae4 zNxRTt-mE>wyUok;ciOWf1v^6~dhnPxSo6X*d^m{_p%};Kzri+R`>`e+Sm~gaS)R17 zLaeJm5S9zzi8=;PWU;go2=30qNKsVI;#07lI6K4mmmP2j`&;~Q2iBv=c8m*SQ!&OK z!)tV88SH%Vd>M773yZ zS#sGEa~J=o2a9p^#BixY=|{~6I*?&`_v;>+DVJy|PO zbtf;>i&e0frvYnP)blGSsiD{biH-Gl?f6go@qAY=R+mNZ;P-m5me>-hes4Cae0hvw za+c6b50`oH5tVF2Q8}=o&*BE~eZ5%&=Oqk9MMJQv#&bQLqO`BaD9w6On?r-uw5VyF zQNznaOgnBaTBJ6PNV<*YZ7$k`y6wG(U!w-(Dt8jlz#=NEyf-sk=U*#0DD4B?!(GAiiZS)ggBA@6VtdpTT0maYknNM zEc9!B8&c-^HTUWV^_fe)&SB^Laed8S$l+ z8(#rd#~J2K1vn&M0eoMOzwFOS7T^9kbu#5niq230f52P3$^h1~_9O(6cPaFF03wnH zAa$bG1NqJlt*Er@4Lb0T2e3+h?X@R`;ECF2S-GTum z;F_b2n1T>AI?iH}dM{$J7b5B=8h_cIlC#%Ig1>Mr zf;7i3ZFSN!g|CKANL_il)m~D43OC3u-HmS{{CACi;HLNqkmw3$mD|TRGBfO?p}bTC_C=OjrU-#r#uN{%Oeclg2q7lNSg9aAC`1ZVHNi0@H=Do{F#(b4_HzK2 zd3K1P&aF0{=)}ye=q}?hH=DZ`D&51-6LnZm1)!2hoH*as6mc@y!c#?JzlvG}XUt3Z z6%TbDoY7zwftPi%zt)yNAuPs$T9xvxb-9sj)8@%ibFWyd!?QG){lWuEO*tf<4^hfdMt-QE@N4@=D%Vo`Pf6;E;Af>k7RXg5WY$vcf=CH=q5 zFST4)LQ?W{20sB$8xDb*xU)(+V3vqMj>N+o^gn?#+n@6h0*gTGcpgm|v=^85zi}x> z^6R58@H;GFbU2wJW)=4xjloIMGaf#gRdkL8skJ$O(lI1doCKm${LRX|H;QE{{q{IX zhGP;kMG_$QeS3mZFheYbqhdflg)U0LQP6T{pK!+H)2OR7?AM#+7Ia`fh2omxfK-rO z=qvc^cH+Lyq|YpdqvEoL4!HgLQ9~Q9+xZf?W4-&&Y#9 z8owi>leCXeWKm{>0<+-xLq6y8yBgdjRveVU||Ba3oGyH)Ahy)E5Lu}VYF z-Ks5#c81K)*8XBS? zqHRI6$r^vdt@^ft9F--j%mXTBk!aykmGZ!eW2lw4J`$6#!S6stAV z$%6KY`5J0!L78HTf*jQ)w3q7H5DocDNP5>^l^VOs5qgkYH| z%4q1A1?>}sG_=ElGR33i=q{&ygn!fVkCr@$W`K|jNi_3>U;CG&?YGzI-Q%P;omrje zXiEiKY!|&l&rsg>psH9xi2}l zYlJZFq|yuT(39SwEe?wu$keeEJ3dr}R$5t6dWQpY?UMa+3@&khjT60afjek#$|j68 zkMknqSp}zT7@X*B~2+uwLJnA+XB9EJPURH{?JR!-P6jLSXUak-ZJILglKh3 zInAxxO$tfy=DiMR@Hwn}bDF%H#(hZ#eCV#WUzXbpNHC4=g6PW2-i@3s3H;z&fjsYP#a0Asslmns4 ziBQ1q+m@H|(w-3s@2U1@il-EJ?nCh{gotIOrEnC-uqY@4u_;CDj!BTy$4)vVr z-5Q$qF_n*M-=($rkC1C;&p|4%n>3}>{-dRuDekIFv~O1sIlGitf6W{2$Lq1-32`?2VP7P7XwSQM1jE_}}@e-wyzn&MM zq%I(}6F_su%NiH$Ax~1qPOC|`12H?+<(_VIDfk|C&)YQHRn`gbROR-iD;vJ56QAKq z{7L_2;~$N}e+Dyt0~~M`zjpe?MD|HB3$bO`uodPWdi#YqioTDzAn!d()udeSz<5{C zNiiKXR8q@9BMUWCy!C;~tbP~qQHbz^!VYSiJ83;y#wTbgdk*8DaB0i_%92v~1sPOT z=8oS`edOV6M1K|mG`zD1h&r*eKWQJ}ou;rB&TJS_ur?WQeqYAz7^w8eOMM@Ef}1N* zb{9S-ea(!n9kXSkN;gBMp{X)z3nO_17O~QHFv8Rd$Lr-LvXzR+UfijS%Bq;mLI_G@ zajL6gy^lHr*I(^dxpEUhrLaXSoKy;nOR97n&sA8c6*en{OwF(*S79_1WK&yULF6(u z3q<%K)J1z}sJi;rVi~`E8l@4b~^nSA+B|HEGhXc&^hMt-&S)-T)}$ z57B6KLQiYek{YC6v0H;LP})Sg8^7Xn4c^w^=LCMB!801%K;TRbW@_+#0*4Vu)2&|^T(HfMW)j0r)or_dvU9FQ_;E5V43}xhQbfzlKR>Y{v zGaHLjku-ax(x|L8f^!x2YlX5(VJ+5nQC1YlRrpjZ&X?}WPO2Mtx=Qx~cp2!V?=cwU1~@hhfl@Tvys z*D2o8=#LtueR0H_8a%4O?+J|3;CC8aM_?#GaeM*p`IrIhj3e35`<=sqZ+meZ#X0Xe zo$xgz^^>RI)dTNd`$=krsl&tg8DH|1!^w+@IIAJS%!FRPP9K949PWqjtwdQwLV<-8 zIE(;z=p;QWsw{l=2#SDakU#LQ@esOpZ@4JS9s$g2pjMp!4gOB=WEHb>S(+;6n{{RO z(d0*WUGx{-P%I#k165Ty`YR(SQzVV0UN3VsB9=F*a>7qh;D9j6YWJ5a3DN-bKK|mN zN=&R%Q9MOU!DOttL;ga;Pn_1tT`wUY`!Di0mjdUvDFu5&%S}JAN<&pF$Va#|RLX+< z#AFQ>07U%{rFpQ%{#LP(18-{Rng!t?Ee)NqAU{zIpx%O&#@zi23z%t!3k&=@z_77g{XARn<(L+vfdPrRq0Mi%5FCMn2irwx#FDhFvb ze+%*voiub%Rpi*9a-yk*zOf*GQC&l-1t3L!!e2wW1yCYQ4ehoJ{lo*OOJ&9v7UmOOUPD7H$VYsjq3#ysCuV6VRzgmh{i8H?yq3<3a-xTZ zc3P0XXsw|Q7UU=DX=sTB`G|@dqAiw zK|Uf%Lk^4IPgK(ovmor~rlE(Hp`R$Ap{ojVQpLERsxl+jGe2=zL%ELmh#xhy*-f@x zLn|!EN37FOVGAlJ-q+CMFr9FJ;ndI{P7CuBBQ-omRzrs^$VcqcP(uqUC%)ED z6$|ngA8V+D1^J128uHK(?muFJ#zwi-256{<1^I{$8fs@jexiwnA}z>ARMk*b3-S{s zHB{1qe1xH)LSdM{(ph$Iu1ff$8XEEuzX1~O&QW8F$=lV~0_x`3>NHBI8L&xG;&$mX zsVU*~m9e}7)4|Q=GDxAde^Mn1Ekei|Wz3fndfCpW&S758R-t;Jssc%jA*n17+3vw2x>y5|@2#{XVpHxlj`)U2V0B{6}axzg$-QxwV zbCh)YD;@Q|$9#*Z->@J#;Oat9V++DN7mSmylVzxdNww1iIV^}?&6t(Rl39?{@*(Ii zD}$ug8Zx|OK~if0K}RfzUiO$5s{;C7LQXokmaxNweP*divhN65WI>YbQ-Wq#kko2S z{KG9sYK0Qi-GZdnFoI$&NNRN>$Qf>7Qtb)xm$x9PmQ7Gm3zAwD$?~7%yfvd3LDwxv zrZR*KPg{^oq#r?hEQq+Arj4*o7AB*-iwYw?vLG4dx1=@4f}|FGYAnWCkko2VP(KTj zT8#*5Z$VOP1VN1yjcNkW6K3g6<~iypXA^N6`X_8DQHT98y*M9@MDl3H5{nr=Z-YZXDmEJ$kgC8(PPNv(K-T3C?O za#G_IwJl7lbt5Rif@CTyga9gHK{AzI1U-3ICtQ*}tpX_9f+W|U1f8-Vsr4@zW@tz~ zCzyh;FY~FLC+I^9l4OSnO0ghGmQK(+79_RS64cj%q}Eb`;w?yOy-QHTchxu{UcTuy zJCL(gEH#uZ9#T97Qt)xm(8TaZ(xaz0_TEKHJ3A*h@M zNwOgX6}BL$MW3#V$FuVG1knWjVL?(WoS>5yB)Q5Hw0o8sC+Pm#o3JnPsXeDCxdlnG zI|LdcfG zZuY|W>0+@8INAl$yk;#LrEk8f z03=dJ^8f)kiF;Ix$Yl=K%mJFYA~D}n7Ise!KBsThFKUoJb|)X65O_#~&PEzNP3TsQ z9zlRqj}y2;gMM0bJArdGxLRv|K;SzXys4v~Nnmdcp4Z?M0^>CJ1MTWaF%2TLu0}U& zP=0D(UW4yzuo`I=1}MIst}^#*`P}Ki_6jnO0}{^=Py?cZTC<1dxPZ+8#6ikInx|}| z(Sw9;(fR6wXvneJ5Q>jA_%AdFtU%x#fTFsNuoFf2o{W$t^jG0Z{7gX)3NFMIEeesZ z0OZP$JJYZ-1h2{Xw~JVG*~R<3Ja*E)Kh`ldMZSUo9p{n4i!H{Y?8!WMF}_uq$~!M+ zvz=S^dU?nLq2h4-aBJKSX`wb9%Z0Jz$W8QqfM)XF)Pf31FfLcnPzxF=!PHzqpcd3q zf;G85!Z>t?Rt2Q9$Harp(GX+eY%+|IRl zPYdcRK~X0%M7xbOVehZ4+-j)>)nL&R4hLA0oa&IsH)d+NWK5Ts)JN9B@7i&$zJa3gRMq8G zEQ4b^IxJxgu$T7SC2TdjdxD29W%XP3VVER8}CVtYz(4Gdhyo8Iv?EYT{(R* zXPLI5v{qE^^V4(&@ADC>g|mSd;D@y?!}ol|-r&1GVxHWwoGoCr#;31aj?c7gnY_gc z=JIMjyM3%G#d7@WL7b?ZQSF%)AZxmm?l1;MhMr`1N}ru?{w96yDj3_k@mH%^4D&0-n}5P) z+ZOYfPne^8n{qj|X}x}$x$Y&_>kp*Qlanh;#4+hGfAR@)g+I&k@^Bdt%uB&qRhQ#E zY#SNVa2dU&!=CAKYXSSr7hWDonKta`k?I)Ezg@#lI@%1iW5bqBdp%G~y!Y(BiXV~4 zKl~IQjf9U(-}fm?wXx<0c>G${uUMJksKIGbJI2^Ngy(R+b1my{Y#nUp<=3$Wes!Pa zdbU+=$l<`dy_%pap#& z(&$lrjvq=n#D3dVO^`$#b@;=xw=ZOAF$IiD#;Z|JIBg{YAsZ~o?Z7jGTulh9$HVS$a=AFJ~w`|*Z)Mo6bGPtLm58uqX zS6p2HY;)V*l{=yISU~S`AA()p@Gu`?AD4b@GwvL=fB4fau-lj@?E-nLt@!r%4qvd9 zRrE?>xgPaM-?f$5Y^+`{p79Mlx!&E*Z+^puR+x&-6=ND>KSio`)es96LP{J%<^=SC z(%auMf1?F1=6}l~4NqKTeaqS#+q>C$sco!@F$EXhwy~h#H@l(42fk{PmHZFYP5Kkh z6R83QG=L~R&8!X8)!XnDc&+|cpY<(2yp6TCr%S!4lATad*YR@Sv9Y%8JncJdmouO5 z_zqvxOy_sLV{r!WV&{?HvvsV)3x4H$HqVyEho-YBg-iT{<*Nox?%^Z1vmjnIe9a$vV)DXz2HlBu-awc?}j2s7GAl|8}yxFauBs!9Ch|9 z=8S@?J@)j#sktq~?;sSgf+uFMcWkNY$1+%Dn{9CVy*;eE&3XMdFKmC)9%G2^v~Zw) zJ6v_7s~WgM@q5vtbW*`tgN@%ZXMVu)b>xe5@ z5%}28!BkvVsc{qnPCWY+pXqdw^stuRysM<^wRAKIztqCx5Ng-v5dMM2w>~6X4#_jx zNPEY63CUFSB-No>bpddSy_M!K_7@Gu+iB@2Ev>4h_Yt9}sf8Wgl!b}1q{i=&S8AzI zjE_t##p-G-Fv9~wTbVVY06$i1EVlAlbpl)U-{R@AL@@k8y)Dok%L=uL7)Z_F=Tg9(K|Jpwis;VSX z!>%cvCimW?-sbmKx-Z_-iGrsQR5ie$q1>Xq{xv1AgZaD;-^* zV!ICQmMPg;rXND|=bh3Ai>v4$#uWPK-(@DD++2<+_}_5<%;2QGjT`QFmUo$BKjE#i zSozY^No>AD;vZ+$OxoAD;g8M`mwnhLd`1=vDLWDZTc*qGrxa=^gks<#qXnRor=)g_hn3fy-P?;xjX=CuKHn zc*Yszax7cL_a0_JWmk~>K9{*jDb!F3!7j(#ReaA;@UK*I5B!3_Eq}6tzd8*03MIdX z-+b~LD|oXbZu!;M<-e}r&OGvyugkL}e{h-jk5vTG_{|qV1OD;|3n`<;wO<#9e9W7X z|5_gSy1dlK+?hx2^SZpy$CUnoPTS#B^H2;9TL|0R9K#s7(l(}7Oz)iTE$|@k4r6g_ zEGkUY@-t}YiICko?p*=;sM(G=uymQTz*6-=L-QX*<(+&DwXLyP4$1aT4nh~)*o>Cf zL*(@!dHuN!U&1vuJ*B}-DX1Z@YoIbWHap7e(eiq{yxt=464IyyVCv3Wo$``SMtI3*zu-S<+GXzrB1 z3FgWUGSwrKG+~a7sywji#ymr6|!XCw~)6A1Vq(XW~-PJ)tt8 zthub*@IOVK-*i{Ab3<%}iOZl5%K9Gho=WgIqv7MJ7*qqBH-A$?NYd=JI z%KLffE=ftEc~$-VgwTwWpoyDVdW79|K z`r2<3oZ>tN(5iGyiW1F&@;AYJCM{*w{e??K_q#>+zNCAAAeHM+1gE^0hb>!55=|k0 z6U^Ni>}`6;2lDfY3?F6Sp=VALvN)pY}@;AXeL>h{1mlM~D?kkCcuArpL_&{ad za)MJ{Vpkn2ty`od(cC6~6U@)?tLawaQn4+RAlME_OSF3cIq3-Go=WbD8s69*?$KHJ zN6E32WBN7u8<2e|_xYk-BO30+VARzZSM&tSdBM}Ha>>e#akDZKLPa&;85poZ7&V$V zJB@uo?=^Ho^~Pe9F1f z#7aRcOCZKTPziS~QC$jpL*SHLr%2L=B*jT$)@Wq+XlSmd+)N&eXV{&yK$r0f|1bn> z29GhHv;|V!Cd?)HmAk3BB4&_5kG-y_vA7Uf{}R>j4T;J{lH|d!Q$32r^9{I`LQGs! z@T<87kOgsd#f6wIxt2*TBKJ=2@jBPWNJ%vnQljdEUrp7T42Y@`E=0JbYA30>F2VDK zcXEffs7{JN6B91}^IUKL{O zn~VLk@|EAa4P-{uh4i6!RUfkyE_6*+3clOJg7N zIrOycbzDl@b7Z^cDfw}}0r^2>-pMOp_v3bb>BkF5DSv;*uckUm2IR(WT!`_KYJsF` zLge1b@4wEqMsZ!UIEZi`0F6LK%W~H2iq3Vigut=9r)<|R>y|od~|Dp0J zZwWpjfoxxYNI`@pm`eg#JV&J<-V%%^fh?Y_QZUdGbSHrvgKBit1xXiOPvzr{)?; z7R1#P7lKKya*~V4afbZsT(uO}EJ%rK5PmgPdom!ZXk3W&C*;tu)^39GZZqa=szd z$?pLC>WnCeOX>X6`N)Vx#A<#aZT(1+Tr+P*P(JLgEAyeerebcYTVRk>mx-#Sr0OfF z+7mfmQ@qagj^Z*j*9~ZCw@zAI`-$t>-{h8`2)H7?vur;n z+s&|jy}!FvhYD!7OBYc8VaPn1Q4#M_L=kH(K`04it`3tPB-N!%odK~o-*%oAcP;}F z?gev!Q2ZnCs}s?Y_kZ2~#>2b` zs7m(J-1bhod+8id1fH)GY{*BDQyT6ObSWCZn|S=RiLDjbvX5`#`H4aONq; zg^FP4>jWYB2!;_sSS~>x`wq%J;dT2Pcn)_LVO6q^wCtUEE0VV&hS|Dg) z5#+T$0SR(od+O^skk5W2*Cl~1UWT9;DLd%Rf}`$&hMl_;rE}`3Q2tK(Jdo5YsKD zYn#*;F|^O;@k0-g8}LEm-zk ze)1{{wmJFZt89kT_ig#2_7>g;x;VM7HrxIXL^;1;azwp>*I$C;wfpa5rT$syE6vf+ zjCCcEX-+BhnaV)@hkpMtULKvJy}$o8_20*_i=qllsE%Pjc8nJVkh!z|l-;)H%$x_g<27tIg zLcU4Dt)=Ek3e&3*`?YsT!#9rBn%P@Q9^W{6F=C%AZGGct65akK%t(#iir6n}8+v16 zkI^Xh5&U4*^&Y?liT=13^&Vg+C*MX;Gao{)4^m zEDbH%i=aCyhO89IRz%V?RhcXAUsaUYlZLeAZ1ndf%W;qV?FS>|PISxS%3{3-ybJ8P6J4WP z>;;dCLnEg*zxO9ASJ95^3>2rF*$2~5Fwr8BP34co01E6$kFD)v=OT%2X9wcf_qID#~$N#ya$|R~mqW6Z> zv^I#2=?-@XK_XdVx=BxCx?*nvWM9Bc%7)pu=Yb{O6&yO;3(JMPg1p5Iba~=U{BuS1 z3bONYg4HPV^Dr!jp(T7Ov1w5i!Ac7gm+&2e)n`9A`F+7k8V4)ed6678)aZeW2|4W7 z>a!3_(m`9pzq}XjLh+?WjRPQhpkKiGD0H&NTe!pvJiGTXKItZ_QueR6^w~HMy!)8f zeuOXMpy4u0RkHK#H(B*^*Loq(|3Rj!7o_iPHgG0R78=;e3U>bLCQIx?MfW}OhmsBgt9OfhMvhrmaOy#o})r!Bf zkb*hTaGBMR+I4qXA1v=H@E40}`egz{1tDrfg}b>56?Qrf5Nq7FmDm9-7j+WuCsG;Dvr+~6{jH1xR_{X~<4iBJAEGFi`0>3-yF%xa9 z6D(xQ5Mv+^7x*9W#n@8L&MV(z;e}szr_Gyh3LJQIk5%qccCwd8LCi%W2VzrS(s%9e zA@^xn(PTjApw9G1Ys=f^EW{9wTHE`<_{9-2;d|8~GBJH(`r>_gnqwS4dk@PW>v!ko zeO9BCD~R-IFK%SzG6X0qn;ztG_gO&6LhvRxE6W^&zh6Rw-wVMRwhQmGL?heZ&Trjk zU2Grl=m%_J@joW$8&|fxahYFDNdNr-&O|ABs|<`YezPI%)Sk)_(OMiJcY-)!-IIK zC+ur>XDrWpf~8_F(!~?D%w~I@?tDsTzh#c*o1Wn$VGlo!8Q!nF9=?I7R+R-u^mBgV zF)LO2fDfwwu1#f_mg-xy`qfV<~(EZ#Jyblje8;r8*cqa-sIg{tv?w8<=v(9)z8@m ztS&5;9`_RIw<+J`@GT4xwF65ieB2#Q zrtoHWcszx>T48L`F^GT$7J&6L6fW%!$5QxTcLCL-@Ne#L5QX!Uw;zSqxa9>X+}a&} zR2bo^Ivk72pG`nfx58Np-*K1d0ScdRhqqC9t2?}w|H_Pd#=b&!Ud%88*y6!F%rGAJ z^2G~&toThe7Y*|AXv}7K<3{}yFST9hac2OEPy32)pBeN?0i9zsr3AVzoZxI6qA*4X zH`=6Tix5I7bOpC7aUJsou83xXxU+!K0!zyG7cinCPQxKJvbVC$mZ-+&v0EFdM&g#b19I%zk2g`M(73aI<*9qnYQ{X{ zR?_PmkU54GvMavoSU#RZ!=5~g>XH71#21P8gw!6r_f*&=pOvog%R)q=Vw9%eR&~@u$vW85JhB-BX~zoqqY&x?CD9KMroVV3(SaoUj@pf8zO{) zx^4l!Nq0%WKKWqt$39d#D1c9~1O_XD40i-fr_f_!~>PY*97u7@3CnzUe7A0AS~2xp0Xc#k4RK!<|{%=X%%=uF8}GaaFs zGh(t@VTeUfw4WhNe*Bj&pVNoPc&@pG+?V3#Z!u>CF!n zF*?{z@RCK1ktJ{TMzg{}zo}QUBnxu&;RkeTw^6<1#{ZRl1KD}l6@2=t5Oz)F?YxYN z;Z4wEA{F<`skC;dpp|o}*DyUK6IrgQr^jx1gm10+N-yKPX79eD(suOKLuS`6ltrTe z=6rw}qi(aKZ7A;VsHahxMRQFN^=ObT_+qoM1HrucY{FS#cpT#`mcEc#od=pe(v>4OLvM=)Kp4%5q z0Y=sOMYsR$i=LY9RJ;444TR?QMT2put1Ds;KJV}~TChe1_<;gOlZZE;7ka%jT0?I$ z5K%=$^k1FP%dV<3n$b14GkW$+)(4M#ol!%`^L9pI{}xi6&HvLG~; z8AXB>_2#!Ox-IC===UzNGqN4yHb28rYO0YpD9HzGo%ryZfH^rMgJ+cK1DVvk~7f|LBPF_dO>aMkqV`h(B`} zeQi^CkCMi0cK>1eZzYZDwo*qQ!v=SgSfo~+(2 z9#;|R@0G*fsc0Oueas^(8RJ;UZT?jyqcron%@0>H%GvJn>y?Z-tp5!@s^mlXDPb?i9k)BPJu7;{y6S zpfAI9v_MY3C&3K)UK6kGMOO&$WdqVRDx2RAHR5a^r8lZ-_}h%oYlZmir>ry|UCpR~ zJHUc!MzrlQKT*vnX`7V(XEmdlt@J=V@x;1(TGCT{ZMuP^MJ4})irkqutzk4^CB~&s zs$ulAIWIQSUBNND+J%xyvy!8)7V^l5M;ZZy1)yifBV}{Kx+^RcmJZ{|W3iktR1Q+ANCv3{Xg2}~m`s3j)EQNpkhciwsBC-)=mr3%xThSM=?j{ofHFoy zESdrb5c2?$wto;06J$R4nfj@CyJ}i|Hv&nE#9s z6Y}OK%9~$FQJ3@*D8hFy@dbf8FBXyYM|tQL5HOE`Hi};YlK{9{^fFrX!}n8E6!W2$ z(T<$!1rdOS1jNyMY|)Oabw*Mih(>v>OUJodVl)~xn+$qDe^y**0Z~e^EdLu$SH^Yf zK`T1%1=y*RVi-UM-6(>amt>Ta2_P@Uc>o#1aOH&FB#QDdkfdG7fDG^CmMTMk!gxVs zk*aoX95gXsEj}hp=h85;evbg1;e!d7LV#|GgGvAGLWRY z7IcIZ*dwv8j*60To;)lqZc2xWF`>Yw;JWosRyL=UPesdvQ;Qmq#R`9uI>GzG^86+n0dh5IPt zTaz(C_*k?>C?i3os~d?M67aSH-XtItfSR$YQ`aczEKYKrchjjM5phh(F{>ZnG96aBVeQg-XWke0o1|3ZX^N43DC`aFab6IIZxGt z4>~vKeNmdeMt=iF;`(`LcTnaVN<;fBfSdvt3gkW}!5|EhjJHVtI}&fegM7YZR>bU? za(F4p*OPhPZ1BePT*f4_pFr^qLair1N&psv2$`rnC`&+R0(6=y5zw4~2}&cBfN%h+ zr7-W5ACa-(3V|SFAVpCEQkOijK4(2-a#QmGCrbKIxCu`86eBiY{-t@T5o;d^smg}cI5m&MSl`ylyxBK!(}Sl_6` zN?hTk8W^2wtd+{*Tk5Mnn1u*1RKQF(XjT&W$G|byJ0cXo8os0fhMoiY-Uddg!k;5I z@i-cLnO|*S_!a(01w$_L7Y&Ruh0m&Bfy+EF(x?>hCpy0z6JsNpY8}BWrGvLBk%qr# zyb7)5P73*}P%>XlA)1>-T!Z)?gbGtRQp~^ctC4VInGy#5wydF1mhoCVprKLTmdKkl zG$L8;8R-)n8l@O3H7$K%V|ZWhs6&o|_WNPn>WakVx6gKrWbx9t11d${@|y>ZR7`n` zwio!_CdOB+RCT^G3M14X`ROR*yCPweB6rUt&R75uwq#v4UH!@P>LtM)4| z7H!0p?N1Xe^!nLlmMD(MG4KAz&xPCrbv)hXoveqUWJ_W%vj?N>-BRH%=yy}5^@F?rQ!pVX5BlJ05E`{O+}wYF!N@XjM#~C|(3;({0ZVqNbG2W|( z5maj16Uqnk#gn2Q_y{`nf||{l&v_3czRVh+`QA_lNo`Njk-z9+lq-524@BX6lkVEY zBp%e$hzdQkvygh5D`w+rk$z2?`cSO|FzeWWj)qV3I7ljh&CpE)PZ}rv5OUa#t37GZ7a_nVZ;<3gXuhq=>)Gi(ufI8 zYi$vSy-SyFVtWy(=@a~EERP&vR0{fj64pavo_FnP6%&2CCdPcC0?#R?=r#PpNMj%l zAZ|=kM&mUz(2iI<8MQIPFOLvsPUtxlda;#+wet(ovt!(R6 ziXE?0a5L5MdQ#vIUVpUl6N`-F1;-do@fg~244#*^^OP~DVzas5Sff5`+my$THCi)U z)AXfd4bwEBM z%_c~#0jUk_>4o>WDP`pya28;XBGx@qtmJIE5oVjfYfp#wE7ChnH)h#bhXwr141?PS zq>q?s3^i=~($CH|3Nlu_2EUYK%qj3*b-Zj8(~ZfdcOOU5>imynV{FmHnQvdTc!-^1`vY?y=2p%4|>eZDBg2(59L0Qpt)QC#H=q#Z^uz#STw8Xq(^ zE_GWF`5KSFWb_Z&DFzOrTg=SYfcunA<=X4Vj$!> z_wgi!+npNjP066_;)zR)#!n6FyTH9yC(BWq z`z|vomtHV2ZyIhRQgH{6SisvaGlFbi@^_$R>%^BWGe)y=6VeNEV>z=uN?-Gl5mJC1 z^GUzI#@K30pSIc<&ur_`@2xQ&7w{=VFJD?>SAmB}pxvu5f4m+ODA_z<113w@0FnX{`Pk6cAsC8#~``yNmWww1$ z)Z-xz>q%Z#4oSqs)I;w+_o??9$m?Vn)mo2*TokQBYuA4W@}XRK)LwNVvTxY)hU#f( zHc2eP*Gr_7Qj7&28lh-IKW_bH&>)8+n6U;xRM^pQuONwimOTzwlCf zQTtY=H`!|xW2n^aGLgMiZ}U-^#^ZvA7a}D;@nQRofO6&Apf$|LqVGj9a+yvFrcFHd z;NhBf+xc|Aajx*xTXfq`40wkp9WX*|UHPU1MpxUzbav3#!fe;lzs)i>8LU|h@BgC_ z;rR`E8Vr(N@Krxz==!A!{g$5bqfy4j%KgDF95aGh#7%BHZj7>Jq>njnq}Z6p@BHyk z#?PKrF{_19Ne_PJgt5qWDt*vN+}4famkaTiKN;b?CMg% zN9~L0ht49+wk7=5&&EvVh~XoCF~+iA7xUkKK@eRR4P%%vQ$*@qOi8Ca1oYqXO!ik zpOxjpi-u>=x3%-d9xvi8cU0{0qH!IHy(EwS(t?!qI7cmx?nW^tlH4PNmorq&a9)Kw#}?3I&*M}?0=AJqd# zTqOn+K0(p(f@tuUO|RwaRGs>X(dbE0%i>`XHVvxYp~vCo zNVsVtddRQm%7YstF6<=&`5^2xejOf(_qgMCBk;}YDw0GU$@aTeB&Vz8%hHa>3p<0d zNK=_)zk}yH83T#X2*h}zEnS_@{oM#?{R2JyVI#x0mGfd-I5OL~3<$s~WGGhKl4mzwyyn7B- zL(L45e5x^`6zGVv;lPA9BFfVkx2br|DhDC!i-2s5pH`CAy@Gnd=Ps0~C{L?)>{lir zb?-b+vJYro0kzPX#|xQMy;tyOIYwCG=wYgsAIE!E^~(K5MM-b3CF?3Rk0CMMGtmI4 z*5GpV0r6pOBnm~bngyUf#CzsJF%3cySc%WNX_Rm9WT>L=mPH?NaGU-it)VqEzD`O8_MHv8IvnCIY7xl zlj|swV6B>5>p<1y${vuJ~$KeXLx26_D4!AcJL=x02)0VL(Y0c&ESM+Pb@NEpv}@ zEgElKaW8M&{clATW>HbQG9zFoT>}eTixb-|w*#ff$u@Awy7%jR4Ef@=fzNxs@s z9x1*K<+N+kpmP=s{^{TlFnxCA5T~%H^m$ zOLE+GOYrCSjrz6$y#IY9^lFBrb3VDR=#H0`bX}+vah#v5Zn*cxqfx^{y7zWf2~f@5 z+zbtB@XdgVY#8Th@Ttl*^WR;N)k8ow_(>`T)e=YlhG-n44>)D4<^ZsXrB;e^8@<_0 z`k|UVTKo8ZsvVxWWYaaw>K@D*TC)H;2xzv|nj4|1I)wfHI=^I%`g=bjOfFN1HtIo- zAhElIy$vTsNz2u^T>j$_LiGbr#eb`>@}zAp{~(KBR*PlC6>KF)LJJ8hPW0)PYW((5 zm^C0?bF}8ChjsWrU-RF(X!&pE8j*SfB0FTcDa*SyLEi!r2};#0qdDoh2%M39dEZCI zU~JBO@DUax#bxrxkBp|wGn3c<8?#D>`GmhQt2B>)haVQVk3R;=JooYXkCpVXl0G(S z*|zZO^fQY)o){gt-xG|`r#&&M+V1gnSSM>c$qzn(@*-~j1Fe2Mf_~2N!T$jKkgubk z1b!Vq%s+!yc?x<6pDH2!K=>4kGj9GLTi*c|Y|Xe3s!qVb!=mc-aKF}`On(b)NaX5YI5^ZWBW+`Bt7 zJ3HHVXJ>cC);9>aUtFQjm%{xmKHWq^e6*3f#jv-IH8pR<^0%;KStj1Tb*!!pI3TLN z15^kyGWsA%^Z{3Vv=RG--+RzNi}oH+B>TPN23%EL@q=R&;~n1j}ky35WEywiDP6>^B{MJq>kz4=vB~I(+P%Ro(SzPGMf}?NyT||2PyfA zd4LtJ%R}p>l~`rmRriq^J+VES2_1Vlr$-4k(l46MJ~VISORH~K$Q3JTaMHT>=^h%O zY91NW>{+TVst86j6FR^ZS!CV)@NQQ|dHg>RD=@m#u%7kW;x5jQjP*S+;~=%V7dUs1 zEy7E=Nj;N#sYlOHTUxY5Smc}c-eRqmCj;xMW_noN4ST)Uc z(V4NWKJz!rY`UbIN$N)g@w)_6+{~>*#*dwmQk=odh7;_$39<qUnMW-N z$PiVVW;1*>aqpHWNLnk~_D6cBs|8sk%+9P+nG zXA83n>s+gg3`aRhp_jp@JS?Zub_})B0*R#+mD(HOxF8P{nTe!N*iykevMGFrWpVjJvyF(#V@G?~^WU3TS^iDWfE7AY0=ARu8Iq^?!e!#m=ArydJq!V;G%RTrt-E-A9Bf@o%q4Yp*tmh{Tfu8Yoca3 zR?+hYfQk%q=!27R_F$o*`zeGh6%QcQuv!YMmdXOLxE!mG?W3Q|v6S+wyIPz58Z;bq z1DoH9DmqqQ8(t!M=~$rS_g&q^4lh<&tkkipZq<`vit`9Mx}U{QI#war7oiy4=RzsEy!B=cYbL+3rr`b&^f9}RBBxIBot=<$c=r{-t4cE)80B-(uRJr> ze)<|BY5W10g3!ty{HWzQf?TpZFM@=M(_sbbQ^Ff@p**Wq<8?=*G_NGnS9X0W-+myJ zIk=A_E_r$e3CR}GKFnX6{#G>iVYM_p#W;L)o(M0y@!23%&WJretg8RB*X|CPG5OJ# z@kOQ|!{;Qg)KNV3VPTbCcc8+H{#wO-g-?F;IMws1sOifBoewpEfUUKnwJ%HZ3 z(V58No{AN|EK18>iXVMhb8J*RgQf1(j6j}$Hw?GUF2C^VGfA~XOW3N z1PU9R=+S$ft-(vgVL#R!lP&JgYP$c^$R^lp(b%6g)OY=tgr=G2HalvwnC;K}FwSo9 zXCc~DAx?raYZn@1YW@pZG&fvryI5QQak6bgl4W4I7pN*b03#!=rJ#D5N0*CwQ7!4U z+8N_4xia8FHs&P)1;2j6VztMQk#H=aJ&5Y$N)*~3&($i{R<{(s8Q`q06y_Jq31H)$ zLTU?W+#e<#z9dTqkK)WxyVr3`Kpy?Dph^gvrS*AwPvzoo1~lV7WV|Da!dM;WWT=Cc z#xKO1Fc#8wxAK3x`Jo7@cJp`O`shjzUjXEl%gDSgq2Tj1PSEKJcT-77qgzD`31>kG zzgoazF@W+|kd**d(M8ZDqiXQ?3t_MHXoAuc!4)l-c9)&8jZ(S*<}w=iS@m468#e z%ud1~5hT9|MYK9e!BO)a&z#V|sRzY@yJEvBi>-)YHP8(^Vu}@kafGU$gv_Ss|XJ}Vu%%C%@ltif>nXrN=zN~+NMx= z)Y^Pm_*7wG{*#)aJHgnWYK}JFK@Ugq@$t<>%POo=kROnM=w?sWI^dx7izKx=$+o*( zIvhT5zLz20L zCMo|?T28Sh=a?oTkJdT5*|(%@dKM#CHC8LIyfs>DqVw|MKt%15M0z!h$H%L?JG@Mu zZe*E$GCsp2R2Z}V+wmg58mk^S-Hxs-(YGH;^uY1rOf{Aow>r_D(&PkmSGqZ!G49?! zVcS!GRI_Z3kHr}XJ}6PZEI3%(_@NjS!D5?SXhL1QMwj;sLLzm4Mq5*!3rW)ZGK1wu zAn3etA28d8l2prSY&4kt-b7rAU^TL;d=yV+D^Tk{0*xI5>M@Awqyw{T{XeFp6K#>+ zL;7T-^n#3S8H1Z>5K=8sM{7ErG1s?@ma9K{wCObVA7ln?u`zf80*EmmoaJCI>qlld zK)D2>TA)^$U`TeLF-hiVDG!G5o{?TBi=*@!yCVQ-t@U=SFDQPJ6b_bK5L1~P2_rQy zuKzI|oeG_ArQ5G&Za}oC5y@hME>)#c&q1j#ky6K_Q%p87!+?B;sMPfdnO{uddc?IsK;j`5x+*U zwpGSav|$LUD>NTP9f&Q`6)q~G%3zKg3LMxgmqg2Gb}{hpNRUP-)bL>kXpVaTKcVl7 z_?m3A&xHi*{LmFGn&9>CibFM7OyyKb_XZ-?xQk^>%aQ5%ZxFnh8XREXyTYp$tEi2N z6n$zj-;i5>%UMCQP|P;EN8zV^1b$i?D5>VSSE6?$tSqN; z$fNbM%48RCYKIRdC+x3O5-bpevH zxW!-MS}d!aHJG@!_zT=!$fSDie;Ef6z8c{b)e!tY*We^z=S^`29`~YTB8e29W+1YN zQ1c%X{_dX$|BVb!_@Gj}v+)BF6pIw?jM}ypH%BDbV+O6ub1|eI3(_yTAxUwGj4rn$Yy;8x zl&>vT)MF7f>jFiy@VOWr)6IJfYw*RMgzPPfL zzJrA8Rd@pxF82#0{C#&x9}YMQf0NJ(Hi@AX$z zUFJv{(NJWwMgIg=UE6r9n2T?f*EQ7@V=#=U3j=7Rh^vf*m=>HuHmJtb&992<2`t9h zjn-(@t_pnvHpD401kvslc@0>W*6E6HYRH;8jmHL@@=&yH$Tn$fjS>GgWVuc&G1FG| zi)oFRF*>6Q@Zq)v)YS; zwja9;6PrMxG)(Mj!fJWe#uO7n2j0?B+WaOSHerES3OgsVmag})ag&~3qD&Co6IqPY zT8vT37BMf8Rf#zD8>S6-w%k$^eGRJ4uOKa3GwIYnfyMPi<`+PlLLXt(mUKvXB(bKN zgQ86m3ybRmBGfG(a7L;cVJ?BVp`)s#q!Ngx21AiQ>83FeY38XD(s9I!|0Xshu{fvq z*j!S&iGRQ7d!nMy<@v#%;RTkkB0X&F34A?w(Y07%~{hNtG@4#b*QQ4mwM+2m%ZkRaJl=TQW=#Qk* zD#O}o&{hL7Vw-5vj8!)5q$0g=9>tVFZ{?v7pAg>KOZYNe1eOfOPX zSlA~wK}O&{lp2f$9w&3?oVUNFfn*XRIjd7H zGh8Z%VD0J62Zf8@nzL`UNh3vG3lR05;F8Xk#hno zHI|rWKA5*23B|B`s|E9QaxO-}3=wZzuoTUrnr;AqFQ}=5UIdkNawor z+a`v7TSQ^8=%MTdDz)zgEE-eIzU@%Xi)2E44mgn_h8ffcv}h<(HXK5#Tn>s}ZP{k+ zRzKm|j@9wliC*1m;d@P_wqrFsj~qk0GBw9Zj$%~Q>~^ef)Y%d@hmS7g>*qozw6SYA zRTASWv1)K&q~4M)Znk5W(C_SP&(a<5c5@a}7h$*aML~QA_Nm6{8;*)OL#*t`s(Jif zDC@My0B!5e;!s}{TSxBhg8hVQ2&dBzdCSVh-W(S?L=y%j^%&kEl5VBm{0XY=-8Cz0G<3ziCjc{FG-bm_@b%hIV%c{q54(F%OSQD>Pn<RXEb{aD|ChcBHROtbN6{ z4^dC3W3c2)$iZ9P;fdY;AQ7KuEVWLDiCAtn|(ed*^ zm2iLRZAAxmM3#WC>SKU|0ikg~gUyW12vmq3*FhN!7b>9`F^~miU7K=SDFlE5-f50V zgAqpA4M_Lo{3oS*;n@g@P`UJ>`akL(QjRH~J!iMm>>4Q#-Hwvq2<3t@$jWwp8rT-Y z15w;a6lb_qQHn*kL0Cn^Imn6CSU0-x)gHxDiyMN(B}vA(cgQmJTN2!gpY}kV3F1D! zyUIO83ewd@_`u6|;9EMm_azPhKO!m`(&@lC$R}IL`-Flq(Kkegu^wFu^zUPFmna`e z66D_o>D_0bke465*@g;-8jGtqkCOzDI2%6V_dOBi-^F~wsLE5x8bOihIhbW;*uA>+A0ub-i1hMM2H_cBg3SMXT?QKtk9Z1V{FKPSW7pQqsjW2bSoh zLc#=Cn)M=J8JJgIBPYN!AalAY>TGOLl1bb|J}A9Gnq-KoZ4d<~eJ(7yRmF?2YT59>w#a2DphE)g|44r1fF0Ss#T zqvF7D*3518CK}_q@~Vl#WdwG-;uCRP4GYQ0&7#K$R=L)2G=u@V$ES2SRBUoTU90CD^ePVrW)fozBE!7zTXO!y1bKm;v zsxuzWs%eWqXJM_vk=sZ#l)Y_hXyL*DEL*@FipZqjmWrEE;Phxpa*;&$&kmiV>f)3cWKCCN)2U1RA9}NuUv`lLQ*7I!T~Gs*?ojuR0UyI?Q7j#U??G zx4$WgVU8OmReHuxKmscr<#X0V^SU5nBn!iH%FD%(@vMqwoftKWg=w=_h?og1RNHHX zI6Q{o=)y$iDz1-WpKHUGNkZ+a<>Kr(P)uH~Qg&M|=1yc0L{xS0D3+!TTB;H*Tc#3D zSSATIoy4Ir7-%XkQPE45s^P~im1MX#r!bSnYc~loZ3^=d9%GqN^LxQ3W3i^uK3t@R z86w2>$tdt-p|TDak_XPup>Fo==p~9qvvAR9Jj>DS5Jls$;~2GAhR`^PU&bN1e~DTX z@Hr~lPGH-#qZX)aA1zebo?57~-MCPdI9JS>2(mi!RkEE6RI)D@sAPi{s3~Z^K>R$3 zZPmV*E9o?&#o8}e6>a)_l`L|;O6D@3vb3srISKcQHlK_v-8V;NTyLJr*lV6j`Dm^T ziI&r13bG_*4rNKIw*6gkW(tdS&BmaJR^f^5(5Y-+rKxdL>(%K!s$#OChtkF@TUYdR zad|22wy2lO`jl zvxanNYdXX6-rM5QbXL!`3;Gm_@RW$oVb_CVWBw!fFhx5Hg5wkv-ykA#p&2Ndxj2jI z#h_fa7nf>^&^&DE_7e5;*bvQ9u`Z9z^|(1xm2RZ+VTK5s3F7uMSX++~cJNnL@QE2L z!{hx78+Anq9^{-4h~kwe=FY?t`SMu@vJSv76*i^6Lbi`M#I6isHac+E92 zVHT@HshY)_Ya_3Ue}L`K{U&vzY$ckSL+X(~T~ezbOLOcQq1>G=3xS)UK|{x&&r!dg zU&wC{(%AMOWo?g^xCJEW4hl#43qg=UaoGi9^I4&W3>r9=Dd{kCePWP?1(R3GzDd=9`OR+*}s#`tv2KP+j>9Q81TPshj+Tni@qlan|2dzn+)qixec|Bnmzq3=#B6 zH{iahFND`T82y|os4|aD)@Yws6oo5TlX6&D;)n_zpzWfhPZCe&GqcB{Nw&JPd!l%^ zfCY#Ti&-0wa633rf`?!MIdCCcuF=j9kjzB0#mpOr*E%j{c^>iOZA?0iE5qc(I2-uS zu>>CC(S{&>TY9ASZN|i?g)FS#iC`mfQLMg{sdm;F(P=Tu^|(C77W~!dGF%7R!Fduq z#O3K|$<;-SSjY?ot(JqM=67*;1#78|9Vy&bf>+K+8?UvWi*l=w%-t)IOfNgQrUVa( zTlP6xPS^-y=#$!a3`VfLHJBjLL^nZBr+nK{m5Nr~vUX2Aq`9mUkHH$(|J!&;e)~ptLSF^5~&xO|-7Tzo!xrZ8zq9HX^Ds2C{ zSlW~NL>5%dkFcPtgiG&<{IiA`g8t5=8E+yktaKXtqp4*^ z-f3N)T&bI)+wKx^YguL2s9kcF9C1MOSqr%qiP>vemH2Zz-Ka{@aBq$iST|z6CGRKc z1df(4n;DFC8ET4N3wlsa7x&k~9MBI$)_agL$ETXcTAx7T6!u3g&>?;v$(xj9%4DUOE+J1r=@ z9;#hFSRGm>ie>Ae!aqBZhL%8KSr5xrHn1Rg11qPg*mMBROdl(k2Fe+#au(lI(_|Qg zofIuLqON!h5DPc5DBba`Zdl6=VCeYz@*PLTFB{QYEft1MY#81xnYRfOis|2rC*QDU z8oX$+3B$VkH%zCkV1*qMy}!X|zH^UC(pCIO-sV>CV^McAMwqMO@i(Xld14j$MoXWz z;xN&88}k>TTbPH&MWxs!MsH!^+BM%vZrZe=;>s4*yxO%Pay5phT;wd7OrQ7T#sT}f zYUVCfW2d1abt`L$!{Q6KvL@O#L&UFJ!OOTymF#a}+y==m>{Pvz#inh*4pqHc-@(G` zTh>hLx>I!gmbHM1@5*nH@^L#3oV`c z!usE0_;*+h{M{9J4dJ!j#o_Omzi|peSeI1PRJ*_~P`$CV7Z6zW)v_^QS*OD3z@28s~ArC-Dag=|H zEH;ax-K>K95Wor@Vj$2Sac4K?VlxVy_pn4wPa_t*^k;t#1>1A&V6K9JYY43Ain-Sw zHwVN6wLzt`%Baq$l2BrJ6}AVB@|o2eTG)ZS?XBLJ!j9xk?kv`S&(f+7d`0^0nn%XF zVv?MQyJK)wJXYE(WYc`Mi7Gi=zgw=i@Y~DkNA~~D4XTtWq^>ZaV^ghMdUqbchuqIBc zpkYw97vvW(H_ec*p&!V<4lzFTL~l^ZK5jf-*-|2hD$zXlJ$dVm3&tr7*=iKYO6o;JEf zY~RQHT_2-1fjVr1_<0|zpB1E%Z`o(#>7fFik+MQY@)org0iN3d_x=m`Lj^pwQ=C!( zPwapK0_6W;dwbv?Ak-CkmX-9ioz!e4#V#YFHENL#m2{QL)Y+b}z5wLkQxi|gMdC9) z4qV?Jn3^65pi{X<+Ym-26&Tthqb#fju;OVY^-@VAZKO&tABtODNI0ZRygyrtJ4P-1~-Dr$*IN+7KEbU}8+Tx$qe{USt`G1!h7Z^itd zAib;z8W#{rX^0d;;3+S#P)o&41+=mQUZXz)MW!8~sDR~GKvt3RJ3$~R?K*?|55TBN zkfcBuRKzYj;+Ky@9I_+!S`lGthypuer4?bVf<<;jRxV-8aVG##laQtY9@qi#D!_+2 zK-nm(selAKz>feBluR0sf`&k-DUg&vSgUhgJEEL5gqx}gG)CDmFQ{)u5OmDME7*>> zYDHM>^GF;ul4DkcHEqm}*kVOkV=v8(c~%5Qlq`zmwmmy0d>rJW9r0;t5DGEHj%faI zi1v0wv=w1Z$TB;^*NVWUVGgO)a0#a!^OnX1q@6}OL{eIrZ+8;A4l)0%(^di*)ND+) zhumpJSo7e5y^j+>*qVJj$dw*MX^v|Fw_2JltwBT7NPF3NN=is$t(W~bI^oJh40PCj z{5ZA&_Shm-<}K~aA9qw!R7GX}W{s`T9IPy?CaNj6STXX3+TLohTtR46cPW3(St-tt z8Pc}}pPZYo%o)y5ai&4}dE||wl1p)+#{o`F*%$z9omfjdw5Lke%nnFb$v*1D5`aY~ z7E83q0;~8H8v(3lK~pP6TB4ebX=_BqBP_(QW;L?-&kjdW3aI{j#~0y#_i^>64+CiQUJb zPi!y#IL4CvT-u=^-y6oRv{_pv#ynJBiyX4|n9JLpHYL!0tQ1fF3>HBZIWGf*QnxGfg`kKJ)>Z*&)vf5IM8@N%*ECl>1L zcMCDKX(A5&#BxF0^k*!IDmND6e`aH;p&TK9GNx))|1S9Ne_cASUQ76QrS$XugP$h8`-Ro2biV<% z1gx6`y1hGSFFoC4IExu_!4mQQ7Z&EU9g9QQnHkQq&yzTq*rSL&o&Suwi4UksOBaL4 zl0tL%g9bT?*X7YXGBNj|F0U0FYFhS*i)WDqH5v-nb1WiJ(-1PU(lk76fHYjNN1)sy z|604BZYfkkjA&(eS;N>X!Cj0x$J%xb02jPMcx6(0V5aNX5<0ee5(vPqOVy04;O| z@+KEA&-a(XqVpA9#07bf)yp%Tf`x%nB}xSn-1Ko2Rm2&^xdKom;!N;K&A(o_iMDT} z;8C`U{THCV%)TNDFS1Z~%n9}Fh*`A;!uukQP{m#m(HF7CY<^i}Tx7k=O+p}x9`y}L z3ECtMU&O{tCvoc{3kmpcfx6k1R~b34jH9T``hqxj5%4SPhTWSnzoIXAELQ!>k~C}Z zd^1z{vEk1xih&U9NZ=(PC@xDYoFLHq2xzrmqdt z#fJIJib=C!2H7yFR!l8hjO7%P3&%&|=D3k{*7QGEf}?k=yEyy@yQ3{PTWq+@rdI4T z9{ZCtkFpJr9cY^Z`;%S`MdK@MQs@;E)gsSt&*LLkN~lt6q^F1q_?WnKg(cSdLZxa| zmMXE7sxO+Hr5$O?YsI2A{wP{pWz|!95P@vD)Y^yq3QT37;RzyBCS7MzcJfYulQn@0 zP_4{UqGXLF1Gm^11lTFQzsefxwtNYqZdY)2nR=6@LVJzn>YJY{%irZ1$US3lsTK?L z4Unkw^0Evs{@YccZirLYSY^jsQSRc=HCSJL`=xNcj^XenHp|!;_Xg1@NvUwj75W>@ z(Bi@`*5Nt>tv>~t)Xu!f2I@kf00Mn$1NA0QD1nwqP!D;hr{6Ck`vwbg-xNiCG^ul* zif?W}HCQ-Hytu)tyVs>?>t_QoQdGamD%VUTN~yD|M=p6lG>Tl3C>*Xb7km66Cf#IX zwRi{j=}ng4{#gys$ux8kaksGdk~T|Bxy538+>5lax(Qbq^_Pi4NX`d++Q^!<-0<3}RsFX)Y;BZToU^tiu? zo`12bS+#N@N&~2;p1@>Sm0C;8O0|IB+GDX)qe0^a>_HvxV}zGUJPnvCZ6RD`WEh8F z@+y@az+>_LgJdeBlL({oIY=(a=P>ap&BehaviXa6r61sGb`;^+o>a?G451%M z%vY-s$Q;>+%{ZkzBrcP)Au6j>CCVOI^Qt2CZx&pmDnVpTs|1&&HqHfE^|Y#>PK1}0 z^qrXZH(Q`BS4C94%M!DyR-s+Fjv4ft$s6>DRBPH5BkrGoP(wJvWjb)28o??9m`{+* z9de5%E?1d90#qga3$8M0pM#JnE?gk3OdW*sk^<}@9-T24Bmu>pyDYZibUT9yW~W?7l%*YQS1=&o(7$oyxruIEH}edCV9OP z*Zq~WHdNW@^6+$?Rje@+&~Y>)=F_Qrc9bz2+Yn$B0gj^Q00nJAE4>L&9fN}$ zHFCkgo-1drL+lY4>OdPl-l;j-)e|V97NDE8wXmb;>(re2f}(Z>v6^MW$h*gm?Fp}n zXfK*SWf5+EGMUkx1Kq`hr>xA%n;#EmLe;9~!*16r}k*I|dmw!YmzT$~I|94!->HQqr@?VI?&ta@@2oWQn zvpP-B^`wpIffUS?6N99lOzetD`ZY#RPd>?{eLa{+%N+R=KA9sOF(pu#fUc|5Be6>DFyjurk!oh4Qc(BS1+7(|eoe@yZ45niuh!yi{c#Nz8X z+1p*Tf6c-p>Oc$#snnuyca_%UEwym=j#I()gT>0%tWV4%R1@pu%=$s4nK(k$q_OPz zF}%oQ)zbmK*2`Vge8c>!j;G3nd(_%J$31GNbP>273m1%*r@L8t@x~Z_aMrA0kr?`h z1-f?t7MQ zzb#lQHDx=AYb(6ov2N|>2gq9L8Tp<3>L=}XA~~QGPNgky-c-W9Q%3?aD;t8MN_(Ez zMa1426F{Ngi3&!@SwO zse{NkhC->x6_yXo;QkZ@Dw}B{poCScPzb>N|9!MlG5QZiN^$L830Cpp6hL0c@{gES zg8AF8Vr>b8{sP?zq?E05Y&zY88&@!h1beLPU5)EJ&Bh(@D)&BR=x2=w+Cg8_YbVG| zEkl#`QK)n?zXS?-lt?|^_WL3o*Ec*%K)G!v&%C`QE*-EuNj0}jwTHa_F_9MuTnm@Y zI`Im63IS<%6@*-W!gB$(m1)|5?}N7tTpW3fHrYosaOAbMb-lz0N8Su? zgMR184S}6nQ}j&(Qt=iFtzzI#L=tMX7LOfyP3^y}LXM0-rs=DbGKYyx#V29T*`fW532 z0;+CA%5ZClh!;{>`-u0>+}R}`5ajqlv6XX!=LXd$+3ym)ow%=f!a?<|3NIEVpmN!z z`u-L*oZ$OO^}P_C;VXYx_1&OP&XGY1o^kbd<`rB{sPLO&2ZdRx`U=Gr@(ovgtAw*N zH*`o+eN*T&?Fe;TN^4kBA*BQ=&NbWwi2hI}YUV98ypmo$xf&kN*E)~yYlj0I|(T_ z$z|QVDE@IpWjIbWIJhK2-FRrti>mKme3V3$yNg}c$Hah6$*HZb{2(#HjR%B&ATafk zDma0U7~I~-xvg@xI8 zmNFUY?-(}`?7^G%Np!|qp%PxUx6MzW9m+i|mn6oyprzXsiV$9@;(~zr$Efp4MHo2+ zhfvCL@?Zuv2YB^zwXS7@JrYMecqPqDaovN5Y5FhrWMO&J~yNRT7+)ta* zt)Oc;?xfL}MQ2N&j1+o<}Sj- z9t>x95n*1ug5N+)R+M;|y1UAD3X1vOGSSwH`yqD+dGSNt*S?nw9bSCE@=tXe=;_~D zs@p{icaG{_fhuitpEbQNQQE8U%sjXk!L43!`1?DKEU&WBEV8|Mylb+foG9_D;)pk| zP(5%J=1YkhngJzFz}$%MT!9`Wk&5#BR-PZm6)nfgbGmf+d3m1kNm3J3 z1wT;Os>1D0k+-M{PqZk8yK)+xY*VT#nzsWC>- zfeng`8`e>r^x>arGDLM>42QmNL^od^9M~G+k?RjjkV%bH_c^Sdm8)W&FZb_w{55y5 z%$18`&t<(()B8A4F~qHe8^KO&Kq*XcaFSC??B{x6QQ&Pv>E{$-L~~9xwFpf$Pf$`D z8gm-CIgBkwmBqb8y!Yi5y0${0-M>6{0Pcaye`^hU1XTYwY#j>gM_~`hg1(`wEP>QH z$Gy5A%lWb7iS9_otGx4B*U6Mrr@Rm&_54W4_b97WbI(vY%pX@WHKdTo-N-j^R8L8N zFEadiM1mg`GfX_OsFqSbXn>e-I;&@>WfE$B8KrX^?YHm8WK&l7OJbKF_s?2^IPMq8 zBZsss;lwTf5}m-VDg}M4yhA||Dja@`A6Uxi+x@GlIY%|1gMu>(809@e(v^7DzogDd zovbefVvih!H?6I8d5g;6paktlqcrPh;IZ&je;!$W+e_2|4XyUJy#TX0FSxwCbm&(E zm#ff81n-F-%rCnq<45WTn#ucv8Y!dyA-W`l|BPHx=d}$k+Q7&r2z75tO}KOgvXaHV z%s)Ed$UU9r?0|ExbcL^!XF?aiLpA3_OaQN_`Cg>M=e>|3jka?P`bSI&;4$Gho=eV2 z=iVth;RMU0l&faru5Rk=eT4WW0JDR%B~}wjpe8R4? ziY66#tjF&Q>`{Fw)>hz#tRQee=>(M!M%{osz8cPmAC;5fA-9IHilPOy@)Q7)?gHik zu`gpwWU>{1aEN^=-H_;O(5Mn!+iR1kw^usC$%B2yYs!%WCJ0+f0N#gvm zB$Gv8C0;u#tW<`JAfR+U+9z*M@f<)XX;i^$ScQltA&_k?B}(qwdP&reN_j#-&($hI zX4_H4Xk`}y`IJtSKi-%XC}$tjfMlYo82eIJzHmVixk{9lL~iE4wG>w?VQLfJ2_4=e zkru>1%Zft~)p|XgI8?QIT?)f!J?L-sdK7w-*M+=R8?w2HV3P2v6tI*45oG`~Is)Kd z2C$j{P5>a8SO$aBa}gQLL$am;VcmEB`QK8Gvxxps8Nf^eY?FXL_G7_I4US-PxUo|H zA&*o5G2f0@Zw(z0af-(z#r<&4R*VB$}>R7+{8$A0}kA! zjNf#FuRP9#F>Vn^VPs&<-O*xK2(KPT){3QD{SDOEBpdrN9y-g`cQ%cWaXm-^{Y#+> z<-u8R(I47GGZNyjGJpgET#x{}WoZMz)T~n}?T9El;*83oyJZj@1Ll4-aNtIrAn#NR zsJO_}D?d|g3&q0P<(@bh%3DL3s1M_nwTZJugD`&1r5NUjP+-pzKH+?{+ne^VgUTuP zQgDnsoQG&`h#lcvujy5ABAmx)T#mH^@fBPr_`Wjl;L!*^YaX2xJq_HTZPHHU72|zyb(2WGa`fZLudK<*%^|;LmkY&s&-MGbQ|~m{f(gs8IvJ^!$6c(mOQm z$SbXK8oT1z4WT`*t!mpF0*@!$A?ptDpb8&ojEzDH&2eMez&<1YfogMh0FBM!K~vWn zxbGsiC>amunvF#uNj>sfn`&-bf@5o;ipLW%w<@pRHpdo+mm0?w5GsQJw3J}pRb~=k zJkuJO)`U5OT|qkh=C$7uRnRtRTTUU99}Bl?yt8WtJY)~DPYkKXt2OtQUTGmR;}JMS z$yt?g2UtQrBuF9XJvq7e{2b8zLRRj$A#g+d8t8nL5#sl1Jhan+L{fe?hIYImrX=bF zWZKDPpjQ^lekZOa<~tIu>>z+u32KVl@fT&6J{P;CDtgNStCD`vU!qk652>F^X>?Y( z{L|J(w(Ozgl9per%dLj_z+p_sQBZfMoxq*SP@g;$Uq|rD?tlGB0}jmmm9yep1h1;u zEM7%$1B}Chs`Id{dk??`3ZHao9f34oPyx|)z)=9w^IuD&ASAjv5NZ=3$})(GcEo;( zC{eoLtLDiih*sE-GNaxdu3kfNzdB~7r~D!)@#!1 z6cHTB{RTLIp`|0rLtQwnlX!ZH+X6%?YN(543pSMGj1Hon1U~;{{yi{=qL8Z*vb64) zxTZ|xyNQ>KJYOt}8Z8tfPr-N<*Ih+kR=%UG zkh9S>6!cH}nTj6_B%ZZFjGV#N)N`>Rn(MMwT&LpaFR?Zu?fsx$8Hobh)>^D{d!uyvfdGd z*)QNcA)ng5vA$Otbz|q1qidJbuLae7(ywDo^!>bntXz`T9(k& z;!UdfL1r~?&R#>>oXIOIK`gv#MpnBf#?<0rwG(H7f6birR9Z8BK%=2d=njiMd>7Ep zh#ZeeqKz}9L_dg=wRo$vQQ=rIllIxx`POYgjS6?`{EgN}FM)idD%U4mqsDJ^i;|o@ z_kJnA+-;`l5yK+_P63^SW`)P?dYwyh%%nFZz4hm>i4`$CJn=BF#XJ|k^al+83T{hd z+5WRtn3rcLcv3?Rl}Ecs@>t#akASnp(^x2>yI&QRYxBB(V{wMQ^r4e<6D=BB z)Dy#N^Ug_KkiluiDWu%SBMUw$9`|>Z_Dg1?YZwhcf1}&_7M=I~X}~3;C}0zWKkU7@ z&MM$__<0Qu?_R3QUuc_56u-prDy{)AX24X(bC7r+$5TBXk9TtTq9fJzlnDiG>+u;f zKz*L6DH5&Xd2QS#ksZ&Y-Di#k-EgA2R`5eS|5D>M+K7(xDD}MgxOV1~s|D>EaBoMi zfekPY{rm^jBM5>HXpE6xsqHihlZg)LrcK`f+7SmBu&mO*o(v5Hxjd_lgivrqFSa_W z+$qRz%m-^UW5m5CJWbQ5pl%}nU4xgPJ(@zcuf(*bkZqUP)0Efn>^RiPVUjkJ7HRs; z1rM6?0F7H0q>DIKnJLO8^QoTMLueDO^KWFH{XelZnMY#TdpwzExF-T5DVq!yAt{&# zC5$UbN#V^kxHadi=KMQt;#rZ{0=O_TwDE9;n{{a)Ydyy@JSm!O@qDo|7CC^4oJAQp5=J$EQ>Hj?TW za+(Kwi@9C+c1$mubmc#3@#L3h4}Md7!d*P>!Nav{-Gxt2-lkgb9SDt!&~gz>J~y9~ zpMu-$a^*&ksnJ|z7YLMgBEKi^Uh@HFuyl$y9`6aq&;&Sw-fs$3%7Mnzs&_GezC&%w zmL;xOabKp+-SXB#noQCk!HFG{FJ9wsMrIn5iRYsKGT&q zuc0s1!e<3ve9DuwnjHm)`f%>3b!#t<_v4A$N}CFt`txyG&69#D1NoO)%?aT$gvaA@ zgrp&Sn9tL-vftyQS#2r-1~Ke_haRkdkYi4>dk0bk3@3L=Z~9uF?zo_E(3 z7l!8=sE0(qk7xIK9Z~HEa2Yq|SWU4TD6O*u*9EUNni6GLn!h@ZMJ?_upeCu z+~$fWL%FxMax>vLjQ@r_xIB#ir5PX!hx3Zs=Bvcj;k>0bC9$CT2rNsqp)CtqkL2UE z&d%udQu5!6azl8e+kmU`u!Fv8qs3$R(5iKosyWxSA;g{!k5bJFX+e-|g6Nlud1JV5 zkmn0mLy!LRZ^M-!}3Y z$e3@PxHc9o^Yen57Xy2-@_cPH~gjpxZm)T>9Fd!{?8-&e4EDptFJJ!%om zS3d79FXF?nZ!joHUx`~;&~jc9@3VOQY7^=Z74DjKL1%Cix3m6*#y3N1YCw&$o&vXW zRCLeg?X*d+#ExtpsOeU4I-76NU^L2{&QEK!wd#u|xjaPcS-+rnF6SE8fwk4y$v`n8 z4PWh~i-&A+XdY3}EkYSR?tYuGL) z>1xhgk1Wy6d<+G$>=rB?VfU!6Y!aXaBO`(t*uq&X#?9c>HE+e585qG_V#UcBys7Wv zJj9ZaX~@*grUfe4>i3CCGr1BLT1{qB5nW4*{7E(>%B)JtIf;ic9xCs1D6@P-{4DI0 zYpWIvo5eS1uuoL}Oa3qHvD{3&n{R_@YNC2qMxvI&qtu)xCYX5DNME!Y*)Co@lWcyQf^Nq_E4T+u7=^J$|h0O z%s;{Q%P2F~2dIoR)d2`?|&X1MB+I~xXIrs(^}T(OoY%d@(b=Q)|xc+0UBz2$h8njWg!CX0-soj5m}M`?Z$ zA7=9wSxvwsAB&-0M~=J+A;N!O(+h4-LH`2p#XU!1CFQG0QFfsQSt^sV#O&D*%=TAh z)OO-}z>Vs=0;ejJ9Vma<-`s>OmT9)Wh*E3OLusO%f(rzv2F$g*e^%mHqWmC==kR8Z zw}YL;&Y_wRQEsNA7cP}D&V}B4YA|N}9pnj=tzyz#-a~u!cX4Sh#!kG9+hZa$M^{DL zdHgdsGGs+v3yKXAhvxC%nwyJU)h<*u;4mAVG1aGjv`a31_4&Z3ni?o?b5JgOgzJ3l zuYD;J=JODztCIklB6`i|1GVlUqIf>5Be7;TV{;Sx$ zfRAlBV}e>aH&#G4+<*t&dAq|DO}ug(broh=K{h3TI&(&z9sUcET`%amkeAbV@~X0H zxrwf&$}5q%h%bo?z)S-@%M~=5E4H4cl0kVt7W_`*c51qPdM@Ol4HyPJHS)l?P$Vrz zcT*QxV()G)5d#!eQ!LWa_;6E_*_Z&gGF7Zz%p0S^U0;k!9GO+{W-)J~(JU`$u!NV_ zXz@^W>!o~<_WLlQ+sr=`6_#sU3j`B>3=8M>UXKw)0Sht%IRiaHqUJJ6?mXsrN` z@(y5So(%^X>S1AL(*HghQ$7>#QXpzB=ha)Dpl0pgq0ymQv-VX6ct%Ao>$fJa$?pS*{<+-9_l)?b4K9}U*8F{mkXQG)iQ zE{BJb+RV?ERI(GWB{ljxnbh)^Y)Rb@a}hUm0#XIEblh_yEl`ac2z{3~pmZ zg|#TxOwn~MlrhO*YzaaN(@dSP;4?1xU5d)4BgZ_psMm|gkyKGKWlgvAB17&asphD} z)FXEsEt_%c7S=IAZ}qS&%L9j@;GlGdAeR4u62d1N29Y}ZG6+7poiNcd{H<_ahb^nF zJJHoxXa-8N(QMRO6rlb$$_MqVVd=zFIWq>F&!D-Wii9DY)bhzW(SW3!?KDBab=bz2 zGO&wMOw{Ke(Uhw$!dNn7JEIsJlxs*3)-B0~R)Z{I7|3MjbQ(ZQZ*;2CcYu7WFi}!= z!9|^uWi>i3^l}Ul?1!?IBz^ZQBsCk;$(*3gXz}kl-b6e3goytd>$VXh>ucyP^TqnF z`3TR;Zq#exniena>@Gsq^AucS*nd6xmYsu;Yi-4s>v;wLC``M{&P^gPH;I6%{J?tt zjoYF@YCnU%Y4`^0m;NGVZ{XFdzCdqH%aZ_QYhx0Mu5HX3K;@Sp& z*z+6=bCDJcdXwnm;+u{92MvtrHt|oiVb0>)P5h7C1;5+bOVO?VjZxF19+tLnNZCJgUjo!b={AGB7hUD5Ka<=eTZGflP zyM=$zb}Oc@=x-()qAX)c&yq*)Ia&UPAIkZ;N;F!Cr6Rm2B3~J7`hW1H~C`4PS_xg5vO+Ye$KC^0OQ(U)Y-)+){Z^}PkujH zH?T`>Q@LR-j=lubaP+xUD?nx4>NzB?@8SV?Q}o|md~j9+gwz%0Q43DXIq3ndL2`nk zGn2uCWh-r}nE2DxXz%n4oox*?x|s6SMcLU`j-o43k&qC7Kxub~3h5%a0_z{Mt$?dt=HmRlLO@0<@T*81obQh~hoGx7Ou^sQx{V)+YB6 zUH%_kR{~$t@%{5|a_0t#BqWl^N)lp8#PUcGghXD5eT{vmrPkPLNr>(Bd1#A+YPFVD z)mkbVOJdhnEiGM8YO5PjYpJ5m|9j@X7peaKJ|B5^&N*}D%$fbnnVBzCbFZkKM(ueW zv_-3B*PeF-ovtr#FXZdUCJumt%i<3g)*<}V;)}(Mm$=nDj_|U(RF9!?I;NP31L)Ql zYM6UtFasMqmEWz_aP3rBh8piyCpRqD|9@p+|9dhEQ3s7IOac+a`h=m#LSX;gTf0?v z+}*Ivka+}iJY~@#~y2)59Gt4e7`o=y|=5G0~$lMbVO3OUNR3h`mJ}NaoYVz~u zp5&JTjL34sE~YPy`BIH`eQ>i3t@=`(Z2qkG{}R6M3Mu^2{f6+BIDMO%%1{XZWp3*{ zwT#L0efT*z%J}O?RCk{mN!feV^&Tg%mT|xzQ`|oFE00GHN+973iK3eO)ek+Zz65u8 zU@&;TU#;42*(S&AygP<>B-~>ui({^~4=c#qkN|qQ)&qK(j6Z-!=Dnhq>P%m%EbG5O z^2WK2O+5^~(2@h#iA}ywn+~8=cBYF5)EHQUK0N^Q-9u`Uug1B~x~Cd1Ff67I^3^)Y zVfQ2#SoF`LDSCDFEH44Z-C;csdUf^t8v~l*w}PW3XLyvV1rkiL4tY^Q;dzXmG^>tU z(c=Wu8N(P9RG>zBtau4IN&^b8i?{zvWR|xi%Xg8b^!Rgjx_7Hde?3!wbGlO z%NS`rj3XiX2;dxB+@41;ImCo|7Gv$^V+iSCKwArwytds*wqA2?g`Z=LDkGNMsB;`J z*(mi&K-epGeS-%>_FEnNodGYV_6ISMn@gh)s>yiLZs$RDRQMZ@dDQBs`pI9Pg^~OW zioOjjpbx(ee^K*8SRGcqL(>nT8~=;eAA)`H&IUSh2>t6(`t1-lEI%O6!)myBa91hz z+L8TLGo;`d%7zSnXG}S{iKD{64dO!s7he1sLW?>5(0e zsn<8qwZm$<5_=yv=%}4dZ{_A3!9pc&6sXyFb{|6Lh$X$F!&$ z4LYV)SJYIp9mDG2bNc3(+D&QkJ5@cdjwsXk7HciT^K*LlxEdQ^D}->S!+6aX>$VK_ z2$~~_G~c;2!{e(j4aV9kGqfNfzUKRShntv`awY)d2gIRM+PNH_l3 zC#|UZ8!5;ZB)kTu#}OE+%&}>ST}xLL%*C*=f+mBV%GVy(P3@f!HILj z+?38=j>Nag0L!$2mVBiSh1L7XS19kMH>mR0YBRqfEm=P)0mkklhPjSzcu3}ruhr-> z$M492Fe;xu_*%`W_FfXRT^hL&aWr*m4zA$m zxA8ifn%w5Zo$=tV09P0Umjcwrn90|SS~=YK4e5bjOona(=V>#(59zT;xiEJFN4i_J5_@f{3PLYU(l&Y)`0UHax*bx-*^tID`^fjw$sEA(qW)7z)imeJWf@#+V8 zind{8@eV>#El|D{-_YMv)Fr)bYkGc4-5uNsgx0&*PCt|gNe^U3hPRmWB(pZmtY>G_ zx2G}d9Yuww)%*x<@4SkO#2?JZ^aD)|`W8$d3;M&e=$kX>wLpJH-Cv^*8aqgRnI29l zGFG^Ltcek;Bgd+VSozs>`Yh~^qjC$+svesEn)4v=MMds+6l)5vUZ=Rlw>(cCKV!u( zg%W;7zcl2N*M3$_?FTPLb+EcZ9RuZ8E1oO$Gd|hvx3}?$2M^Sq#wS#&b~8R%i=BkF;PN)$tS1L}B94XGG~01u-jSGEKr?K;+5>O&1FjM(Y(FMBXJtV?p$oWYjJl znJ7-bThHJWP(m~XL}wgCd<{(7c>Sw-9)@_|0AARWu3T4}*8B|L*pfrf&;<#hwkD$s z<~=&SE`H%Hg!4#pB|GUt7*sSms@Isvd=lpJGXD<2I<(R zZMQU%<=H1kL46tm$VH#X7ETaC>d@~P+ApM={LR3ilo664zkTM&fd>*Iik=urXA9NO zU;>{Ux^ucX)O!>d6mP+6u1r@2I%g-nco5Zj%+Fhi!4^tR1>qr1P{KLbNy`r)0 z>mBvzkVEG26mdswSZ=2?T<;JKxuZ@seMFbDgT}A9q{%o2NP|dD`yaaZJM?&%s@+vXO`9m?uG&{= z9Y*W#svQFkOm)m%o5Q%F_qz5ER#bk6gUXV(Nd!~$J=Nc13+C~T`SAs6druAZ%LsAo z(pZltS&!-a5XAE$GeInxAY-`s=e>JKBRZI#-BWvbw>_ZZ^1LOexeCkkNAAG;*c%NU z3^4Bjz)FPj&rrJ}%fJEPQ$0^wYUnyDoOUcyy-PHbXbpiE%s;*bcjH6$lslLjzFPKM0~9>CYtNWI+g{!jFXoksNM&k?=L z8!|fPv}azZy&|uTLhMA|iwn)4lkT={>+1m1%gzjf#Wy>!kD_eb!Pcc+G>e&y}!6ZV>-OBx|p6x+U z&%htdrsqbWz7ETo%hPpJN?o>hs|~gT{~E0|i7ra5vh=4(bn@YlG7{Jx38?_MPgzJq@ zN>`_cpS-M2eufLEQJrSOlE^z{ng~{Es5Dsi77zjDOzvz_X6W9O9aXB~FUBG!@9{p8TG*a4oP`z^EW95&3=t4Qs(DTyT5_OhY zM5Bohy7%nif)J_Ryg~7*rUrDXHK!FcRxe~ z(l9R(8F8o=vir+9BfA6i9WUIt+r#{nTQQGac=cwBml&dWy`YNTVn~^&52Qju&gW)( zi-)Fw3q2g|aaZY{!ecePRYA0km<+7`&RHk>VSl~KUd0=H6~w2}e?N8OS&6;bi%skb+UP4fMl1zZ-}bGOPtN22@X5=)SV{ORa7}s?fX3FJ!UIJkQ+56c zp^1T_GA?dh5-9d4bM6~;W7N`;y0LPptQ$4UwRhBw+VDCs>W2G6H)AuNyZNK~NCb6{ zhi){`L->V0d>3j61L)tigZHNN&Pg9%Rc*UP7Hi z#TY2yP^cJIHgmD8B7TueXqLCAo{Nir8Y})kTV7GN98!vCQx%aOF$qY093PXFX{6ki zY4nK<=W57l_~EokRk5+gFNH=uaa{;CGzFP{7Lda5m-VD<1~Bdapxaf&G5>w7UrAI} zlv8v(0!_UOkb3(9XQEa0R})n;a+^kq2B;{>|BFp|E1ifyg?krRebE6YpFzLgJ6)=Y$~Cv9 z2*QJ@AJr5Ic*6}pq4XDnUN^q$*Am^blCNN9z9QYW{t%CpSdj-NI;z@jR2HMERk~VI z)nHR?s!u^!55%9j1ry2H&2zhaJ{MYz1H#d%J36HTu6A{Pb%Q_V!pIXC6L9`>(MSMO z>XKGlj8baMrir!1=^9IC8IumoG}9wnV53Q$Eg|f{ioKy^R7ZUE^BYBP#E4eN(~cN) zTH)Ew%2>WBWygy6s^^+OW!t_mRMt_?`9-P>rd!9PL)}XkVg)6|0Ksy1$%5Pq5zwpP zPvIrZI2`>+uXJEOxCNk!^{ms0()sen82Y@97@;)I$_27Yb; zrz0S3Z`Toix5a>ZG^VkLAD{x9S+Dmob#I$8E+)Li>3nPQKs;)wQ6sNhz}*&e3-D%b z_3yxO?n;w!q{HR!jM(9`#%H4%{>StXFk-&3O|Vs6B?G}yCXS@;PY zCzff)XO!PSf=N#fo6>R0)MgGlOIMnRiAvA!sC!e9tt_+9Pff-3w7NgJxh(1CIQ8nT zzw@q~IbE#@i`G%;HOALQ)Gf$PoS%vmyyoCsK6c8{JN8Ee&Mb>%N`v$B9`Uklyn~S$i%G$4TYbS|tli#pvXsPl(U%o@kHf;zN z2WMztOHm&?TFY9BbWHxPwZvF+fGW4b&splsKi|?@twcuKJ`bS6!NvO9-I16z1xxcB zodwB(J>&>X9}}Lqx#YFtlCqnhH{yj)Vx=h%qsCd});1Z5B|iDX4f)dFpx9&)5UOJ6 zL#W+%Xe{hfSLzxYxM9$cWKpfb2N6ap98GqVA=OyO3HPzWjj)uL7A5g9hI;Vr zDDh(ppT3G8WWRXIY=Du3z!oP@|1}n??ivJ7V#cSP9aCu<2YTd zzfbY0B0*_;iYBCrI3@KAZAca2eruQlW3YF!ZG?NWZGf9C8n=gC07==06L?t9P-|wA zuSImgRRlvUqMBbGz|zeHkGQb`jIH{2X^lnn!qWGdMYPNsTZK(TCUh2~XbsTrqaM6!VbR{5A%ac?i{QgIU|3x&7LR8tekUpe>dr>nN|SsDt~>ZnqV& znAruUix}_Fu^5Nu^ZV}LY;G}#x~GdAkX}d^@8AjXH` z{@1#RF0iCG>@K=1?mcrCbr*kVuGjjiv;}mzAM_I&O_gIGy1V36LG?0B$@WCgV3yEb z|D6U55N(z3cT?Sg7zPdw5Tlh2GB5(i16B~+t zF@r@{_{h&5EZ$UR?<_%S6h1_(f)~WzA!5GL^m9rcD!M5*cQ8RB{WMgxS9;2TFEt$| zCM(};H!u%}i5`ks24oVFPaRr49R1-o!fR>Mm3rHlRK{yS%SVV-%7(2*92`l%E>JZtWFnS6V36KX3*u$SBpl9?xsDtH%AI#s+7>O6s0c$g}AJtn8sC9Dq?W; zdqdoXBa_=`5f?L}1!`xKSH?eJd>og&^(ZL(0^O1H%|+d=a>HHT|Ay|`T)jLE94$uT z9O$>B#oJ2wMj9|iEQP7;@feYZ3buVLl&C!;Z5$-&LH)*w4CsE(IF$aUE&VwGk;C7n_L*XfGHwa|ohe!-~MJ(MTlsPFdRn)P8EZi9sJ4N zC4KNCG+{k%HQ#L*L$)$59c)7$#z-1_7nwVd97;MFS<4l89 zbbFeJZnFmF1q=k(J#oM1&SPfrN;hn=kn5rVn!J;mr}+I5iG7;2Cjgn%@7IR{XoYaC`%&; zz&;OIj?54dO1tl=aE1s|f`6y7GezUrenOo3lkE`H=?Jbg7wWTWeD zGh0lm*c83|zwsmJ``MzSQtn-f$`KEgfOjZhj#%Wq6-$tpgmB*8JqH8dV`@BCjQ1V{ z<76ph7VVoWURT_gP}|v}CUu@CV#_ZokCVPg-@*-Q3oV)_>XmN_d#(fKjv3~ud18uE zZ6;af3tPa~nC`q}iHJC3;H@VtqN4d?9GsH-E)YF~%YjI`UpiY4=rGbJqpQHGWr3)n z#Mr?})~xcWLW8 zBHa5ZY&mIz4_fRVEwF5g`ziPGJL0ITQf54DST35jYl77}-1w5Z!SIZSY2-7SUG~O9 zlkj-KHgeSLz3ymE*vd-E;uho)X_ANRG5pj9_OHkROgO2M%%Z#NBD@6AZJI0hj%6KQ2eP52SZtNjj0GrjM z78ZeCi{F-@m^T4RA3V{ejotS#ATDVKe-mjnen1bIW;&+Vac~dHRC=V1xrunv!K3$dR!>phk^@{m`7uZvXQ(c% z6bb$2V}~S3f+5-}1dgY$h05hP=GQoaW`h2(?7E6cL*76DkmZy3My07*6*c$4oBZwh zIiq_R=q(1?mC@hPgjJ$R^gnN4Ahjt{3D+c5$a8?4N?0R7F(=-jA6AJlT!ZvvmEh}n zgWeOt+C+})s!-NSQ6~)Li20Z)mvab*@NGXroTBT_ToNsOPlS~zp2{-$JsU;4-V=3G zbFqn2lEJSL_;&`cW9)!`MuL&gXV;LCSw==4mv5hR_k`ryxpMwDHtj|bl`M!v*)tdRR5f52>AErJ?;>COpO)kBJXG7vhTK2wZ zm9=>nW~o@*I_vjIM3T*~HyF=)&eu(34aP49=d6aZmJj}($7}Ob5b7QvvR<(M)Eft1 z82RVleSvxgR8k-7AtEaCjVMfYm>w)7HXnH6n&lay5wa@4$KvR_WFRDQfsxX5J zMC&N~1K~R`vZ?0cv^ZfbP`9Hmb%xnicpv+z&M@P{ncm^I!X1pordeF4=(fq>Ij2ne z@`=z8TJ7N_vH{xES)bG6574L8{hU(Ph@X`o9#Z&+BGR)TM8y2#1{C-HL&8gY_1sPW za3>gZn`|G7K)-(`fK!4F$Gl7|ai`(@>u>sP`tn23TN$vODt#mpE7bTK5x7A=fG$z{ zeg3AQABiFH(|9(t50d0O#h9W2>73#Z1t#4-3VZ8mK-ax!!J)wxmYm-MEKOo2ZX?rL z(bA(WXn2}dtS3&S4r?(UbR^qak?ynMxg)vxMsjx2*NV}mIn-mFsO=N_3wJ;`hHmh8 zpFm63iFSecV^9qNAe#NUG{dcBAZsfF)CU7BC8!{?;N z13@r8PoN3wMe}lp5HMsrMf=x_n!(cnDEdmyK1;<(pBVr3fG~HZ*U`;;jLfKV7^|9hhT@2 zE!B1SZ1wJuh}9VoRG>L%bvQv%@QElJwh0RajBcLyXMMm0&( z1zz-qGa&90MI(qg_z7yDttA>o*GQOv{66}GCTS9}bth3phfFH4xAJ=s$F~cyH=!C!M#0ZVc!k z@9On*VKbVyc^Ea?BC2+ckh#JINR}p^UhCaq_Fl*fKil+3EP?w(4oJ6R4;-r1?+)eo zfLL;h(2_&0tz+V*l6bW|Qem-U*=+M0x1P3a5!He|0s-zd#>H84q_Vu`p2QwBG#I^} z?ry>6Wrty;ZH2ky;W|p+ih1TgG-j(vspI!O#6Qcay`rA;*d^2V0Fh*CT9Cg5Wr`nv)`18P*AY>h|n}5dK3kHnS+eN#q$p567$=Du<#w!h(rd~YoRlzzi zQ9UOSS0Meue~4-_k*AZ0Ymfc~hzgU?$rxuBhtYdGgkM~=loXQuM^enh^nF15OLY_I z*BwyZ7TU1`s;m0BXa@7;z|Tck*zD7wSN{j2GR)`>t@#{A(kb-S=c0NKH>O<%d0tI) zB_?9}^il}+!PU5dgkXhU_$kgT8M9S~x{cR$&3$2X)>8&j>Q4AmR2@LsJH_ajW2My3 z2Q6_Is1;0JP11fHamP~7F40Eoc@p2@18CGPQ8z2|TLkp}|BZ3y8W5*}XlHBG4##=t ztVR1~4c{!4dq;{JK~{KMZZ0RI3OlPw0QU%X^M!A~X)crSB?ZHlNN&ILbz}C##QXb! zv~&7!xaYdBZ|%pz@E-b8{zCuy6lh0rI>+rVIa+th_yR`g`(*t>M5LYsbzw)@$4>Ls z54APMk_a4r=Ki=pGg{UcBt8-Xk~I#Josg=8WIR3k0=vcADQ35btbVm8%QC^;Vl6U6 zI@yN-`yzir)^sn2d*5g|Q0 zuEO|Zi!NNxd9~rzF6i;nS2CTph=JwA35)$r^kukwCOC}~_KoCWRmhoiW@H%O)Kk<8 zs`x`{%R%~p7VZ%NX$6eP1tQI!<*k2hG1O>w#^q_y`%EGaSQ{5KtG$s3)SCh26cu5q zi?Y=3)9-u4Q^mhG-TG2AE7O-1XY=cLgJScrQyhDPEO}x|cpO-9vBzWjjk{y3ID+0r zutG2THBb1PX3*0-5w3K-PL=nHwozreawMa1;0%L7I|-8q=G4;&4yU*FirO_7bb+Wd zF@;LE4T^OV6NR_linYp2-4d565`KqZ$+=fO4(2%rSl+UB=^gj%gD8y-!3%bObuhmRd4&r`dX7=x)Jd zw%b^!}0Z^XgVfBHb2W!`eDDQ2E)nU`(Zu% zhpHS9zU31o8N4jhZRYy#QmX@^ozk>By>mcxRsy=yZwJK4kPY2fezdOS$c}7evR-Lp zYk<0T#9eRNlSb!@UP^Q~`Z`}U3(}=#N5P#8Q{eQ3$6j+QVzr6XUEiUI0uj^XrCDcMa8NXDDj1JW0?KCc_n8}}4_F8kK?{#EXXC*n z7|EL(ZDE|FEns?jMIXBS5K1kz9fcecX_`*~x_}Oy>5W68NtQ)&Wrj7GAy|*qGx-Zm zITJYNd^QajZ~fzD26rzx-^b*a?W|KfqWb>L6KT%yJ~=nh1HdEAJ{c8?IfjFzq$&<} zB-BFB?O2Ud!tK;j3e2usI}*4g=jD#sbznzocUV-(nvIBs&(Z1&AK?#=KDrl!ZgLM{ z<%w@mwp7B+L_^GNFX!l2^gSIw`o4r1G9rpT4&AHcB`o6@D+2{*C;$p9w8<_8a=i%MKKI1a2^RQTvGa%zHz7)&zDhSTW(a zKPKWRY}A?5=_pn>f7xipQM8*&^wCk_+hkZVfqG0_(fj&C}K9Wb4KIR@8; zwbc8#sH?=M)1u>|V^⪼4-sLOhwIUCc}sEK$W-N2LP_K?)pzqBqvkb!0Imeg<&I_ zJGyiFsdUKnt+Oua{SoB;?6mv^>tbDR4c6CX_;Ar?J<<5mzfAX(d}Lp{T%S*`pMWX) z2#76qTp4bk7L9X8AD=m7s_&&N3=5#c|$kx?}i zXZzrMHB6(puSMn5XUPzAHFUeg&}~&ooVAAe%BubsMw%ilr%=zi$qeFw3z^<6)1HpD z2;JI}rEZnKEY$88Tsgl_qtA{(pCc@E`53l0tEN$(T$OK55R zYRqTx5{oU%oeNGYd_vDpipZ>PbKG4fqTAxT-(?F+Zy;;k@3PjhToEXeqRS=AUw&rX z+hPlItZn>WjqmYl{Oc+Iif^&@iM(&Y4fWPhxR{eZ_*RsS%>)x^_26sno5bwT;a*;w zSPeG4PK?4UqZWAszNpgL(b$Z&4Vnb13eVs2jbQYZ7P6 zVXZ~(P_5NhV_hQaDRCKF1|v?xli&`~Y0*pxN}(T53$tkZIaIrjDx48R;dwCSj0g{` z-HJ!s39w_kwoAD0HRlry6L4BOnoT>-h~e1g3_L5^xAKeQ(&c$iIxn%Z7O<3I+#0SY zaeQ8O0bTe8*Htu^ao}p1`+H^+?wr#R&~s?hSrHYO!UzoE&bGOuLV#&Gn6sPs| zv*J*v)+l>$O#?*}u28tM=({wu+PE}~AC6f}RY`M8uWS%xs%h3sMXOQ6K-AYk^AAgf zq){op6A|UULy#+HG|suz_+CVpTjKzPf7A1mGyfVgNi68~|^KMS5Vb1gR+H80 zN=da^L_b~-o0PD4!UOhZWgm8YE~22bsOly7O++`M4=#yT!9nq8Z1CAhxSnqPJF^y- zWgwb97v~B3`w|jJ%cPiJL=?`$cEL|2w@9P6N|!^791gGOa8&q3gk+tKVD)#6bVu($ zhXs*6)H<^RT2gDw=)|~~aJJ~Oo?aKVCKV9<96R&;kO<7h9J!Li}iyD=}#rO;Sxw=_2R>|xo>Pk6D~z%(z!_)XSjq#h3KafI(T}D-e-)c>wb|OMqP4Pc4BfsehASQF zQkQGO%46y^(KYxRsP<*aH{p`6;VAhj)b2WZlfpO1ejR%R&Fa#w>mst!`Vgb&gG(3v zWV(ADML!O>{}z2KrsNTTLkB79e_S219kb<*zLIuNe8Y3?J4v$=@sb zF-LfnBRmhkFN=O4Li!2^$rK04Qrd6>MgNe>-9*tZrMfrad1Q&DnKxmV+BuRA-$dE} zMNe;{?5hQ_y#J%@AJ;Yzh0d~HfC6%q{d>_!bv44Afw4xxm6ZKVAi3;Y01g*|-M@)i zS@k9D6+H~U|D)_HBMPd|%d&SZMUM>wSX`~=k=o!V>^)=mJpdb&+mgdr=^J)l;0u2! zjfcI+?TmA)VaXC2a7(zQojA$Hf!GsX^FP2y0203gKTF7&$paAb++&1*zw5wnb>O|` z^Hq~@5yKV6@&Lyz;a_G1ki2BktF>g@pgT{>Yfayw3gbWyF$W*PSOJ4bKpSkm61PjwNLF#`61e@ovF_F_OB z()Tn%kp8lfoL_qf{@>CsIBKL804$52O(BJFBJhJy5Fd_zxc>Mk^(_?sKI|`DO8mg1 zG`CPh_}n?dq~3mWWR5e`&Gcm<_U!t|Ai#7Lk#Wz|Uxgwl>pqf`I?snxC^paPU<$zG z@f-*>0$%g0U~R_$A1B}p$c?)rpmy~(N|#k%4@n=#tO@^)r@~0fdd*OGNo zgP6|a7(VpwZQ);OHf$b7G00fi%{kTvD!46bmTBo=y@?*(7LCFxID&%?bChpSrZ+E@ zP{>EV6R(j#DCiEKC~_CUOW1hziFsjL?J9764*DGv>y z9vVVD61%bQ`}HHS4{xk^&yZf>ub+|QD7272#3lGCTzCu{ zSYB?$CnC$FG|r`^ehn}YmYHq@&F9Ga6nR}i8=hkB(VrUr z1>eRFhiKbhqIrDzL!8&1A%YY4@(De>Wa14}q3bA+sD}q7E~<^uR;yvPn#@(GXH39; zs`gA|`2MmJ2Ht&eS9IBrA&)&r{nqBb`%I{~v6xoAz_M^V9`=MY(sKIog_sxU-wivq zIPeQI!oIm^Elyw5Ei~zG@tWU^?f+uOHN5qn+&}&nQx(&n%SLNeO+%KwtpzG=b}ZYh z6@9-!igJkGdzEg-#%VNZM>qd19%h1L2h})9-bKXX2>J2o>AquN!qKRjoM_;R`-fN zSri1M-?GgTc@9WFyMgrQr)y(9a}b*30UFO4js&FZeK(8s=CEL|dw3zI|2N2I)aK8S zgXlYty!zsXG;hze%~aP_^YdybBlSHoz5<}?6zb-x4Tw9vpHu637T2`z!{;Qtx!Tij zH@=2}p;zn$1Mrcg@(x+HpJuvg0Yipw<(}liB-JbWry?`hg|ssulL7gqD1xPE0toVc z4J7#a1=+a-HH5S<5=7W1%)%yXJ{XA1)drknpDjWTz5S}}r-5!-#WL=WlwZ&cH!U#h z)RtEh-eo{aCA=1p|B>(~`(#cz;ZO_i&YQyn7%Nke96W8n%iMa-#TT;womcEx129%b z_4XUm79R{P3;suuqk~2DS=a>0;q2sk9EfxTke8!H;S8x@2z1sFiEqeMEAOKLsutki z^;0SA3G35?I$naQc?JMBSL%F6y*YB&rvHcg z?X!}5*a6A)ZOK^ zK}ze_Cor*jJbv=tLAk(-;m$msK)Rt^mT-II;q=j9$(z0|0}+{=5EGcFm5{- z2XB2j>J$KssQyOO>_rsnqlLorFVRP42) z_b{u9O(n4IKj&hT`wO=es#{U3O4Tc9e!d;;tb>c04NB0fB|56wGzDCas~#ZWf7qP& z{10s>2W^}|`xS3R8?;~J^WS`hK^t|`B>B#JNjv{Pw0kM8qE@ZUDucAuLh28J`4mA# zErK>w)EX%p2hrt<+K?vKUUzq~{%JjhXUK}ZkEfjDu;0N0PJL^zyGyaRylx4u ze4B^DU!s`3_te0lG{aYm^x3&@tF=n31HR1gPHBdBKyZ0bDTZbj z$&g<$w9Go5jlmTZnM0_apVlZUXgEYWmTWzkRO}<4BLi*>CLeu^!=1r^fwag^YnZk1 zEoZa3+XpVxU)EBvgVj5!UtidZ62BgcRR``QJI%5qDfN&Q)b_+ffE$1>Uj=M800ku! zpBR9nC4dhMz(EPfVy;^Z$l(%_^#-Xtavosi(izA?xuxcb7Q@6BF`X!(?f_KY&v~H;@^I zOMo(wjCl~qxzBQO!SCFHB(G)j(owX>)IEr6%E#6K7jsjiX7OAKp@uSK$w|C3A8o*E zPEW{`X5XERc=^9#Buuv_euMlZQMCZg++-z)imD~ff_T$W$&O^RoHlhfgS~K~(WXwu zr`MtxfWv_m{&)m2QGvbt(eeOoM2#nqWcup-0&p%q1M<5F=gc_bGPA8gE)4SdeW`k& z*0A;(CjZt!p0D6GG03wCG)cw&U|xV)nN*BBJ?$}`1IQYv!9AuQeHp05c{V`>ulocF zva28Vrze40bXK#aMvEDZUx0^$O|dufI+pXg7B`&1v1n6Y04oB>Z6f~@0Fj^_KqQ!E zpA;Zp)5CXuO9NF8)gD3gv1$i{t&1#uyyK3;m!OMu{3e&9_bY25jX46^wSo;d`BV`E z@$Y2_(mfb~w~&^6^GLpaRO~HZLIV84Ao`;+=9=I2r{EwhURlwHx(8_ul*+wnVUQN? z-NN9Ti4s9J9`>SrL0V1aXb$N?T3E%KsCLEPb&HGr-ec=t6_XaUj?Z@kq5Mn$(ih@8kPZaOHH=xH zZiXPO%92mT{1!GY^dxKRrYwjGX#TIBQrxpK8LW2s=uvS+M7ckklMY9|8*$QOkgH;E zB;5j#4H;w#(JEzmGUhQ?Bw*&XLwNx?1&ka(u=!N7sgL69%Eej6P|j-BHq@SkG8OvFND{&C6709_xZ`^t#i-x-;fhiHkd0}Wb}qzwlx9P*tBsV_KJ-ja2( z-<4UCt0pOX=D&ty(bX3~=JOd87^>ATdjb^JE5&|$W>NQ0Ei7>IaIUB3W5duP{Nuvp zo|ypYxwI%$iwO(t&fKAEJ=@T=yKV0%q%PalrEIzoss)YAXIxGq|F~pEXHP>FxEV8I&qI+0SPWz+j%T7c3hn{van+De(;bTdo~k3P<=j;{%81z#|AoS`<8 zZ0+Xf2%*yG2vRM??KsKK)Bub$VptDMg7CI;M8$4aa+m_n10bc-H`n&j#*}* zd#YN}Yu$jv+-tREa5>p)wZP}hq42v$GPS$Yp>VB7#KoEDdv@6Na#tv&{|WAtK6;-+e3CRU_1~Z z)G>a14WTYZh~M%;^!=<3AAHo7AJY1dQ1{L>rK(n?+Q=?av;6P~)+{~M(+e?~Mc^ba ze#-8_qj>hHZM`YKsumT!lyP1)+hO(D)AMvL-f%#-iv(Sa1i80EXjK>Ll%Q1%!`0n` zWM(SkC(n2k&@%|%dbx+3nKMI>nQjqUO7*gk0pc41tC>cXwyW2%h6k9LZHm;E#|;G)G#k*{dCmay77VYa21tqQ_{kfHl7b=@ z;TxCJACJ@;D)%~5aimtMiWf4JpN#AjdoM^iEw6!u1Zl7W$gyNkCwkUd3+mJbi4;}N zcagrAY6^!uWHb;rlEls;sbPd1Ieb^@3q*>cI5#7QwL8$-DD>(zJJO{nt!k^$JvqI0 zraQR5*v(S>&BX19QrMA!wi!G=;uBUd@HEZLo?O8;}bk;GG{jLKv$}JJHPdw*T%ei!>%)gJPUNA@8-I=_j zwUoY-v08#;W1&yXJws5bc{vM6KLg?ih#YGTI*S1fbU+gfXgvcOH)&i=t%aifM*sB0@6nsoB17||vbD7K-ji;*Vo{4{h4yd9c8NYP zcSJ4C%cPXKPE%`ZBTQ@PT5WBJ()b!#Vzj}g^Rzie8-lf=Pb{G2uF~XKtq+{x&c$j2 zl=NSzVI8fHa`y_IkHso%cO94n?DR(+;A&r??r|VJMLXlP6x>W$x31O*i+TCe#`vjV z{9K@q>T1=O*VDY=PvKKfdlM(^7S_}HV-NXGJ*~HraEa3DW5xb6ZLAOIRB~^iby1=& zQr`yJcpN-G(*T@5Gkzu*KTYJ1;(C#Gn6-wcZ%8+5&3bO_AS=8de)85DRk#wqb#Vpt z)}FZ7KtE{@^g090r24JP5`j(e`!1K%SjZ#;o(ws(GY^|0~f&uy59~>|_ z38psVm_>2e}*#uAr8^WssD%;1f1g;9zBPV-12-HzA7m1j-KBS8zSy;$P& z9eJE;gdDs_8X!h>XA2R+hvu{Nz11ba@=#dhSK3>N5uV6Gp!<&`gT^rFYE|F z(hsgQ^S!b)XiYQC6Fco&nrV4K*7L4-^S63C>lpVmyW6ESYpQo4KOm`lpQqW)wPrq< zM_IGBof}cc=<`xq(%I&i47F`ZMa{L9%7PYb83>~dEwu4b?NFMpuED(ZlSn;Nvmr4patlCtgVLN1`-^gwyP5v;BA{QKwi_L4l{z&hG4cd2;bb# z`E>&`bd#WP9F_9CL)}|yF$sW-*Z3HH>>k)u0z&>G=91_y)p0iQ;VQ8=c^3DQQ zjNbqWsc;|;dV<7DB>YQVB0~mCWJPco>*UbF$ziOML#Ts;H#o#Wwnli%>0ew70&q!O z*qU`UV}VmwbDC4VR$A2xhF{T)Y$v>9a~jr4Ymij|tXVhHZ4)x3ZVFK0K0zO#+h7n;3goL|m5EwIvJ z#4WC5^fAuXfH!g`FJm_NYiF{ zbm`8$RXmjSQ8Y!h(W(SCY=)X)H*ZKp|KHD8_dJE~rv4lCZ=;P;R+{PCHd>H!#7wu_ zXqC$iFeBlLNVR$%d9~Go;_5Mg7mw-+xq?!C8ZZEDswXfGLyT`^(t^}z6!Dw})T^zQ zs)LAVB0MB<((%-AmK~nC;rXv+W3Fl#BOvCZ22?Q}mXeN*sA0MmsXS{S*Em7; z#P1;`1b-7hd5aCfrzAk|z4TVPRxN9FJr*N-TC`kp8w;fH`lZ)yWf5YE8ce}!4kY&v zV!d2S=nft=nPQwlamk=?gpN5VYB9w%gCdV9vOJu@^$wB-OyYwHH=FA43r9M6(q zS6?8C#fA!Du}w&~b?J!L&5S{}_*eA2qZ>>0h2SUeJ41b=8$o^j=vF(edB_1I|8kkT zQ3{h}n}I<@eYBfewbz)toy|k^8!%f7JIt_R3@c9HSM4U2L6eR)Ghv!mn$iC3sUNSg^n6E zX>5HO-9a1Z{e{f4yu<=>{1``95UJjcIx@#ON1|V@IoSXn94~Wt5xL51V9ac4!#hh4 zK1ww^Vk*6L7cJtc*$^x+r? z%Wli;Dp<7x_W2}7T4U*vj2&>K6b za~JG|hQ-n3E--c0uS4s)X!T?J?Jx*;MQ#KkdZHNQNR~LXlV;l$sdM~;zqz6_s259T zhibkQ&{Z24_#TVQ9Uti6(DVz)5Itsd487A;t6uJ1DFCWURcosx#*t4qtx}x5 zmZ33YVP0H=lg9)cGC!B!*)xupT9_}cDHrDTUayI~^1}Qz=n!fhi=Vu4#=`ty9jJ8& zt~)KYFz=O5i>hh%`Vj_s9{lF=78#{bgUO$x;2kUT_%Hd>xiVjLfZA8rQs~p}TC2w4 zNcGkAxi4UH7=hR?*XMVo?9TPMdktQnH>E*6(3NY^as?fExH=*_7t5pZ3YM&X(WO?< z8Re-;4XtAB77nDTG_sZflpJ`lRR4));pkuu7(%+{((@Wvl?3J*tK7@G-kN)nd zC7Ir&<{8>+KGl%ZQp;PPFLPh3rIk1FRb9NkRR->&>$SDP%8Q+HfveC?+Jc3yQg0_! zh{f->pUWk#GUjt$)Am=&e{QUCx9@nl##K7(pvQH9yt`d4a+M|9X&0|@chIvqtVQKd zL(?;w)gPfl#?M>)!>e2$8aP0EQ_0;*_Ig@BrN>tKy`I)fdA>!ia+UYC(5G1KDowUf znFd;C<<@3i=T5+y`z)_>Mw5X)%4jms+YNLhMz1i?>ly+*g}xo4{TQ=kjnSHRZd+Y^e9@%4p>C<6aT4+@#3(0#x+F3ke#_V5!JT{2DhigH}Rlrf! zZwJ4xB(wK8CU^7Psm$aarY_iX;J_|GVe3qR%LNRc=s~In(-?3He1r+gV?v<=l8GyD%8Ct=C|U z+6yGCQ4WPl9hcH7)>@m8CCu>9&&37O*4H%uzzz<6Z3F(rX+Qv7ehp@W4nb7>n$|M( zd;ij8%1#9)gm|_KnGt( z-%}7kcVE{k1!haiWLwI~WNSgGqR6C>ky_K>woXPo&;D0gRtcbqBej@HWhI5hhEw^q zD&pQ5S0w+BFYOtr#U_LX%NC{LC-1(|Cb|crO|<@0wy5GDiT({gc@GS9H%3?gl)^@7 zk=1Mbrt!1~D{vi5yHt|9I^G9n**K5OZb-jnb#otX7Cmu~+;HwdOl zM7s3>S8es^P3DPdv-v9|8&{BSeW>?mact_^n{;xN7LYP?qie~jG`5o@b?<_^75J$r z_MuQ;lN_8<4R$6w`(ILBqSr=gVXgN0Ib?oSsLv$ADU@0wRA`A%Yaj(uyGGxP(gHmW ze1bvD@sGAAXl*ME#GqlAb3QIH=d`QtFy{bN%A8|_oaP)OA04*Hq*WgvVQdcS#&<4*%P#$ zRr4@+Wna&>4by6zU%%s_mOaXE;j|!2d!#%FqZ!$l@0<+F zU7M}FV}cX8dA3&9G$MESY&a$0=?N=^xFb0-ha2% zNi*?;zTd!}!0jAnt5zpt>`UYseX@Z(qYuJI(H=g8kqmrGuib8O7z1b>hC1xi`EP^+iizarBB-+HcjmDZMI@?^{|c#jg@&yaoAh`O*Bh z0AJ!qm*3LHDxrSV(WccXJ9vSVal77&a%@^`RxN~!n#!v*W}MJNICM+*Kzat~j9!*O z=k&kuqyNEw?%|JtEYR!EcW}Fb4=#vih$-81kEHHN-IBVpUsSZ5>diO-dC%xya10v) zw)wmTNpTCcspX^QF-sWlKu~cW>FHXP;+gt-Cemh(WA{@z- z)xD66UKW4&qz4$4VMbsw7$7;t<*v&5eVX;jF;^36Lo1bQ!=L(l$T>V{%1S?(!uxWJ zFxPeT z5NENEA&#dLBN#LE7;C^BK;tfE*%YUbd$Phv9h zR+m|I5PwQ{7omgbP7@Ymjj`363KnC&`zHX!RIxe(;iuZi_8D02R88ZF;Kw(17PU=~;4iqU5{SP;J9J_+Gtc$e#VO=2CbbXO#ghV?Yb@U(`e9a%Svt;rpw) z?ZM6HnbjfW9Ct$~56LXk7ocv=1fJ5GrCROKpNky=9zqeokAi7DoitmiaH&=$VNf|v zyg3p-^9vFWkclUzmq6DtRN>>(d}%|mzB>y%u?hQvS}lX~>M8nc874>Nv*@Q~TAbp0 zmdY;IEWtyjqMy0U7vZ7Gmu|7kgc3ZA4aAb$XyO( zwNjkzqu5?663cS_{I+WLIa;A8Nj*3rEsx95bhut8i+**337(pT@p9WG zWM>I75XhpdxC`fa$|cUO;eR>3fSX3QOFTXQQ(sTF-hhr!G@#Q1%ozUNXpkt5QU{xcnAFgU*r~RZH3$~+w~L|R7{cS0SiH45V|dCE zORb>NFBJQtGWaMGpv_|iddycYv2^zR3HgbnZGld8W0F07ol4^8y!2Ab=Cqpz^X$Z019S5n zkkDM*t#aBa2kaLqh7*R+jSFJfYk;b$pg6_hZXST3xd%WWY4CL`Kzc_J5|)wH8Aiw_ zsj}d105rw`U6CNSbWaS6Pu+^TdwaX`LqA$++r5C|1A94K5GRp?s zwL4|G-@nCsh5_XgQCMUSWwSJ_PvO-@tJIU%$fA9;d`j zKUpGis=wAxmi0J!e(fg==}WlZO3S!_Y4=F*hoaGEMO+*;a3KnggN)WGMSR^#OJI}E zz~hob1DHgD)GIsS|uNW)B_@h4LuMGuL%BA&x9rt&tc zu}bR4KUs}45`FR3PlA2-7%a>8P+J$@D!>f6PPaC_jSw{u409?vNghc~yR|mJG|Tdq z-igozY$XoDe+Z!`3UP0+q_a;}bZdsv7rO>P-I6piMp51L2{*5?l&`dffS9!x7fTVp zSU^G$y&}6Su~%?$Q=2ug-P!(vZ(U<)t_^>||6PN5WSzmh<{7T-ziOYwv! zueDV0?nhp5q=>&g zp9!}ZN|~M^txRV=<2Tn@V#@E7B~SUf3e3ea{20rWAAzAr-|}YbEJ^-F70++4gXduk zM##H=;T8+|nst`#{PhxzzlEOGv?00(*yp?_DQPr(g?JD@}P^It4w z1J)~=Qbe;u(R@xcJ_C8=dQ1C+_6oo3D)1W=z69Za9-vAu!M1k>O@(0OLYYuok&h(u zHH!Qa++sXGyxvkL;04@}s3M^~6#4<7BL;B44VESTofLlP72w}vWJY?egip;pxxrG& zq@_O0e7(^!&|NEeC9}^y%L~o_z!Ayu=wXEL!VCr>vH%ii`S1gldAPg5@&_!u5?b

y$-LI&(ztMkPlu*S_dq?A zy1$FgIG&sUOYL4&@&uJW1LJmQy7(^ASF+}Asqy;+nopT}m3G){Jw1IfmzU6*_h5YG zb>-%F+W4N{Q_k|PO1kzQb}8a1_5`lij^+zUrM(I+pFknDQ}=3>7XhPbmw(wqPLfMS{q zu$+5erFu&R<7sxDiaLkLxlXB6y3DrTCIk(Lsr(@7 zVYBt7_>XlDeHs+MT~xNQt?ANddS9pc3(*s#=H_=<#WzC@~yuXDV1xLqT2fRte)I=%G4W~*0e`IneU!DzJiA{=5A5)esIUn9$rlqNu< z9RPFPLi+k^)J;2z`UdQDN&}eRTSU))1Ga*WeWwqi$dg#Phhtch^6h>z#VMC8OvPjq zs8V5lyWc0`w7J$7TNk+J{SGa)%B_(@!b)h$-PFb#$T%3Rq+r|X{DME}=~H^FSARS& z-M4^-e2gh}1PjH1|i)r&rK!AX;}lXe>Uv{{+AdwSZ-`3t&E8OqYIw z4Q|YT20MhD0`|}@fce4(y7V)YJE{9GV57+iFxNgoyMF;2O!b1zqVB%}W|LDu9PJV? zlj;TRrtS@>iy9Brh&t1JFIz893`d8y)AJ4R(Tgs^!1Mq`T?WjcGyw}~hXAb91gymi zLhx~5r>c%_wDAMPc{|?uc$Rhu&Hk5_<~}*t_=RJzsVP^~pQ?_={Tp-EdP@5@+;yZK z0yfb_0n;h!3Sc6o3CN@!0*dIOfJBP=9nE=&fPS9qF~HzSW*O3+b?d^A|>wDT9>3bE4LJdii$5XgY`jZopLrO}+tFm2tH8 z28Q2AI(7r;@Rta0B3~a4Q|WGs%IoL3tdpiaPRnn?2y~ypq>#1*r09&2&c&cM$<+Cz3GgAVDk3> z#L#4bxnVXv>47X_-1UIV;N{BYU{#$%yNfkQf+sX_tQn-nS}3rA1qDWidQa)LyO#u~ z7pyBDSAM=$&Hj{^tsUm}i$YcTgi_5?BnYjMbp;c^#T~ zGI`rjV^7i$Z)7}Z_cpA;$`s=(ui>>5RAm|M zw!noq)mv0>t;nD21!Wkd4A=KYCw_sRw89XpQY)^7jUXRfYyT@W#Ru1_dP^lZB~~I` z>8KAXF`3T51V#4u1>kv>FFcpOqYUpQG3tB9!0|iuwl7>XoK%`ZF{JdN)Jn`gL-G1{ z3?=xXtk=)dQa_jtsa0lQatne*9aB%LHBYFHK=-9s9p%|VP8}D|rd}1*z zU>jp87GD{}r%9SPq3K5PucLRxZBuG2mS4wXnIyi#Kj`u|xa%wrv_+b0pQclR$QrgH z+u%|SY;s)O7eI03&i@?V}b<1(sIE>)0wtl+F-4P9tOtT_zYcZYwK!hct({ckdBSVb{`B} zZU)gMrbk-UsjE;~lY(ZV$2zz@$1q2sKgj4DAa-O=xr(BKmi1 zFd&uY3n-zzfWWF})kSwH7n=8D1=KUt7D=5#Y+X?CmqTowg7#w>rah+&Pa1}U*zg4y zS{Ad`Pi>C&$HEKuzah46fk`dX9nBXP7-gw)C-LuR3l~eZa&`s=j(w(g_`E+M{ z+c2_+*}U-+@NkU{S3D7Fw-VMhY<WKOHo0cvKjvtNXgOfTLO&!5fFTTE$vmc9WBnmEsgZ$P zW_Y8vCd%e*naW;1=F(#{q>HVG zm-zluIiK&udF3&>2EC=4-C{-(9i*fK9uZ!LzKU&o;a;PL&&B3h9iOkqc>|vx#<}*k zy3S8Hx4$iX_wWlxqmO)B6|RG7)`_4nS>A^Jn^;k9%_co#EwD>j~l&dd0G56&z2{5;O9I5fVv zyQyg&n*N}wRXpUKkvd%|?}%iS_TaQgvEt?^Q=Xcyd(#8A*}8gmMpMT@uJx05g9OX%{=<~V(TiBDb7iMP~L7&vY&%#II2p3txHsr*Gf>e zN9i1@(^ADMPX7s1Wp2m#cpc(NEm@hbth9T0aOxGtyD3$gNkWJ zc^wB8e3U}_AS)WHyi`lT7MklwlgI~A)g-I}hJvqQzLJ*bNC?A8PCDi?fF3l#)y7VMhSmR9a6I0E{usDI>3Gpvs@4-7(0TG0AcIolqI`C{w3F zrMg|p2wVYW%&DI0tp@Dk#WXYWN(2@B9Bqpg0cfW3+XR)1Cw=vB&lgOMn~|*oOr@LI z5sK;07dwDuOifhpeH8b##aK$+W^&O2t0Ffeo5g-mna`57AF`6%OywuG4CCCC3>cV5 zHzU(e1Lgg!2v{dyoEnQHBW9p#c^PEH3{+J!%IgZK@@HsVe-U(6aVJT{4gX@MTGNhR zs5CIC%y1Li1S-jGBp!$gqZqd*88a}hL#jdW80Fg*fo(>4^#@gXh}I53PRdxtIavu+ zF;mzLj>bj6Y!ziBiUp$<}&f|4!V!N8HnXv94pKuhC1ISre@jcJUy_H;Z146n>`Bsrx)agH&p36 z@#<)ht-Ym=xfx>N5lRnEvIWrZgKW2(YAALX9*N=GFL+2Z46|r2oR-mYCW|HMw+v)1 zyHH)wZzrhf&8gF%;+s*PS3u#>pl}?Htq{9%mx$BGN~6=t$_7*CW@LjCyS3GobZIcE zsDf2ISVB|kW`wbyFZNK1nx*&a4=U45$-xp#vYU|?SqUnxlFmh=RpT_WnOCn*KzUbE zkK2)jhJz}Q@1qN!ynfaE9%rpC?4Tor(-ZkEn-JE#geGaLaqj#F$?#^dxSoEFJb zuQqs5DDxoA9g1rvbK2HqYKQ8FCXN}4x;Ft6$&9}mh4+IBc3)Dq8Za6&s%v_AU@r(S zKw_foc?553hJJ>{n~;^Efw>8Si(Dz`D73zgm~~?yLpemYL1>x_q+r0ga z+)qVp#7Di2K_e1v9n2N`X;va`{~9^n19UY}46%u}NbI0pPPE;F?Xa<s<3IRD3GZs@t@ETQ{yz=zzjv_I|Q)qusu3}}? zTHe=rfWhwGSh=MUI}W#Sm&e+Ius;AQf=aTnF>xLy7-z?!AVx4e)nwEIV=RY<2ZCv! zP+xLV$Egu?zWh#5M$DNy4J!39l_MSruYgKo%3CdjdZLB98QG5#K?O5~u!`v&jGdV% z?gmYaB&8OGUWdU)K1Sp2w2kxf#duOC&qq_;oi-1@nIIxLMxTmG?@VpS+k(7yg0C#b zS6A>vFm*f(&x5F9LoSWSZD_)7N>12dN|;eEnE6cuRqUqZI02@J8D2DqP0P%tR1k?5 zqx?>TPiFp4b<~1z7{}XL}y^iC2 z`Wl)d&adOVY7MQOh;G%Z4{Y!^J0{vj_!ZzhbFFgmG)?|MEK(-fI(r=kU$ue8O|s4M z6hGM!jd%LR-5pM~Hr(3c!kSaU#O?i&g-I$D(e zl#=5QD?}3cv489(^@d!w)kYhger+e$&9_NKS*gORZ3 z!g#uzY`Z6LF}qD=%$jc_|3!WsDfLgEk%h@^Jmp1Wf_x+nVN7J4$T*#G7GoCULdGJ- zwTzn>pJgm(+{buWVWIXO6E%$I7{6uwg|U&*9IP_x%h--FlCcM4f5tl)lNhHk&Xib) zuedUi%ea(rCFA3arHs!r?qNK@SjBjX@e{@ij4sCC7_SS&^);^$RU$iMC}U^F-i&dK zBN)ds-p!cGIG3@2@e#&i#wQuK0}DkKJ_-dC9WmBQrD6{fPjiuI`72rt ziwci_rEm({m42<{+**aHl1Klmy;qITcQdY@q2!+)Qn)xzVdG4NpWd%9^)7{F4=c=F zsIX?L!mpoU{oM+y?ok-Y`ig0gMgLEntrVN)DNHO-SUz9jX_kL^Ov(FKD2yyrSe&Bp z?L`XDJ)!W~4GQ;fRXF`Yg&CO&eRo+^|Bv3Oh^6Zlp4y~v;6{b(pH%ojDf3$tF5`$~ zysTt9mu#)UzohtHoRP)%vE9=OeYaax|8JP0h|_FvovWarxe#r75i8gfYx(9I3kLN?&n= z)4iq8SPN*(Yi}w(=8!`B>k4ZMSy9Fbiwp@Q7U5rjNy!38DkiSFeWliWSq`8i!qCFA!89^;aWb~#P}>@IpaRY z!;J4S)-ax9{Fd<-#zsbSM^zAC#&(R63JbL!O!Q~GgE5J53gb-1*^If2OBq)(KF(Om z_&nnt#siF164C!oG4ToG1x6R+Z;aO&y*jB9*%?C_J2UoXjAIW5R8ILelGoEEU&-guK1LHMDk1nbpI%6xGZrQJP3Ij7VNTcowZ&C=QoTUTq{E%}g;?IVp;bV))cA*bGwhX}dkmONL;Rk!4ALT zLQcLV_n2er>Qm&FRiwmLR1Zpa`dJQE%&{e#3txChIjG>eFz_FPn)7qn?(giY{>J_X z=8g1w*v}Vn(tn|5C@NU7_I7n)gWu15l1kvVe}nnyf5RVTz2WB+%TF}3$Ec6uWvr?PCNpQYJ2@FTS{0-k{Nb?ID5xuvCfAQ*xxR-kt zyj+zUj*SEdTO=^5z?%~qZ6q;TIr_T@{U7k^uGDb&e~>`t{QsXl&r1LE7v{(na$OoF zGVp&YLxsi#4g8U4mj#b!24Fz`wKqf8s^|Hyj)I zuNK{}7niK#-|#=T;En!W+hRPJ!)>h(`xD(>_J2bWe7h>KF&-Fv^xyD9|ArsZZvrEhpz zVcd3w6B&(6rLvsIh{u$}g};X9q#6`=@p({DZMIM>U)=hVfO2)Mk-i1jWDU``7>70kK=&3NF+$z5jiB*bX?DxzZNt z*Q!X9RnR^xm7f-t1*!*u4UAQ7Jf%x97Njm$B@x|5d}51~Y*wO{&3e3;EI-RwHmd9y zw#SRZ^0SWhc+*#YN?FF+yYjP#?ePk(Ca$l&$Oaav*0g`JY-RZnC-7nUZPxp;T+Mnv zmOo`#XZZ}<<9$i_na_H(!Awb{F@bju<>w+N@YZhP5wfOz$9lZOCqGkI#`|{iGm>Sz zEGIutu#A`1~E{ES38BX+AAyyP`H+{I+A&QjG}i`^7AZj z>!joUjO?o9F8qAn4BAeQFOT=AR0U z!q>62k&8%v1Eq-V?QC!8OZHi1ry#tF;@v9JqC)-XjNiXS-@9~@J>v` z?S>oUIi2wij}fj~p3V)sNPcctbWj$oJ=JZq*~gV}W{fw1j5mV}FC{JB05ZH9>2mo^ zqSQBN$kVp2g$7^35B+laVPGoTCG#6bA{gRR%P$5c@ryr-#T!j5S8(+tb9}2 z)xfAJeSl`vca>R19npH)7SyWlb3aj??fcZRs$V#3LcqHiCl7Y?a7^e|*ZEtc3LR1@pAYc|`{=l-YW8 zG1ZdgJ==D`OYhmX;ElmyCv5YvethkO?RGPM{j7txJ&F2P<7L89nqFw zOpWyFDcew_`|*@*1Eu^C5Y@e?WV5LxJU%67*4*@zdHHjuIr8h@$SNZ873U0(zZB+q*F+!0uV(S_=+j z{(FBBk@Zt+e6;+gx$TRSH)Z5;gu6$^F*suB*URs*Usy9=_8GPvx%0x;%AKQRAOC$n=q|E5%ssv6Sp*_o9E)*nG@2n}m-kZDL0))QZY~bf>QHFDs@FXKd43 zHC&e|+tTA_Z0*d&k5G}w(7R_4=Emie{GqK4)t$jx@-b&^JrUKJXKg)v%a$8KE2|bv zm)3oSfBXifwBI~CAdtR0YujY5D%$diZPCHsKDIsNiJ!;G{@ivQb@<>pTdb*po;hdh zV5&TL_?&I3$*=79TY1||Wnb7F^vD+hK7K{jw~AbGaO-(nKa;8W;0IsY{uDKT(qw<)aV7- z>4K|N+{m=7tntqL0zp+VmE}mw6LNJEd1OvjjwEXg(wUo_RhX77FHp;*FzT1FP$aHv zO3fCGs~IDW8k$lIW(ljtCRBgrs>nkYQf8t5Y?-iFQIU#MDNERFK~~o2v~-!TQY!Sf zw=FDFnnXu#L8eju@+RDbwEPFfwJVr&=BLfdk`5}F$xB-_V!`a$4jF;lIeN1kLW-0akXJZJVCk%>yhB{{QIV^ZRH=bX%f2{{>#ET=H4HrPVYsw!&?D%&x& zz?qw?0$+QJy~mkRAY)U63DWU87=P z1ft!PDBVQIBBP6xGLw@xFD@GEKPL)7ppq4o_p<9M^x5!(TE=5jB%STL-bMpJD&@o%+rez5lTNw9~#zn1V{H8e4 zGHx~N;#;H)gKUo_2AN(UQn2Ba$X+NjR_CV1&P$u)=$j}K zyIRxDw)S>~4Xn&d%g)b27G;haTaYSJ-ZD~mJMsz~i~CBu;^vg;|y;&4eiK02kOMomNlp0?jB5(Ig4aF+Y)RK4yiCK z5}o-%@4P?L4j9?+oOW33>(ZtDUQXtY06_Y9Syg) z>rm5FJ0~w~NlHOZO5PkfJJkl#zu}>MT~nK^oa{Ms=jEo%mMd0QAjP&9<7iWpeCM2$ zSxX8WvM)6@(_rGuO^F>S@?O-210wRZD9>5oV4uZq1^lGXle2$`!6`$IN~yupd+qJE z;4(v0Motb#IsM$;-rJ+1O}ltX{UM;I2QFSo6Fb{9|GD4WexsB zkG-QuZJXeDy7)st2XB$xI+h|mA-LM$Z<*Mmyo91bo_7sugh5KmjEQ$n&F`O&B?4|% zpe>?jBhZ|S+q#tlv5ucpkg3!q^g)C@7&jTFNPD1n@}05;lnE&ex1;SN?VUX;kd)qc z1q}6tyGp|kmHiSByQMy$wa9Q4os6_cdsGX{+4TXPR3XBn?BO1@f_tt$;5L!%y0(-Z zWp6jv#a^bSjZGglQrxyd+0cyaJ8H_EQ&R-r*pyFXdXzy=X_o>*Q(#e<;II@i*J;@g^l zV9M%Z_x7lO5uN!dAgVP=Q^`UQ9qeNF^R8kC^wF;Y?dZ0j1A;^>tLd*U_E8=+!nWq; zfI;+dSA@Hk#j(Oq9Si-bq8qN^65{q>0tQe@H+!&01B*k1X`>MQcVU0wBbW{>3gN;P z48#zPWh+8%bdKWxMkm8pLVnS*V|R_VN8@LyCPibmDW{xhdnmq=x)$JF$!RGe3L6C< zMB6)iS2d&bhJaunQMzXA@Sg)bMJ{W=QZl9?(X(qqXk~Z%G;>`Do$GFo@Nhx7<#IqM z75A`*c{C^~Ru)bqi@hAsR|rL+N(iNgFRMrv)A=6u&e%UO^|W{KC>8oMmjfb1kjrR7 zPkW+Ax!`*L7BEscs1U;Z-vaImhfoOtBjD76!MGop=gh|1O~#^%l6%>QW0WoFWgmtq z_rqTHsY4s3aZ38)v=lk@!5FhsI;L5yZA3FJ3KQO>!7Q;sl9HZQkS?d-V!Hn}d;5VU zw+zrBZ>dXf@nSk}?zN06Zj)o{X~2kzTc&xAg3LU%n5?|Ty1A;b5ai~r>_u2|reL|W zV4hqjR8#lf_NZ<(Qk#OM7H$tx@(N@DY85$asUuHL%XMKizqdWGhfDGEux*jIK<&UZ zFy)w+p1UL^JyY&yG=|Zuz3oATMd9MoDfx?LEs&#Pv62h0U9(7SX_Oeu0*p;^lcQ8H zX|s6YUnZsW961t{OCewFI8{iYAWdet(h&N}kw!_5!o_MqTdm|AnTHw{&&tV{ zwNR$y91P6jn&nE&Um{1$3L!bT5S2oh?M_%FMTaU=wG?I>dp9*woZ~1gkmFP>i)zoN zPD)w0@sx#hORAJoG;-4@rCe;Zs2k^^4k8P=3zXYpDT$??a8$yQELEu#@(a_`vZdQH zB|984c;!+maAakPgcVYf>mDIi3K0uUne!?ZMD%iIXQas+vuZ(RWjJLAt&xHl+l&a; z3P!C-<$Zjel+iphRe=prtUY}H0kQD1x8;JT)2$NUA#g)4$y>1z)SYG|sRi|rthv@wD%z;i@Vq$nBZ z?m@>A+%;r53SjFC@g!==?=dQWnFctZO0Wr)N4cQkFO!S*nps&QXB7 z2w_snqMBO;Q&=V`clDJkQHHvL&t-lqrI3BAZ&gX+mRv36`MmvM2&$}7$~QV!ofHdl zG5%_W$b(DCdFfaK@;yj{L8(5_Xo#v9MIA*+3-W{mq%USM1J8dTmavGmzbrG0*_hLW zT;`TV>OwRFCUR%xsl9EM^SA?6vWyFg^i?btvgXwZVL`Sr zP`IR+pH=8^$V@i~d0~E&_%{)6nng{BnEQwvVfo;Iax^^@jkJCh5v zBgqc!Qu3I2v$ai=Cuog#;X&O!)3g;YndWKVY%*tP?`$#WX{+(a;k5V2oTinPnse0R zWb;<@`~^-7W>0T5&(?NqH9NHvTg{ni`BSm(apY+Gx0&x;lref$H7esZLvv!`TYT*D0hrfCNLQuv%K?cigr z zR#Iom);3->WoWNmHO-jV&p9V6w(mSNx5H1HHEUn(BXf-Qwk1}p^6#ge4eYOd9yCaM zKQc~xzVl$Mtm{DSVDtd(tHGL{CI0+TS)4sI^Pipzax<_@)7DthwL`vWHQEc2^R(hV z`P#1oGVqK=du?#8_WjU2?NEYK`+dUL{E>LDm>&Kr6OqX?xqw(cWx_>d4gI49(ZdBQO$c zyL-*kUZ;w|_V(?b9G8*;-y&D@v?qQvIrFuhKbh#G!S;~WuX?6oo97JuiYydv8;0v9 zX|1%HiJIn@mW^!}JXSgCHEY&vXO<(m|2*uPX;rP$wY9!Z?Qn;A+KJu^bFz@<1ywh(pnR;;erp?UYU2z7~ws{%OxLDzsTqGqqi^u4GknOkWS3r|s$^vibr&ce_2TSN#B|_QKG7 z?fV4L&g3HicA`Bw0@>XBBQn^qR8-YNREHFWhb_tCSxtVkYdN**ULIOS5ni0}cWPUG z7HGTCZhp4sYt`*DwDkzk)m}2Y^4VOoq}rpg$j@l(N8_n%rqiiy=(jW{XP(+th4>5N ziN}*Umgi$+lI#Dn3nUPBO5yx=rdECQ~RdOnw6fe)$BHnZ_;&6eP+_k z+1gH@e9gM|XOq?nE2A%b@T1lN?M$zbeV9EceQ%Ox9x2-gm(HoJ^c|Czh8Go@7x$}? z9$M9-2$b28otKuOD#nS19h;)<_r+teCT-Z6JX?D#34tw!y$9WF6A)qE=|>mZ1cbHy zGGsws9-j716#-eOl_!Yx78S+g`P}(Cafwah5^`7>sa5-BXF0PUyc>@!I5XO@Ntzk$ zGr7NLrQAAx@xy*E7+!jJ~$M*9c7V&5QqJFWPX2J#@q`y0}WSuDKdbsOX>KVmX+F*4xr@n>O7;{s^!; z$J%ilev31pThMEhkm!g=)I_<$E|sybLs@cED4kgaQg2*Plv;b)j@!~del@{El}Yo^ z{uf>E0Uynpja~>i0b7?n=VW$B*BmyLZkxbEcn}IWsrlxBmGq z=CCE!5AQmkNSXR5a()ByUevA;Y_f2%b(AfS*!dU{tl3uZ@GlZK3&Mh{ z8H;-zL_XM7N}|!bKGsQ2piPc`%QD_`KK1DAcamkvyXi@6jNyXKZDhr~=X~Vx{juC4 z4{u~gN^#VOR)P`zoI`RxvVM5a`Q&5AKbEL)zR9#lY!iG|ZR?%r=%*WQuG=xG?Kiqd zm60WL8;aR1F;evYN19}$g{#5(N1$YP6RL*x6WxR1Dg2vGvd(&-q4PP{kB_0Z&VX;h z?myW*camN2v*cp^4B674t@M053pFi4ZoG?)ADE__ny6iaX%Dxd+r@v3|7~SV^VVH) zYaLn^y02I)Q){cew@RG-Jz8NaS@$Zm@Hn80ccu%AooD5e;6BdBgxsmg+|99d^BqO zy=g4ExGXuokFBnldEq`q44!5}Q83cidZ95ov2A}6#|KSXh0AE(Q}OZ5P;26oqD1a_%i!x7PlODnsV7*suz4mIZ;Zo9Wf6|mb>yZH94Y9`3UW=Q;9W`S=4 zocSGzSVb81ti25g>qMsfDJk>)wA4&y>y@TrhO@i@{_4gUY_Vrsf6$P{z=3-8vk*7q zeiq(5h>OA&fbG@bimzmmq0d47&j!Zvh$k|5f1DMM!Ry^jbX6&KweNueGBBU|rF8fZ zo3)X=#jw2axl|bxn<3-QqW1U-K!Z_v%X1lA?Ms%U7@4C|GCJ0JHe6}OKA(((l-SXDRdGIuvY8(qrexHnm=-pA6&IqS&#&KIJ`{e^xhISBUt#Nfan z%lU!x_g{>AA%Vk40xap6T55&tdk1yQ2e(QLt*J=~F|d>3S!LI~kdRJC4cc?GR?6N* zp5=osifymxMVjfYr05N9*kMle!w1fGF+~f}GggXepPZgy%R2F5f>ivKSp@zEYD&Q$ z>Pp^AwPnBc@Q2Qq-yZbGI*_S+ZLqW}JztBFrLWhL6Yq#46404zLJ89ArM77)ST0M_ z`ftU@Iv;JCEc@SX-L{*<)@0(D__+DQzb^Moom=vF?88d?GpjwR`1_eYv{4;gIM1vAR54M3O9d zxs&9)fvibNN#xLgU)*JeCrit%tEkLrd1|d?k3LX%|35f6PsXX{z|=wHnQp! z=1_ZNer7@{dZqSE$c@*#Kypsl;`0k(w2MBh@P ze>u^;Qs4WDp4Cy*&FbF7`G^(&k@G=gjP=z=usAMQ-9B>u@0+6wP_MmfnC$Qnm7vKE z*XNb5veVl35op#~M?P{s_mAtZrgf@L!j^lRgXOGWAv*7JIMkbV0;?C@tk>+Uu1dB0 z4Jfx&dA5N^tL5d_tZmOnKjtcUm-7o5@p1L(q}_`jV%b*KwImt&dP2uEZ0Vs6Wd_3MC+ed9)8>*tKYD`dNKNm`!6>~ne4U_n>t@`x!;Ur z9GnnR!=ofJug{xMD)-kA;h%})OAd^@ExUAr_e!`Ikp*w2$(owU9g;IMISopdpU!h6 zTcRwhz@mfo;WyL%%S>l^O)(p#aXz547(3QvasTPVPa8+%=fVgMZl<*0E@Egu(NjY7 zmJ!{!rwdhV77j6(Y7J;#{3o63@bO?R(7*>BEuowkx=zZlqYT>dpRmc|qEiZJFC#XO z`$13zvvC^BfmMu=|C1y=YNFjYQ)>+)2j|;onu!U%Fx$NUsT8;Ue-KZ9@S2J6XM~>& z29jji2qFDqkck5*+dI(?m*fA#iG80s;&Nb_Ceky@pto9K(KC_*!{3T)R}~g|3-Xt! zlohsyABUZInATNq!S5+NcTARHwW)w?pp~=ACT^M3J}fs`&c2ld!E)`bv`iOzVz8Ii zLsqS$@DNbUnpx80&oNmt`OkR2jc>?N({5o|GVaf5vK%bleVSv3lt4E8IW4_2Jgs0) z-eUzEM^6K%tfPO<=%{{lmBn309RJjUOO{)I)?!PRW3`&lxdn6Q+9;7@lnma9H8VPX zs&lw|MK7r%X6y9Cuai*!7S(KqwVY(s-Kk*pf)xTzSj}5tu715Htc_n*MD!6gQQ{>^ zdV%P>5sq<03<~H-ZMnZ87G+>6$Gi`FI#~wPYLbcmR;*fFB0%}p;#@LW_SE96Dp|TC zYF|$r&$*xUEScWma3!ViaY!u+SQ3@CcX^VtWC zq?Yu{YuV}L^8}`N2C2NWi0*krkD_nMLoWRAFVSE>g+Ntu-K>S#10VWQYlMJ(Ezzgw zS+8$4>l;PCqI*AO&}S~v8HBs#owchQ<;ijm28;*9Q7#FcAx{(d%YB>e<0Q)EwO@}U$4qIQfu%EtRwHN8sPQM#Su@FuBm8Y zm`!BKp?Wl;Y9bX!S#IKklVPYewsdPd=Jib?O|T9Mwhql~EEB@-r(xM2$W zN#Jm$RQ6g+>vzdOBSiaekeZf|);V5QpeRG5Da|{D(jpNf>J#-_!J_Muq5a)dEYbu( z0in}~!5Ku~9MzzDcQ&p;W=D)*kv=I|HYfcrnAVa!07N&Mo*Wx{Utxv9DsSa{>Y-_v?CGL}jBjM!eKz`8^Hi+$ z;&ah2yixQ~TU};Ae`HTNvZSmjOk(?oOmEgGG5s^_c5R<;xm&|kW3lCJ?R?o4{0np# z&zSrZiGc-*tEp8}Df!=4n4DCl&H9T@*dj^o{TG-=RiQ&5%d=T!Z6mt36FobK!99@Z z;9!G6ymNd^tqg4BXLPpSz54J24mt1_E9R}}M;<=jG(%2Zz(DRlpaPCtoj!FwRd4t^ z+%FBUTf~3dp34>^kVT%-XowD-A$meYzxxqjpgT?!<3k%yM?dn^j(4;;q!f#BfxZm& zWE1__)|pS8FF2O`wS!gmsq?Y>IU(@>)$;ay@Ui#W%dQOQtc31<6c(|k(m8vG*YpEvh!`0BGbuYEr5Hr+A@>ToR82?)g$yNoa2Tm{$qAdvneqRZC1V?E4(QJ; znbZ)OKSJXhN%VP%o{7W&5Z#knHuj*LEWw8ARc{+W?eP!Guou~3Ot+4G?)?2nIgKzO ztiEU;fm1ELqV=`wBFLNEM!d{PcvZcfUXnc&!m8ewFPf z{_U#PQ;LQhELUwWDPAV}uTpw)W|I$^-rGeK##Yx#S2C_}`S=>?wVG^=Wo6ifZp~Mr zqJV!uF6fVtV(=CNLZfgP{=Ry$q;ZVQiL>(m&-wVnGVQ2QSGUg9b8X>i>xD0!zrSzU zX{?A_?{$iP^dT7**Di@0F)?z|`oAxowV&UOK$E_W6XR+Er^iJ_d8cCqIVuDM+jHA$ z{Q3T|@7I$9#n!Peops;acG`%F3gjU$s;(@0KSnOO>Y-*E7|FK(1X=bdx9|Pfmguq? zz*il))i#rUV-lK)aO=fU=$&?bV7>T|^K+LBqB#Ey#3-Rz5Ncu{u-Xc{-25OT3tqWR zWIEb)uu$oi6N4*=zO6*}E`8oZ^zYL*$BCg6`h1RZtJaLRK`{(7b3XP(A5I)cDa}}7XadnaN$F=RedDnG5sy0GPL_Ryk<*UAez6)7 zg7Zl4Kcje&To(G+rySDnW2?orhoAhd3`URV8}}Gv7;kyLa=ub)@5j_!erisYYqhfn zo3L3k8Ef=`HA=mk=qtu83#RqLGtrNkqnlcHzj8kIkQdFscY>kr)7FcBM;8}ps;R$c zfZPkJ&>2z%LMrc$KD!^s8-Fe_Fj{d8F$fg177~hWz!A-Avzg;*4BB~MoRUpYoJcZz zL&@JPta*EvPKr)oQdA3zA-cG>uUR{LcTx_stC&n?-&DnE#Nc#dXfDO2vYV}%U&-L+ z8C_*ef0$gM1*Gzfvou?Rzja}1D(@58PF2eaAGE05L~kS6PUDz$aR{M(=yZWTK3{?Q@Ab-X^E04 zVF2Hr{eST#V!Rpw69-3 zLAJDXsgqabi=8bqq_|}Vxx^^OlPfd2MpNk7tio5hFYpzQpS>3v}ow%abh+~T8 z7LWuQemoLhitvzz`=&A+ol$BBY_?GK!JpC9;3POnG54simd$1eqH*RIGQFUej|H7Ys?+mL!aqM%Hy9QAsUr;79$MJ zC3@#+ZX?k@H`{+2!wIBD{%g{2Z$hO=NE>798W^S|?=5;m_OI4<|l{xFw7!1gs3d9SQIG z1vFX8a1T)KkClU8G-%xwD}0Dbo!$q386zXUtQEucah&k!l4L#I*7?lehksQ^Mt=pL zc?|y8-kACilzg0&d=V2TH@{%ZzWRk7GT|RcC1vAXzc1}~`FQ79##noLWJ0Bkl7@75_fAMB-Z2 zP9W#rzqO8de8YOm{t7yS1Er|wQ|{JXG3UdYXvan_S+iB4+S!KZ*ziHn+ zAx73G)RU2k4dg_J+9`>hn~4AGw`F-b%|Hv(SjI#dkLOV=s@5?htBDkK#_P|y#HEJq zl7!esl|TFmQBgrK!fwq4JB-qC|Fni2uJOj};!dgEh9}7)&h@c!z1)bQZzuPJk~1@! z$W?qk*|fTEtnoTeN8E2DT6d4sc;_*hup0UVDbSInQ|~4cOdOdPUGw*nzsd-EY!1C) z$b-#gbvrBXI6jfICaINdMAa{=(ePJC#~Sf+YOG;hI9{Xalbg01ZS|B&oaFB^lJS@v z7Z)QVcU$=#osE7sevk1r-hIt?Q}$R!vh&R^)?(@2p}uQ1HA4=!i;?5q>ct{&W{-i+ zc6P*p4UQ>sandu%&XcM)D^kRv*|j{$Zi2NjFsE26Pt|z&nfWP5-6&>Sa5VEggK{z$ z|DyKRw+o{Ge&1=GH26=|c+C3C*UsOWhdWrSW=6+Y1JBoZ$Pw6XSox(jYFf*_c0O2d z?{?&sdpzYNv_zrd=s^>?-61(GBcToaOQh5dFNH{hYbCvs<7H~DVLj}RemhF;EH^CY zts1o*GIOQX`5WioP0!cXC*{aanFFT}OxADLcuaB_ab`FG^?nS^Ih{wC+nTdH9q_DMkmyVX-uGh1{DiuS6p0H>x5Qy6J%OS zIJ+v}6e3Fk^U2{`ptWfUF|>l*mHAh0=ijT>NxYH-?hPb}ZP>g;^Cqzkn~#90J^H`{&h5nD4)WJwbFmxjinQ95KiXO2Qk@UE&O#Dni2pb& z`bZk%sr-2oGCg}J&3!=eXjrEhO?d9-SayHYX3P^<07e>$fR$r|DS1baTiJjf>c1`vY-$%MInH}2NU@$@Ee08LNq+_AA7UyiyEE9cW2%YJOw z^8Db9biF<%11fvST>%vHgCv=n@ga_^c1p56f;jAad|%r*Y)V+3&dx_a+?qj`QrnFq zz7^IvW#l|=bq%GEz<3Iw;F1hRav9=ROk{|A5^>oSgh$nhl^-*$?tLHpy{qtFG3Z+` zetwXi2G8xAy^Wgo?}l(>mE(P&b~q8Xzp4~xeuBXvDLoaII@hNcO)vz`xtB8QOHP*< zncW3re^vvznblZUccWVc-7I1KLzzBZ8p)C@s4|sGV%sV+&6S>nfN3~C-;Q*6z6yf= zB!HG3VGbs7M}r6Y<5)yU?Wrzxl{LL8Bumb9sVU`MYB!dnU8AH3>`^jyI%9zm-O66y zR(=_t1^b1oXD!-%jo2ED?-nH^^GL~^FT9zX)v99`J`_NalL(2Df>{jbku22_I$X@M z;{Ou;l4~ZoXU`_h@g<~TxD4ycD!>S|8$Q$xRv_FCH2$w;IjjP#i!do)%QjU7u9Eoi zwfK-E>u5Dx?1Q)HM$gdHwzpE_6i$QDo0#R_*PD9z+pYD9(t6W^%fkPLOL}9gW z3W!HT>EG9vRo~Z< zkv}w$>pwJ>71m!f;bc7V`~RPUJ@);7DcF+lYs!W1QLqR9jaGS!`c|(}OTTX<3x9}` zygLjD*9)ZN;P+KEsvP0-5e|M}0?PX?iGN-*JU`r1Ge-X>O4cf2xMIxwAzU$hkT=x* zEac@4!p-;hRE%>!*cD?MxaSWg&5@C$(TXwn-}h9E;{V)JF&6*3TE*D(Zx&~yV$4R4 zqhhT6cN>`x!AnL_BrC?Ae@h`=+#1IhKd%`79R8o5R}3$R4@{z3rFq2A4C2C>MDGS* zlzTsM640&~TmHj}vGYHu7#dQnSgPT6B?!8-68z<~3zgsixi1ANFLY96RO?NKxs7`| zo8bctxi3ZzxQ&q`qo00x_E6((WSUXGNrrYti1(&tG>CZBYjKz2ydfgCc_+I$pd;B-1cgc8s7;=3c_9Poou5~y7lLC z4?p9Q0YlK^H??OypLS;ICiOC!NnS6=Td%QH4=d_raF?zb&b?_y3@MlJnq#eEz7`!}qC(BYmk_4hWA-|?7C!Oxd56*4Bz8O!&b^VyfL z_O|mPtVSDIl8sRRS*ANiru9J~Ubo7>!-2SMmh)fef&zVv7!+7#rD-F>SZR8lV}5xr zXzc|e6;ja$1v!#6bI~x!xjhW3kymp28g;b}zMr0!(pX)t;;=N^7vW(CEzZ*x_BE=- z(w(uaFMEv<6kb9EW|%Qcj`YP!Iz2NnYtO*?9td||x3>Mu`Sb$?*-)W>KP&Vv=WEfY z`a_GkqY>=BLouH2)*IhDpK(=Wqb8$8Wa#x%m^e15v_pFJ1NF3?&w=`6U*hf&_=>gG zmeKvRwv_h=#h_fiWs_OZFVbX+N>LHnLN@g?*j}tb+EB(!MJnIMj#Xvrd@2Qry^8v7 zewdgMZU~$+w~?dP(eItfu5*mSH;U2k979~2M;to~kvcd64H}@T9AH(*5?m7qi->_y zb>7F(I-P)Hy#wA}+XkR8b`PlDO$^L6V(#q(Mlq-R&tZyGq2D5J+o%wXuQ^umd)$6; z<_G78&+leLMRTdkqy@y`YgMnU#A#ct_I~MwgRS@egFUSg zgN>TukY+Mwuu&`g!&VcAeFigGkcv@-vdzS9;GQ$sNKVDy1q|I*%+S33#2yETWk8N) zQa;#jiJx~&O6r7mik5n7MD?Z{g*R)gga2{9{M)e}BO_BRX5*BD)*FvqYM*d~3a#z)Jn%q2tGmnjSJN}pTIF&! zczX&3p3He30@+7S8>&@pHrei2WXKuCu%Fl9{9&vw9Ur(pjO?SViEgYYl@BwTq_#~@ z!g@Bw0GUQ1!^csGXAy)r7{T!2==QQ?^l**mq6HG~CX&ol9FNbu!yXF~mt7`q!dKp5 zP~ISp?f(LF3HQW})4Haz+dDhlXr}$ID>d>e#F&>1d;u&sh7d;%B?cxFcg~;?R-N1t zT6M-V3rd$U?CdJy6~#@5_r=ZXUxerSZD#Q}$=kw#TZv>qH8?R;qw zlXPwDi{KqQfmk&0MZ{;H5u(2NBduHAozMMtISEfrCP}ZY)~h|7tzAG1`7}&A5_uOUN>4xd-G^5?P(0`lTWdwKv>M)D>Ytsj2>fH-!6@Q;_>_m2;VuM z;X%dX1xn?f6aCn4W&0TIX9sLVub@_zV;9zxXlp;ae=Hb_3dmTnl(=>!8FhtdqxEHP z=a-Mp-U~JqUSyFdMy8L)?VM+8d}Qu6te_WnnFdR1{LV3Jf}z5n?7tf_eXA)gcO9hN z_qGi6BDsgGIenZ@J4P*q(w827<+l=8W;Do1z)e#U8erY-<9znryxD-X>ErR4z@8 zBQ~99g6zAn5c8zvMn-}R%5k8LY(o4n@2*W!YfX;KHR?;yhvD=>K6>DO^Nj}0WLg1? zz0{7dvFs+mLGMt3QHK|zRlPZ0VAPG}k+s2+pVOoua~VQy9;HHEz=Z zXW0_5Vv7ojk!z|E^FCI|?0dvG1dNz&S=r7;&*siH>cwJw;kmKQcD5lP<7XqId~*zI zNLF+cS8yIWb@b;=amr9v2FN}Yis#QV;NFiFRa-1NqyF;@SSVG5Y#n68UdLD+8^ITTQ*^F8tqc^+2iIo7Rh)KCN)yUwD4#`yqGA(uf zSMnfG%&szGn(=u;vlgGWio+7*WK(`uePKfePuEH!W2PIQBW|4e+B=hq%g`TDC_@(- zSbmo)3ylwC$0EbkGD&6?aww5~i^HaG#bU$WjF5{AO^`gcK|{;_IA#+Eaj1?b*1)D0uUKTf63TEJCt;(NsXBbss zOJ+bfA9Ta$9w2&mFd_CF;_wW#6117B=Cvfpnl`}su4B_oEYsGBe%&>UO?doHa{6|Y zGY-cLaW%+U6qz2w_>uC98IsVlRc5{L>NytFu)@rR>Z!AgU%qY0WzAW$$aVq znkA*QfpcSeTDR}y*eu$`U0iMHWRBn2a}1VVxWwv80h)!Qu3VahDifHE=5T&CGJ)2b z<_eZjht6iD;!pLoIlw|Xd6KbRDq(E(nZYPz9Yoer#Ok?5gVt$wEkIe!1&2$DOUdFs z!xVCG56;$p{xmB^uX7BW546K>*Wo(D0`=h~NGcSqOugKhV`#0Z>QFpr`pB-fnRXt+O94EwGHtF= zw>3PD+d$0Nc`%ll=5PYB?Uo^vkd3O+!nvx_vbj(xw~Q(+1d2-ociMr)4BV}O*%wt` zy%;erzCrU=wkpzVo>6@|>}Ks)23lmj;*HCE4vc&mm^+R*Xgo2XID6vD$gS$7_U`U^ zYJx`!wUpi2lu2aqPb0c#6UP>k@#l8MZl7Vj*~1zC%oTEtTujQ~Qc}j+nI*mR4J*D| z^rNP?z-m6&`Rv0xFeG5lvU`!CYj{?X^}}H2GjA78#1`zywPa=amE>z?t(gz8+qUp6 zO|9GcYHIc5+p?{U?9P5`$6)8W*;D-zm&L1arZe0 zbXetO4?l-~tH7{A7avZ!Pi_<#t*r8k55MMOx88>t8$3@gcNy{46{?Zg4PN57kx$Jy zj$wfU81O%Px0ksww}M=AZxF}dB6@EVN8cd^-LC-4dJ=sDh{ZWX_n24YpHO$~D=-!+ z2NK!&BhPLprpV?+M(Z@zTghK+G)jZpND6-}T5O()+Vu& zj%-X=4tlyD9*=(3HDNJG++LC-iFcvF71r)cQCr(%O;zSEG`RLD%NOHKxG-0vZ#=z_ zg24qPXA*Pf5UnTg#H5e7bMd0c5#bEhP~h+D;FV( zmNIdX5!*->@W-sud(+k=HhWwuX6 z<~SOCCsztF;C8`)=o*HR6Nlnu6thjA`Yy(x*)FM@3}k+X@~CxiCNVTipJx-@bM-l2 zaX!&gKnyHqv@GHsi;aezI^iip&QPJi3Wji{OwKPh!Yeo~xx`q4JINaYibH3NVlBbY zPET?fwqGuE_IF1+?PxMXD_p=rAiNjYChi-aRaM-_dt>~xgpR!Gi#>wfmWp2di zlwLG8)o$e}-#9rrz=7m#CUcMNU^cD)43B=&HECId4CXCHwXfqOn+EFmoupiNgp`dd zNAXR|s*U1%mmw{=C8WD?hUlOB+C9`2)v520dQ$#tpvx{Go$AJ+WbASyVrk^D36?D+ z(UBX(@x5OMx`z=xBVPv_cFk&G(+qrjmm7Gfg%|j7h@6F^Nmn?QbnFr@E!SC41;V0y zQxO)m7Kl+6J8SUl!IHLh;A)M>@87lp6M)NB`3UFB4=!I1;-Hf0T2nF9AfCWBAto-x)y?a>oh5GQS|eU>3+j{W=-_V58mDZ7WXtn9c9no zW~~C(><#34$#HXoVZBxu{pOQ}Fb%PZOzm4`#>mQ5*uG!0293fh!#vL%wc45u`{%*^ zbPMn*nN>3{`VY_TT5ZHP=bVe{#zz?%n9judW-zfUC+ zDOm@)z4NJO@d9F>AIZZR?kkpWRdm#=i`L^Zm1a0DsMiB_NlpH?OH!OvaL4`@XrrR~ zF9Zj!*#Spmmaun&Q3niIP{HgCM+`CBvNi0r`@cz=Pe~P!5f!y%QB~aUS0xQ(rqW`81Ox5csO3Ho{&zGn} z@pAI_T0wq$N?7HHaeX7o=FoO4b&NCqx8*yKiE2x%-eg!Yv!eg{zz9}{?3I>ttn&|! zewz*J?%f(s-7jl4v-&mM6#a*1mv1&Al`KZCZvnZ_&s5+5%of8c_c~v&;_lS=#cRpXtHTYoQ#e*UAiWC^TgQa$U5fel3WCgR#H#FGJH@m{J| zJ8V|jHfU9bj1CyqvTWxw55&kAOqZi9|IFz3YHZj+QwyaHKb5V_-?`Dvl8){$DxEJ* zv7m+@r2M=S#EO$dPYH2tDRIjgV(2_^^+n>9E5y@RiRZ5qeLoSsHx=(tPkUu$!Y+Gd z<)x{t{sq0?09DywqC58uP}$01B5?` z`Ho}g9$j0hX*HkdeDLwyJs@5|6c|qau?o}=R%wPAv-16hEWg6?( z!r7!g;v*I;AdlTFf*^k}1?BVHB_ukQ~c0*5_^%q=!r*;f{P(Dc*R zvbvaabzLsk-$itWu`Q_&nz}%+usA%_Yw)&Wj3xfV6zV-f^eg&;`sSFvInLBnI(6Zy zLHj^MpZ!Mo0>a9$#H`e>3+a?JBeQnL5I{n4PYJl6$XLE7=MOLcGmyh zYMzCl%Ti<6cEGS2?vMU!P1$f9FFB$eIiMq2$q{5(=p=NAs+-XnS24nn!X9pW5 zxQc6hZ{l>tswvKPj>|{T<$@lr$0QB!7%&?dXmvc!d!!nDAxsY`IO5~E;FVB+zags7}MFFa4YRYSWeJH z%N}U-*RP`=cOBGpD4YAVMycpi3;@x7ac=`+?R6A$)&57BY<r|( zR17J4uaE~`_NNTpKkmWw`tTAg*OcWt32-&GUeJpeXP!3d(%l@6C0TNSfgb@@q>pFE*8E$&D^W%l3{#B(Sm}9#dpFQi5!k z%O%J>9DFa~fSe%NBn(X^34CQs?U#d;dkVP&ir#5_4o%lLt{I?q;EKLP#J{@KXpbE_ zJRLj(PtC9+?GUa?dS-$SuCTq%M3OM+j8RuVSOD@oB&TB%Km3CqbI)Lf8&f0tp|TMr zNbj>yGaSdrGe$FbAk&4}0{%cZ#gL+V7L$OjYEHnBTieeVDaq`8ApICQJs<$i z(S#^^=8+{uZJq?Vd&aO`f7{3aT~qZbeZEFCU%3_oqdaR}VEYZu@0>N-;GEw#$#USF z(H37K!N-e&=Zto|4Ix=hpEK~81Dw;9^XHH+xIsBtE}b(vOT{^?;lc|5x8{lF*7&asNEWyAp>&waLaW!iTN`py|$+iG?u%RQ}Zic$os}x1g27Ola6e*Wt2#8$Ns~mEmUTRw;W$1Zu`!@ zHt7$bIJ$)syz0DxII)75EW0ikUFFUNa&0C_qMe|U^O20WP?_?}lpO#S8m3}M(Y=Ly zI7JZ=IqL#af&(+i(-ar3WkC$s8_=K0vJz*7qC8|Vt6pve2~M~2qaMk!j}!q=fNm=> zpy=5~o=U0;>Ro&h8U-2f>qOaHhKoFZDvK2A=!+~=7faQ9F_M^R7g4ISFGfmr%f(2k z?z|YOAcI+|o#bGdVdumM{?-z@P-d6VM;0oy< z-wsWPqGu<0>~ix%0WG)nWC2Lc?pja3RlMurT`MjPT)s#sOSP1u8xH2&z?Rkk2F6HoCUca{8d2=LgPa+Q1w z`dafA;n@A^Y;t7CfN~=&r)$>~SyYbp&kJ2`_NnCtZ*!&8S(N03B;<#pUok-0RfYO#Q%)&C@W6j6q^u0=i-{fn47`Lu_g>98AQsmXaEWUE zC9Hb2&%`==gGz7f-BAAiWZ7}a_*(WMTn=C2y{?yxZgS?5@ts_xB(F;HDf$(Iitc@g z!4>7jALIbM$}byT*uU;v z21$SlMFsa+!8!Q&Tiz8T75CRL!j>N=%b}~F2q}fTm^A=Pf$AZWLT70?bwwA%up0>_ zQsmqfOouyv7x`IE4Lo2&ZYcT`{S;)@f-7}fJtu&UP2(DM)rfgcCNRc;l7NC|&)FEL zzihf{4Veoa10)HmtdOF6zm^)1nb`LlGI8lOWa5cyoVQ;`CLX#T$;2O7CnA|R=~`7L zc9ntGnTcInqUQd1&4}-sf^D+vh{{XBre5?pd$M$C1rwVB+sE5k953H%q(9j0(-tC+ATqzlsVd1{FgGtY>}B7hM-Cj2IkTEb5Jmeb4nl zx9UEq@u5=o{z+TUfS)Mf2sH`-u_c)T@7T#d8HxNmgP}nU4QXg6y%=c?g7sk|!`(+U zJwS1UzlI@KelpPRT;XF51A3Zu+p;<|k;B@XhKKQ-8%Evs7+$duv+D*Vc_{|0L_gj*=O(ocs|9GTbM$UMPAMeTsg?fMQTFr071W^om|ZpQ2wepcn+gA-GOLEQZQ9 zkKnt!1?4u-Fq>IDoM-$D@|~i3ejtX}&+&D|j99q{ zikKgrPqrRK*&LMb_Rz^*@1EcYC`XVS*nghV6aJ7BdYSbw2gfNf(C#CXR(t$d*3*p4 zHa7R7Ca0|NKf-I%-7EaQlkDhaCU-@|ak#>!m)SY|c!t#O)0%gu-!!<(Ka8)u`pOT4 zZBm&HaNp}?qJOAV;xK6gN*h!RDY{EkAU+L-MEmrIU(sD^eZ0{5lw);otW7O+KKASB zeNA++`}&&Jwk^?*J-9F%@_eOCv(*COjuCxqLdTp4$ug4oZIbTmqaXd>Q+*&JA9qZI=y7=Xq#w<~AVQFVitaO{g1;Kp zN$!!cqpykY1QZMgy+>&>{tHpxY&HA-Si}guO7=@FXJ%8<$ib#nvA|gq=X=vRQM@Oz zKK-y4*)6HvJuiDwSKnFacV9c%)6c{V;-2v9{mits_sY55&rEESfs2O+nK(R;qk=7Q zW($Kg$MYbK*`&5l+#{pBpP8K1374J^GOf1?;M|GSp3@>#3;~flRa2M6{msg$3vM`~ zhVJvE_ba+D=(D1`jL!j}+Joo%o3ID341|eUUnUGdOU%wO>!l{rM=MTNe5>BKV*yQMh%in(c=oF9O< zZ6!FsR0B><4`3-?q&{9C!)`^wdgL&dHOznUO&0<=8G|7@AqR)-aSJBe-q9SDH$TU; zqm6GXg=7mTn=hmy+sdv;cv}$cCxO3QYraJf{G!f<6H?2V{-@NE@wP(UT}cz(tVp! zbY7(|w+%LRUIp6S+HMh!lq>oa{p5&h5IIyQ{XJ&$@a1YA4>F+e$q^VQ29JdBp!-iC zttUlzUr(-htQVI$pL%%O5cBQIHR3qgH{5zSoZX9PlR)?8NP{4 z29Gima0xh0+{uvM4!v#TKyL>x^{D9WfK}G^8&oeWi6T62gqcaYL3*D@pFbI>7`()P z_#lTog41{Rp8M-L(%P{cljMFQ&3g{<%o=G{KQWk(?3YmAx3I$H9rzZqC+tT&b)*?S zhO?X$Q^$~Ej^h6DeD+@>1{c&vig0WR_UK465np%^w`Jl34ZJ{dbgqe;dtg3FAQwf} zrb`E$gfP=GaC4t5;pGUW8{UD8^FVRny0u)({7&xlc67u3r$g@RoQ%Z#Et9soOc`V1 z^t;T&_e$`+qqTkT=>S}_aI6n^?8*NC?zMx1Nk)cbBUXH?q!V33a!nU5s?0TUHa|I~ zgA5pr1qR81C}!|(Jh~WXBV$l!o z_8Wz-w;Ue`{v8?|R17J)w^Jg%IiG>OR^;TWQMj>!@tqFWmh+6ayHa7Z1h*9)8)c?Q z$tbgPawp`NOL{PAJ(Qq>W@YcuSnb3mA}As6Ak;B@OJ^EsdV&VNaY~8VfaHy)&yHBV z9r(5^&IEy^x03Wx?5jAOBd>O#`urG7LL$ej^{BEH^y5_g zw6Rvm5A$dASkrpZ35OmpF1~?j4k1zJ6)S-95Zsw$&N#ERjGw6IOK}$0STkoT zwoZy17Lt-aj#m8s7=v;4uSsK>)!)%v^7}iQCZ=~ts(QVvzoU`t@9+3LwOxiB8Q^Gv ziz8h-a~#}9`yh!QuMdCM*^hSqP@}|laIJ%792jqYn)*#D4idK-ahvzm<6N!5=6zn8JC!>S0op;k^%m4>mmr&a4a~!x>6#9PX z(&cm6G#+l}WET4pEbdZZz9$t5POv@ zRhCaMGbA(-Bxvu+?QG{n**yW*waHPclBa}ea&Cf|*%8O?(xe<7%kT(eda}J{rVRI* zjZn)z>5_)Zyo^dHtr@yv(2|yuYN|EJF${-&rhH%yXgXH2}wPtG(+1o)m1o|Khf;k zOm6l^Tks+Tsnl>(9k}6}bTF3b1JLt)uHSAxIT5#|(Y%$j^mFtnM}czi3VjOgpm1&b%kKh0+`^o470i4S|jEie38z!60 zTC{G`qPc9HY&LHwyHtCXuuRdfSfm(GELIFEmMVsTa(@GS`(5TtLEe{7F>A>&*EI8O zzM40~tcSlmk0dJ>n)Uf^TA?WqG;G_p3p~QkO)(qs9C@6~&x4yeKf)^$W|{BH;6i@% zLvF4zTkvMoIO)I6n1l|0>Lj;kpsT`* zsk6+Q{Cjz}sv>LiZ8hf3H&yHHKUyo-VRh%`2KeJQ?1WtzI=aWn!D(hAev!xib$tf9 zjH@%zSosz35d~(P3|nZz-wuB9SksD=vO+pp%Y-H9N)9YHdB9yOQJlQ>@^sMQ20wyeA z%J40Y0_)+E_(tE@?+}msuw%QNk3#-r2?C&fDB!XNOr9>n^j)p zEhp9>b37>Q%6JDNUOT5`_ZE0gcC(YA+&i z=!l35Q6@P^k2d9Eq=^;DzZtiZW2aW~m#+GH+F7k@iH9cFE{S&(S#PTSP4-cLz4%m+eBIiBXJs5GNOBn)qfb zj*ZSWVR6_Mr)L48_cP)M9SUpIEM5W~E}ILpZLV?iKv|@U21?k)Leq1z%*OJgowN4k ze`gkHMLe?@wJuYpFgsVU?MTTcvsFiYT#hDDr#Sd$V;H`%1p@$^o%BQ`_y!r~?f4qE za_?>J;mLoGhMgx->h`tY<^3V$~|BSgIHT+M>gShYPRlbW~Qc zN%O&3q15hDP(NrN{x~&BMq#U{Ir z6sL8zOJc_c6URAeYYp0h($;I8<7BngBDoSk+G|Vs9J7v`nPkp`u^0NHG8u2UN1=QH>q*sK#MRD%OzNt}aqA&#co$`sJgfGrqyH zLq6&nHQEdo{MiQ{{5e6Upb9Nj3@KJ9y3ZgQro5kzDPQSh$~XHMxhl>$cF_6k4c4 z{fb430mWjVnD$sVqaa+N=CNxEGTbGlO2F2y)=HdgDc}VgGU!Lpgj7a_l7!ptJ$h#U z2rDT$4d2d!4@gx$?b`re_c<1PMCtq=;S|T=B~BKBDcl{cd6uzcgA&D|n5*bj%(HfE z#i8_p3)%4Pc*AFHWSy3*{aFKEaZ9;n0m$-!6YE!YACGD_XCduiz3g)PB7_&JWWQpO zVnDGNh@z5x3t{GrT7(=%t2wn0t;Qt-$rub1TqnUCC4l(8i?nkXMv77u6jH2EYKWK) z0&EvE+mXYSK^V~2@8f4-q4^=QD^7MVM-rjg`a-tXgdm zFID*=#R^6D1tupoYy{*i#>An|5-?=j@2hIVbOy~?j35t#q8g>8p+cu3g2OaUGLt&s zhH7NW68n~0{o!7wEqBo}6IC=9Y{&?eYu_UQa`%$(xW9&x=4qgPTvSFaK|Sv*M;YML zfcG)4$^_G*B{Z60(c_*vh?YW?>{l8leAg09_PJK#%oKotZ&&gJ(hx| zSZRWar79)TQOVS$b}fe{l)P#w^BWI8F4ZWOl0Br16^iaMW+&SPIJ(HmrDphBGcL?Q zFfd{Ys*G2bYWI1A{Mq0^t}5m#f4Bo|m6_bRGZuwqmgZwshg3-0(yI)4WN^f#X336a z=u%VV0(u+vd~q^TD+iQ4w#-a+;cp2u%cpFG%EtWYwOkX_f4S=Ap=7_36e$K2i%G6@ zvu(Qhqzh_TX=PZE9kz{OAq}fgbYH}j3zbYxFUPEQIy*j?YWAq;UV$ks%>m@jn3Z76 z1~GC22o9d`@vbm4Y};W2IHS-Sm#ko&KL<_>{Rl<>P0n2M+{ZMX0s;P%g93kUgCy%^ zjG-&dE*MG&lh&)TBg>{*4>VNR-Mob>dYsY#W^IGJJ4e2AZ#Z}CH($Cgpau+VS1uz1h}y8@ZXncv09 zcI81T)`JOuc9JVskTmS$)b{XiG&!U&HwNuR(}kLy9y6@5ZlTdF)_Fpq!RF)+S-4$kH{~KI_JkL?_3% zSgAIH7=QMWx>)4|6-yOEDif(I*VwL;szO8Dmoz7UYS2wy%RJbxS(7W) zHU^8wCPO^iK!-mEK!`u5L5V+?K#D(i)`B!w<$4wK6n#Le)gIPlco1f^#)a8kI$$U% zT&EqA99)M93k@tgWg|Guhrol zd6nG%hfn*WD+jCGVz|!^!Ad2)SY_}A6Q5<>;6g~Da{3jE6az{dF+o;t;6x0?jFM$q ziMj>C zZ|)Yc&Hx$S`3O8IZ>pz=1zMfdzkhZ`2$gLZ&>8%%@nW=vOQvx+I`K zihqI`;lihji+#eiZl5bLn* zngR3SSY${Ya! z{+tB`{#*kI{`BBtM1@LnU(@6(dK7aNy^4867e1LmMbXg5Zeetj$X=)f{z%9|4OzyJ zA`J;B7Apo7OMwyVzjRC3`Y$6#NW&`>-PcKl0dvq+nEt65of2J`Sx>?W2Nnq6sUefL z+LnQg!h8mIg%xf^zqxQ5YTNkjsBL{P;bzY4+)7o4Zez}j-^QGoxeZZeQ#`oJ$hDVT zE66oXxsWwE+cdv&!G~W{z?g-e+(%XqSkX}_dKL2&eTs#OexODlfSfqFq0#r*&gh43 zXWrQ15Pijt$h}=7$^i*p=8`6$l8P0BKrHi?>W`3Og`&G698uwRMsyrvFzL{UZcs?J z9S)g?K!87`c0^a~h}@i>ctC>$DS8$26n%fD!EtFh$2HqzGh4 zF=AK?0{jVpg3*G6(UK;hqKXxRilvGn#R^6DPg)UyG+b~FW{28=n|7G6WLT0r81af7 z)Vq=>&n7y#gIz6RD+Ce#tR+>h3iK-Gk;8#R9N9rx-FH&fz@4D-*&$%Mi6H#xi!l5d z#=t^lcKP*Bkzzox7--L>XYACu^gNOWHLO&_s;u0=P7~i{v8VBUSdF2;5mn#@nLbFJy>UL-I7lUhdts%Z%LT zDhqfGV=Xzk3w8hSZq)sCd+Zi-MFnN4VOA3pJ1L)g^y>M%#VAz3zYfu)RgG{3yDcp2to3 zjjpa2>@#&=;o3fXKVjH@d-Z+r0ekyk!vTByVB?IK?&2Pr4+f!x~?E9v3}gzKK+2-$K?rSZ#~pW-+F+ zmD>t^_uKPe+aMXZ-(IA(=RDR?d!bgwxsF!f#V9!%SxTq5{?k!iBHMM0QBF9;$#7(F zE4_eC#$#LKz;5s`BH2;|9{pjmAj06lc+X~;^cl3jK| zcP8wpP|khIn5=KGjm9G+s6v;}606&f{SK(zXq$z-gRB*dJgRa*ch!Na72`!mu~QnJ zk6bv4RpN|r&K90$O;N2eoo^iuQwfn3Vy0^KUAnL#g%Q7)6^)x8l{*yWM@&6%udLuaa1qk8AssD%afB_SAxFb2rI)E$u7-Q7h37}Y z?;hn0Ax{Z>N)Xwe+5?jC_S8WNDO4K2Vv%A%u~;#vSgIHTYMmMuWSttt_E4cA?mG;L zWbUFM7nWSQowJ-o*$75Dlqge*gJx=Yg>#fW^4fC5ksFpi=NJ}a&Z7(bNBBlp5NtSRYEvZfCN@T|DwL-zk(IY|$0}Fe zt{(%9PiYDj{fb4T|CMg(UOKKjp|yIRvisserFI`(T59**C(qb@`Oq?Z=k!9E_T5;a zwpPuk@hJMHp&{%=bKmx-6OkrZX%S39D?|;pm%Ht~Rpiv!lBy0hd@T;DlJ~6cVJ7R< zkd?HT&qn&j7M-hTbIQW))@3)`I?cPt@Cs}u-P0$OoV0Tbx29vavI{fKdm&u7;r$7` zx^W>qV{Dq%RzKU0{i1SuB(N*x=UmYj!Za(tx2LEmXRk!-`T9xi7H*$JRtGeDiHqaS2s>aV#QiEM~skiR4BUdvgTz2wNHrbNhAI% zr*MhUX|^(t(&Q?7m69FI$kVn}bs7|T8s=k|c0P;ku$8B^^SQ;&r%*}!l_ZC3lH)eX zHIfu5Nq~qu%=AZ4u~abxlql}|hxa^}l<0lt;X!1@c{mE<8#wk3#k32iKH*C0L|EP& zv@6JuP6ZAR`~(Gk@#SQRO@^9fQywCv+x^mCnJaFhM=@8?tC*+gvz#T)Cq7tN${x&) zwsP2DmAG;NJu&;3AtKLnZhXi-2%Ze_>jUN26rv8Podqn)C7dFslif z)?is|01yo3}VvwS!4sY+aFQBgK9!8%vtx%r_*|gkc zU@TRZ5LxcyCcie2NS}YgjI4dn zu@?-ULuA=}7p0BERV8!NIqm%e=OY%4y&5AYuH4fo>d4OP+A7fNLnQL{G-Y}q6WyAk zSL0+&NakX4pY!47i0An%(L9DbG#rcZcv+2L_JykU5oCS$smMY_zhaSM04Pyf-R!1M zRYpfzeq|Lpj)t#xO4v-p+D0hc8ME2{O56RWa?^U}jPr?l$TfZc-$A|03AZ&mJ+ZvP1x$F0MvKJH+d4wY3ejU`qYQ!Z-pl!UAyXR*(j$081a0w!PzJS>wUL-DqY_Rw5uChle~R;Vm@PgP&h z17rilX{(NKcEBLsCj{5`usMLAIU(@nDy>&BPtm7*@Mc{UGE=M7I+WvF8hCbY+bPFl zQzWw^wnTuVU!h9!t0db7Nor?n0VzYuBNec!uii_pBIOF$@rRSgLNBajkVB*^R=S{K zsbWa6Lebre>E>khe7RQNOXSJMTU26(>E6`fOG+~0l9G&J+2<;WS253}e|(CCihji+ z#Q+e8vXd&yVE-k!?dmMy@MUVqaID~5y@V-Mzss1Dj=TK-wRhifPE_xs$7gBMlq}6v zPy%0-6;T&dL`4aT2o?;A3Kj?gx=RgBuza0>D2s{&1+z$t*wzL?QNS8SWmS}5T`REw zYY&35Nbx>ro==v|dHwGF=l*g3xqiPh%lkRcDRX8flOB0Yx@xC6vE&ilPIIchZ8Fnd zOA?EDvG+}^N@Dti!qZ-Gvk7LhiLZOH&rPgaVsgyh>FsoH9re~_vNqlC+-bVsCEYLE zCEdTUi}U)9UEWr|zf0PPn_O=$7HV{}iI%%H;guTx+=LS~(YH1Fjft+6sN9|R))n45 z<*h4aEf31guGlYwFn{m(tInzB@9e*%UfRp5^41xXTgSWsQ(n)PX_YUK@E!vFB`*+6 z9yv`9H|Hl)t=go@)@}Mrwr=&`vUPXtmaVIjdk*I5lOO&vQ@r?Z^T@gUZ^@6`rTPDw z{0~k35|jU`moH;^@NaMPmrD)N3R9)@F4sF;q^2Af-a78B%S~IRmkOyQFN}G;>^4;r zS|TlM)%JF1d;3k{TCZ}_TUU7Nl((*wwbxv1w`uOB-Dc45?3NnC(^@3$t(G$RlaxaF zv(c1Rd9^d%y4qVuW_bsJtmSL_oNsr_`%fO1TcdjBT`>E0d%Jr^wbwvtwfEZGebseh zumA2l!u8%L^K`YBvPx6dRGSU*T3qX`lcwGJrmgR*y%$Ml?D58Q-yW~p{5@XF@jX(l z!larR+E;BhsZ@&OX4{mcyrwF>b=q53$@&sGv^`XN>qx1oZhCrE+g`72ziV13l|ng* zy>*$lj+=D(IL91$Uu-w$gZVIlJ)A7=by7Cs< z<=VmyuZ2al@bCVd7QU)!p{1Ouduv))6Ok4okHmIld@bu#?e#tfaqR2+az6)g$Nrqp zLCoDRTat`rGQL)I*6q`ePP}bZnVnl@fvJ*t!fbrJ+^kbi%Am6!p!jZI_5&1mnzyz` zo~l*0zozWpnNpUPGRb(_tTWy^{*3&)u6&tvEVaYey6uMj|HB7c?m1xIYP{0RIDOCm zl=1Mt<{icfX{uh*TW4f_@B=U(9^eBo-b@c-?@0$FS$^RcdFtd6_-RuWuUY%Lj`@oFs^PQ$j`hQbpjpXqeoAp$YJ8gTs zQ9b8vk+;rx>-h5~lJ?f|)n2K$PV2v4Fpx8#n?XB0!+B`2h z_b&NLw*~T97P%>s#$NFXlN3H<3ZFBDubINPUh!`HD!iPH-a6&2KQZ}rri_)h{eN{= z-Rp-9`?yk$5pK3pWWlA2nI~M-icSM7Qch}{yZ)WaW>DcEAQ^t z%Z_|xfhw;8D{o+J0IL#_j>OGBLA3g7~SP1R(pxVCJrnaD=!!Sb$4BP4ruhH z!o@Rm0Rm~XjUU`mowBDenDI_0e^z1)M}sIL3O`RJay zHQ!S$-=*z(UzqMxnl{p2y((`VdEK;^^w#mD=~ccHRi1>rx2`-Mm5ZVob>t0`6PLBz zHxC!rd}-fypM>@4M<1wxwOojx)UcmYfUUFG1*>SKj77hnRTShtfSsK?yVEv zI_a%b-a75AGqQH1pSMgu@0m6`M;3aOy>;AMC%kpiTc^BrTGoz}=&@$eKfZ1C7d(~U ze!<82$1Iqhe`>5wUVgZ9z!#AJf&riW7mDV||9w8c&mW59CFOsCU?eY+CpN`C`CXU% zcJj^-$NA^mru7{sFCzau)L8zTEK8pEFBzu%jmuIO*W<6$@&0ST-$VG@Q2uU|r4eo{ ze|O2!M4FWExOJM!-=Oqqie345q}P#Y#B}77uR%F!n0XJ_N#=8e^63KUnB@OMZm)3o zBKa)O!Ccdk*??@jO~%OAvfnIc%Gi_6m2kFA*E!WnEI2p-@6a%nWbh^49j?Yq$QM-<>NHM1qCQln%gm}EzpCZ>=#^*i1Gwln zEnkd-_%R&CD{um@5xZu@MZaqU$!r7oT^zmeSO*grore64Wvm(B-Dq>&4<_Fhw5%PfUm#Xqpa! z1Wv{wd@T;+i8z91<0yUw$MF`Nzg377-#Tf*h%RYeuI6urQGV9 z_Wbx-9L9@qMD04?5Qvg+v|KJs195yMPT+fR5?A6BK20t&rhXdVi!*p9b~c+0ko|wW zT!>5spBHeP%dj6$!A1B{9KavoAU=RY_;|Udn)bu^Y#cE*x5EPnL`fKl<9Gs2;Q!(z zz89zP3S+r(+IT>B(FeE+|BUPX+t@L;Kh9qSf^vyA?tGXwFbH37ET>|mk(S?r(|9os zHP-UCa1w7ZmIE(7L(A(mQzvIMakWDN4QFb?1l%1zjDz@N9K!W{+F%%;gs0&UF2|4J z{-{=%fz$X=Tr^M1H{t-^iGw&_?n0S^F8YAxUxX8Qf@eAZ zG6e1;p&Gx4{SRsb+i+jp^Kk888omuz;b*Wfru9>}5+5)&xBvb!O*pMU8!W;lIF9Gw zOHTkHc|%6LwzIa``=3uf3W0ZJbog`?a$b)@X%2*pKttX#;V56i(nXa1vjE z(|9Tlt<&}w;go0Bab6|hyrc=gVIOYe*8#M{=i>+-hm&|Nc3#%@p2mmZ4Y;?l^zVE| z;1UwH;w$kk9KvwqHoSnRy29bS+vPpSvvWc{Yz#cDYAPim^y|B2ZOyENfW?2~&U zc{0E_F1ta!8ZX8ha2$VylQ(Mq&si^1@5%b}L-qQ9NC(}bK&2++<2Y`EoiDWfSRBHq z7|Z=g|Cd_c2N!&$9*jreF*x~^s|hZFUrD$h_x)PSAH$F0wb=Pa%iqTV{4I{)9e5Y6 z_a7Z_(EU~$IGn&UxD#%NoH-{0T0@+i^ACi;ww7>o@DD?G@}({|A@h(>u!kHv@3?Yr;h&gz!+j5l_O^ zcoweruhw6L3!THf8CZ!6^3>~bZ~VDSpluyZ_ysqutNs^v#w`kUz>Di^c}MI!Onnx9 z249LdHq-Lq_*$RZol2mLz&*G!UlW$$KnwNrxOXe{27E963a`a~;KJ6LU-x+3fPre) zIot%~!G|-vnKxw}un+gdMR+6*;z>A!7vL~9KR#vJkKiwG)U)jW{R9#uwC|)9Qn(L} z-lzi{g5!7wPGb2DA>E*2&0m9kSbjFgD=#!Q*MIpr5HAoS;g}QD5qu$z<3Tu$|BIax z?eJmj$LnzbZ^a?8_xS&i&U8pZ6FhB_Hh3g^w3*5L~LKHi4E!Jpox`G4S!vs?}2ov0m# zXQ^A@H}SD}YpIr>jw7?x{csvzgA4A~^07F9Be<`7k0v}opd3Gr(|8@8cCY5Yj~~I` z;LZ3CoHs}F^G?zY?1EchcNl?V3EYcM$7^su{0F`UA8{XBh~qed2SwRHyaYe4cAeJq zG+`YH1^9hqdB~l_yK(wK%|Gg7DVImQfyL_XSuar!HI@goVZ0PaaK^Koe`N%E7ion! zz7m(?IXHonIEi=QN?h1Q+xr?va2hW#c4dTB1eTMK!7GjB(NK94pLfgn4t@rIj}v%5 zUX73HD)rmR!+YEdC-E?`YZ595%pf6!=io}b3a9aAT!sHMmizXZraIvL*f~_)@Dwfg z;TETu>%Ua+6F7~8B78Lt;M;HzFU2AJ6b|FnID(VnW^&IYFil^=`S29k|0a;UM+0Aw zkaE>O;^ZWC1{d9|-jDs0)eTS8_7ZohTVRL!?Tr2MwHVVcb8W989P+?8;7^+i%iW=lL5)dBD>Tl;3(yN zaj==TcRh{|QIE;if7V#`|1AVU&uPN_*#?5@eX2{>yK!=y~bw6L|oF zefVe`-KYa7#PJ8z-Ei_n^#wRwp&p1+i`64=q@SxJo|a9xR1->Zkb`nQ_C2KK4`=cf%1Ip{JGg9=kPO%?HHt72Uy5rC=^%Xe$ zrFx?0h~sS2_U@oO`nSGc;9=~h<*m@>Jb#^lbDjFLtl!p`&ws{|)AZHyL(b3vCVOdz z?QnRX=J&wCkd_bj9C3mSXq4J@g1dEuw^BiV_R$;Aec0b!JA6J{eztlOmXDfy`Dq+{ zTm2`Fbk_Fjo~Z+lXSBSfXF2}@S8GBS64D=Q2N&SvWcBcD`BiEc2X53Yeh?SQ8w1RY zKA+8hOUo;;^8f=jHs@b-k0$IOA-FKlYoN(lI^uLsy)Ey6qnGQHo`e0Ls;|bGM%vyO zoam$jo|!FwM)MbmUGJt;juSKD=SYaAw1Ky>W(vR0HXv^oGWmOPD5Nvkwg<<=KU)7( z9Pg+bG!Q3RYx%?;a{KFDk3ZK2?k2$*pdGBhk>k}#9N?h**mK13aZqi=DZCp8{?=ox z(b+n{%rn|wq4~Z7Gl0}E9bq35!gp%I2pqgh%V%K!WG#P4hUD?L8~K@CmPHMKf;l&y2U$j;6g1g=&AW(c`t>@KOGlw%v_jtA05yr?E6*o zr{m~0&+;8<1S0Iy6*w(FM{7EG3unI22EOzhaUAyPZ#apafDSMRw2C{C@_0lV1Y4$Fe9W4h1R;dDzK&<9z+EAPL_lLo&h zP$U84-8fRAZh9^Q?5#c#r}A}*FU6TlbqmL3^DkD@&Kmft${4${zD>ekr#UOPA$M>^>MFT%mKT0RfrPvz$@m5A z$M4~Eo?foEXY=c;>-E+Fh8=Z#oNk~#CtEJ>-!bi9j^i%pVKD)xktWQ*!6v#77vc0) zy+5!9rv~eQD{(TY-h-Xax{HoHUkB*pV$>D8VNNsoyrnnAiTiYl#^LZ>^*kK^UK@NG z`?)211E;HX0AJ(4MyA83P{u>VqRa0hl)sgLYSg9p?- zaB`~FABKI*$kc5430nSuu{r;en>DbKgv3Yc_izz+NVehtxAk@V=>WnULr3BO`}kDs zFVwR}zHr~$#qo~n(PHoVA1%;37_&%7J*)#*m2IH0mVbbKozy>N%?(#Qxkh-G^DA^l z$KVjWc5vt=I)DgUd?F6v{y5rHXJ`yg)84%}DL++i&Kh?GfrR|x zxe08>4pX=jM~~MTYTch1!>8luaaw*gPCu)6xo*RL4!SrFrM>>-+c*d$n35_S*`fn# zb}1dcuUm8$j=rb95r+>|Pr*gpLzJV#vGxe?q|7uKO^JL%*wk&6d~K4));8f7A^J zu!~Mn`*Gqp9dLJ?Ia+;j)~D9h>;F&!zFvC662{K?>gid_y=Ait7T{FAUJakXiOyR7 zO4eL0H+q&C_+Dq=d&;9bK0y~ zE#Ip%a;xWvJOI!El;WUEU;%*uBV3N7l&{1o{0jE{s13f0i|}VSjDNrhoWX8}!2WCl zTeZQ)@*uNWUc?FG!?DA|^{~_v-THgQDR0HElh__PT zj-!XG=irQdAF+G)tT(A|!2$1v$8qKuoAWO?Ob?RfB*f?Ih+o0!n0gaV zaYOShPAt#?|K>U3q}XNu;50tu3f-j{+*0ga|1+F6ok$3B5T4;#o{qaxw_q@iT(76u z80-vHyV&1dPumqZ8PWl+$I(gZZ?53_9~rC-93UZGq6r08>MqHQQFq0@&o%!t9DZE~ zI1-0W(HV2G!xIp5vh{h!Wzm(g|IPJ3bD>sPK|=g8^&6fePLwAUHsLrorQhHT{w3?b z^eozg6Znv;bQcs=X?ZJ`K$yTWIDxz36b@kDPuf6#9K_e+C?1uqkEdnp|E%@hy9gvW zDCT>XXT^@vqx@0qEY@AH8b^ERKCZy2hB{+E;7FD3qWw7ZmHO~OI^)h`UcT#eB9P>H zyR&d2PY2Ks`?(>xDx1&k#>i|rH$;Goil^h~Md}4O zaiRK2?1Z#~^`7OXnti$%NAMQ%!(2W8qC6_6xp)1qf3+T!3C{bLo*l>klFmq19RFIk z_yX)ZU$^KQ98Br}C*ibBoJi8uhBq~r_DCucuO5n8V9O% z2KHe;7oR5A=q^Z5|41C=x!>b)`gHj=I{O!>?yT7M2+zEJ%V zuIAHjDZKb`E#IbgoyQ66Gl8~p(8cxj`=f{G2*v6sQN`hDWNW<1@Ka`JAz7@DXkBGwjHx zRm?8^hy0^|(it2e$BMaKn7>5_w7}Sv1_D%giG-tn(uALkr994zG#jQ4k>AZ&8tnFm z=HG~CY**ipn==Ei<9~K(`5v+BO*st~$aUQ~LWidt=gBVlk%JINE46`H_>OTGmw{EGf#$Qt2ZaCNfhyJ4pgNJK}70uOm;>9i0&*Bj@_%2@EO3Sz6>yJ{`8=>vp zO?&_G9C50;Yx(&jT$2!SA_PWJp~9~TQ7SB_d^P2VbGg3S3+*;iM`;Sv! zgCh*+HrES8oVVy;0j|U=s8Bpm8~7Bz)l>a1&QRXz1|7iF1GKythsiI)4`TPNY{EHO zVFzv=Q0L#M4HjOY?t+J0q#lU7(%@9jGR0%Gd z$KvjI2JVYvxD-Ew%kUex+*tZ|J|nP}gkNw4c1Gy{r%llTw!)RT6OP=Zgb)YOUpRuBjMFXfaS*k~etbGE!k6Fx9*%?f zX3ui|72Ki+)m#$FI7lAD<@ja17H`HCcss7d`*G2Kwfz?3IcD(j#;)A(1PSycA%w5M zVLTc~@N^u-F&xLw;sjogllTjKeL1atkD0jwo(9SMGp z(#g0G--D;&<#;V#m(Ay(PT^9#9arH4E`e$SttPSs9QB>?AbcK9;H&XkJOL+hDXzfF za0B;ih6g%{%p*MjG9 zcMi(;a9{i#9)zoLF>W?l2RzNQ?Em8kl#*~ZF2e(HIUbGI;u*LCFT|BNfvfO_Y`Hm0 z%=Lc@fi$Ph?ra6zY>JLJgF9kB2W?Lr##iA|JRV2!EF2em*Z*Y%ia3hb;O=-6F2~z( z1^ySO@ZmS6Ffx&F5!P)5S(xEx=K*Ww#-1$J>Ij^Qf25?A95xS`9%Vhe!+yc-wdX47=U-El|U z7x%=2@Kv}NkH^#SEbOKUEF(~b*I++K?Iv7^x8rgSO6O)BV333C2%N@UaRy(cmgAp; zW&{a7{9hc!58^Vs5+`v5F5s5*J6weS!QF9-Tg-r^{{Vr`UO+x{g3rf6JOmHIQ?ZYu zI*R@H$!vZ6CJy7TjLr3b8i8FTRO3T$)hTs2D~`iq+!IIeAY6(k;3&Qum*Gdm-t|98 z;1v@5oJODGBK$iJ;6rX>pJP7`;WKa;Uxp)i^le=KqXcG>5XVb!0>6lpcoRjHeUE*3uS>vBpylm4 zpdx%C4&XjGh=<}3o`%DC9**Fra2cnq`xb#}0^i~S&eQ5_1>Bq&tHPaeHSU8Oa$1IP z0lo#t)vhz2K$O#JMYaNGK?N?v-{WGuFI&zr&^n?s*c}()zW72s$g@-)L7t~8eZol^;fIB5D~A5&U_Gfd%3T#c7uhZ%echd8Z2 z#T7V%GjdFL*Z(Fnbc7DK?Z;ss?u85RwK#*P;z9=e04~BS@gV#@4&21`e>;JOY(d>S zbU;4b&bY07O6CWBD0LR)akl6xT#hH<6rPK#ISZb~4gbsae*=L8Bm5DkX&`T=4ycp{ zj>Zkyr#*0Y>R*Ec_!eA&7h=E57Oo`_#-HP9_-~xSt?twTMQHFeT+M*5z)?=?n{bHw z4`H{Mz-t6jH25JN#6h+N7vl_$P(SZ321JJ~a2y|tQ}}eXy#JpD`;idB*WfT7i=&+P zE-u6Ka2!8^%kc}im>JsOSVT8kU4r|v0ex{9 zGj@%!x&D_E7)QcdJRMiy`PgBLp1?);B^=ej-~={nu~=f0`}d zp!o&&>Hs23)ovdGq4gS=f)gaH#BuyHj^ctj+F%NI$4NXG`^&ZcTd+^eO!GBj=Z405r=5+P8@z$%a>xGS1$Yic>(ukG!vKE7nHRLY{{5OC!N+0eWnTZklt9r-T44$f(cvQOqk%VYN*a)p$JvhK z47lC{bcm0@VP@n39AC}r|4rs=g`(Bk;2k*1lqIl_20q6T$`9c92U`F71=?PQ@}>`} z9qO;b$>;d|U%!|p1WEV`Cum@784b43eLfGz@e4Toh7MqdXZd8s%lgV`(}kwK<2?GT zzDRWz9^t;Dfx8Io@@s_;aYa!53+^;o^BX>-4IYM%#p52;{DJt#0qQVz$iD}>1y5^( zPZH?#lzId9-1i)fk#OA@ecKy zxDO-#9v9;}i**348)=6faNGLo0r;f`>Ph&YhUx{L<@|emx_0;m37_1p{tXAFsE=Nv z9Y!PS-dQuC65M&eme0i<%GEF6FW*vsYi#a+cX&br4VP*MGjGrlw!tCxX)k)N5<0E_O`vXSdWAfF}WnBM9{HYDRNJ5~6CVY+4e`)zY_^`j#1#xDK@^1J! zJP`LFe}h8LE}(wYCD4t)O3(7@^kBUq`3PT$ci_9O)^gu+9l$?(bU-KKW7cYi7ve#DT7EKq z#;4`(5&{Dm!0Wgae}|pcTA|w`+Tou&wZl8`L(I^VIDU@iulF2re!W!({0-$tYq{&} zAuytYRygia>7a$&E1sy2^#&Tt<#pObZ6J)RuT_`g&#qI)Jv+{AXKH(I;FEf&w`6^` zvF!i-1P1ldgo4L(3twRd&cFeVi9xvGiJCtZALy)}g@5Omiebmq({LUBXpUI=cYYzT zc!4JD#fROiZt}R!Kvktqc^iBeJ^@#Lq2*^8%M2#?x}Je}ZA52uEUuJt@A3a#1iDjU z8E%Nz;R+h~7#H&B`8VuL*A5POLI)6-p>B^O;fc5m&%sG{*^{^mzm45$w)i^&4cWr|IKmbk@stiQiciDSs6POg z;;}eP{%l;$03K7jPC0=$Nbu9ZH#kN49^8=fR!{4Ie7Fk^(cvYy7?))8Y41+Ya{k4c zvWH1X;#YAw{sLFvzivk7d`Qrwqa@-nWXyb>4S zzi@Zl{8=4P89o_T(cZ;k@9}?M5=uzu%Yg2})lA_dSZaA!vm`E}{2QFc|KKuw_zE3R zApM0YpgXf>U?^PU8u<8qYJ9 z8ED81JckSL$KF2wtA zcd>W6;tcka-(npd;%?YYF-3z3bjP>hG+vf9 z4ZMkibhs4{!VO>24hxvE6R;omSG!Ijfe9o;=wLn$GJrKW!4!Up*W&#+iTy9@0K(Mo zZ7ip$kJGZ)E0^=Hf`ogi;4q*B9z^-4IE4Sfes)p2SG2>vY{2wkg{_mEJ;40L!+J1nRCVqCy& z`7k^UPsJ5@4ql5N#Yz0~Ydroh`cxmgeP9yuWFO%)&U~ijJG1^;-SBnIkAAK`Dr@SW zk~Q@&!v2pnf5_{uHsJqA1LH^t;^{cUPpizsX@2kLVI0S+aOx9n?~QDII{XArYp4(H zwz&ix0@e66pC&X;>HvH2fq}N3<=yO;X$L3a1P+jY@0I#sbO=uH?RYbAyhZ9M#0d>Db2XK1U{nbI-m#^EKh?7I~lUB1mN1TBE7>(;JB@ohp zvl@5%Tu1l`4SaH|&d6?j^fqFN zj$f}0{zwBCd5$=r(7+hV?|xA8@1uMON%tCw`X=l>VL31gbN- z#SeLw51qcJQ(FIR(|}CbE9!|j$qNt1%9?ZfI1@x43;^jo&PtsXNCHfa05_fikX54Pp|{~}%>;y6rU z8411T>lQVw&;}Css*lG(&hsAFkAt|VQfKVOZ2lMO+j00)p8tPn= z=pSw1JM72RII%~|^WW87R#dI-f>XQI7h=Clg)sz@RG5XGy;`9>TTTNTZ~}ji<2dg< z9Y7Sf#VPvhiQOcDYX~Ii@W0q$3zy>z`AO{CuLInQqqxcYI)LOpEk6NA@cC*v{xg~| zjszcm2*>}>@&wNOseTuScdEa~QM?Z)@DUqzKtbHivnwB){Z$(nMnVYB&Q`$B;nXjh z|1nPET{uF1{s$Zb_*Cq}gN@z1h~p=48wmlt3@7PeJ$5MHiZhfq{ZKoMQhqWH{-y)I z8VB$Uu{{4Daf%2$O+tbOHsU1y4Ts2Yyh%Is;S+I&{L8RI`4sHOWt+U~f5eGX;T00n z_*Zh=e8F~vl_}i2me`W?kSLhU7vRNJfulji$zDeEY zBP~x|uAYHIyaQq__I<1aIQ(PH|9*+)55Y;^Au-P-5Wi9rc4ZT;QeW|jHsGhj6*w_a z%iDaa2Q(8;?4dW+ zf8ai6sIUE8^CyNqyUzOr`i#|t9+jF9`9V7<#VPjjI_&>O%j-xV zFBRs=)%IQ7_e*2h|6RV+2Ih~|Em($oPgWoKm6n%JP!GTxDKEvp&(iWec=ZhRn6I_I zpKHw~Tsl;G#6;u|%gQ(7xDeNufrE__t|Jig^#^=ACm79F7fJFWlTd@Uc3yUkO- zhVQ+f*Z-S+uN8)rX~GcP?LqZQeA~6^wm+~%PpGG1A7{ze_-^uh{Yd`fw1@p4yLu2^ zw^b98pQw-9hEwX7@MXNjGHAP&mwm5WycLIeN#>R+Ef10J`$^rIU3NR3=3b*C`j>!j zk$U{kTA}G;^&Xtze*T1Cw0tA=f5!W+*8J;!)$+2q`gQ!_!|Jns({eXWgO3qtOoP7P zHDU5CT45Bf9HRal&tIlK#uuGhbme(^!z2i6|)vo;bj0P5xki;n*#f^7p z17X}7`*v#m(b$g{UUx{ADu)#PKVU5;`lK25r1obivDi-o9llO6<#O7$78|w zaDXjN<08s`#X-vJ?A8vFlpldZl%I_KT>sD7Ew{hkJs<)XkPyI^XB)snaRiUXaXc0K z>F_=r#*gD7{F<9hV8oknf_+wnBa~NX%W>msb^*uCkvNGvXUl1?PuA3T2NQ@B7=<%b zn3*l7!5B{9a-701VIN!gDNfQ}1_#xy(_|03fP{`Xj?cpm4GzU&%5TLf9K%6Q)914J zJM5njyOR6xj01m5S+rdV4viBkN@WrC?a7cj^p>R!}Y$-J{>@MkWOiL zM)O?=*pF zdmLY=zF@x@P{c{&h1f^_IvmcvH2Q5eK!qy~XajzHD^48&3_xG@s6zX zwERlB=$iaM9d$W&<5Z|1ki@^?5DkpT(+2$bZk(jSHg&W-Lj7}al=3T$WtW7R!IwSD z?Z2Oc<^v5l5sra04sg)?js5d=ihXrWgL3+ie^l0swY-zD95d+&>NC7@d92AXFw(Od zagyw__ecogw)J$x>6kXq0|#heSk@dPmDzmC58wz}Jh#5K7vY$B&e)YH31kOg0_MT! zIz6qrHBg7ws+Z%)8uec6e@Q*?5Y3NrH+};SzpCXu_m2ein!Ei#UW|ZzQ*W<~5m$r8>o*m;`Yt-iD)i502nN z8*6?7x5I1k890eA#VI_hvE2Td4$}l?kx+>r!DVMDYrg6? z5&PvOAAbMmFxLcH$VVh@&>1+~xQ%=!j0TRT{7!yct|R3QDKEnH@EOL^L7WcGHJ1C^ zDZWYdLh{>^?_NfrfWTnmqojdD^!I;;8p{CF3}BS8%vhN6@y60&m=34l5WdyewAV=6 z%Wm+m!@Ui5ox4nebZ{3P+=r_)!C8QlBXx$B;`!u1j)Qmw4&ZgT7kdecDL-H=$3XI7Jq8*drZX~#@>bY!2^>qHf(E8`Tn0rG;lEq87d4hmZLn*07g^ZmjT~`1NaUc!S@); zd7hc8r}Yw?ctHKEXF31E^EI%Z1Rwqy2N!7hFIm$-9UmRsujQ?=4|l==d=4&}r}_Pj z&H3kNghNS4)4_P0VnBD|C=JZV0m`4nMGW{A9HG6Bu^(?2d++}bGs3+jByh8Q-NJNK zN8AnvFHmgv7Po5}Z+e<$|gIn?h?{Ri^R^W8WS22SDkjE{7j>Lq&6f8?5g90Q@nI;CGyAwq@kjAek=(!tM^2l?&S zKXHbu6Ua z+I4!GfE*+aQ+go@4wv66jAaJ=bZ~99ocjYe7|Sk-vWvzVOM6k;n}Q?wztkV#4N%Vi z*#ycM;XGs6B@vGDg*1?)fhFW;*up1_Wq?f?;7aVnYj7cc4YxEl*Z+4UApO;qe|rD@ zV7X3<-0~wVpR7JKTfdLGQPx*m9#zYxf9FmOG{{zXRxOWT^}mlSS6li2Y3U8Hrv5o< zdAO?oU2Xa1gZBD&79I?EHP%`F((+H1_gHS+${SG40NPqEs{r5f8V#%<|Wk z4_I#BI(Go4TON@${X0{#0jBH@%kwQSx16y2vgIw7_uzi?=d?IHcf?0qKHl=FmV3qq z%*$`*j<&-0SzclJHOn7Z{>Jhjmg^Mc4BK^D&Dxmd~<0y~f(V^IA>7 z>))xeyx;Q0N9A^SgXJ>I?^@oQHT^rCkIrrILdzwVOD(@+xly~^_J&vFyTrsaL{u+J2eAbW|hBVl~-EjY0FiX|1fr)gB||uCCJmU{8#WFZ$@hF793~! zWOdB|`&s2zS>>ZFPZzs#Kc=RE`>lj>%dcAg$nrMJe_5_`Ozti&I7YT#22itw$7n*0 zyISsT`6|oTTOMP1s^!QrJ#(k{9xLHN%S$Xj5}Q|+e{%a5t?+A>-->-#mfx<+M^?Df z@)pb6VqF%>C3=SyuC`p~*xZe1V!4IoBQ5(a7v|b^PRb29-7KGNxwqwuEMIQq#&(!Li(>gp%U>#g!m%z(rGW*pVMZ%r)8&u5!D`?W%Udn~ zWx2uexdS-bayQHUbL~1MxdCUU}&QK4Sz(3OT7KJd zD)z8cbAPhJ)s`D}&7FnAEqAos#c~hJeRAE^8IT)rLYBu`zAZMPJilH0GAsOql~XKaJpLVY55Y%S6eQ&e3Runb9{>YbXZQn zah|aJiscQlyr<-zWIEQ~{Mi%hW&Sje4KsgEh)tJ2ZtqibH~I?8LoAQAe5>WVEibfO zo@>`xlN)f}wfvRkD$9E+CEio zJ=WI_h&Nl_YI$dDz%#Pv4(pb?p+{Rj(el}rFSdNG<?of~i-vizjwb(SkE zS6bd~d3TOw&owwbcLrKpKGyPSmU~+sXnDBhNwsaRL(^*q#B(i|Szd1WY0EEIe$8@4 zEqm9&&9ws3zw?#lpDgdP{IBKw?zuDC#`3WT?e*^z9SoTMoq*;3mWNm#Yk8*S2P{8T zW9{ErQxov|cQ#u7((+H1cUx|7M(zx>wEUl}>EG#+4QT&Pz;b`f*I6EI`4-EimSZ^E zzw?-tu-fvQmN#48V)=K=`z$v()0-jlRImPbga&F{Xt|r^^DSR)`FhJ?%dY3MYVMIc zbF75LmRDMSDfaLxxdLvEy&ZDmM^w^o#pYC zBbM*C{79}{XLWACdE4^GmcO(7yXAeB8}-QD?!$8|N8K?w0mtcTxu@kzEMIN8*zy$1 zcht5y>Z7#-;-!|Kw!F^r2bRCH{Fmi=XCIuAR_2gzUMpaHY;3@4IgrkYO)!71ip?{B z!m(B6&+OP{^JjT%hxzkzEdK@h^GU3W`SV+BfceuPHo^R97n^7PbdRl)Kkk5Y4t8q# z7;gC{%cYhVS$@`X((*^ScAc%c0cW@6#`;=E%~{{p^2wHaSsrNlh8)X&zBwo0IQLjy zYWX?K?^yoQ^6!@G25QZ?InD}d2gD~@4p_d-@<_{5E#GZ1H6EI(`cP0L?c-eI}^d9^m!9D#?| z4v3Gne46Fnmanlq&a!Ly{#rKuI}g_i7(Z|MP0OEI-fp?ta--h0wpiLf{Gh%5osJr) zc?tJK%iS&avfR(|0Lz0d538~E?~JMmnC*9*iRz{*d6%H&`Bwg7b^UDqYRelfZ_S$i zodekbEfk#ZO<7HY9W0+_xu4~smM2@DiEF0(f!Op{0vsTYkgxr?u=Ijz853c!y)(+%0NpxzKV?%U4(qTP{6l zJslo781VXEW%+f>pIH9J@(-4Ow!E{(+P|~6CgAn&H0+l<13t@bEdR%Hq2-e-ch9=7 z_UH7<2Ji)z`&+)!@{riDq@2wYt?(_DXIQ@5^8J<=v$=Mi7jpy7YnI=!{GsKK zEq`J8JIg=iSoY?woPgsTu-xFn+&z1k<-;xeEf-quQrqTeIBT6U0K}kWc7nARkHjf%dfIzWT}>A zuPpmyY24Uxn#j^rmgcgwl%=&SZDeULO9xs0Bg-+eoFU6avdA|(IPy(EPKhj|WRdUG zamLFsK^FPe2uHpH$C)l4J9lQvKkk+#DvNx}i?dV~`6>}dzOTetE{lBCj3eLf;>cIf zIM2$mQkEBFSue|5vTTs$U0FVmWs@wMW%)>!k7fB(mhG~b_r5ifNpZ%0UPLbtQ zS4f#*x6~^93 z9YEe>diD+NYO9v1Ib(@g?IXm&DSCHUGM8$)u7IBgfx3aoE@rXXwzYyA+j7nmQ>wa(KzG zF(qqWY}R~8U5ONroHlOC*y~45)SNCQlfuR0$JMGOk*PK9tU0Vj^Uv$#ODMNqY`_os zE!OTcu7eX*N+)CZgdvI$4#C%CW{efd$W+O3>RlHX8csPN16^#G&5o* zz+lR_;ReTgniL*3d}Ow0N;rJ5(p24q?uE@i$m?+W>b>ARd(w=)%0a_aQlr8PJkz` zyHQ?uQh0DeOyR*9>3*s3;Gj+6!AbGNtjMUP?BJ{z9h?@?H0@%|vJ;x0Qm3H%Xd_-Z%r0g+iw&M)xvesM~ zW;-2rQu9NWG#)#0?C|1=!JA6P#)iG$+}~!>1 zF{1GG6GwXy)5GPHPnS?mrPE`HHNKW>HoxEe?EE!He$)KuI%|4uX}-O|nv=FQUw-=k E0vF2YG5`Po delta 326207 zcmbsScU)A*_dkyBT#EDs1r!kkU8D#IDhetJ0&!7n5yjrH#vVmYENB4DX2o_jWA91S z6irBi8l$MO#Mq<8-eS3HZ?Q$?`#dujB(Kl=_t!6vhwPox=ggUzGv(fF-Is04xJq!0n*<+mRck5DD;ugw@EM~7 z8>*=vD$TvFgi7#}0tvpN?owKKUHMi*gQBF$E2@`W3m?OIcVS20X1WNJ75@ExSATVy zU64=TsvZKpDqfmTWjl#JW@pb1sHg0jHm#4j>^Gv%_EII8 zW{;}$m$XS`;!(Eavs=0Qt6d#}e3Epula8jcUeomsL0)DZJyA!S)w2#Qs+8!^GMOk# zlg*Anl?~ZaVUW&Z$W}jgZ0-}JgTsO)oXQT;XPRSBl@9rm%UkC{S&EFYtE{%J!}Bw0-cZP7$@^L&hyGh71NY<<^<&qR zhI9&CJW-f?)6YrV^z^zXK#!EjW4b!9RQai{N8!y5=cCHTf8; zdmVL=+b*TShiVV^IS$l%!RiC|s`hcwv2m)4$0d6MTWL`5dJIvw8{?}C0iH}1y~$@1 zem++>xz$kfj0xU-E8_f;QQJtSZg%rmYkM}T{L0f%4c$8&G-MU3T6~1otZs0TaMqcH|OngGUp{2a-PwXcHz2< z!LnaVd*)(zIv4+`F@b(cxVflJpbJwT+)=Y?t`9mq$!IVa#_e%*HE8$#aWN!MYi2SS z3mTyTG73Z5zo*s=x}KX}PY~U&=V~xp<6}#-*-u;ycRm|rteFZjYhpO?8J63zC6Khi z!PSs%ZJ`YV3H`RfukHHV(o$p1e7M5^2@Wr#1wE)g1ho{|l;}2s4*0XcTOI=)BhU_{ zvW7Y{xR{MsThyu>)&s++W&wlQax=s7Ks!T^1tsI`_Cb7MV#-G({=UZ5`L$L$C#RrP z7?n9$ZBl!5L`yqY1EdXoq*p+<$#TgIIXOmq!<{+Cnh@gCOg&jUS=p?pRZRhnegnC= zaGWWKw0S+Qs4d?VM82}zH(U0bEvY6aZIdAOacZf4VCumdtNTnQ=TDQ#h`6O6t4~Zb zm1jxn29C`vZEgo(73}`LpmY{D{M!!!n+-Zu2)9YR6qZ~4* zX?1#4nTAdX$FvvnC!2z_JvY^#>cj;r5*`j?Z1!j#+2}hJXf#{wO`4PH8X8#nC8j}e zA0U$piMyHFG_+mZN|bj*i)w8*dBR?8x}b=w4SKWX8hn;+`3;O+0k*oCV1EAJ>WNx$p+&l1Rqux9E3ccXA4VjnEe=6jgaT967K>iP3~O71 zR%)Y%DF$sl9x|*^gF%^2ArMK07b;H>T)mk^K()@oR^{q|aR=0NU zSM+>aqbl3xi2h};7TKeA2%#shgeR&)l+8`xrolQ?z#-g(8rHybSq+y%3`N^2DJe}h zad~)r2y{|0+wp=L@AYs;Lu-ru9dmY`LG2kC%4VtgkqwnYx72Nstt(GMsBM^5T3Tw^ zlT*)Z_0fV=->CZjvv0zd6L9&$-32FdPUQVtII!+!#@VG=-jG>IWx~eSmBKP1 zL3S^a=UGvnZr`|?>Sy($S)jTV@|L0kX$8;5*(c}iWhAgenZO$nQs5p;0Ah?LdDBoR zw@;UMJy6J-t)|DAn%a@;3LRd=q2uJ;WEewoBGdAp84KP+_2HbIH7QmXn8^2Nu)OCW z7qh|Zzt#0IzvUha6tVoM93Jwwi(!AkEQ&n*M>hO@4c$D|6m-XAtT{~ptqzO9mTIv< z;v7s=QsN!Zp~a^E))n#GqgQgzQ3+GA$MU=EBcnn!8z_BG(4E*R+ zhcP{B6U*VYM06F{^J##nHY+#&;#G*WM{U1QkJPWx>3xY;!7VHI72$y%gRR<({;Na>wC4Y<#G(FBx% zNuiA3>1DNAqx9G)Ahb+KNlUmf<*F?bBg}uJ>fuG<$e4f0kq+1Dsq-7 z-NzySf|!OyiKWoC`wL zPcKPT;>m~5@g;QFdc6_4yC88vzY$I}Rr?<&z5z9jv{tocd}}X9xU{010@da5H7cDY z&%Qpdmc;w#7Sr>&^P&dE{8M6NmthYxEtg^PLZaAGMltaJpcqROpO;bmBYN(CWppHp zaiDl?^ju(iffj?qjQNvUQTK$#%<)ZERNkcWCbeQ!A5_S%LR3tnnHd#&br#7Khds5$ zRIR(X_jz@6GjHQu2q<%^R(ei#Z(756>K+)&TG><5wX8_@{W-Nq(;6N#E7I*&gPZwy zJk6>gE?ZsSG{7UFBGoveO8BxOmCrdB!xN)ts;Q^3d&y&?SH9^ADq*|$nwl)`&fN;O z)FJLBT`O|$N9DlXleo`=dztABagUJP%PVr9IZHJj;8KzOBgtMt`7vUDL$PXgSY8D# z^#Y)*qotBOxYYg(g6fcnGKWRsGNH@g@T9cV_8b0a0jk!lQ;ok)Q>%4z7&;0*$@{I^ zof6o^5_tl!~F6Y{*oKu{aK?-{%AHF{q+c0Z}j04-uL(=-}b zpE6m1WSEBFZ>p)UmaT4S@xJo(l`S?F6+(3UHq8 z8;6m)%w4eG2~`7?`zV0uw~sn9Igxczze#SQ%rdE3a)^@KOEtFaW$Hv}5@ep-N%K@x`dadP{*87hOFQvGFw39dfw2|&2j>5!ZOv}1ESAcL(x^d(w z`f~`lO~4H@8%ORH?$xHM%TpQz{!2MY_Y$oosLj@1LFR(<#sWVv%lJIMq25mk^1TES zZ7uL3v(efgL-E3K)s*^>8H#+;+A7Mnfkgvag)#P_T4y<$Xw=)TLjn_fH_%^drU5FLz18E<9&go@-m_w{UEv2)Yc*Gp#!S+Swmf_bh+q zHy1S`E7WBrKnj8GWDyuTsq?cM_|z2`T9@nx5G=#B(?x2} ztf1hG!t6*b8|Vz=+Ft>gVYw;UGl>0Q+Zt;4iiD4eSyMG+GNze-z3030|g_59n|JR7btz+mmoa4HzuZuIy~YX=|CM+RVU2_ zZ4IJr3xsx3%653w5s(H_zdIA8C&-j*1n&nT8D^onDdz#kNMH=xr8(;Ix4Z*A(dBFv zAR%W7$?+rZ#?Zf9BFF^?blQ*Kfqnu=YX;gKO1W#UZYp}xBZxVgX^2~)`t=HEbo$%> zP!xVgePvRZId6ZE_Br8}A?48IjkO-l#7;7dWRhpJK6LJhW z4o~*b63FUcX{vtFC&|4LLH8sTE6E+TN$TT1W0;>hpl_|(gP0L9opo@Ku}V9qLB^`> zJjr(*GRR;It6mOiOx@6TE(X#j{UX>Bbzr}6Hd>w2Pwo6c8_;-TQbB*n+J%uiB?$AK zQ;%MIu!P>};cD2HFV`e~2WSLHJkc8MF#@3lv^zlCW=Qm_{Q_M?=yvLa{&mgA?DWyg z8^NMY9$_!*POG;kQr-3Lf|VOGozr5scw zL7qDdzGYOaBq(VID+d(VQELSy0i%L77bXJ5_BkDn2z z()}IP?K$gXH#c%M*v38-y_C#cMdeNpWI5I788U|ee*-XRF7S!diWZLV$XK*`Yr-;E zls|Eq^0a|k>!WULanaN7S--SqKhkK{QOOlqf&nAA{tG*?|aDN5NrS3NPQ9$1yh zv6cGFg<&^mt4${Rv#-=HlcU&2YX0O9g#5uj&Qj)yuFxt_XeuJPiyElDiS>=6J5pA zeVOV!JB4H2zn1EkrjS<`D{CvwvC+R6cox@x*fx$tSxzUkeVaxq*D&by|( z3{xHQzsKBPoIgkTIkd=J(2}vwimWq6IWVQ@U+WD;IT@f{n;pX3i=5_kSG>1>++sMF&e*KiKR(pT*XJGPHy;)mPBi*ZI zPjjUlkdhvIsO`UP=o*YYS&W?lS4&;g+21zuo7$^l;A`yUE?PFt=ftY&aP-;e+Am^_ZUIpl`1(|1O@jP)~msgKeuyn+7QJeo#kjs#;~@ z4<5+<8#su8g0{T1hDChS*T@uKsi{AlRIa~Ns~4SO?&`y$6>Nq&=SNj}{9HBt^gH`P zz5mlvY`T5+^8z+bty$dPad{_dVRdHlMCHxXBDTejDKDO=j$3!Icy;gA)o{n)ZHJXB zkJLuLTxFBgYTGxn4E6WzYgrw2#IIIXLw)&cA#+n_?wHKHRQsKUj1|q_=>#Ed)MX_N zls#?K<0W%htlEE9Bjr`=q9waNRvH*+FZBIqWgXw=p@#A~ET%AbQA1v#^3kW|5tMjT zQ(OJEpXC=B_gW!3OAcKVli{txh-;gWP2Op_R(v%WC@JS1esk zJ(ASr0BxOE4r@t?5U)0)O~X=S{ui{kv^sn}-2>9&#`VM|ndOyrmT5io%6+PvY4FHY zkcp02-aVZ`s$F_dJ$Gap`=qGHQH8OYYVTt$*<^L~u_~2QFVYgrtEb5mX)4ufrn>J~ zq;e@ieR`~&vZb4vdc1K^NJ53-vZY?w49(h&CXk-5EeYuwYx)l`C22pWh-Dp6SdKaD0}3q~H(TvbECjS&(}9WPk72c_IRG25DDc*}=g+O%8gf zolo_yw74_0zipvzJN0dpB@WLf{3%=eG{Q5jL!t#tU;Ah$8iK5&y5Mv~VtwK`j4~Yi ze=a~;=@RyXrhFwcoCMrysQ!DpdRWU$obh!|x137O>}e>** z2ta?WQ4{})#@W^Izv?{KyN`&)u4=4rs{{q@VC4zkMu2vb7cmUlU2LG_N2`&K0$rQISUUAe z(z>htE(W`KmY1WW)koKYmA|8lzP}j7SR?iFr4N{$+WK-&W#18X=Z!vUr7M9-t0QXj zo88oDR|1ryht-u=1~&LPLQ3qQeO9+j_8J}5R~ugro2|n}YyH*6S8LVS8;~fg9v@VDUJFH_yXigI=0%1{34<%hm@d#lT!^q%BTL37VoFu6Qn=c;ud)$}$C1R~k59MMbd{irtcD*EJ6 z3RBu|6Sb=KWDu*bTAtKnin{ely-FvlB7U6Ss4JiQst!+mF>BO#`k``jt2+1T8l~k{ zsMT`2W)1FmLs|^eR`)beeBurdG0U-L6qmCS{ zofF9GIYTAlo;PvnzS!ZTUU*jB6b4u-E)g_KD>z}y&qksuL-IwHvhMt}idy}7w9|(m zypuO*uDa@ppE~q;J!HU(pGTRVf=<|ITZLIp_KWh~aiIa?_(*_y`+1cAEi{Q0Hw!9u zjQOw7F@UfCqEVE~NF*ATe?`wQm-md{Dp%|oL$o?jr|qw-E__i-+53~a^~D&^W*{-+ z$QCU@%b9Go$;;+dBLvXR^sYacEeADkbp7eiLsl{!5`Eu|3)I9Zv5L`xw2Vp^tw=a?xF5| zz0w$Mtl;u=kA=NiwsE^h1=wHi3y-mE*19PDZ2&_?J-GA}<+v-qtgrzdDQ*=AdoS$8 z1{jaKRsh{~5xjkh8!}c4EAEZFjRPxCE;>o}_RbY(Kj0nhz<<%H0_;U4!9T1x%$^}> z{o^aif7?Ou8+$rdpq=0#Y`^VL0p{R9{N57*YnwgZ6KBoFg1wK?q>t@*bS35&xz(;L zh+?}AtT$^>e8h!?E5U`UX|?73JN9pJ;?d20iY%W%8A)+B zpAp5?+}Uu(=JEm$HcUDAnm_hnyWFZ}1 zIU8*5;*=?IYIXRhlRcVeIhMAVjQC6Eijz?^igNy+iznN=kz`akVi=yx7|+bsxfBVu z_b`(pW3OS-QXm(dm+$ssJK5mkPb#x$1+DPCHyi9@kL5mwje|HDIDlWVXEBjS`LH2M z=_2mw&$=r`i};v8)=3$;i0|@e2};vNoYiDC>(&CR85?n1D8za_b;ic;Kh7d~j6`9O zapZaY+lyS;eHK6L%c>ct09`&pZhy)#MhAxl0JtL%B4`=-cp+Mp3fhookXHmba*OTa z6P{R=)l{yu=e?`4Sk|q0ZdJBUVe5HZb=KE;=6q2@_ey+4bvByK;6{JeLusN)!~dBt z4c|Rq7~W#{W?cj6tfPj znyik~=@_wkoXMxwWTCEa z;4HJXDU$O^d~;1^ay?QG=*KSu5V2AMQZ0|+>kLyoNSk926sK2!;7$zwrXt%M&!d7^ zw#O|Dhh<3j34BEm3sSB>DJ}_O)9sXBAMq3ui(yti&V)|ghp#fRBo@f8nplYP;vshm z!FX}vA&!0(@b(l9(_!}^_zlCPAJ;gSal!a&~4Yc~z4gm!y9`V=PWJ^V(395KO zrF|pubm$m%#^y^?`l~<{s*nf+DU@-5xfp$k2^sF01*H*S(`AQmpjFpU<`2BRpUhn+(DY$fC>fiDmep zcla<((-39Tu;DPs7>?02?l#paOq^mLupI(iMpOXckSHSRfbE)~rxPCjvMw|EhFr!l zr=MM}hWl_a;XUnSSfhVe1{VC-Y`-*=V%j#9@N8N*B zxUZ-e{Y^Cu&_3n&Vp&8+CGcqn@W-0c!S(`;uALHNM3`na(`WtcLuD`plHu=nN$LK(e`TQV_1B2&PO^5Lh$l!UU}F)cQ%Y zv5Kj1R$U7~F{O&B6qF8+KSE@UK&`bckR$zl?JtPV`z;>lctV(y_A_c~>xs01uB-FT z4cIu-M<6OFvFshUQx9J4EFjv)0-UoEli^VKS0^iO>P*&Q6_?OMbXdhTbS7ZX*M@hAV|$gC z<9R?Mq;#?TvqsG1dHs|#?yHSNeEmp`(2wuH!Z|AbB>bY_xMt`3*r3w#P(%eL*VD7t z5y*GZxOqR)Xf~C*H)b^|uLaFcPdeng@u$!?h&OG_VjR(H4cZ_+rZF3%Y(B}aH%5wI z$Q|QZR_;e9;6)GG#)vLYz`q475ADVBaHm=^7IcPHQ01^^9NOhIklH-_LGtECJJOFZ z8;R3uWF+lL+9k?3{<{xs35Nk5gj@?*`YAH$;}_ytXvCk#ff*t&N&2kv8G&j7gln9? z=x3B>Z6l9LV1BuC30eQY$aw8zf;JLTFp!lmJeNIz!OhWKZJ(g`Vt)kQq}9eW`q2u( zM0G0~i@HUVg|$EhX1N%_(7hoHwg-~zGpHExMG5|Lf>2U$2_4#26$EDNfO%>Uj-mJq zikP+@C$RdtcY#L+^YStMY&Qj=^>FW4s|vetuhE#_8H*9%OZy_V((zgYqKs|8B}^<*MM*Hlr&O;VKA}gdbx2Jekpr{slUYcJ(E$ zOF3?$z|AtzRXBlLU@F0*V1%|5lAQ?}0Fv`wI~xmLVtz+Qd4Z5iwGJFGExtIv8Ee3l zzdG}+iOBXYoaR>&8MbG*eRI}OxiGT0Npp6K<#t5p5wA(uvN6cb#ckUjBAY9V1iXr3 zghN80yv!lh?n#G?1}o_6{tx}{L|^g#d>x%WiTX0SOOWJyf;gGWS0$lWhV$J?>^ruO zk4a|rnJZtH%pS5JzOW?=HhCe_#s%XAriU;=wDmbeB|V5i9#`SwT=}_{%;dIChXdls zT~b)E`-FFpXSaEL3JYSbdAAfa&T%9HZL>?XnxL?iV$TOo)~+FZQK9Ep-60TGxcyH( z{uMchljZ{+v-Q_iXnXAuSg1}m$+}&#W)o}080w(2o|9w?`77Ac;2UB_a9lynWB{!9 z#s0sDqI}@;Uf>jp?(D{%NY8#s7LMa>Q(0DE49M6)bM^_WJl@YJYN;s(9)zo2qtObR6(H3yn6pc59blZ<8sG|^o#G`HYXhx2=#cDb zVfanPaM{x~7h?;kZa6@4#XHl_DIvDHY8g7mv){3RpVgWLCtL+)TU}C871qplA1^gpQQx?Go)y=qm^K&DJbYY46WN+F(V{owsR&fC}YP+px{b+y2Ed87x~- zE?4CX+pz@K-WYb$Ehq8fOJ9Dr9ZP1u+`m00sBeouZO>d-z>3}&ma-c*wl@4BIpN_{ z=VZ&nWRXW_6b8)Tn>w)ON`Vi5(18V1Pbzi)uh;UBJ(1VG;&nQ*u++D2fv;G1Pl0#U z2JRucrkWAIwyomCG2l3e%iS5I6Wrg;=XYcW;u~V@-1eQUdo+!sg^6GHMBU%tC0tXB z&|*X>j5A8~EJ8Qn^WSI5%E~JI)cY*7@(&o8cTg*8@4ZI{);_C?Jvy<)*aGw78!}l} zwzOF3%(^h8x+m}6g$3sxe~AiU_%08NNE)F~!T238JP=70PKlE($C7uwP?GaBW}6R? z*&4N&_$pTXYvdsEeW73Jf!-IwgN=ba!y57d$VJuDtqo1u7GTn?%}iRsdw8;@WZE`S zC>6S^5V9?t2H(QCX(X^PaZFd#a@}@%b{|aWFp2Ib(eD$wfG1@kd%NMm=VY<^&VP3n z$AkfE`N1q!%ePBW#q@u&wgqLa8&|rrR;)H}-IX;8j|2y@eQ_h>eWVZ28;%@D3#2N% z#w08khcCb98@gijjprA;BL2SMp555cNIPc_5hrrhcrKGRU5L^km|6Gc9@pEISGyrA;4!NBAf$_PS=^tNXJk_8vdlpVd{iwdSw;v+R0_ z=t{WjW-_J4QZnukg|c=amz+iCDH&fO63QJ_lFnymvk{K90o6>L4PXP6n`S;_09(dp z7ds4O^>FOZ;|8$>ERP=?#JE%J7YLE;PHx<7Al&lVV7Pf8*9NmS%!ylvu#K!SZ!{D> zU&JpC#ooe1{)T?K@&H`RL4ziKz@oiB`VHFNBPXuKfR6?Xc+e8p9fJ7E515bgq9y!gt@ILlBjE$6=tXZ4id|Ka}(XW`XX`~&a$jkl-Oj>BpwD@>t1823vpc>D+!*kDHs zTBY?mKyrE<#%77^l1wJu*BK9Q^rcH$Q=7|?4J znkzpu0*j9$Es8xyvJV}U`3XGiV>UzC%=zYznNQ-FMIuejG!2K3amiiG_ye@=W#GO7 z+*5$NYAFCyhoaF>>n%l8tNDM^qOHd5MR%Io? zkUsGa8Bmp1nZ%|m{}%DjCt=I#-XcC}3JZ1r9^;UHy?yHYjb2f06US`^&pE)i#ljkyR$6w^-s&`zK`@92Y{KzzisUIE(3iwR z3mM6SyEr<-SYU~N6>yrec_9zX#s2vZpYwLPEKaP8OpRV? z3|}VRn>A+siQo;Gz@w+JreR+}(SAep=uzYMCD|LMH8mIuTyP?XDhz!=RnTAHapUu* zu^i=eBzKz5>UjL|9W_06^Xa|E!MxdYHq$W^FwLI-4m`^%emI?-wqu!me*tT%9GuPV zXRvhT>;~R`1`BqoKbu6yZQ>JWuolXlLcVnd#`j3>GLwa-k13>u`BgCm!$W`3AS)vE z6CHu|FS%}4IbtXfs8Z)7vEzcLwzQf2E&q5Xy8p{r{Og&>yu0(mGg)NS4YLI8I8zK3 zPaqwRS1$|W>Tu6b8L$4Y^*^fnVW^d=-T1CgSt0h5+gq?ZY{x&auold&c%y~YwPR`g z^ei^hdDKi%soiV%phDK%>Fo?roW#E=WG$SEWhsR}L8;mp8q?CxwB8W^IX=T zh5@R#;bJr}^wubx7WB#%6r`*LA*L(+B zThl&G*{g3ez@iP#BaO^hutqGsFKa=3;5B2l zbnF18CA`f&MYgH@$~@Mqsc{GDS#eRfho%*zf8)W7W6!CB5j7!KaY7bb5{p-VJOeu8 zv>U%Zo%zK1Y*_APQesJSvJDf9flM6fTh2;f;R6icPDk|;_8IdJV>UyJ*8LUg=)*FS zqA+pwa;a_&2QL&-<$^F<=ZCQ5d(b z65hH+?;bruq!iS}wQJM(c9m7n9kl&_G+#i&QAICKD^vIjo^0Jjdh5`b$Sc0_cU33< zuAF=pkw-{!Cy<-*83+VRoSUGe4>xor3MWC)(&6SWx&b1h96?kbQ9X$6s#tP4>?AS@ zyfP0}(G@r1d;v2>b|(K6#(lDstiFXcrB*-?igA44fKbNk0;Lt_@>>gVNYsN@Tgbw4 zAA&=hnM>U68lJ^fG@mtp)6y7RHwfJvXlomL6yKs~tpXZUBMeaLzhZG%n0Ol~VVssg zAzQz3SZyS?zFD640yL#-F{zpy2DN zG^xGu$~j6G_vp(nX)*V-rQG9l>^SH0h|gJ_%oW#Q-vkiC{4-0+%%2aaF#i-lS~Sqo z{QD@DnO_ai3g({!N}3%N)-p$U79GaAYm?Y{MYB4s`}C{m73;cq_2O1c-y zX~s?WwYA_BE`m*^i^(*f?N{k1j)6q$4m8XGg|!M`K7gUJse-B8n~s1=b2^h9)=5t3 z*{5Kxge3!J%S9ikumj0ga#pqGj4c##cw;zj7BT_ZBhE}NOLG?SQ=B!b=OFwQH(S^? z*%Uj*eqPi&+F)!>cS|%~=kAe+g!yAbw>D^Y1y~4>=|l&~i!(9S!b6 zhrVUNwE*t4N04eafDwz}oeu6OE!TmtkyBl@+(x}KKQHt6)`=kT73upls4v@x1$_eO|1;7>2_`3P z^F$uF45zhDyw5VMVla&?WA8N#g&;U87mgZBj(Ura%4B52U2xP}%^6q<4FJheo*ArE z`)2}oUXK1TmxnB8&0C-S0s10ff@m(xS$!7}kCWD60)?F|0>S-mAK2=!f#~a0pdX^s z2k~#0vnEdAq_Qwh;SZMMe0>3TUV($pyO0IoUJH*x(I;~}iuA>ay~cd{j22qM>j3Yy zf>jOcyB&^92A)p#>&||QEU^HA@!2cc)ZF>=!Mp*$ychPyg8pLl9(VCmvUIX(wB|33 zq%Y*qyY{~c#CEaksFgw_Do}=7uj5A3ME&=FaF8Sp(5COBcc8TuV@bS_56tzKsXoDCh+yESeWM;psXpQF-qoOj5supUs;8%m!_MIQf2v)xi%O;xhqpHEDezK0(ogh^o;v3662(8_f4%($!NN)fUXSJ_YgskZxNp#SIOM@_P9Nq-^aN4{ zq!q!7QA0a1n!jGlYJ_x0=c3zv`6J}KX98!cg?iT$N|!~|2JysotUVje3)iuv+}y9p zU5Q^J{iV2mI7)`YZ2a23prZ=Q1K~q$GC@?@g_hYN?t6E_{X6hBB+=9FiZ8k>fdS^i zq9x>Y*^=026tK8&MLHp41i`JjjsYpp_<}Xgupuv`i+uT4@Rc($#voTI(fS{zZH&a@ zu+6g1k}?3G5wKzhKICN(1j&-%SDhde1R2FgzhEVHQ$Df!7^ZjgbnPc7;69gp|hkEfdKVuA8!2OD`9{RHvPcOzjxlBI1m_27biwA7M zMgvRXQ?{~a)z2;@zsY@exOd}+EouQ!qG96X8gMkgMi3HhP0K<1f^QQbV&8~dWA8f>X&497&w)rE|rr8l4 zF&mKBl9tv5KORFvYER7&)fe7K&)$bgJk|2dx*(r0AUy}yAvTYji`yJxo0RJ72SL~+ zadh^irO`8g`!vwe6`V@N-bdNyO3qGwXtSh5Ygc^uG}iG=miB_N3lgE>Z|VeCsuLiO z0RAGJ6FU*$Ljv@ox2(djB|@*%e#f)Uu=uc_CZIZaFU0DpJ!~pk#tV%{AHezvEHpjU z!pFk2k-Yc}HWOBbKs)vd`RFsOW_a2=-f5C|H8n1H(<<`5ZNj&l!68`&Pe04*h8-KP zYpkNVO0Gzq>sT|XF@ZE%__DJsDf+u6fEC6aZ7T(jl_WW9QPZ|PX$-!p`9q4_U(4n$ z=a?zLmo#Ad^`u(07XnaY0nt2on{zCt+C}hSuhGt&x5vqxa3|-w*-D@9n9tR7tZCr* zoC*RqC6EQ>0!B9F8Ru9%zr_Dr!sB?}@H}f$C$b9|A>34tKL$b^@#6USoALfq8iFVC}3 zf0I;}ZuQmrNU75xl^Vjtn_ghyAtjB4u2vOw-Go^cbuHtT3#@j;B}rP^S_)W8#>*_L zV3HV86Mya|bYm;#0&CPaer$O~$4b@(I%|9x>z;8VY0-bbkuV|$=-5)K72Yo+CHu9L z64OJ+Af0(l)c|H&BR>2hYwYAGb&lp=Uxd!=4|ScL4cc&>X;jGJoysK2zw(b0(Hr|sdhp#UbK;(@ac-KrSP9F zv2g!EW6J%oS~BiNzbBIh@t2oa-S-U=Iup?H_O1h&F z%_a51*m2{iv%~?N_re>mdFFl$vL8W)lZtNK@fr)PYLL7$1yJ@jwwJocH5OX$mrSbX zP1N%{klDtI)^*0LT0Qyget)%A9^5p zqOZ6yqGbRB-Vv$vLobyWsiyGH0dry0>rTAa->jPd^G?o&t@Ig6vt=J@@#ITm{%>Nt zFv_0K{hRs46{5lahu}PR7a<}RM&YI9imX41t1nTZ{1RBJr4oTIn*@I-cAFFXa^rR8 z>)(;EwD0{+vllMgL$H}AU5DnrL_i-ic}I|o-8vAoB@9-s6)-&m1SXl!1}k;~*I#GN z!oRF1Jg~nd`S*b~j2uF%|Ew5d-LC}+yw8vAxcd#3+_9M?@1>J7NiJ4+Q*aT08o4N1 zNNfTU#BwTDapz+Ifvm+66I6yVX}eWdK4 z7E{%z>c_%m)!Tt6-P+ue_bJ?n%e*&kvN+%RL#cbky}?w0ydy{^VPkpBExbbzE~U1v zDAh(aBdI~U)D@A)AMz+w%F`6uqMea1(}KU+lcCx;2(gU;2$7mm_>Hhlzuxpf+Ye@F z*IGzF%_Mp}?o!UnKY+k{&=H)yKus^#%D8td;iqi#7vI`_1(gzCos9}IgUc(#pYG&>(5 ztbC0WgVd}djpTyAu%AG2&{^PpmQ5xjGlh}(+7}u5v@O45gC|Ey33GG_4WtAS4{NH) zc(@TRlne(6;vub?<{>J!UppibopeME?H^Hr{aTSkG|>@3+65i41PBCbhmg`-+@!Ol zh4H1g*@lcOVM4=TBs8SKpgHUG`z4~CjCqbi_hF_YT|a*;?X< zAu*Bu?{Js-xRs`h2(@^daN}>xT^1Xzg$m8iI^DNqI63foZRx=N0`IfoBmT!-=BI3F z$8UlycdTSvKq(6R9xvI1*KX@xyIn^}9s?5TwJJme(~nC;R~=D9dquVau|*d{n~{nlSQ{sl`R^>*2a$Nyh!BA~!d@%U5mP0?K}X;g zjgA;75qB~Fli6jcAWFK^eO2l_M!d~m3BRnKxp^k9U8taIEf`zEb zI>Jp0)DahgdF#jQSpCtGU|vJ%`#9KE!T0N=??Y0`eSa*NXFOpw1~g1XuiJ+aZvzBa zQwG?kVDa;R4SFwwgay@g1v|)6YNarm&rqka0`Id8DW@f+uV@{-F-NU4iywM|eTi98 zz_*y&DLOhz0kU6Un+BVxnw=#13?$^R7%h^BApCYsp?r*vXsA`u5yvH>tB#1*UXdTb zu|*+oU5#NW@d*JY<$x`&>t4O2l~`k)ch| z5lw-pIF@xaXBWSIip{#*k^oVYR+zG7OxQ>U4E{3b%;NL{AaI$RrBATljPjx$a_hN)dbohe#kTDMPeq$=P0TQd0Q{ zdWd=xs0&K?q?c?!i?8vF(cq9s8Hi}4P82DK(1fO`4oy=AOy#(n9>nhagsMzP)(wAK zod>>RNtr|oA#H^a-Nw75oouX4nIg2V_WOTq9IibE5S&%Y)&E3npdQPAc*TbLkCOPg zG^dKhxW0~mSdBOOmo@4cC<$7CfPDWqSwdQ?`ClfJrj6`ogc{kq=GHzvDQhr)$4PiS@D5?v3c3~ld3|*TFjxOq7`YRF?IjA-Wirs z?TxQM=L3x(q*voYhyuF*mY5I9FiD!Lj``h}_jo~`>O+S4)a0w*vXtB} zMJbMQ2BID&sYS+oQ~2tE79dI1fdtE;bfQ2lDoN;U32mT5A9<7TxQP-PAf<^!_l>9% z%%0sCiSABtTZfw>d3GtYtj7t*5yeHe^UHV}2f` zeo)1LFY8Mnu%t}XG9^on&f-_bvK1FgiRDOT-jyj`f-h7Sw!gt7NTOF$ZK*{xoD&gU z7k-o}bvw_MWZ&y#oy*8(VTDFyF2MiCf^k|e$wG+7K5~fT3%Zs;HlrFVycnJ#} zb-ImYI9c!phaDukj~7426hD4NQJjaYmc+X;k&@6sl9=YBf(Exl> z6s!xh&j>;JKML4?6Q@-F3-Z(k@PF)-5w1V1$IU0)8cj9XbI*KxZ+`fDT$x>Guf!|y z>-oIz@#<-!gHo?SpbP8pQ~g?FFIj3_yH3Q+$GKFL0b-9gA@#u}PdDSV#o*ajJkWdZIp8 z9hIix+ueoqi%0@U`gF3Ed~+Eqb8=-Pf%iEW$)7taX?@yB@=-c@6-h3-^k=lXhlssy zLShC;(3>8S^^mhkLK{iwc^$e~LW3l9uLoaRNvYj=kc8gDqz4J}b=nRR`j>=G)}c)# zbeDtwaOv(~$WVvxim>x@}PxMH`F&0K;kx0R(l`g}hFN0Uaj{nwR}3U* zhO7fks1rTAcPDfU*omICNTSmT?Ih5Nry*C{{Md=m2k{JU0-=Eup+oNH7dScNQ>cYe z_oDbiS0yQ|yI_r*glvHvQj^?H&C-Rmpw1^NksO~PysMk?UhZ8{igVYgzbA+2?z@jI z8FF8?B)N_JLbxwdvl10VrAy4QGK@(Zqhsnx%+F<*Ff9uhnhgu?O>Kx*Qz8>m228G- z$^+b$Be^Rb$)vavqzGj40IGp*#7smV8R08QmV-n$qB&8)h}RB+Ij;<3(o8z$n#2q( z!-Q!rQnF~>NpQDl-AN8%@bWi&l!p?M+o}xOihvWL)z$}1U13hr{*+>$B7Guj7g1I9 zZgfi~ytToLu@=qLm;@XbQ1_acnYQ(lZQYhA(0x}y$*=a}xz4BPY|RlVU`1~mR~7h^ z5rCD0j0Jc8q@Z$Wf(b?(aew`n=(azC49fm+;2n)he6FV`g8ltah|hz$;?1&(b+{yS z#+4#@p*pN3?}}YwY*x`hePP@m;ktW5NJw)M(h-=7LKaIQ+jJqlbs@R9Y*NrhUOVnk zCgn;UNO=hcTP!fxM}~QK;RKldH=r_<>k5JHebENnO!BJSQ}BWGiU`NmH)H2)Z z$S!qCqGu2~O`o=Y)vBv}%Xl`29bhZgNC zL7JFNE?6qrg!H9SdRNKjOl$|hwheidVaG7^8^bRM8oEx|qy=qOCg#zS6$kQP!v>r4 zY0+xQB=tQhH6N0;ZI|2Vc2eq(D}Wv=jB%ew=$jIK9bp8C3kjXc$9O4K6BJSNxZ21m zsWtbL8>!1TAv4iMCPRv~&16g7G+0*fyRpDW*ze=ZcX=s|mHol|m6y_8DOn=(g`X-b z4eGC95F+!1fh4|SzTm07|G%JWS_@viiZUX10(8;60o!)5?cn}|qCQAQ{TH=`>0Oq= zbRPpfyGIeahD6Vh=*EP8R*FWleyyR-HV?UhLFcO4{M$|y6GUx+5EDnKGgZA?E;&q( zB;PaXk$k=u1Uv zqNNa%1^X)Y1$Svjbgpj5334CWpkVVo(b(&eHo=|Rc+kmU-$6AjOtgmr?VL?Km%;(s z5%KKqON#TQ;!#p@5GlU=mPq4zmsRQk1ZzQLv4Q4rZttU1NgFF<$1OnCNM4E8jmaN` zptM!LD{5v%8nEpLsenlZKP-W)F;GAwZa{HQAEho+?zHC?UnNjkp1?QwDwDzny`h%D zK`^ZN1WBM4PRc0U&f_kP#GsR;@6 z+Z%ZkAP1wFHu5=1#~bOX*3sU!CiIeOVd8wkWc~+3FJa>59$kT^92~oRpYXgurJ+(7 z%eMw9HF~c7#|irl#)1>*cxL(^aT!D>mM?>799S5?#fjodhh>ohnojnP`xXC_yJXBV zTd_li;Ms>(p)9NjC19HI*qVwTzQEF^rV{JDWr99a;8awfJO=T(H5K24L+|P9`?hEv zT)qfHRoY^@$H?0a`o%R09H53_NT~2XGr|Q1UN1&t^l)t*2 z4_jaxmIaj{u~aj`q4a^+RRWrL22(F;0#*CL-j*(tJw~oa@=BgK84|=HOtNJkPJBck zkGl^B3*8{uT2F3M~1wQY{&#AZ5Ji_OQ6u&xtkA$0=*jF&&ESV)bxjv`N=DfBN z<=&7w#h|5fW0f)>Wf86Fp&sI~0MREqa2 zQ0pxZ_(+oySvLX}Yj3t=Rf%urfU7#nI0;NbU_XT(v`23t&H--~FRP{a^D8E$LG^^X z@5};IDeWT&i|{$85T$x($^&VFQlZv%e zfdQALqD3N>_(IGmbngBOION=6hbJ1NE1e;m6Mrlxaa&YQbLAeNT}Sa%R)zB~>nPF6 zxqJL{9VNp1JIXllKjbO9Cu5DOAI`l)l_144oHq)^5N+mzLlu*IZ6dTr{SfAExbqjU z7OK=x=7#Z~L7^PD%ZG&ie|)`pU`@ySKb||skswzRatRW#B*ZR=Eka@qVrgxOUD2ZG zX6wSGNmSgXwCGUOQmv(EwYb)Z*msKBcddzi=dFwH>zSEzZ{pM6FMs5oc|FhbnrEJw zGiT16b7ok@Lf_q%Ds`fGa1^Ui(SWLa@}ws8s_ohRaj&8tP<~3g;mC9 zp<}DCxWMU^sB%Av@NbK?6~*nFd}kFF%{JcT&#JJhrHbR~uxWtms2<^xg^-DKRG^DD zu8PQKqxnDI_zG1qOw$0o|J)qY^ zF&u&f*Jp&Phs$G%BDb=*jin0AbyXN?;{$52;*p(2QJCR+(>YZXNPXUD zOgY$8p=;~Ez*Uv6aPO$bLTp8k-+*74ak*$C;)vk6)mbw~L`CZb)Z5gx$>xPOb8E0h ztl}(Q@XPJ z%bKj5ttkJcCVQ{8>knm*9zj(v_3L94XkE|LhwJFJd_-^Fr4}2_lE$l1W|i73p0&Ep z@6|!AhsxhD|JhKaXb%^UuJMGr%)x)F%}SQubxr2fHZyA?6!JK*qQ1pbJuJZo8e|*V`b_(wWRICW}4}h_`PPVG*$yB9M8(tN%QD- z&7(U~=~hF(FWo+i(HrWGPM%MB?|9~He*C!QUEX@z>Ou3?+fs#k`Kc%Tiiahz@38dZ z_5>DDXWws@%kO&C#IJ;An(szvH)ySDxIyR6=e%TdcD}~uW71CAP|sFdRoRj}utX?I z(|EZStOkbk9b2H6jpy&QV2+5Y&_rboqRJXbcUYO`xe7YF!N?9-72?ZUu=!X!v2{xp zZfncmY{^Ejb4U2emaI#$w`mxKk-J~+Ci!H;F`qYX#iA-q!Z>%6T0%PJ7a9a-#_bt~ zi%Pr0bZrJ}7oG;as0O6Bj>bRO3RvllCp{nNc~`A6qxp$etZwtB$PHII|05Xx{EI12 zc#dGieFHdTeG z;<$eYqxvT8BYpdVI}l%&SKXNXN_>gLZzypqc(&BHFmTg;m3c=c9!TOp_e$~!jNJDmUBk!7)|K730j4B-y!bpO$b z%`n)(zxhX9SQI{w`mPI0u!Wq!=tmsN#yO@Z@?H?IC7b`}`-_<*2V|LPvPH$k!qMn# zS|7fNaBTA^CG()}tdXrIZ{MAj#V1|Ey0Z?Yo^8#~T~2Xd-pY@5XLW2CX7*ssse|po zW|TU)b@2f&w#RDC&TQnTda`i)VH$a6#;wE; zmP0jqv3EmaH~y!61b6jfb=mz5Ji8Zbg|(3i_GTl>9>z!|cP2gfaG6^lQPsw5mV+1i z%B&dwvNvns{2aqmk-NbHVM5bM^2VR5L$KCEox zzahbOCQ-b#3a&(8{390ND61tduBy-Y8%bE0*{{QoC!~eku=qP)2zz#7HP5EswX6B% zJ}k^Oi~rk)g|pGC`647)cLlQsN)4xx6hB+V=l6y9^eVokFRVAL;^&dJ&EYTl!umrk z?x|oFtJJWVoD~1HQjwousaUqIlzFpnS4tMnc6s`PDtHEcBmp@XxR;c2xOhR%0Ktctj502E%hPhRt_GZ3%_`&3d`?C_omNli$ zCiJKL>&4JCTq}p$VpOXsXDDM>%BS9O@@xOK+ck2NXo8hcl1-foNw@WI0`#wuQ zM-z&3AMqpd57?_8b8`r z;`riD6w%N=sVa-UiBuoK4T|;}FTTP_f^%As>E+l9NOXd?6ra(AM{UPaLyrP_x57E! zyIVZ|b!Z|k{h->_TauEN^A`WY2rDJNcH7@4YaCrb@Lkjvm4Q9pQp*vipqBNa58hHI zg?ZY8a#BGC!c7VvX@XCb+`OF#cOxl>RN5IdcN7lutQSFj+~rg7yyhD8nu(aEePx1O zy!@UjKxVKPwvVvYtvMT{z{CauP~7Qgx%f3d$FKNKgQ*%EOW+p@H2Z4H2}p~Ih=ppL zbG?}i(&qJ~X13_6)3r67vpVrL(`Xru1`t?PgD={Udl+3KEw+K0eEfsJ|E!WuO2a&5 zHNyR3Bs@cvY`>YON~$aN()OP%y&Ta7dZ-gsCH{QhU{=dF7bJj>r45cik);%GXW^}V-f#%3P@J^TF`7eKA(IQ_W=-yX zi^aAss5AK&0%YcRDt}+id|CZt7~{L&JcRHjVE@exB=|1*=1zvGRlC^{2HnT$oT#NNfc>`+#(%@05*Uf-%;$Jg@r)uN08xhzVz zMgLWl1B-bsaw!HaD$46EkjoxqiSfvxp)4k>Fswu_{$baLi0Wd#A~Bnh-f$j1jFs^( zhGTh^$iZB1>tSqONY(VQC~7Fr8kK;an6LaXcf=z= zeOyO2IM8OYu`ag+)ri>|!zO7hh;-l#GJBSbVG?dkv>j zL!M@L3fMp`Xlv=_h|U_SuOL!=))cA@HD1=M8mS=1Dads-_zb<5z$^%0&}mo07n$$m?UXsceesk;nw(kFYOWyO;r#%+n!iI*Q_@sg^a<61ksl3 z)f7g$2T6B;(zS21baO;U4XyO5K8b^BeT~obs#a8x<0Rxd24je|h{lIXRayDZ=jm}s zcZtauEFjv;^JH+$rWWmfaY#W9r=*oJC`YvGG+x|GyI4a$3ZingCDy5$wX0QO`^8wN z!t7NftsH7mJ&S%8Zt3QTBn_2N5Yawr0@ZpN|JSOp98q3Dj^mK)7*I8fLKg0+Fei4W zmSuHhOH9UKKG9xOxVg-t-7gMk)$x*6#(?Ts{A%GITCq?=!xTi$wjtIjnzgZ{_$|PR zk<(~}*(+-dL%BCJ6l_5`qLYSf7PMV7(a_ykPmfSVL5_10%BF!?2@UO)kgRw^L+fIl zGC-tu{~J~3i!HMK;(~%4(=5vzv0p=`4?j5mGXS=0SkaWgEN4dC>oKR!nkKhK)GsnHy3l&>a{^18K%6aUeznaje zH)dM8GRgH;>H9{#YWfYtp|b z_(#htW9cOd`53fABzt4!MMN~6>J>MoPRD^p6!N`R#ffMYR5Kj7r6@zcSNsV^Rjl=2 zBg|W6m|w%86`UK}ZgB zv^a4P2^ERcv+`c}wjd*_p^*)#S)qT<5z8rW&mHnyrC@fj6!v3_7DYUgN~~_KM@z^V zXs2Z!RHd7pAr^gJ~gv+T<2!Io-{ zc#Hzdo7>mD9?T`$^EwPasI!C4ihT;Qf9$250;yPI;g)KS_*O$hyysRcVx6p68!8H7 z{ore-@+#88wu}DSyqE>$hz=ThSzkwQyJ!qZY@aEu9rp;2$|1)73NmR04`D~WsTwQ= zSS}luy2Fgy!1-6JY4pkKGgNgA6g(gXq zYJOH;fa;|wsX2k>j*=}d-bZe*j73;eZBxr9 z?L-%NEWW0H(fCJ$@^mocH*6Q;ieEeZ(mR!+Hge0dVU^5n^hgYO{Dt>9Fi|&4P1N-% zm*k2#DyoBqnr%DK$R?O0N+LJ%xxWh^i-?^_ZKQK^6TPRFwGd~}w!Qc#Vzp(EvLuV1 zWN_vy75+>#AGtXj(WfDS6EAHi$x^z|2-_UR%a3C%oHJoW$;It3y6BN9eAgX-iRPpS|>)BT46l0(1m>Zr7CxhQyCqA z5$U^;l*Zy%H^sV0Dg2}rzVRshtQ96Ig@h8S9OFF-)3icArSQIH=HTWli+ciiZS(7IHij^9?4-EoulO_i! z^FO80bA*1XRd;KUe#Hn4uF~KL0+Tg3PlL$>w$Naz273@#Q-kkousMOH2&D0Aibe+z zT0pBNYA}hwd%EPVEgGFO# z!MIr(Eu_(Z=uDibK{_od`|AY0t-%8tJWgPD4SuV^O#~)rFhhgO39PQc=^CV8rzok> zk2OjQ<_M<2k2Lrufp>IB25Ybzfu{kA@n7JYkLkcJ*scw|-)U?HmyNwDP6teW4WCI; zKRF47dl=Ar2T83o^#vF|;|sq~Y|EH}jU1BA_o0`s)5qWh8~@ctP6skon`uUgamW!o zw9B5A)gF8g35u|LwJ59Vk6>uuR#!JKBrwT(E96`zmjFibOG;>U=nw{X7yikRTozREKY$BmR`(CM%EDdla3lZNbg_cf5 zv0PIOmA}vk7N2U0?h*>LPtH$)z1@_8Jy$7^O-Bv=Zb5+}RzrI%C|E>kXe}V>f8dpH zXzY8h8q?5M7KBZm9d~;u~I{R1fl<>@XvXbELco_l`K$vprLRt zS$_@rTTr0rprLZkkd{pBS~k2MX*-eYe9j+rlHXm6e{kzR4m@G zpb&9RL+T7biGsym4e2w03b{^0BP_#UvDm4xo)#7;rfI0X1qF*wG}O?70>w}bRk5I8 z(Op9&EGSU4(2%bM1&i7mdW>#@`hW5)Ok?9E1}Ib%*3d8u3K7qys)F~hpkQ%bLoF>R zP#n_`t>vWSnx&!178EFc)R1$mg$0Y*8XIImSi3?)-7F|rysx3=78EG@X{fFR1&bsN z(b`%1OlqW|Ni{VTDk@FYL;aB!79xskwO$q!EPOPSXhDJEPKJubE(;14XEpST1!1W- z4Si=p!Pt688BSM_lfu79V;;|f#TOd#I2I^A)=)n$*$@pSSx}(prlE~BJf&=|p&u+L zMAXvI45x(!i%^YCw4gvyNJ9@SC|Eq5qWr&TL4o3$hK^WJusEurEfy3gwrXgV1qF*0 zQ`G#O{TmAl6kMyhEGSr{X=t1U1&a4Hlv~|X%Dx)<-GV|ydkyWdpkUEZLqBT>*B=q7 zu`r82SQOJxVG9ZrFF#WS|I15uTSLNv0>v2(ow1-`v0FpCEhtd@sv&oE%wOq~WTD2E zc-1lii8_@2(aQgaKUV&*>XR|e=(AqJ!0;S@S zvPKzAq=cTl^LFXX-}$t%9;m9ph!~cxM;F!|6dqz>?9jt4Ltws?*+(50P1{=I2tQcB z1NE@Wyb1-e4~~1G;{>e-Bzi$r4I_@HNsX3aGzjQzmWtBehhqMPigUXvY*P8dbQa;v zq2m-qiSm+uWk9mD!CL=Y(uq+zW+5r{3^@X|&PSvZWVuFZ`0HDc)S^$2MMVpeTK|w?pan^-n*{xns++9TDno`h739>x@FVP0KDCWx zxzmCq*$)J*u^?H>XE?>gw-zKzd7U7a1<5Rr5j4(%Bv%$egPj&8)w&S>>lP%{;t6VI zK~ie~LDeltYIP*2lm$tx0D=lykktB{vV8Pu-dH&4cmhvbPAT zU_nx=D?tGkB(+)*^!KE^{Z<`-)gt~J3zAx)1pQ<|Qp--zJPVRqFNiC{f~3|h zg2q@-Al;{$rwJQqVNz`$L7go~lC38w4v@GyUe=!JPoI@1Oppo3U*UAEmygj#L9VXE zjg_W-Bs3|fH^}0D%EF$4gv@^cF<%ENGBoo~AQM6-7E5ONY`Bbv`N}6UTl*n=VNT4) z@GBN-<_#KLNuW!E^btI1E+ue`2AwxG`YE9UH2R^oA5UPC26t-BAp|ziU?;8Fg1`zI z{6=fmB+#zG3=LKx@VSo5aHmFt3B9J#ej1dY;2+jta}A!Q@A5YS6n%9F(#m?K1ONG0 z)y#N6Vg=-?8rCb=URZNXC63WVO!JigL*5ja(S-Kb*<~X^;AL{Gy#{~O;1L4rlMm-~ zhBnHuvCNPr^gH56q*Ky|lFe{LFNMfQ0P@X{`LLUg$7DQp8jCNJk?rrZiPi(McCgvB z5(c!>M;gC44R2~k@h8*p0ZUb0aXOpiT(-^MNA4Ash`ouQ;SxiyIn$0^7)yr2Qf*tr za2+N=xDr(K2y(SWBPFQn5ggTmdP?w`N8r{1EQ5?ZoN@1ZBy{DaOsXl9&pd*3Ex>~h za_bw9V7L}wMY;TfE?N+y1oJ!=jkTb@5^Q#QB;~b47xAn|;HL$RmB|&4;2y3^IvARxK#61SM9X&r|_kq9wHH3rxa2CN3>#pah{F!TVaEOA_l5 zq~y2g=@G=|xA>3*PCT)}%V%2BP-QaPV^UNLw8Psxf+x5RQQ51)q8A)KWJz+*K_Z`+ zsdpz6x+Y{iwq6FWo#^UUPTcxXomj_8VMCAb8LR;o+3qoeEnpW9^XD^IJzFJSVu0XR@Yj$st~U7JmC5;$vpvbAVxd)-2Yp*o>q4fml8Ht%YNLA^u<%s~vV>jH3TF zw2;p-+bBOe^TJDGMfHE)8dGORcJGSr0bjEmTM>G3RIdMX_Y+=oHmik=gZtu#UzpAO z_^jD1(YBOdoXuvjOCPvL&%vi#w)c7QxyJX)eoP5!n239vf?Wk6)dK_pDF7gKvno_JrPM`j?S&D?|lLY?TsvGj%il z%b>5(m#%U5m=9xHK_0Y#73WvK#plpF?Y!6mHmP2M9sL!(s-3+3VL&lF5$}mtMuOUg z=GLay`ZLWx-o_KXfwa6uT1AOC-rB?WEMTtao4ft7>l_4A((x{<%h3*+35~v{2X|Os zU9Li4uLLYL$A-l}G8|QT>OywZ(db`)tf{gx+XoFNVB6Nq_z{KpkVW{o4 z8>_Q}hknQU7u`P;jU+Q}<2yDVv0*5m`W+i!Ec(lz-~Em?2>#$Lk7sM-+8vH3IlR#l zR>n5l-E9fJ8fTB+;uF4SRg6FW=g(Js&z9IG^G-joYBlN%wo`$===YSqTyPXeGI8Ku zY^seFi{Pp&cxr}Ol)y|o&*n>iU~SoTlRx}{#o=p$+Dq95+fTgcGJK2q8c$osx;tCp z?E!IWIDM`EOmzFu=9By65iy6QES29x1VbL3?Q)#O8uPRl`Ruo;?Z=AY@;9s+X(8E1 z!T7#w4Jl^EEM6l&A(;d`_dCVt3thDU5uJC4&G+1|E}ckXq$Ukfq^EvRq=ghrAN*kN zN|1I0si?(2U(VjZCY$$`v*Fkgx%Uc|82sE?KRvRRd@YAn6d?SVgUyqfhW->CrDK5KVZE}GKw z3wpf!2n|hM12IKmuju||H7+K$WqjXHu$yyF+Lhxae#S@2^LgK&S$Y2$=JBYo+x0WE z+1T}7{EId4SYTKd~}}8emvuh>|kR za!`HeS5~&jrT$h=J&@1-m9??ICH3M;yoQF_jo<#2y=xo7`>e&vI!$=ST6}m@oo`*s z5{=Jp`g6Z^?0fd|Z~oIdHpLdl8?I*`7TWO)FSr`m=7%?Nvj~21JvN7bkJodvRgSQ3 zIM*lsQ;rtco(7+AP7YbkpSsxy+bTY21FK!8c`v+|5*&dRoxc0}%a>U`6LI2SqJ?;) z+&^t#y$l=UMYgbkwm0}YTky$gD?WV-YYFh=78YIcY6torF)f15TWpMW3e!lDXP@}_ zV2|Ba(RYvvOX5|wviEI`-1D}wiZ+|Wot?#c*qo8=v9onoSa0h=?-Kmk4o98o=sb?l zc*273SYcXvMoSmrS6tLWA1~!b!gp(YArkuSqX&tq?6m$1mGMi>vkPhQ zwbtK)gf7KmV=AFZKWdlW9vixna24TW(4s%AaR5c00(hd{BS%Qng9ebD@@%qtq5`JYVAnKOF3Sg_T^Sm21Xz~MO5(y3y1*azGlXJ=1fOlRBo$TBKAeQ&6ExBE zbEAUQUCi^+tx=J32lvM~5^qE5id)_WeL^CyvXhlzNeg-Fovfm9ytSPV--%ThFTu}^ z)Xi+7f6-4-UoViAdh6%~kzBw(>T!aQAATMB*WOmBrc#LfxG0?__g<&o=1bVfA@H`> zDETRpss$KSbf0)&qkwJmanfM{c}TZGQ*k6l;2**nLABD*gH%^8`}#7m(f; z?_1Hfjp?*FfXn;<_2zO6#s5Y-CPb!Y$2QvGEbB7+FW@D1v$Cazlh}NN#0MtSOx+&a z=zz1b%bv1;ci7D;m+1|GEyrbcRtnXXLZr*yW&xkJn?;sM(hBjfDl}NY1NN{=rROVx zpYe+vyaY??NdC`5Wa_MhW{SpO%+84DCO5^Hi%duv>JRT{J2g&1~<*}dCe<+Wu%j0@j&@a~PERV;?<5BXMo=?S^ zAt-l1+6#HyL>@Om(~mX#%i{?J@UdR3nI;q2Qm~AZ0mG6Z88NqypkMnZfllfxSgTV6 zHkT*0Uv4fiMBq_C)f{vIb{{y1mj}%P@;BLhP7c(>bE+Wo4i3i#krOdU77RAEYG3Tp@px&2LFV(apf2 zaxPthU@I%xN+7-{tNnd~(@W%G^K(j3is>(ZlT9D03Ptw>HKFKkP@U8S-Fe(g#+uKO zB)Ss>r=O7bzp5f`N=b@&SNvVdP;?t{sOWx_An1~%b6cctkbO46>7Dbit(KA$^B4J>Z2mwRPDM8t$BHhK zP%3K)Nmur3Ro0IPPA{5=E>KER%wYMOZ2I9>JNM5li|rncB-;txS;v|_n(aKn=?8&f z@>YfJAs}kQPvvj2c~e@-Hhc+(%DH0_gmYg>x~oz*$eu-T`cz;R+X*R2F;C0iWb+_t zsG@GipT1!gmy#57ll)CKSCfXK`woYSZk7b$Tv5qZ zX{L(Yrv#@L%)=HUB`Iboeo_8pGY|)w&cvajdp3hW&>h4=p0TE%Gzzj`Cpdj4Fw40Y zKqzwm%HL%3p){0{yMaT+c1D6=n=09EO5Gs)ZYSaCpXQ-EFGVTlW%-+Io*)fX)_pis zbekmzxa=m`I~I6Ck@5+BM!$pW#2SkqM$1%=~jQGLN}G* z^nbDVj#bu$Qj%gWk-y32Z2W4vOdKlboDu}vPHBnr9zd=e3C|YhuDGhPcAqX{Z;dAE z9-{hH`5QL#yyy1fuSPW5Nx-1%7LMo!_G|w8QC6|UwZ@oaF_NRiS>Rb1uR$1BmIoZe zlB5gsK-b^FAj$bR^Kr$LNA1yMhT|--ZDnC1#z+BGs}$@f!3q-ek%Dkbu!aPGlAxs& z)VBn)Nl*b-E>TSiIzZr*T$4!Bmn6kVV%~0K_i1E)P0>ty7q_sRCW0>OCH`Rqc&{M} z=W;BB6qgAz4Zm{TRaeXiGU%D@itCR9vF9(MS|q7HCP~`hSE-gX#O)2Z(jg|Uq4?EY zuagCFwZeh;L~?bPTtps_*7H@a6y?@1NQtT=el=ACG9ap|I1tg2s)(d&HN#0T0Ss5}}PE!A2;%VqBKM zPJ$J^Viu4AaeXzN|9pa#bUL8wim5zJ*UwnYXKthCIO6Z;R6j*6L1z-k z^Xr-vL|TFdB#_m!UkaL7g0du#)w4zlx>y2V63FVA{oF}$dA~lzblw+KmZuoG<*T33 zF*3wIKuX1!ieDX<56FPZ@+J;M2g<~(EU6}uB<S0}=6lO_Hrf6J z+gJO$xv+J`teUDTC}=2xM-wXIQ_3i2rX@H%#ER-r=|KzW!3c;0c-m=J%-LEwF%}5r z-wVIG04+(03eW%tqO3H3UWXoTjBXtLTb{nx0EKmZZG-&8)yFhJ?)VtT(b-w|;g)=c zE6xJ%l{*n_i$1KB9O#Kv2fi&@218^}iSpPig-!Ht(NC4u9BAcs)L)?GbHb_Yqfn{& z?BB{~pF;M7@j%CNfUX3Jx|t#fd6l4YK7vw2@NOOgkNwNqvH*Us+TVDTw*Y6LmHV;R z-bq(49RWq~995pL1WWS~%prowUIMSZQ`rweZRN9nE1!J|*;l}d(JBJYJoVU25hTA# zP&pq#DI%!hA;@F@vX+cM;;Z&Ia3Aii!ZXmyZD`p$^ERYyilE`E1WWS~%prmn7C~Nn zr?Ou*<<$t}vri%WOWNLZ<`PB&Ivri%WdD0&9miZ%~2u{CBP&pq# zDI!?F0w~D?VeJ3e1=EswmY~J@_hunug*k_x* zApdw=My?!_H-kN%V>ZE$c#SW+fM<_o_{@u}5_|jx-*S;fcDVBf7MB|VHmLoMV`Lg~ z0@?@Y>Y8a^PvWI}Y(m-RpJhl9=%q&57YoCH^yB^Af9$75aKzVlCK-$$a4@ z7HO-+cVA-TorT_&&uQllKu^!feYGlgB8YN-$K;55D6hW_$CLOo?@0YL=qt^#&}`{S zCDYuJ=rfgp`Vak-k^Vki;se&7%J}PP%jt0iMpwi2zZ&MR)>g;5j(_o-%Pgk;F)TJu zf1)tJUb#v5uL&~EDyXAGS6mKMaOtr3O;eYUN@@Q5-IBJFxMvRXBf+RtDd=1sKw>#U z7JrIMOUb{@2IGYEq;3CKB~$GTxDN5 z&tTPLTAiQ=8is5YVGZC`OPA3qJ$qaeId7ks@sFIk-(!YAnf~^e-|8drR~2e@ai{vC zjV;A-dUl4H4&mtHYGU@0nw7OGEiMO&i5WHChYH!YC*$9*U@w~sil{U;n-PQ6G0h{1 zMe4W$)`FE8Kz}!u5*4$6yW+|a&n9^o$hTf&r5YDTBE#%oSr&d8f-e_Wu8p%0mZi?a z@^O@OxzA-Bca^3&&=v!D;B^+_TuYWlbE-S4Y%@_-SztUGPLTPrtcJjNw3;AuHEtuJ z6^~XEWL}w|5@E=OFU~ZG=`6 zWd5Ly&}xFrGuj9ZCdh1}GomJBzN2l>dVrz@ixD@UC31P3RicvoCm* z1C3m~ukr^AEx#4VS*T7qvk#{JV4`bHycX^5-pX zuVoRQJgVkp%C-ChzSP5g@Mt^?x1{-3u!$B*-PE~ss zxuov0HM$otZ8NGbBW@r7Q5gLKc2c1|KuY2e&+qKp4)8X)tWuc;Z|Plh9tQL^zd8V4 zDni3$`fl*&Q*v3g(64$U=Fbsy^?Y>dPX;!{*@NCCd!0W&lFO3)j=d?{%qU*#CVSm^ z4)bNAHTtHut|k%U9!@gskFk0%Rn&OAe=XD4N@yHPM>}Gi1u(Hm1g@5g3CWCk0DBV5 zA~zgc*Hio2bt~Qqa^j>t-$>KDA`TibD7+upqvMeJnH`k@Ud~E=Q7*$V0m*>02AEq- zOusW~P*P^x@U?j7W#Geht|pHX(_fDM8FvfC@S;h)+a^mX{8t-3?K28IOB`q>toP>~ zZ?TGHcl1P=y2Dd!dE$q#rvHZZF7S(L^;zrBr`=)|8V-Sutm@RA6O&vuJ|9FC+yNWG zz)Xz9+2#t{unt(p?(}m3X=)kd8bAE%&vS3FZbj~X=mTyAw^?|c@ezR0 zMO`yKM`@8?dMbWh4m&6kQg_<0imqQmqZ0v{Bu;eo!A37&t%Yiu7&qlZ{{3zCM$ChO zvR-CZ#tB4wXL(Nd497?WK{y~t%6Lkxq5=-7RqWrxo7`b#%lraUc`rt-;`==)L49br z%+e_B=sT=0-uOFlhs8DRl?>7PN>tgX+A)7X_gF3VXLo+`9;;q5 zHiGnN$!>(QH4+rcyE}QveHK>2-91kz_wR(iy`aIfD`B_We)n05abmSU-+Z5SwYA`Z z57@L~$4BW4*L8W}GUtzSuX%vYQ%cM&4dbi}Hk933nnXMa;g=rbA?f3isN@usXSxc^ zbxOy2(08vnEB&>U-snv)q4ZpDdM2fZdedo?Zcl0X##6AkSPDQ50?-L| z>IP5C@;Q&#$kK1WPiOF72^lBk)p!i`B$$51WHm4Um_4y|F)HLc8U9C1pnlJ*mL;j;(Y!8*ed_;IwGr%Wxx^tH(&6Cl`J(X z5Y2z{N;rl6Q`1hPKT&b(%@@32C9AZAICmRR9nN}E{b#Ly*`prHFG0O=0aD-NRev0S z^d&smuxbx!*JgSEO}Y7a%nt_e+JCVURo0UJ4X^$ZslP<)kApyY_qqF>zt~c|TxfTP z{Dbn_>hV1UPyy@$dj`5~XP@2Z|+TWd&{+~D8 zYbkxsn_fcct)8?~%p_o-2M}qLZskpnqI6|%dJv@xdDC4feG6km?LbRPAM&Q_QTiuu zI)c*OtTfi`2qvI`1>pS|N|*Ac9~VOUFK-Q9r}PDH`UIu(RQOIxFY?OQQo4;dy@b+L zbh;&4|4agkcootpeaBm+qbPmEn;t~zHQsbrzSL&aGuF)a=ND~87@IVR|6?`!5DUBh*jkX!r z#i?RIDRl|gEAa;=3|uj_2J%jZ(E@Lo&oGR*m`~x58roX}d~<-W%I8a50CIy-TlwWR zM!vz#0>%XA9dzsR>axe&gy+5VSCvg+x0X|j#5Hv}Z@y#IjSIE$yW`pl;_g8C(nEKD8G~VXn!0!Ap^FBZ92)tqOv5 zvz+PZ05mn5Y6#bzSQhX_5i~ohCE!#`LjmVLL(5Rnh|7J5mv2QuUbUbx*7;6B%wVqX z=98NVBbq)iGYin|PE7CS=rZWeeiG6y8h1}@OP66 zjIe*OwcU#qhU#8NU5g49%`U}Ur(yYQKEU6oUOm+ZLsi*|>d2-SH}W=We1r!L1*%5- z>wNh}f1?&#@)f`7Z`8BZbBEiFpKN&T`$$pab0d1X?tN@MRPS@_1)@&9k56|`@3RpY zwd*_G{%`NI-1HXP+xzq&)YJP6#*VM9n4h}wBY{Q>cGKW94Wmg6``h=`?&r$?R7OF_ z=)bz3yIocHGoq`f`)Q0hfBMVU{ZxTGZ})TIg|F&f{y(~(<;eH{ulsq~<-fWg0ai4e zLsIDNevWlf-OnfdSTVydJ@ ze41FDPZFAyr}Me+6tK7R`3*@|phykyc0QkB)GH=i8RhSM76uzpZ0A(IKiKGJtHP@~ zj7jWDhI^I6sAenq(Nj2w>&L_w=BNj|C+6a;F?;0>;`d4zRk0R!>5|4AcIGqpx{}6X z8$NeQE@eE%heI7o8`Esl-4{w54x3-kdQ_UYcle7kM(Mx}l*xpP&IUMI)0lqmLTbNwG$wSJa z{7FvUyqvMi)}H&7H$GseZt?NujZ$pOEk3)v5o-IEuPbj%X0MIsaTSbq?7&T)QNbvW zuTg%iV1$>Paua9G^nmrP%i#mCkkre6PibAQSMK}#N(G~&ttbCq1tW&_{ls0nB06{5 zcf4JM5m)hmpg3U6g0Z+9mgC7BUS>DPPxtc~`y;LUmC9E{Ub7XXXZ%Mf@z*1bO14q_!$_ks+Y-;$MH)5P@(hf; z&JEXmefFdtrH&7OxpVO^&lMfhC%I9w-8m}AcXG^+uIn=puE}YK@H@V^am4(?!`r?z z-LeMyGJO?kI*>E&EntSYUwj)?Wx@hq0iawpuJi4cjYL~pci|`_#Ackn;>%xq&Pws7 zRg7@kG~Tz05pVl}FQ{Uaur+Y6uVOT_mHO^7O`Ha{q8s(vbOphBjfc^Y4PLaG(SUWi z>~2%d=x=i#Xr#M>gLs+?C6gv1%U<^N$x1>Q0bIcKAOUxg4=(}hp|I1y#1#TwSHSNC z93`NO0!|XJjetZ2{6@fP0$St&<~s>#qmZ+t@;L#`6>x=sj|uod0XGO31^|AAq301} zNT{7U2O;)87VQW_;~=GDWYCBJ?ZzPjq6koqI^*^cQj(C)$|jqDf&j3^d-}c!L7>?T zC~G9d;_gM6dBQ_v{sPBYZz!Xc&`8MGB@PodSYfM(Xe$A&6tISXl?1%6fOVm`D-?4H z(Sh7RDpPr*nnqaVG{V)xmAG$#)24Hx2Z8S@^SPwlnvbt(Bsv|0Cn!8NjGWmiaxVbW zv8)YDoF+g=TLOCkxYB8yNwW_v3do?E%vuftN42si<)RW5Z1m`nsMK1p}N z{3M1<2-abW&1a;jYq}*A;k&=+NuZ8JJXyERL)Vmmh6J=#{1T`Pz}2F+(V{=Tn4+p^ z0=2C6Idcgo-!r5r^7c_}ggWDUiU6MB^?%W*(bH>UwI0@7NkfF8zp zL5w9;?Oa7@Vv1U{Crn4F6j?VWKnL7GKotV?nMi(!5CXb^9vK!Tz!w0zjJTUekx6lq z>Q>+bDZ*Yr{CN(b4(eajWKIIWg?>m3e_Gp!Z@)w)<2phg(@`Q-C8^e0W;_+6R5C#( z?~1`lx>7%f1Jq)wq2<643UhbzPo66RtZHGlM*1V3oIy5ks-i>z71s$Eu7C;z94CM}7}$jpkVAkz%^d`+0g(GY zb?1Z54SGFG(I@F0VI*deb|+=t1Jhn2jkF73DB!tIg29ClIP@0j4<_+a+`8v0<}p|* zhg*^S2QtqahHVH#)>yKSr2Gb<)sr7PC_2G}j8PsOAiy9%m-#mW9-am;T4|gn;0geB zrm%qY0|+d*PC<}0kh0hTsY~u>pZf!3bYmG;jHDDc@jaxuZ@p;uiifL7fu1*ubjppo z378QoMv=vv#N-`8#hoeveY&>bQ__+3j8Z9eXhO3Iss4@9=daI;6h5i!AcgN=4c`z7 zUk4(4f=ytpQ+*Is$MsYEay?^n6s<|JqZ`iFM4-{xo~Pfg8oV@yUOfiMClXgK@*Oco z1-9cNzY$}+R=ulK76Z}4yJk#81{f+}avL-qh`c>;Oz*xO1>iM4s6K|C#rgF5M#)0S z@EG@_k1z0_>l?v^+N$KK3;a-hqjaG(m0Wv)->+{}2>Tk{X|9Q}ku0^&ptjP%gOmnF zh~M7`B>1}WPL$fKQuX*dl%nZa$b$JaqzX|zQqJf3&kf*6Yn424o*!*slwqIM*WN?VbBM`7`VC`V!}Ic1BSAAQ2Y{+&s%{F zp^<@UZt>y_&x$oxG2g0uL=y~AKjw>@80!jO7%RtkF^9&w2gDipq;@t>Z)$Wb(j`dS zo&B9(Y-%Ky2?8J=IlIgvLC73)?K4jL;?YHXWuDN?Xlbv7RH94G(^TYNH8W0@E>cMk zothNH?&P~|i39)5qDN$6g?m!G@r;#zQ9&!T$Au)X!W{QkEsQ&?_n|_V=cjcf z>Sjz~QQq|qt6K+pG-}QDJ6_a%8eKiAqQTc!); zK+nmo9NyG9Y&yh)+ZYv##fEDi=M|%q#f%K+ZQB^(Ea#wmSQ}$wP@yk3%Zo*#omWpW zB1#UTyA7B5Nf8MKq44>qnCrkJQtDvw}ZWMU5v<+$lYcFKk8X;ATNw-hhAY`NLku zVCE>yQ+gZCf|s~4sHVHPJz@1js_U)kHJ7##Ol-zw8{quR+hU$(pXwcf_J1yde@ zr#1hqkKx8fMa}veLkhk2P{!`@I{tHCqg`ZaOuEZGhH-<4WrdR>8WQo6&eW!milCu`M*HNZ{7IgenvdIoaCuubd1{*aD|38xae8vUSj(ueCve_BVTXH{e~G8BG!+^JCK;#?Ycmb)Awp3Txa_?<#c}`U-7mv5POpr z9d5)0?V|~|%#VX{FNYxZwU_6;ha2&cms?p`+E3D#F-HeY4c zkh048Rweu>F@qNzVH{?n4gYTgj*+-w8p6AcL=%hV*GC%l*^4+H`i{|tt%-9F ze#bCPgEjKw6Nh0N^^567pe>NkA7ym&|EMAAsw-CW2(&Nd|Bf{^x2W}a zy7bq$82A*&oZp^|w20{lz4#E2D9sZ_8|7@V{Eg8@MRsKt|7^4|-Zsx|jxm}ucBme2 zJI?snHpRVboRMa8E|{)L@H5#D`cfs1QX=LnmB^;VxV&m{q~_WZF`Fbs^Qyg;SFMC* z6Qp*75(9hrku2-v5vbHEfYze+=i7+~q$uCfeA`O#a;`2DcS+ z$Bs9K7`FG^OC}lx8Oy20SAJ?tF3`9do-c0r*cfL9^mS~m%70BY-Yrt-5Tft}^2>d| z5Bu6eO8+()kuuQow=w(^J&@eyOEtEL_fIpTs*fW;eh2*#j&cXmnj#qB4oS>Nig-6M zV{HVvnuNqSbPsFOjJMde={zjmSYZ5IIZy4etjhd;y79WbDjp!~>g!RNzZ_+hsA0dX z>umhz^GW>Z${?i*1YlOMp0VB-cE7vgYo=!XtW8UKEEs z_W|ytaJ5szy((E0E?#AZ5o`N~51wHxVMBl8MP}mGzdTQxiO#1MpD@$tZl8^7wl3DL z5PoH*QL$9hF?q{y8<~pvfJ9RsHp_^x_2SKE(Z!L!Im;Ny4vupFKFj!q*_OE9n{8As zz&;6dZ(nGvvANrSYm8#HZtm=b#?u0U`|-5m%~rIE4GL)A;>UM?j|r5S{Lc57K)F-U zUF8R(G%HwTIr16F+b%N%yEA~-U5*K(eFJ!(4W%c$lJ9%r)sU%8Gaw^#HVjIda~c1^4puy>e6`F7UMwa zCCiKWJi?AWXOo}J@J>h7 z(bOhMJhCRWCX}eF6N!|l30TGLl~44=HDGQrrRdD>m9*W-N?D%%MTwiqWPakjj!0>=lMs+z~Q*AIKFiU{6-v? z&$_=pjxyU?^35lV32aw9k3DI;%l1s?t4`t+{+eGpX)Lne#tj5YgRKox%w(SVyD_WS zg^!Rc4Ruj(IOl|j(fRh zcJ~B7a>Xdgo1He=vg)UJ^i`vBbys0^We$QWW>Yc5!T**KiDCs%FTi@<5oKLkSy!KADr?^wuUIX#yi!hd71L^} zVEl6dHF@~3qDeX?ofz^f)@P!zR-@{YrfWp9RgKAABf4UNA2gG$*gkcK$_9s0-jJ65 z`3acl3d20_^ZZZom~Z2TLo7i8-%WW!x6lTWMW3u$M3z-<2o$|6n` z&ORRrnFEP%5{L+*-B*?OxNL;A8LoK0c~6#3wDs^7SG@338Y8-g#Bme|Dfnh#2gNpE zVi4ba8K-`Ie)+PIX&cK&UNOpqKRZa;^1N*dGIidvD(bxb{V2DcHX?0p_~9$Y09!h* zebuO4aafctOrp%7p)jn{DWHjX$opI~>hkqh(aNIt%jSV~)Gp(MEmXoswT^kb)-@xv zSQX?1mL!Ti;@=Q^!NRmZ>#>gq+`vPc1JtG}N90s`MXi_RgR*NP+C}9#Kl@%s#u{(^NKHhIvSkDsG@=a-+)Z^BlPYT=DnUB)E|&SzCw*J?(;mfFW4~_A*IPXFrZF!3_6{<^ zDc4!lMb_$+dls%vxdVITDaV@RsEg%;o9JRc+9@xVwaz?Kjin!yWjX2cVh-GoJf2#9 zNT@48*Z8})(d>5JLfu!`uIhf_4q5lHzM3hsY+j~W`zVC~qvhg^Xd*%^R;p6-eTaog zto<>5ICk48&vJ4kU+pSSm1jSNX+CN2>s}bNww#sF5`yZ6^YV9b5gUC6uD!oaxpsfM za;?V`MRg%mQ_*Q;{;@~91{S!MD27;`Q(H1~AjTYOxw~*}CZBW{t`$>!wLg5U_+FOL zeA2)mgRd=bCG>bVpwuva>>gZ;yoa0293FiiO!3*uv!#y|Qw@trdKLpS88nc za-8}mvly{{&yU_iuXcTlD(V~CRMht2wIVwCJC)Y1NsBJKVDYDwhlE-Zbi53I?>2@Y zxatHo+bI5&mJhg@uehRSE@(s>4$R_4t9-5!dVR;>EdfDdUd{={h&c- z{5|*~X2Lkn<+qXY&HQmQWY-~+XMAOqgE|#QUO+aEvOAqJSF;%P4XvyqdCq#Xp!7qX z_c+}n`l}Q1_<5UZ%uTHR!8|X|kiZ!Q&_h7;ML(sv9Ga>}_#i|FOtz`-p)Tr`_H}9= zD)hI6m8bITwsaFc{NpvhpW>gZ`P+H;gFQCH)tb0YS|yfkr5QmvfW%vtYSM-gm^C05 z%(9xFZr0&{6U~3?oaO(zlG=Y+yEk&cGsfbV&}D)K62G6~H}?Yp_YAr(0;lN9Ydtas zV}<5fkMK6q^X+{1BcmzXu$>otj7g<&ywzh&DmCQk_+ihp{~uY`0T$))^tsnvq7JD~}EfzFD44!9+Wz@u$#3c5TC~Ay=4UN%QV(cx6@jXlI zEw=AB``#V+|9Bqm-JO}8o$b4`v%7Q{P;FNZxj!|qPYvu-nDzCg9r#e~XPBG6dRiz( zyS|g^JOkNk8vhLY`!i_qGf>CT*=L~kBl#Sk&nWsifQdAoKOVFLAMMD`^z1nT0{Ox~ z@(0Klu+CUcQ(hwAFxtSM`gDmuUgZ5B#J{+m8va+PnrS@^{||;NE$P^Qg~GKXd*~TM zmr})7poNBvKS&dQz!e|u{BHW^6=vYb`QWF$8$er3;@iquQ1zQL*t#W8 zeh2poxa{|NFLORDR#$j&*C9{$r>U9<@!JXl&VZ=(g~2d*0d3So8SUT}`b879wCrv4 zP7{?)bE$G6v7vaAbt;$6n`g255y502-7F-w6dkjTv$1GTi(is5PFP86g{Xij?olCv zO@VYvh!#z({k);%>VMswu^piyLk$3w@#`b_Gq0ipZn42#CGRi*Gh#pH0Os@`4eH?K z-ZWN<#*tnOPVtD(YX;pMmj8QMHCVGS%G4J7OJ7j0@T*vvW7OSyE zPPO!2&-@4N|FA)+ljKiv%i1)s6hTRUAqg0yGAYV}z+ea&XMYCe_x;mQu zYBU^l1IrfBQ=qleZ>d@lQL50|uHKYt78Ph@5mCwOLQ~k{{ELmu>9nGV2r4TO+U>9m z#>a&6@zbLqPR1oGZlKMSOX+M8QM+6ZfRe097LRNVy1_)?0uwST4t$M*$gDW%HHDc) zXt7%GX37}+@>YR|zWWvRFpET7g#C?KSV}$aj0nu>jkrO;a0O;T23cRafzW{;ayq%^ zIV?|r(oMhl+8wtccAy|XQ3mF(b^S!Oz+104#>kWS=%t~Oom})W+x8=;4$IY@5D~n)bKo_knG1|wj%pVb zu{GBqjL~c&|3e84z>iU$W!a@(pH2q}7w6V5Q0?q5>Fc7RdKG&|q%`AQN(t5V8GK7P zFt&hl^MAl zUu1e3K8K}GN6IZGLd)0@!ju`=-@ql|lNnjZ@RXys#YCyXBO61&wavlr$+?($U?@xRoDqAa9X%)^DrpmDQ=q>HLtTjX7ZoCQVN1M@*e) z&K#@Uq<~$~qxU{pMP}1nf6*M1t+W23n)f#ioe{62A|*uwf6wPEG|Sx6h3ZDVONx>h zXUCTm<+K+|XbC7&*I;gvnE6zVu-81D+gLrGxI6a>*fek-ym}w}uoc%bP(91w3q}37 zmK0f47~?Fv+~7hsX2bynzkk>gqsPC#lD}9&`!Lmn6)3dK$3_)1nU}#=2b_(iLX)gM z0b-(ixjzxK#=ZD`=T*u0`Hq5mp95D~3i2y=vz`Wv&$QxeZW~i<5WUqVE=c3)5b|J{6PgfzPO#c;iMjmJSdlJ13gk$D~=;PdrH6As<{z$Kxq zZkJO1P!SxH0lMrZ04mdY2La5Y#~^MLz1*}2221}=(t9Dez8%CpRA+3TM)N~Oy;29K zVUBi*N1lypXI0B-4@z^wvIRjlikrNyu2uv`WE8z z8TSr2xB4Oyvy<|MbI*m4Dk)X!a*R}YOy!VJtJ7vfI`!V$>^7viV`;dqp0n+vh^eZ+Pbf*c4w%8uB7 zh)Q*&QlIX?I8NB^bOjmcKsfRxACcrhu(=C(<+wuBav&-gh`laEkOSe!lsq8Hsyx=6~;R>Q+gFm-^U+uAtDL8|d%;rY|aq#OQgA zT`5h7L3d@I*9GJ5okPw|^_A5M=JK0;;L`7BmUyRlLg~k305Nw`U6wG#`B#m+!9|>l8jp+NzqDtyn1j;Yjl-8g=^d4yZ zI8gUTREOP!eZ&8=B@K5*x*X|Kk@BN4&Sea4pFv2YL<>36Sr~JD`&hO5 ztSH>jtcMXHeIL!Ch1k6wWh5`8}2`eIqfl!pZIG=+SjZS+! zWLNj%t5a@*>2USH4vZJz_oB5k*h&WQv7LkF^hcO&p;F&sWM&?R>lytn0y51|q5Qqw zS#!Z>42ysrCdES-js-#(3x%&RxN{h$%iIr|1i+FqZzwedGXEiPM&H(nCRGu^e*XZV zHj_{De)2n=)>aYiDpumGIE7KkoNe2WI#46WoV_@QD}yb%G;meM(p|*#YC)@?7*7% zR<#YH6h(v+S|3+~Vgh{__?CTo)8HFt&_p4AqpR+oqhGj5$Et~nsT0GH#&aO#oK$VX z7MlW?k(2uj4jH_BHkkAS&c$TB1I>C=1Kp$`y0Zh#YET1Rp&+_0(6~0rFN4*X23{1z z2ReeYM$$mvtXv@Smw&U+{}?0KBY5nS(c*b!-i%eKos#j>ItlrH0IDm5SK8V9`mhHf z%sc7d>LR$xN>J!~nZgaTtkdEWn}$YLkuyQEEHW!)YfiI%^F@y zOfuDIc0)Da|9G+lvzHL$x!!|e`8~pAOf6lw##AC&ShQV_sD88v_HTAgk>cJMb9yJ3 z2BPoLYS4&i5mxQ*tHvaJJ_g4m+mDvj_+nE+bsm3W(w~(1NY^+x^kloP(tfao^7U4< z=#_dk6dko6l7c%^cp5R9gW6)xGnjQ`4)9t7e2u|84R(OLsDU>2^#6d#n2Q|xAMkMl z{8It2569`Lx~|yG^jjTKa^M<97l^tfcZs&xB@D(Q)){q_5okVxaRHbUjKdU6{n`Ie ze~7sc>gi^H`Nb(uYFSs5^_d6n<8Mp*3rLHKS`l1JSpU62q=n5B;v44?yA7QTm z1-NFw)xLv*A2Q&#FDrVrU!dTd4ESllQ4kDrDE^}t0;BH%9L*<<+r7D38n;06r{rHx zK-UKp4+dj+Ktqu=qUd@eT>CeL`r=#Bju|~K7|X(jIxBz&is&<_H`awmkPU_{^|MQ~ zxt@qByoZ-)=PuFBdSaOS18leHgQ!!CNY!>;q(5Rr6ZgstTR~;(i|@2+V=1q`NOy0I z3aSsIb`3;qq+3_uD_>BeCRn1Nk8AjGVYi8+A-V5YAE6QQP3_639Ke*PpBjitWjA#} zkl)W22x6ZM6<#F-k=G{Ey9Oet^Z`KbtYIeMPA0#pe30v?c0*xt?}$N3??t^EiU#iQ zFfY{0(5i-_R^^Y2b9xu4#)EWMFOA|-w*h3GL*vAWdR&?AC;QUNh9c5^EC-xNRbxe8 z?fPh%8!Pr;lc7Z;=#$FO%toTR@1H#X%KQ(nXX)?G($9@VDXfIgH4?2nzbM5u<((cz zRT_&Z_jVY~^zPKJv8Wg}=?}~r@OHVq8u}VkoeYo`teJf5pTV@bu_zhHn?mnljg~Z$ zE;klUOe3gVoCu9B3L@04182DoMc-9pB&%7==r0(=m0>vYC&@Z4tc7j*q$C`%+Hsb~ z$BAh7P1s!0y=iNlDBWugyKkRycWYs5QyVp>g{^Z`jhxoDHVs?bn%4v8;Y@JUAShuF z_$h)iAP59OOD6ap1ggp+d>nuyuoVK6=dgIutK@enDD;l<`fx7$BNKRl^vxblU&M=^ zfg`Xbwwo6jj^2TH8juk^$=pO#u=M33O*@TZO6J$`P>2s0@93t|IpTrTxrs>p;6F^u z^Z-zD<&VGp>K!W>Evnr7T6U-bW4Z4Xmwt6=1 z);~JbQPX)4N=w)tSCUGP3NO9`%>9{Q7$bg`)@<9Pypu2UW9 zT~pSDm7u@`5!!OT16-5^!dT#QIE~MF2iWT?_R%vP_=lX@e0)iJkbxiNz@IurX$j)G zwrwyCY$j?I84$_}qpq;_EGoN((zngTChg&9>eO6RjT(JOwa*T?!_Ti3&v%+uO-wuB zqaOu4vHu}lww7oUaV>0nGrmDWG3;(@E=ss(9Y(VR%Q#aK*Nq*=_C;X!`X&OB0@VnkcK3QS*D8gAW7&!ysesK z8(>LJ><^1CzeS6=H$;{3DMgQ3i=Nu%KuT^Sc4^NJlUG|YLwmHB=Cp-kY(DL1E2_3O z??o!G71E_P|F(_d-w&!{t>~i<0xGfp8LSx-Z6(^HoENKv9N6Q|dKh6bp3LH*Oz-qF zQnl+Rs+KI)XB8dg)pxIOILe#l{yw|CRlcH%txoy_(ksStH9Ol=lnvvX~hM+ecz(|Wrs8dPLM^|&9`Z;Z@C+Gcm9Z4pSq#$N zcBbz;BbNuz(az$CHr!0>dSm_BzY`tpE$VA#Gu`hZehEGB18b~b*r#BMqn58O%kV8w zP3l+o(#5W#x2XihbQ7IpY6G_C6>j7>;ala?eK>rOl(4!gs zb~hdA23?{Rm%w#HP9}G7<>WL9>n?7ii!I(mWSO>S9qS<`nu^!o;!0K9`zlpo)Tx&k ztm$oOUoWJp4PEPnRCV%DsWP>uBfUif?PXz-eZ&!1rJd*_Di@mA+L5Ih6xLU?a_;~& zG)B(JeZ^>Gi@mQ{TIQkP9X`I6FJlb%!shISf2#T>AI8#(exkA8q;;H*6iZb0SFD(y zLU;R#CECRfl>VVe(4MTLUp^E=wL>i_y1(cjxZ;_+o0^{TCP4nQcQ|eBFFKTNaTk?? zdqd|W^2k##M|ZGwE0rA}YG{|XP`d#lq{i+ofM;Nekj5RNIim#+`&Rkm9fS`n5PqB^ zd=>g?fXEHHa2t3_0sI&Y_RK$-rVSK<+7+BH9VjeSPQ@U5H(Uo{l68ZwK9-aEM{5h{ zau4gj!>f;{e+G)O6?e5jDINd-Ez-XOx=(y%tq)L&VZZ2GzQNe=PM<{KgG6M#Cf~Zd z#oU?xr=ATkli3m0c=N9m7@$gg>4puAZop=vn|0Gm!G)SfjRuLp)JGtRxialfeY7L= z;^v6?1U!0w{Pal3e2~8hJ_ErKG@U#fjN7`U@Rai0*pD;crU)*)fIB~1W`{=eg_`th^z4`O{gUkWol?K?H&S%X*Ue!U~@8m{vhp+^he}a^pLg?#3tJa5!GY_%^kaz~zF~5OEJeW${gQNib%6$nU9z&5s#EjG_ z1~iJHN+Yp|0r_%%%_>WUP?S9HNK-8&TKaJZz)Fk_8&nXbbl7Y(pCNCQ#OCzc%J2WT znFl&7!y&m^t!KLEX9&u4N)^!n zG%Xz0{;I&TcpWwOtpk~ptW{6e$ceYob>xHY2hw=!EOsLWb7$gf^mepisA%T10Fh!` z;PvBo^m?cWE;bMGHH*pO0MllJIS!#$876`Q#6MjA*dBy1i|;@tHPuX@KEp(ZpyfAF z0Shh?PeWbF70^V#4ijC1&o<$f)6G49-4yMl1~#zCwrh>AO4^p zFa|dBJej zgY%C>Iyq8=wmywgM5>|ZZC6Eu3xh9eoH>0S$7J>Ii4Z)B_%va`Sfu6X1rEn4krI>h z;v6C+vq*KR(I^qr*P>!cW{E-#Creb;aI!?D4JS(!XgFCSf5REaw_hH>2o?d!_B3HJ zY|)``bN7Jk(0yswDACxoF6+f85sJr?m(zj?qN1ri#g7r8TGW?ICW;WP$d@#CtiZ8_ zNy3vhj}fD_Q_B>c*5(UZJsuQQzfgo;Ur^skB8+J&(aF)Gg?4Vw%v7E} z`dC!dUN11HE-p}1yB2V6R-(g`ae-*bDag&C^9;8C%s1HP%vXdf=NpnvrGTl(j^E~T zb|h*||Di9YiW;6{Fesuu;2F5HQ^oEw)uOq+8*_Tp!30ZB{oNeZt|Gst_0ymR(df5n z;z+4r=-np_e`IcV5~f_I&9i^Q{DAokq^;9Me>@FeB2_H*wj(Tef_?SE^kb?R=AGgK zd`{sr#87uP0Q6b3bOu&*wJ`T@DcjGqs?x>0-Y3d<0>6$LO1JTv3#y3K^oEcUc#-p#fc-A(DMIed?q=mO&3Q z#E`-OrzG=E4B&1c^QMD+jGdv2zh*j^XibY2eEM|A7OaHy7D2=+#{G zFH6r*^Le74=e~1Xr@F~HG;5xySo_JxMuL_G8wr|ke0@LTFA|Y}BPjTEw1n{|$%1>P zKBnvQVCyp}>*+i()uer0n&y2W8W+Ke5@%ER;A~g@^<>IfAZ*^vz}={8{pr}}q7cq zaI^vs^H?)harK}^3xy>scsV$lX3^X)L@VvaXu9-;NcXNa#>uN4UHKBJO#2e4%o**3 zUL8eOzZAowTLPk=8p-^MC-xtQu`6RW=0<9YjL8wNNx4p4G2FcN^*v>#X)8oqf5D+( zfz(wkEv&Ur$WQmujTKli?B7czR*I@HkB?s|5=?FB)0Ltd4wPSCDato}ja)(%M!~Rh zDiH>MUF{uMX`+hh>U&twmBH=yOmZJOGlS%(3Dj(rn5NC%N(Wa7OYo8up6tfqo=W%e zzgSx(XB;(WsP(xuvc*=Ct3?G*%T_f(KEH=*tcG08sP}47vEI=wUR4G?3?{_w~;PtGTH@ z1K&hg#(Cgk?trzZDSHRffVHBkdGC5JtmOs?^nCs0s$5#N7Tr|~da_oG!22ZqzQs(U z<_^mF4&B0~k#rP(6OLJZhhiN^QQu+s=xg{qX*qn_vmX?#X%&g@;hp2~M$;(vmN&fG zvEfu?g9xAp-wPjY*LEXFKWeg0l-H6Cul5q=+v`O0%C=!@DTeo2)a00Lo%i610oQt} z?lx56ox><_y=aBw;RDu-#@f4~w01ps-P>xgUP8Vb5Ph1%Ta_km0JgN@)ea7!>l={1 z-CL;QM)3h`dPici@M*=ErQXQ|F_77cMs+JB1>n&ojfEbNpqp`BfB*rU;l_P|P4gI>;fMSTgy zZo?vA3FA$S-`<_(ZW95qk=;2zX7E_W&C~;ifW{@IMS#GP_fl6Z608FU;BD{mA@=nk zL&h0PMSTPPxlK%}vj`;dX+JYi!!p90!M9KAZxIy~YVi$54lsYJAYX!xw#+IeDeewa_wQCf|X)-s0d)={)3kr z;(^Yf%t<}83+f{@7=GCuK%*CPctf%~vbT`K8BQ3wq1r!Pg^>Ogx zCZ7*>i1wE3=iq=|$U_eYl)D`dZlEFfNyaGemuLay&pV*7dif1q*&*scTUcVJXzbnw z>IJ=bR=1tP%QUP5v;~=0A;#uj=n3llPcLGNZRW(!WqjxVhi~G@E@7xPMR1(dZ+rKghg>_}c@39W{6WJ_ImejSQlrE}}>P(91?{ zq3OFsfafaICQzUHhF0zpbyJU{PQu%Lx0B~y1MrnA{00N?wF|KPe*u{WV2z7nk^xxl z0t{wA<^|^i1K$8)EW}eCr0ZOyu?|w~D?+f??Jm+b2GcATWpMy9ZySl{vy#Slo!o4w#Z1Q;U;iWCTo zf#~N#q`n_wqzf^~fe1B140a(}I}na4IL3uYt<4x)^msrF0bi=lG5SjvKsNvfT!3>1 z!0rMZW&i|bBL<}44iH8P6eSRj>ioA0vCk30%g_MEUUp&DIxy&%nb!#yVvYmhFvw$Z zj7Y{g5RSC{=t6XNARMvh=SI8(fqjouj^uM!kg)fIq`MI2{2&}6(uH`&eR+No3%L+i z90*53TDlMi9SGbK#`Qn8u?w@of#LlaM3P^bKX)J;Nepp?m|T#TE%u44F86&uL>ii* zky{@C;p}qsweEN>!G}q0(a&9V_Eje%N#zVNinwA^lt`cBXxRG&JNg0VQn`9zD|3ZepAHnwJkjZVh|Hfh&i*pdrF zr`7^W(A@tD>9vi>U#{Zh@Q_sj0B5iE%7AtQ*w>(Y?4ox zdI`3MEt+M#n|>CfwG6n+i97>)fM$4L^BzF{m!bE?dKVXCGr$Vj)_$U&NQ()eZ&u z#xj0|)8HyD>Yl!C$pI1NIpr&;zD-wPlM!=3l=JKWFXodxjvc__ycSJ3AQB2cT!T#e zD>*Cofaq)TT!?D%S+YsDQN$tf1`D&W!(tvT2;apYnU$Ca{ib&FU&=fpHe)lk-chkd zdwP%TN5y{A8rt{^R7$sR(12s&W}&8MyeR2cY$z33PJ@0GA%zcLM@;V;k?mKJ4&q|R zu|7HzOCKB;y%sg8H)rs? zGA{j|IiockMp}l_H@_hZF4d`0t!?MW8QW{~DK{uPM}jLpu0z zjo3E7c~SgH(XP`GP~qvp1LL(G2e9E>2n49aQHwG8x|oOuBdqeXR3uW(%l~f5YZ#wXpTHc%-DFQ_TM(mr z4mThDB0G=j+ra}zNsQ)o%G>O3Ir;Z$JJ9JzATQ@`v#Q1>BKBzKaT(?4 zX^o70Z`RO>`NV-4>ck{DF++e!yU z28>E%5uN420g{Syv8la~+MgFOg-m%~H21u?slA;;<1dKmrMpeQJ|!1}bAS}QX@Pyp zocdJcqWCz(YUNtw+v_wwYUzY3m8?8DT)^Y#`->v3dKH7}&8Y%Z50pny6|aw$(w_C^ z*P>7xKc;|7BD|Rovr$c#n|sh%V9o#yPcU0EnY*y&Q@R7rwgtO@YHgcVRn=Iwal26+ z0s2zLCDF*-eKv@?U&85S?oL|J=}RKr|Jlic{C8aj`OnpHv6d)R;)p_>249ZiQ%23B zrI$s8LZ|k7(Z0(tz*><>=PqMB{FVk^5#zjXS4F-m4NPsic|}-0Nc+t(TqmRXH-lQW ziyY$wb!AW>gL*kZeHj$Opq2{MQ&|rkK0(P>MX+}l=5AvB&KjC@6^cUa-yFIs!oB}y zMBN!cgwV^YqC&L?RRB;*tbf?W>L2|wyA;uBxC-3ukx4DDiSZgyy6Osyn}WfCQ2 zExLGJto3dd4uFb!9?iZXF4Q%rJ9aF^kGQ7 zz4Cj}_HxCt3<#bzKhq1{!4R*KI2kZQ+R+>=UxrEurfyTI1$Yg-I(bb$jz=ZzN6-?S zQwp4&@n7|c5+DV(iLs-wm8GAq0gj4y1oQSlpRF=MKkJH0!et0D1uPBXhn3LGMu0iy z4+;6oEC0p?_*Y>5V_p1zsf4wTQD#3Pw2_SUa1}^KKSXtBQU097ChyO579J!s;-0{v zRoRbl@fpQ@@|CNlSY+FjdU^@KjqJ$5%RPzq{TM^vlUU9(B2YOp6q|MWezg7qBK-yq zDrYZObPD~fA_d+QWvl$b5LMHDh09(OCxaY{TSL&Dj8}qgq<%NWLT#Ugp4}92sef27 z)v$I-<_AswLyyR{M$1Fo(_A5b0EzXdgWL$(z-gbPn7gItO#Z@Pz7kM_`69Rqq`fhC zptx{_v_5?Z%1a3_fO&MmWRL}<9sh_LrE9tvgt05X%X`>B74pU6kJ#C>ocjQE-Ynt z0_A$?F{3MNv8+FBMIv!_Tf#^rMHl7EOyJ;`aaw)8IXmk*kf^H})70W`C~eoV@0S~j z?sjL|9pLP_uc@(Jl!{-8?#tdbzXvug`?v`qLzp9p?1rK-{^cj{wl%Z|@9wDPv7c0Bf7y=AbEa>>&^;Z1{!S48F(nqAy$1R&F{%> zx_E@bm1s?)v$sX53Qx-vkZ}m!IWlCt3a}C5?`5d|zoK%hk2n8{VX&FNY*Z3PxEPKqLnm*GvV)p1pM3dV!mE1PC#4JUUvUlm>!Z8D zT4bWZUll>SiO8z|>GQJ;4vG3fa4>|wTAHHnqo@fGltkujqb&Ab##?pHl22ca)}S{A zg?$NJ=p!Ca0P~vYuI+_X%(3qn5Xyl4Ff;}QZ$9gO3<$?Kpv10SQ0!w@A@(Tr5uiOi z!M!;e)&r=VnxaQ_HnIKa){LyF!clvI*vPXo?ETS&^@Z0!6rz$3M3~oMmCVQwws=wE z1JPjAp7CB#22@G1_7CJkG%&l6R{(ak3|)ZW^=m{d)2Z!)-h=}6zAQbXbfBDzDcP%i%)jz?_ z?N4z!u@tp@D5|&pF({c>`=sRj9}#nCAapmf{|XT1}4jxPHXuLzjnSG+!s_w-;Jt#afHd@4seVf5E8BXP*4>~XZ?5jJR-kE08Zut7bJ-aZob ziv6=2rIekP12N2Dr_5da*HYqR7&rE$$&W>Z?=p;6(0O&V#OXWB)7Hl#*}GVPGZ#0L z_Y)E1JD6FhEgRpvyQt<9(YbI4Xms3W_VB5w9QbKT4#1hM!=)d?59ZpjxYP8Bh_7)a zi@m*rJ296Mr!oIN1_&<{N~opD6tLplzlp}dTlb?W?kO%>ai?BSMSbrn{!U>6=&Pql zR%gn4Dk`||0~}NJ2Tw(qpGDDd{GT#(D1|)}bt>-Mi4h{+9Cn?5jv(RF0AN9$a`X_S z`)M2>N_!@%wOdz$wK;g)%dFl#hC=5C7(t;^WHjr`uz|!Hoq^q)8l7D3eb6=G_A$8d z;^j5{NNo%>15wX$gNGhX+H+B{R7FIHH5Ar3=CA{LW^rdqCR5yVQQ2$p4keH8*hn_6 z@*0aIa}TuEi@tm*mh^8l0vqCKUm{)nmzTC$efjbM-oei)2BUJ2PaMIM8v9Sk4r7FO zh}*+{9TfSKr$p;2ws%nqN#n8?d1W{){!dg3-ZI?FjqjS`E`;awCa~16s&*1>qU-;O z?j4#0s#@xMahv-3NBrQnZtzz&SP+L zdk)8~NAUitFtGQ6g#r?2s4@E&;W~$IL63)v*$o64zH-`DBu@~bd^=DX!cLg}T+3CkX6T42b>$xvn3GBM(OQCN>P)93pYKX~h&){nC)_9hU`*7U! z@^CBD>wPBZ^0VFI04u?;%s=XM&tZQA`t zSiIM8?21hd(%*>6L8Ae<^Xq$ylGwZ#sRwYSlO5sarzJ6GZIPFrjr=5o7_KtCJ%Lp!KudT#}pwN*&`ZVr`Xlb?Y? z9UL+av(2JwZ-sw@84Oi>cIt@R24>R{OOZrdt3+4OFWx80VWR4|Tvo~;e+~h0ZZNE{ z3Md~8q2BMHygxF8CcP7Jp4GU;SpAzNQ|>#M=Jaj5*j+aF{1){d&^2urcaq(-YHb&9 zl@$VC6;(j5wrF%XRLp89JUZE-&Eh)}mo^rqGA3EJ;m3ZES?wri=HXRs#NJ>qV&9Fh z`*=f(Pur$2O=Wm+bjqNq%`}T?f=Twn%LV66GD>?=jJz~iQ~TRYjWpR5FM)odNlU3V ztvU84fr)tdgcmGu$3fD{ttnfR)wKIbbYGJJ+6gmx6q4bk&Lk;v*e0f1)JrYvQ9DsD zHc-n#vU2LEf$(L*6ulU72jy@;`7dC$wCw<9A1d4L1ExP(uJCtUc#{hsFbMe52EI7l z*c3zfcKe;-8#8{pfp6f#uQ?2SF%`i_Oz=4f5NsT2LRP8Th(X;C0Ay_y=ql<2K#eU& z6)u^HaK{shx;PzomxVoI06~tApeaIHd^;IF#eN$_Nm+t&1gLr$@QL&rs66@^z9saE zebWrzTBrcUd9zx)E_nW|Ed$LZ7r(6<|@tlndyVtAOGG!~GKq z&u*gt16+8OfkO;DyAAvYxy}M!!{HMRJi8emgb03i667#J7zmKa6N6}EVOgo>QbmZG z_W(TyYTi0WK%4(OSIzp1!<}5Bvg_d>I#gIj`0ilXQiK7oQuMkoD)Ch(tG*1jwhD7q z7&Dqxfes{!TbVbGW4%XI{(YkM@FK@T^gs9f4`KZKgkufDDEi7n7W3=NNVOfV>Sj6} z^gv}uVH%uOq6Z!_q*}V+`wkyH&fxChl64g`;Nx*dtJ^=A8hOgVkj)I{UeW;DnMGP| zSN&gfo!~s2CV0yI+V;X!-%D085e@RfE9J9jxtEM7J+v7#-Q^M1SZ~>4XK+qI(|z5O z{`Hbg`aN*RTA>UcvUko;pa@Dos+J_NaiHb96LJt~6fqum?D(Cz74tp5zRgOx6SIP1da zeT=%W8j;vi(8d!wrN zbLV1+ZVfjIYy4W2LYSMonpXg+g%hnGge2M~>4^mpkI#zX}`RC zV9rf`k#RcpD%+8oaq)Rv37K1Npep5FA!-ais9Q{|e8)w|*Kj;ffBl+5{AF0oVJ3oo zCsx%;%D-a}6Aonc3bB8TnqNS{9L<~Sdo<)Q%kde_^p^ptNeF%?M;$Zdb;%CqmU*5J zT>ruK2Q#dVulRdFKMlXVBv`8X+nvjaY5Pr}gEI3nF#0C^@CA5|zxXckogME2VnZB- z_ffKAeT&M#2b$3+ZT=HXnG{%3MilG$40XW7%e|gY!L0F9sjey=-jLyH85+TGU;JQ# z*&_izN{429FMZ<|dg z2c3Fh&VGUCCa#r~A*Pv20%U2^Kq?a;Ly9JFoL3OYV0ip7wGNO`<>vtc#_^|K>wOK5 z`#Do>vHiLmv-h&JDgd*Cm*g;y1ga{X43Pd_Wef`6--uQRV75>wP!_Exz(kn};W1uh z_!P)n!0YZ3fBr6LU{q#&h!A=+N(z)UYSJQOO?)1YG{Ne{53Ayy06A8}W`ySAp1jwg z2jR}K`a=<9T1LMhc)hO+M2-bpHF%B8+Lh&uZ8k2t(nsQlpMgRQtO)p%iUi4;KC>3O zVyaIcm6Dd!yeFnEZp=whu0^&D0_FYmu=h7dB(+oiT!y#palcY7XZ}ZXz~gG z+A?4#07xd*!Qk|m{wpKPrPct#vBx~U0DV2CA6WoUmjOK$;KClPc)7(f%+Qz`lo!-_ z3Lu)e5FH&M)QZU#8|y+eFc7KAat%Qmx-jJ(L6p^`EtZpFMB?c{III)U3``dnX05^)`8)zpeVBt(P2FI$>!O?jgvw2fRM+6Z z&B^#pvXoHgm{3g3KnjBbNA8xVnPp{oG+QR-Z|mPiA7_Nd83ASYfB<}it@vCXAESL( zfKUjC|!ulF2odrMGt!*9E0Y+ zH*nwronX@k7*KI-r(b3YO)Vz_wHK+hq?~L6ZQ{*xvV!)&MqVNEl*dGHga*4F{S_j| zdadsOqo=eyKOINMLuEPBJW36f{-z>Xi$i6U$zxo55MSa4Zp+J#K6l%>YTyKltROAg z{dUx$f{Zo=QDz0XymF~ztblMIras%zSM(B`2eBT2KQ>o=+}bu!=%*i0ON;!V%B8kE zPT$4_-XSe=UugPdw7(e*pgyi?sN3%Vj~Ck^YaiNck%MEyt00B8=n8l` zw)^nZwF+*!sF9O^XL4<^V?dIa`%+7^wR?y2Y9adG`_#9h3~yJ<8Hb+{M|TkFfdJay zVcu0>5)eD34KQz+`!Vbw@~Jn!J=ocDP1;&<$S42O#fq|vryD$M`!SU2SCW;R=OV15 zF4aM2V6STIHS_@(@*zP>8G2jI?tRMvx`$Ndjt+tw;$K3)rF~FvsCXD{SC_2AZvL~acPm6XHKt-`M^F*$5BwXW*))~%Fv(OrE!&I1@A?F@qhyx ze|-k6sVpm*y3vu!(gH)UdzEEq>hilvxErmMB?Qu(W&p0Z0Am42%6#c;0vCWVngCN4 zKpb-+hAG55-2-1EPtHTM>;aq^|NJeDt0(S+V`iFDvN9*$xF6wW5|XCO5#8wPFd65) zg#np|Ayg#&8zxH*+Ik1&(Fx^Yten~_JU_g>3^665hI-i7V@FBN=ukcrfX_Ufc^eEa zbI4T)nO}FT@@1v`$osn@kD+1VvTQ(e#e9V2K~m-b%L~kgyTVNBw}!MTTt?M=euHz3 zZ+TG@v0?Bc_NeR|d?sqob?LUpEgU7Hp_?>doyim-i#KZWCs3wJTs>W=YA%?Y3l_vM zw@}czm1o^0D@6ahLG2@Cl;;VK8^dJ;StDfi)ZU0~pN8WD+3k7ETgTq~=WBnzhSGNn z0m1{p3|Xpz#Fzn=WEXeC`fAWY84TsQ9s@#$vo|{(8#Amaj>yqV{|`t2hO@<0WKfrT z;D!rva=_1V!#G0l<>yCq%?QO~xw%V@0%a>|brHD}bQvIhCM3}t!qr1bYwtswtH>(a z598=k6{6BX;f3>msjCZ({3wsIl(_e@#9QG_Cl5b^F9A_GkBpG zaRipCxZNt^qW&RbG$y0T9BLu|RKpJjAm2YhjGV#V)MFZ7Rhmidsp z{KN(gZj(MrlY2*bxtWYtLE3;N=NWH9{E@~ij$miPcLOMtmE0*DnDal*yl3HMYHZnU z0|xQ=FEhTr%jP(42g2kRaB2UH+kWgVzeLq<>HF7kPa|8!Q1M9F#P7RQ%$)NQES3;X zqHi1xjFgkKbJOWsq-qKLGPLG{S>RtS?Heww zPj{iwa3*+L)nZ^FZ;hz&m?i2AN=P(>mQ<6iTMP}wikY>~s(%3d@8;a1aiPvlv_5|K z<2_xu|I5qV{$lr3QJj4T%~oHz=ToX$U4{jEU@Imb%?hvE^*$9}D23mUEb3ou1dXUJ z%g4RN{z;xZh+qB#M*a%7y^(7FGYpuY@5xLS5JQ(b*To`VMTR5NzI39x4AE8&rw7$# zs8?~86#JgnZc^zeS>EePGsFMGP5K~8Rxz(M{DaKl=&LYWo*5-;m^;1)tVg*~&_aJP zj2=hH+9d~~5c8ikNwV^yv2HZQ){tG|Jy4J>@|v-B8&4_}Z{~AnTYkS}i`^Z<1JFO{ zwtAuSzB3cJ1QdmFvJ{&yyJuyctszgFOwsgxE%{WtKZ#b=mK8mZz?=b79p6AYURx&m zteWWVHl-8S_NNoGLZjuUDnK2XVj4rib!AQ5BavKJMtavB54w>|XUqDmuAFW1D<6xF zb3gaInYe1^ze`!=V`R}negX9{4o$zn^$3Ea18Uk(UaGls3?>sDldRu;4`?SGR=}#t zf8A7QAlT)5ZZw2~BeFT@*imtDR&uNyYBE)zJ&j}wQ_(EZSe`TC;pfY7kgXxLi-&Ce zC_P?Q@s-2f-9FY*c#Y=YEo*PQ3^aLpAYF=4UAo#tPWO!(#=CG`{$N||sWh~yj4;il zMNMV0_XAv~p(yVUq5DlS53-NX@=uV>O}HtiXEV87d+;0iHAnA~J1#4>xvZs`re=MV zDC=t2!d=%=E-`JQxK=XGG@WL)lIykHkrbIE2be}?eVzotwF|xJN^99b`zJ3esEwRy z!e;!JZDlM@LtJbtuW7G(&`-&-hN&3cO9qqPRIQyngmchO+R1cN3QcY=H);QNqp%Kg zhbe%rc97|jw~L`~;Q4A%c@0ke7rbi0qx~4Y%}#VV{hN-#Wf*aQq($joG_Rw4YWkQm zI>}^HIQ`KHz83UrXW6jW06#CcXS3L{C?usED&FruQcxEe93BmLV&*^k8!PxiJ(u=`OEpJG>~nhb*rx@S?wZ$hMXJ zwjeZm{B#6UFF?m1w0L+Hk(!4+OeK-FnI%)xN|GmXGk(!}GyWJOK{l*P24~ zeVZ|Mw(grJe}GK_|6wllxhMVEQ*I3^2@?qn;0cz#XbV%aI0xWH>-D6Ky<|gEO|tir zi9yGDfHk7!t=328Vz$-%#5l_QF;S)=-WnTN9wy4zPs1KxiaD*am?f9+`)HLQS~R{!%~ zsapyn^6>f&uH4T2#}n6sr(3v(M86NK;oiB10RgVw#su(@kK@)YeaCvbKUB6)-Qt3D zS?`z@+1JCi$EjS#ec9d+q0I(#pMCS;F0(dk@X?!|#^#x`%e{sGc#}bo0z`{Fu$ zZ9iH{Qqb`CqI(IEB&?9$U4skkh{q; zWZgLjl=_X8C4%2P_CzKYUG5mr^Pp9SULfKyvU2Yg`g|-3_Z;ztV>XAElLlnm8y}ArJjQ^JkH$-Ljn4f~p?8F`QoS_y%@^#z zrID@SC@|RSua;BO$+CLolM66CbSsW0A^7nyZn&z+&f=9ax7hNZBg+mfpkhyvzdp~>|Wx&CfNt~GEA6)ezJL1))bj-@}1C-yY;YB zkIei1hi0Ws!*aKjPj!ZsP|vxm>i{wCTP*rRD{Y$&J?Ce1e7dY#Ijj~_;ci*HZC30$ zZe#r$t*_1tt{I?4_Yn!VK9;Ja%J$mB=aiZ%OPRc~mZi$|CJaXjGv!f}cC8MX(_}gA z=Q>$YX;PXz18N%6lK^Uzjv?bfBn?lOANFkbsZrx5hM~r7g-36Op9DOJg?k<@jc4ny zPi`1xnm!tHZJvX4sDUBVTy54j$P)9c`_LdOO@+M^OdpMGiA6(^5b*el;&F{|M$xz>b@qCY>A&tZ{uF;jLgaeIc5C_fky>n&zNma8); zaki`!A<%AAyLfs}wWAY<{-+&{QcE!p7#J;`E%UT{5!BC$+W+?*T4_Z;a^o{PV3ie% z42Zxc$^%T+Fy2(`LeH$SCH7z9=ScrjNuO~g-fNkG+|kYO*uTRKGuG&`Gl|6GMwQ#rE%!ya`0yM<&10Bq{W%N5PQ(u{2ZylY{}Xr+iEjj5nj=ejytYFftM2Wa zBP(OrwAf_j&Xq4XQuPx$A!~HaRJ8VI<_xvmw?_W8-xG@ol&Xv81$da}%*v{!<&JC3 zFE^fayw}*ph(e<^Hd)n_N+)dcgVefUl8Mz&@7&z~$}!>dm;7McLsZSRI_GwGSa%so z(ffhDy#i}XY`!JHY)>V@Z713XZdBhSOqlgPD1X)8%t!j{Go5`Ar`GNdt%*JfE)X0U zG~fQ_(Yr$L^dUrZWz#~RmvyHZ!*J)%)r>-Zc;3@@o~+?h4R)=L%W8X2%XzY=_Uj+? z{X7hwcni1cB&d$&Q0RR5p%>e-qOJwsC_}dSvTU_A*`7uhYM5}?VzV&U=YG^9oxlDw zflsv7*Egi0Tn5mE`Pg8qPo4{8Irljq0~ATs7RbTcPi1juoh)1O>}2#9tvcq(U!WD? zKg13x|_ets7hdTH|LoF5B4`$$fCF-$rfveTdzi=7t2PdaGMvS z5?`F2^~++}*ko#zLQIlnfO+mfM{jjAh(7v49>8KG`b$~P zlti7rgch^~ee#I)^?0GhN?c1dkJ!jNEm5mbQ9@aRqPlX1_S z-jvtT{wrWH$bg%!h|6uP|%U;t z?Aa;_1uiCxvyXg5=T>9Ss@)cJHFlnY@^o|tYAp)z&{@t0<6vR_%v7H;4xEpnxuA-a zLO7-7Q*)w0@oAfRf`IF?n$N0hfl zHr7TQB#*DKa4Sn~zk>2IhEl$gqkLz3aj%6-TKpuM(fzMvGhAO7{WbcQzC(~}ChGpR z3<|i6S$4s>Nf_oPVbGOZzn0&7bsA#yGw7RQzrlv-JnH?846k$(y)~~-0`(1zSSY@+ zvHBp^ciz{Z4px;YHg`X5`bO^aomqk_QWX~-S-$7D%14u<*?#)J~XOxPk{t4h6gL_SKrI}We@d3cD09E8+Na@ z*xVB;e+6EgVYvoShjp@scEpE1T_>ls>rsgN<)Tw9Rqf+h(UQT>H`#y159j=R{mO9g zDh)4+sDy*XQNWV0*N#JoVVsF2EcPEUa&Q2B6SZG2J7WUAZapSrpU}_^m|uNM85`s$ zr9yv2VZAiW4%#16g|!W)kl5!B*(hU5{x+0B(dkq8v)6KxE62^DG)t6PB&tMN|m!V_YcY>W>^<(%_1H%`1}4%U4!Jux)JzYeDJ8!?Vg zUb0D+^qdMfzr^q*mD>c9zFmW;-X__))X`t0@st!!gz{@4d5{Phdhr)pxk;9)nu~Jf z>tqu#;PTbI8{i*gTs?qYj)~Y!NVIme@Z)4Z{X&0jl3^w@E)tW$rVbRbSw@zr1$z4z z$XXUJj^7{Si-`wsL_sFfxXrRd;dMvhzCVzTZ-e zIiT?O(}3AMfC_DulWShdgC}Nysv6j&cBycZ7fWntu;Ga%F^(oC zQPYgE#)4u2yD@f6j3xHOhgf5xCYtd7&b~V&=J)sWk$XEcJ3BkuXJ>a0%kB_Oprbw$ z@&GhoJpV921*xsjW|ae5L>t5lA=Q=uT+Tqv;+;)HBvf?Vd#L`ff{Q->kNt*ttrMHYdy^!$FD6Z2qZT@$IX z3wa?Te9IAD^_J>*v`yP#fcyO@Z>t*OeD?tB1uM?0Th;20i@TSja@*7i4R)pfuPki) zP-fwWLL&>2oUB#-l%dGNcjDxFgAh5XOex&wZ%*4mHZLPM2~tbcZ^@+`ISx z622Qr;m7S^F`V_oAcB0&84BUw%B!?fbuqc6!qvi7#*?0qX_p#K9rM-CT_<8k-v<9; z=`Qsnp$HVFUPrLJ^4i~T(tyyTGq|9-bxHGS6CQYpAO1EV_0ep%B zXF5*vD0LYSOtJR6Q(@5$7(1y`jOyh&k?D+K?2DxZDqeJa1sF%M1=z(Kj!eld$-Uhu zy+Do0%HUkW6)8FAgznT0rB-&+Gl`Ko2XH1t_X7^QMZCT1mWjC`=F@c;n`<7=)5TL= z!M7;EedE2voSQZ}^GwEU1BP7y&=Z3?ega^!QR;_*$o1+voX0|TTpe)MfN!MWLd@gL z)T~fV!~=HOh3c@-pg(xfN>>m4E(fpq;r!}}-VZ`S9X=(`$$cLdhi8i^WgmL+uPJjM z?2A`Er)B%lzmB8r`>v90g zl-NKJTVOkT1eps~@(#(_)@=Mn2c?&510CH*FxfiQ&ysz_S^uXseS1)?RV{{Tr0>@0 zB>rOR?SSda4QdqIky`p%QV*#i6`Fw!&nZUvVp611-9u_MWm{|NeF$rVH|V27YB%N4 zJ^JmCI>hDfudK8*w>N0OVKv6DNfCrI9l-M}xFjG=%|z?Nx0*>`A6A#Y9h(X)CgU0U z5c#z`ypR>&8v*P5aDHM6uUM~y#;i-i`2_&$)^I2O*)5HzdJs!4PcF=9g)d+n^oA~W zeD@i0hXy7WgJ!Cgwgx( zQ55&RTXgrR+RVqhC2KRu&)9s#DA&;m&&I@mr$)I`UR621m< zu`AKG7Ld@r*Hb9MeCn30Tk$0~$@iE#HLH@1EBN{J1kTPq4cxy4pP~SG6reIjSG#Fc zN?+qQNW(9tLcaj#*2MVUuU~C}7zsSqTZ7VP$$qH(1|A2X)lHBFLhZm0EU%bn@m7)F zLew(^*+iUc{%1XRy|^9-TSzs($2#!tdTR5%8kuDV6^g545uSbrZ&p%$fbt{2%oq!; z>Nhhvz4-Is!QF5z;4$3h^C)dq7XVrILAm&OHZs*9d*2`%EO{(3T2HQd2#@C|DO{zu zl`FeUcsIVl_zlE3lH?FGYOF(1MJbiqm=h^GWBAaP$SOoBnu+n>?k~x|Db}E(`>b&E zk)CawQ*ik|g8c22Vs*P}d}}FI_455#1MX_EnlIRBLM>LebGGk57!pp|zTpe1et(}n zIXQBvuEg42GY)xYC#yccV5GV;}51|`iQ0m zeG8_K0sSrZ=Ag><~;6b$Xc9!GysN)T_iu)>wu;1D85lz3LR(71YOr;rBR4>y|+I&L| z32pg$?6`VxA3#wz0ix(=J$iIQ^%pM)3A&Q|P1Pss0lv|$+lAtfqpsJLp;pd2c(4Pg z_Yg!rX3%u8FkzH_z((XzN;C>YU)YE`GEuDliH*pwlxQM|rrU`4#+P=D^mppI8sbd` zyvUh0-c*~`=#6je$f0L&gz%`XzoV1oExKiO@C#=loL@SEH%D;WfzK^I?;w= z_4D#kp_~%0TPjlDdunjvlMtC5yh{i+(E%rA$n1z;+maYkBQqx;|FG~-(D*z&!8B=y z0jVF(X>Xu{d(h)pdJ2Z7UgUFM?W;Turp)_lN52gdZBy6gFm33)ZvKM>mCu&VE_A&_ z4W#GyRbSV>n8w>?z4IvGfg0@N5@g$>$vKjcb66jSARZKH|3LK>#aId8lApI9AdQ~_ z=-30bhsO&{%d_{eZBk)*KF`BMwW%rCYcFS_S0Rj=DE~Co(FpUm+p|4neWd>3monZ( zj-NBP1wx;g$?~mj5z}^2U>#9~c0N|4!ivYi_T8OrR8nu3{G^KdU<(&s6{A4z=1ES! zsRtcD&o8r}3;8x*PIUM2M7N5xDXKesw>u8%~xxA?h$quq)ziy{hFVrg3>8V;bJQ*bV)_Zoz8hJ~}QeH`>j@(Yc zFV(qJ`?gPt$H>=&ivNkdVXG1Q(Q|dL5PBX&G*M9SFvD*Pw* z^G$gWeWYRccl{5UpZBY>Xcw;ScNm1>QKM6gKYJ!9iK|* zep=SIji1W;R?jtUeQ8yj{XvKbHml|TRnvwbsJq|~rqi>+P-6#W&*J&IDX9+IyVcvg zo$vp_7|}(!?DD}8(aFOJL+Y!#7rj0&$|Tw!>xdTjydQK%1OId|1gF z$a&kVM$Sj+Csmnq7&TZE8C;!I!gXFp9hZDnwmPl;>Q!~}F+4zx>J*a+k+4rDM4)of ziP{Je!}&A(pyU00d0|a$L!vikj#gHa7!uoV#q7qle>vZNo2eDVK&9XX-NLWS4sT};!)5}j zZ#-e=Q}2)e@EMv1i@dJ__oM^9q82QGk9#118Mwoy3( zmy}kH9+u}-!)^Ay`qe0#}%bncuYGeB92cQqdkt&_uMb52-!Y=Ur1Gh#7Jo1y&y5DT=+s+ zM|{pNBplnZ_SQGL1grpxrr^d9GoizR7ftOp(Gz**lk(RYeucZiR>o_Sz#>{PjN(lk<*;Bcflemj5gL ze`Xte4kUoj$=!B7op1k#Pfp(aNHJMap4}st>LS{7ltxzRNdZ4&ey zgRp(ZXvZ3&TULFH(w1c@*3b9zaEWEPOH&tPoBl59ic!-JUoWj`u&XxJqi#T<-vKzQ za2(ltdRF&0v!K-LK-hYATc=dWweH$AC-`K}4?l`w0cSuLybWMd9lB6M3{x)7pro4O zRCV}I$e9PGnvWdAF8%F6rKWS$izg%vk|R6lOEOV#UQ z*>aQxf|{-#+Ho_{jQ*_qFPw5&T&nY;JF~vl(*XKWskD8(&YfHTll3_rKUQ zqYL##N_cf3b?*&!Hp_na51UgH^4ge%S-~F8)P^DqZiuTIqH@LZk2l?FDEwjXl;56> zM3~Ev0jNn<^~PC>ZzM*$k2vdsJ&Vz}kIBPlVD#o$i zJYa0av^N}l4f=ki^cVWmg*XxJTDugI*Ppb;VpiCv78tc)UhwPh;xNu7mCq>;M}i4Y z513ML($s1md73sh7UPs<85G$>Ojl;Lp*2m!t>hahfkoYHCtscQ_uiLtr!&=I);c5| z$N27uI)&5YC$vTi?lW;XADi%Qm(pRSk2~Q$a}L62c=>PSoo_0pn*1&$vOO~UpmXoO z$U0E*K`Y3i5Hxon( zrhwljh=!&SbeDf-P}P?BnM%D{inMmF7#u7EaC2IBB$kte^?A0=f@H%eauD{9FpP-# zt+=Fe&EGWS)ldB42@vDrY2?;A0g1(b`MVSHr3cYJErnn3HY4=+88jC5sw;DsO)eaa z>bDY+_1j~(ww+irhZ{_rGtsyJitVoQn`Pf%w zsWvo_QhkRlCH=}NF7_5IWuPHPDCB?#SKTIz+OL z%_!n4$Dock0nInto=!CzDR<I-wh?#R3vZ zVuon|%}o+v{txTJv;`+YR^okpLn(joY}iBxlSCue7R)r~2u@h*spQ;R#3?UMP^;D= zR(W=c#;}ASt(cERX7G>ZP#+AN~%wkDz`V5$Q7& zVCh(g_uGthqT~S*R?Qs(BD(-Xm zNq2BIKk84B?L;<6*R~Vy;nnc?6j4ptltF`1#AVa_)G}2xf`j+uRB;!MUW3|;29;Kt zpn_bpXDt+oZP!Wv?GSz29-Vm<{oNkD>iD-g4c>2N>6hhA?I6}VDu4H<>Rm(|Fk;59ODQ z|K?Mhh7Ce@*pF5Y5>u3$8yG9&)u%y&MJuJ7gv66RSkzT+ucyEvVvsUrJ(E=<+-TGs z4`~(;fo6}jqWW)%{$3qA$qI7XeNH^K6rs|H;k4#0kyhceR#N4EvjI$E2ON59^947mGhDolqo5xT7mJnCUs3D`u>|I_ zLnA~!>euvBLykLfNvC+%rX5U?T644!}l2t>?L|OR}H0cb{_uRRvdT zwEdlCW{PR%N`h<4p6l3=;4=uxR_}x8*XMnTY2sujQ)Y~{3vMwp6Q;H7CMN0hVT7|K zqiRKf$PV(tx4fN29#^zjbD3dIk{_=HLiHD)(7ADvz-^59a087K?~Nef5i72Mr#Xk ze$+bf51gEx67H&G)=0MQ4rl4>+}AfVNzR>Fg)Q8d9MXUJh|WwDm3<%o?#w+wB-qv| zv{S=7PI*UI{5!M8aY@lAECnq&^TQ|dZ*FwE1vKCt(Ny{KJz9ld&(sf*SN4iIlG&e~ zQ`*pjcSJSsBg+BKH9MPl`|w#(&PZ1{4OO2c=9H@hdokZSb9*`EPZCjWJHWbtaR56g z&+j|)P`nXQldT0(^1=kGJJ{4?8mo`?z}Z1x1a!_%MYYj6$!Uvu-#X6QGrbzsnhb-; z&mlB&GE5GBG;6YG6!61lF7T9`oBESws0zeF(ZtaiBx@x2owtu6VHyfnA3!y<<%dre+%`_3^ z`{xHzb;dQM=BVuk?OSwrnh0*P7f5^NpUP`X;OV5Veg)N6y!uWa@H@*~=PcUpl8ohIp(TT}=ABVu43G=D)89;l4dP8{^&=RBomi<5AJQEEl~t&7UcTDmxZZ zzzk7?s?HKI6`H|_nSk^y+@ShW`Ychm!ad(om@_7rpUe^ym8CPtZ?>@dO_CEdd#gZ1 zoHFpxi*jk#Y%v;c$O0k!jAC|?5vkZQ)BkJ#wr&T25k?Y=83@&x)BdbVJ4o#*at7uef3#L{2lDVk$ zPth%z+-K&Z?!$o$W;XNn2W4f@yHo0XbZZebZ@%cNJeWh*=ZmWFvUAK8%|sK}l=Vz% zlPluNAB7!8>H!zCT60A$<=Jf7nF}k-=Tv=xsNwk&3QywY!P0%Wk9*((QAN105j2*p z3q%u)DMuEFVCB16d3P2dv&~!zOFOvOnW(9`(LE;o{~er(NlH67^Gw;n`6c`;JGk+8 z|COu9*|cSmXykkYT`2m2D)eL#hWoe2Q>(?Iv8feJTP#97#=(-4JYb(C*R=&!O`#X^ zHY^s09F=#*(YOyp)6`;^S>VZ+&<&>Ngs$D>Gn!p?HG(GL^n$JAh)FoSlWfIy*5hg1 zu3W>7%Mf$p4VoFYAprY{vyE`ubc{$wg`UMd1>hA<;Mob6E5PIi*8L`wnqdJX&Bsy@^F zIF+6+6*W7jN`{Z&93fM~nh+0UVo99PHz7TtUr`2FL7s=du!{fUBLVyFdKaGu=y_RC zx#E4cD@5NJOXEm%A5vwk3sQCpm&@;itef1RAi!ocsfjh9`@+Q%6n%FLd|3yKb7-6E z{Rbcp$@_j4$<=>E51DK_tY4af)Omnu?d(4D4$$(-k<^@KxQ0GCcbd2EK8@}z7k<=k znP^mfXCBH_I;Dj`Hp9uIJUG-TEY4rFm&>Oh8foJc)w*ROF8vwyXc8nCqLqUjN0Qh; zF7(3uf z5ohFc?M-B4hLMpk@Qr*rg(4%zCeq6fMTGMGNDBK%#CShMzEiC1VD&2=?Sg}mGj@I7 z{75vbnZ+Eo7#wcHeg_UIA>iOL5zG06u9jIP-MVCsz^pBFB%S<7)bo5X!o?vw%A|h- z0<^Q^BdFpE5moak;^wmr4_Qn#_!I<#&zK2iSMZ3CGlJe+AzEd9z6G;XtZnV}dkP}S z=2sq!XT0DWC$a|M7lU(F16j)loMWFe^BNH9yTLZ+QqE7karA|e&;ITU)LNht`sD0~ zf<6KPj^Q6*q>R=TI9`Ch48r8u;$FShX?-GSiu3iZ2I8M~96GF33n)KG=pggqN1B+w>gbT!G zI`lF6wCbD5=M!;JIsBL&eIml$DnLZcKW;;DxsR#Jr=qU&;=j2QjGjcjKNbEy8^(fD zoDSc-OssGx;|T1p`iGSBspzfr+(^HED&i~de2EC`&xYX&G2K?ZB=br!uu;vcP~8ithtVO71Z1um72emU!8MV}mU$+4ub{O`?Zypff8)OV<}aa~tJJ(fv#c zS%vYyfqJhJDW2vRw&dm*$pz7lRbqsxK2=*SYI&Z7Ar&$rrsTh43=Lc@QvFAcL{ZFT zcV`PqE9VpnvnMF^1Uj}_M2SfL_M+#jMNH6TjGsliw7y`M&rUrTQ2o0x)aosmtf0mHQ{7 zS>}JFcjA7$sr|+0BCC7@L^kq(?cguO%`DGRvZ(G@9dnX!qa%Aq<;MG7l#)oc`UnW_ zWWWOfH)4-K&Wj$vvWy~Rzdfg6C{VZ$^&HOaYpRF-ej4+2UpNrElx(Q3&Bv<;y^UC% z0YL?tiB^X*B#XWjr+IXzY=xZJb++wMgV6CW^5mCq%G1K zl<)^e|G46D-o_cLW=umO-Pynn!4qsu3@f1smPrst%U7|KEU4yBViGEunYE=L6EvD%LM|#5{5O<2A z5k&9&5;f4;5{;s3I4nRu%fFeKHFy7sk*QGOXh?rVkF zbJ=gU`iuR1(7|-tZLOFfKKua^$I;ofqJF5Eze@(Q#*9OS#_gcFX2)V6ejQAa--w!N zBL|^N^8O++9t9>oL#VK+V^Sq}qqhLi`z=+H2~Wr2{bphy-gxI}Xk#Th_yLhRdqfi;$Yn zWhRZ>)LO*?9_C{q|IN*T0aSZ~NH7hjeH%oT3JLDpU$KNOX@dw2X?6z5B@O(q9LaZp zs72Q{VE*y-8?TqrpT7coWq_@eEH>@WpcWfNX!G?j|LR`=S=1XtFdw6_)d1s_YHS7o zKMVL$gscI%<{aiaj6U5cQnSwYe?84`#%hSh>x^(tQ{VU>q7qDy^qY`Z5^?p>=lzH1 z5)@uPZK$Hf@69`og@do5ADtOvEwsKzg_d zieE%^Hj9vux=gzbDZEa*6B8@gK1Oa9)w80o#x8O~clFv%S!o>Has&qEttSyD>3=Xw zW@fd@AlOM)azE+qY&n=J!Vi`F@M7^|Sq`T-=NVPUh#cAJ} ztC1@%fDg*3(w1UR7W=N;cTVK2+vN5$^f2#xa56Iq-(fIlk>qxtuOq|#d@J_=8PDm% zA)gzDo}SLa@b>x|{zB)w254Jp+XwZxIGUj9TVbGHNoiX}Seq%JF6t;d;K?5Pn0CfM z6Na0l$-0;x(#s?ve2*h$Wyzqg9;YKwcaEMuK5ImL!ZQ~EGq zclb}pnqB~Q%yu+cwuuZa@c?9cm`)qF!RXzW^lc)_r{EY5nYxv8f<kJDJW4#K2nPs3mt2IyziVbKJrSdx!IwE95jgGdzTE^7*J2RPnpi zmW^~3^~n={$)gyN2SjpimWMtG@j0&99+&4xsZ1izUmG_#Z$p^%-~#0o7h@faveZ}7 zo;>kfIns;rc8F#!Zmc+~&-GjM&kpP&@4ZESJH^D%>zEI4u{Xlp9Cyo}u?0Ph;Qln) zv{U$+-00{|5vtt2Nq2XOb`d+fa3rIW;f#Yp8)=K$(+K)fuY6IfMkA0GO~d>u#X2zD zBX!p8%!*PzoMqBaa^d29A4>O#?n0aLMWrfhWr4E`q}~}=RKr;PDu3ntRJ^WuEv$%l z@C4?B5>}NHyJ1?}K#z9| z?+W+3F&W%2Q>^Bi?~&IYk*eJ5MsMs9ot2~AX!{=Vc2GB_;ntNH-jOX(IoH}+>!Yq6 zbk^_npk{kTFXgAM^x>Csdt@iqoB}eVhysDPz`B|#EZHwg#-#vh#!!i)6xXf2ltuF9Vn?#cq$J%QO`orwCQ%nqmzKLnLJgJvlB1o7AbL0GT+6EO0b|e zH`>BzTU)@~6%S6)=|U9SvlO~tD3Y~(1?U4xI#K*S(Io4+WXl{cbu=U$rH3;NZ8;p3 z%0AmoMyeiq_htrrcextC)R?WX)e*7iZ=Pmzj(5wMi+&WqOM@F!^!i)A;gw!x3a?j)}DUf4<8x4uYEv#YXW;4te8;T zFZ1L8EZE^xC1=8A#UH{lm% zk0|cx&g;_n@`lDp(epED2b|ljR{+&1*CzV8)uUA8( zf+_X}!uk5uvh+Jqxy{-{NVx*4U1X^CYy+v0e`{DaKk0)tp;QMNSfV9@LhT zMyjmZCW%tD%GVYOIs`Y*>|~mJ2=F!qZ^`Ks{>XwG>~n_U;!YZIT$GC$1}4%Z!q?z8iQe`u_ww4f zNcJsY6kZ&)$Q$w<^*XJcsP^`9J{1#;bWYghdIe{Z^9|Ta!Q2y zUxBhytYcy0c1(?X-A0_$XFOU)*h>^skK zmVd`-vA@$(<_1GCD2nlE!Hq^|lhI~llQAaSCNXCv%^m%iL6E5?=UgdXfeHqqzBZZ- zlIEd~I&nsXm4BB>!6pjl;Ql-#qRMx$0YY&Ne66zp1lj<8II7VP0N0`xMidYI8s2F* zE2@>>Zi6`EeaS;-MVRYc05E}1#_D-0{eD(dQLZ&6-*e(K?BVY^2bi;n&v+M8U-_eX39B~sjvVz2VJ0sZ--2r>;K z-wSXeX-Wwf!~*OVoxXr_okk@Wz&XD$Ue3mbahHMz_sogBQMGD0X<*- zUekZHh1c4`OYxhEt|0)E{StjNLi!pT$y^)B=j8J<+JZupe@0e5r&T|r*oM&0Ka2WG z&C%p_6^^#m$#PYsXLPT^!WlFF3APS7gsXA{+O4eo{cV+hM}4jrPkDuLCI*QfN0{Yb ziQiNxG6h>5jNA$&|BeD&|BA#lQM1_qNqbH2fZt@EODEUFhD-MK=ji2OFz<*cU@`8q z;;+$v4_c(xD?{%dLLDx`PiN~$ng?DL+l0PW^ucuGnplgA&&FIAt(BQ0DF3<`tdzu3 z*bR}xBkB#&HE?pBvL)YyOTN0TI2~>>oUcrI|NHc%`mEM$reBE&6J- z_a=(I3UL1|`X|VSgq9ZlEEH&I(J!q5g%B6CcqjS2h*cvz*F|D$@Sg~JEGH~16b%AHeleF|5z3e zLy^;I`!>X)MHKt1a7vzdoNWU!N8RUqf{_3uJ|iwl$mt0g2)PY0Lcl+;;nQt+_c?qM zCA`OQ!?8Tu@vHE4@dJ`KR;JU(zly3AB93qnr*lD~TId8wJdU!Z`3P)>GC8;E^!u+O zyzld;)Ph@OQkN0`yMxqC z7h#?&4suKnpZYS#X=)$J(XpTBDuV#iW<8IfWpfpsJv}0xDaOWgGqcL|yx^jCToxLksxRrYo z8}9?oGv?Vo3Mq!;|Kfd=^+=3edJjjW9u-pLJ(x0nEF{Z4@dmv6zqto@0`1ntmTEmrrTKeR$ZlqhkzxSU}|;h~=g=^u+@a;?r?2XED8uJZWyBbn$^m zQ;tVc{fD9k3}gKsivG&)yJ`PJ(bCl;qf|7UB6}o;BY|m;a9-=tE}Kxh$nw|_>aiiz zW3e4u!XN%7cEjM%`H1N?{Z@`~I#eK%~)(fl&Gw|*w{FGYLS1-hDfA_A~%*AIc7TW?X+&!=yFMT7YuK0Z&|) zo8~rKetWtXw{?%Y1>i3@CvwL<=D@g+NkLECdq(WtAZc%aHlmk7Tg^S{cyR!bK1u5& z@;s0}@dnbDAGVEgtB25dSJ1dc+Y*pI`Q0ozzYGd=e|Y(cd%y3I&4@nVK>DbAPu%+! zajyUThD?g)v19W}p!zQ_P0Rrjc$ip^jb^!IuRnE=So#bGQ(F(AnMO_1Q{XJqFJ)DZM& zH-md3>=|NVdo`aJMCPsl&auxFBZnS7XLeH^N6pJ+ha~q1T1Tmlnt#?bBbhP@5ByKU zo&R^jJzgaoY{9*HGkN%8Wqu2VLx3j0%iOw|@rCT4b&ok?074BRe7xJ?gQ4Y+qeIBi zzT$c;Y?0(}W@uhOwU{k1B<9Vo-VC~?F`+_<+ zTLwJliPRW?F_TGMO8s3me$soks}|?k30)GGV9w|AH>i5vX;GlqP^!pmfsbB^jzLj$x*w%M-$z(;GRGJ0W4o$ zlRp$#q?Ytj=iBl?680W)gVoQ>-N4M);ft>NUk?Wod|#t)z^sn zXfZticQ^&Qd1#fh_AHj1GF@MB$(#ZX(PLyreDIU+iTjk1k>BvG4|imEd<6sjbTrWA z4YU`d+Z*VIfI|%z80}`D>odBxfj(`ZcZ_zb?U3(o;JY#2gYmFA^gY0;VnYdR`!BfM zVIgl1Ouyxi0dXv z8~2K~@qcKyFRiFWMtopUK3!PK^%*|@Ey3GN>C)Dq+_I2rLt2wRfMGPQqSjDZ)}J<1 z)CM*=`FMXaF`N%|ln<2w*AEkqz9pgdpmPSLdut7{{1zbpvQ1s>4bSSG zH5Ke#^-f6d3sX`2q)}LK{LC{20Jx+G_RJ0igz9R4Ik80T@>bs9*p_ zOF$MfpavO`v85#524He2;1(PZZ1NcCUNQhPN-545fSIL$=>}j{Dd30!SWpVs3jjRJ z2beng-NhH5KC)O|F}=vV+l{ z5nPbwb;HqOxb)E8HUfmhD^ZoS4q3BiJ3GuQ92ZyM7$Qq6w;!j1E+8`m_XA}j8FNME zfDUU@i6i$73GN@vPC?NgRxOBYD!`rqn5wmmn#J=dgd%0gk{#d2idyYHvnOOq&fT7f zcm>xn45r{tNaQDho>tP#OeeDDegJO{kR#CAjP}H~Q~%O7qiVl!(5RC)kKS_^ArloRTrhx zVLvS@>%sd*iwRyLTTBnBK;(54=XEtMKZC!~hkc+`F95ks6nq6B5}XPk5=_n=?B4QliREdyzZTS(BcNR?*lLrH7$Jt1#-QZf z?tutQLR#`QB>657YMmnfue}p1;;encJHpD{J+Ya=oZeWi8Yr*5I3o z5oIof)+rJxi+#~OA)l9g4ynWoQ8HwdP~^KBCp{9mD)B(l zEdcqTC!Gn(?LEonnR+mUIFgw%VSD-X%K z#HWKnIYCnPEO-maqN^{2%!hhXQJ_|@TqB@zu9f&i&7#O4EyTatV6IT+@q^GI{Nupn zZs7pwgDE{oiw<$=#@wOno-++yJ6m@RL+Y|!rOc$YL0Z7uwHcQk&p!^CQPU@*r>Gzc z3+aB}of%A@9LF#&Qo!;g3^7z0!xQYCZ0uF*45pawuH)`^sAXEqHF*8G&OvT07RR!jLajkX1Ap;39<>i7n+R&Wth z`x|OA$<}TzMhLw&n&HJX`Ws1jZ{{N4hP~4G$c`7;R69fqY|~#RZcVxkt1r5z+p>SP zs%NnmQe3sP*SZafx!2mo;PSH9+Jw((Z^9iP$pm(z_d>KDVeJ5$ve~+eyFw{_O8~N` z-gcPQ$RJ$Xm0Ux$8j*qt&s%p{48rXAmor#F9qeU%9in#bMtwrH`i-^%m0WT-vB|jW zQ*NQ_cgA3u&`%DRqX)?1_P|g6aY&Bh_QbbtdgAD?h&q}zU%D2mMYj5?3&eIU;0^|i zJ0XNR#?P`L)XE6)jf{xCp4H)rk3jh$E&m9$?nIU$@IvW>yYBW+JL;N!9-9B;y+bs{@THovWV1`W%z6Ft5@&;sf-0$uS9 zWIroiW`8z*@&~e?N3Po(eCrb)pg?%4UBK%E_?PvK&=%B z9P!IebUji_>RSyfB}kS5ePZqzgi6f|Q9#-o5FbF~Sj$G8T(?FB)ZYdzGN4rrXn+lR z&VaghqIaulwUw~0)UUf%IgCw2QW)m9A1|?BwBG9&h2ERn5(C%MqiULW)(RWWR2DbK z0KOWjz{ zQCd^HW`8V7Ypf{0P^TVRoO0wgB~;g1D0#oo$sYI}_X~OS)O_etb*;TeBnpXLeXV;o zVW&j*%xhFbb2lkFZ&GqiZHTD@eOpr-sNB6lezmj#rdc$lmNpRU!W;Nec3r17(OMt4 z!F?XBWhgJNk%-ayD4$%TFQTymn;iptKs+6c0q)9GimVOXw5xQ!ua-onSgj!z^8BOx zb(Q9&BY2gL)kZs;ca{D^kZ#0klW_K~PaUlvc8+(~(RwRIm&v~_R_Zx4u`Zz1>8H9{ z7v+abG_}7r)^wR3_eZ>y^}sXiGWCB0JTK9zdf>U`5}iSicGTCJ^-PD)2}aFB?NN1C z8&&r(zV+=_QD1Z8n;Yma8U2}oW>TFDbf|%z!|2Zq^k)Nso_LjFlQlmYWY#*Ck8H_B zin9G6f%cg-ACG3OWf>cjFZFaY`qiv;38>jjlEDXDUiqERHQ0KV5c!`{KC+3{nyISyMXIK&xEODoFsy$A_%4JQtXH+ByM; zl-1lOrNDxT29FQpDX^gyqO3}y_=Yg7geFq=hNwRg;Eiy_K286f=GLZ^-%txx?lz$- z4K@E-SrT7h%i~){$i}<35wb~}+LT9u@J64qlM3c9_{o1^=yC$S^}&~ANlZ7;JsADc zKr<$Lq@i!W2L6$K9VJqKa*aQX|p;#l--3a*^ zA&U&L@nD>0bX5mzJyr}bWVz%VmiJbi_RIwPeg7ufAyZqb)>Nxs{z4l_a{fn02mKow z)KvS)xoJlL9{=D-?LRD6okqrMZrEI(8n5LC#QoriSAeUf=8SYs&ULzyoYTgmh@X_y z3(r&MW?D1P+ecZm)-7vL#^~$J3ACyiCP7USXje0BxiYaiTLeOATyt$qL_A9K^|klc zfR$N$zZXxgEwnh-!EZo8SbOINMaNR77FuxCJ*}beoTSqQb*!@5{L!2f&S}-hlG_a< zqbRo|2haJz_=FKtn~ z8^Or~F-z)iGR4_x=2@2cEDYq3AnV>~y)j2GCLhIxqs07{!z}%2Kc9@A#=!#|VjPPfBbG@}MBwfb2KShH@XSjT2c-CUFn z&^mUabuz4F>Mw^q>?ZKA zW9P+DpVpdxrIJEOP|4_H?5zQ>=I(2)h4{9WF}MYRnNY1~`kULbXGHTuJ%Rq_8d9yKG^+zmhZ>Bf3z4V*RgrBpjv8>Zyc zr;n4h0A*`^%1_oRm+x2~340+`+C$fXh^@*1UNou}xpGoX^%#IQWdTMmNw`20=C+AK z5ogw;8f~>UO1*kCwyl;L_OdP$Cmd-L%Cf^1H?-ib>6oJ$MhJ*`s~+8I3kyk$1|-^P z;mX~5a%~fk8=nm+A$Se^NANaCfZ;! z*I;4`jgb`A$&IXIn_9cHu(nGC$uF@izjbmg>sa)m>Ij2oY^Z(a{-)HNb#hN2FUm5o zPUfZH-}xNR5|^vqLllb@6~ba2n_}(K5zn0&gKo)n^t{21rTR`lW%*|f^#wPC`YKRf ziq<@61CoEGs%27`1nX1`BI$K->Y?$3d9WejdkKRnxqAJ!N z%nQ}DJAhw*!gt(kpifh^uKqV5ldStCUip3~aZ7G$Au7{vb;z~7=I?(88L3mU6XL*b zS(sGkJ9N~jNf~viS$nO&#}_is@=goLailhFM5MasV`PrA4@SLOYqH_{II@EZV#QB> zePd>G1x{1?@q_eNdrXsqcTmp`T8m0+@N_fs@BshsL*Bc{NBpL~w4s9*;{6b_&L>IO zMnopk3ppn~!{|l_ZG5$JePvog%w9Q_ceX!@hbCYhCxrxS4em^5O)9NBOBlgoL12lgc-3hbA z_iGu;=Pk7$lzsVp#J+qs*OZFNRj<^P%jbAs0A4MhT{u*_eEvuhC%`F>$5k_OiFRt; z9sao?XxD92wKFzA-D^{u&Ml}aI zua4*psz%c)^lPMd*82NfS!C|`KnEYDpFxJ`(JN}v8(p+&<+G&#s4C^6X-OBYof2J} zZgkNq#ZIkZXv|oUKaAq!F#(6n#qxXl)FWjUc}fE$o-*1RFY8qiI1-nJsw@q*H;6?wD06pSS` zz0*x=)z|~6zP>JZ0!$7g5c}1-{Hm1QzAk@Wjo0N(scv_4<tTSkSVVhq|OS6NvGS}>Xn^f*ShG0+(X`c74#ml^0T4SSlD6}JZ zn@09r0PYIno@*~8aVXOn%f)sq(#FOC5AZ<&^v;`Fa&SdE3&}ep`3zn+WA-n>DY!CS ze^U!c^aMv)&29X?lgz9flY4vaTc&YGQw8ifuy-qe0lUqlQZc+*dpzKB2LLw5H0o%Cvb9)^*m(^!*?hDaQDc(_k&!`<}1Cc)Y89 z1fAzgO$TcYqLr0pRE2DH_WOUe+rv+4xBP%lISRy`)+hQubtn&ICb=;4MSi( zXkM944bfT#AE;EeR4xC~JZ@}<=6zCSQV_M!AGBPe#>gtgZ;E6jOsbsc@E|-T(?$^- zeZ2?{`O@OI&;@PsrM+)ymHbCZ%0z3@@x+|MGDQ(i_utZ*1~#!X;_3Fk%F@%9l7?#0 zmHvYKTxoDtzor*%qj5y?54>pZP%S3zWPof@+whbB$Y>M7A8q348rh;Y8tA2rer%vs zMqgM%4~J^u)vElW@$3f+aUD!s`N&NjkG(UjoW~DtOOIuBg%34;TMJH{Vn_3(F?G(S zcC2} zs+Vp_sa!{Vh0)CRkES=$+Ewa=(ZjIod|7JOiI23|bpR@3*D*qNyN(gE+jV5ByWre35Rk{K%2;+3ht)JKUfUTEx9a~h z?X0BEW3--Chhi?vexj9s!zC`5qVaFhJybe&abf^&&K{$cQ8DJslad{sS96>; z)1>@elQxgn+AHg8=9wmFIVPoQjlB62watn$vKlp?ti3RG&TBFS>mQ|iIQ5*SJyH6H zQEC<@K25{&#$;*lnczUK&d}uz$U)LB}Ta?VNTk1kmHz*zQ{xKp}Iq9!i4%nm9)b4gM#G8`RV_sMb3%0CA_JJh*a?X=KW$kTc?19@7nh>zkO?708?Tt|nN?%SPWG4eD@ zbo_jVhX>YrC5IVz6}ZH>M_CsVbH?pXnswWKL0_~L7DqoYmvZN7-IQbgbbGGWN}1(P z)pHB1Gs}fZOG9^DGU54be>kdoEKOj;wHTub(yEdWL=&E0h-DyHl`lcPjKjt zz8gda=(L{Cp!53I0O_CepHEAXh5D`8Hf~Em#Rc&INZF!$By>;cme7@*qN3!SZ|YGb zds=@Jgs}Y{wOHry9^_K1_D+Q#W^r;b;sJkP7R|A0U2s44Wvdp{ejq1+!_cbUkt4!k zWGjB{DU`Zg<*Ju8ZmO6t;oT6pzRhB|d_R~G7^?WVZ?fON&lW{Co+UAQF zY4m)!6r6(uoJ={r0K|Ge{_yc0Fxm+WGx0*Qa;nRHRnEEOoTrB!O{fe{oVYSP)7znV z;XzYQ`pFbMl7oc#iVYcKNAk#0bk0C#s76fB9LFppDldzQwj0=F8Pv z6~`db0}q4aFp0^;GhXIxHsUnen~VNIP^$%4YXp1Kq6L`uPNlO8u-4h+MgJ_&Qo1|6 zi?rjCHsOB7G0Rc%%k+CQWTVG`)7ncezAe`2eyGoFty3B~rOE}y**i<|N8nN0@GwJ>Jw3=B*ok5Cg4?0Rp_n9@M+~yBn za>l@+kqutafH!ooaSpY?{SEkFJ3ULz4wNRqQEeBx^L2a8km0F6YYs&};E*z)?-GnqZj=a|9lQZj?Fp=*k?(7@f^*m#iv=9>ie}FX z8!Vl6zOPk@J6nMhZ;r$ZuOM+hnRxu}Qs{dORruUAU)NBg56VJ(Y{IT1uO;wYoko+F zV0N^38m(EP#VQ9+)1@VvCD8jFbTe1^{yd!GE$P^3W}*9%Lig>3ux|FFW*=ywuI=5p zl-V&Y{4x#wK_Uh&vHsmM+ z>4u(W2ew-siVhg~qc(iUGI+j1r|2^Szmf4-NIJF*0pIFUlx+}v_otD%hdp%+H4a6C z4CFgD?oW_C+4FcZGTA^5v>|^qkWw_hX|brffse6q-aZwjE@Q)A)}e@>xI}gHv0<0n zS;GY6Q1lRg7-?fYr3`C+-LUACfiJN|?o$R|iSZi^{9PNqse#Xuvcb8;p=hB&aN0%? zXiJ^d%I3A=F-U!{4atSfax>DOk+|yJ{FM!Pvka1NmM`X6?jjrVP#I(Z+!Gy&1Au+U zhD8Hgu5Hd5gpPtM#RJqvw85`I(eMwWFfJRQr2t)hZh*LAgK8Z>UN)+225339(pos% zyS2)0vEQBYA(uw#q8MPmt3u`4EsN-h^Q z#;mdTSR?rw19prG*h?(fyT0eyVsDZEXLjEMlK=DbNx0pa*_qkCJ3G7Yt#t-?YT6o! z;}`$R1xOW0`z^eJlM{WFvMc>BL$auABwVGH@Effw&sYrm@i`N}yx0_@d~4!WmYBj_ zcR>$2kCQ|z_{b%uHU1MYMo^mkNkTasP?=-k_CugYlX%EdQ;6U3*E0TxB!Q{QOrAkR zvWAaaYT8m_=t~)WGLZ)_GX<82)Z>RH@~+EF@xhOE@=+5-b(GF-8DNl8E2L9vIinNA z@ng$O(J*wBS#I)F?oQxA%T0Ct4(X-!JGv?gkQB&&CMB-oe@^gFQj{rqXTx&F3VhSZzAZl?Ey*=EHA@Bi1O4?kd34q^^{v>O0zD&OD6+!VBmG zYCiezU1uua!UR0tKGcC9?TGDu(ZbbjrZ~X~ANi$qrq-pbA8GeVE0hsOdCm2vRJY@g0ZB8(So$@JP5Wf`eZV(8oZCoP1DX``{0Mjb#u!v5BDPRY2(2fizZqqbvsR)=OW=Oi8C@p9vPz%2x z8X-};hEIT=|HV|f`d|Wz<#b1|)Y=J&pqFHe1@;D>Y^tyUzB@JE^VAKd*2=AS{KN*V zBkvC5z8g)!K^^ZBXLEo2?7HYr-NB_V!rCw-cNh(<#uo4R$c?5d^_og*528->6V%~E z?MK4!eyAm~KRHH@4a8+F8V`*2x4cXRR<@0GN~HG9Fwz(PVL5K1=*J)~_CLT4MK~_) z8MX$UQ3_f-{t!x1YcT@*MqADOH<_w>dQcP`Y2CvRhMewidG}4G3e6h4r6z#?CgPbu z(x*rMX?q2%PLl43e0t(%*CeTVV}vz-D)cnrW%~XN|9z7wqVfn)^R!Lr4(3{C{L!~5 zoq!>so4LnkQ=HH5w*}8u_}O(7lM%{pE5ceFpS0Pu)93arfgXb&2l@)acr>Zq7E_eZ z28s42^q(^I*MtrvbRa*x#Z=L^ucX;`6EwRdO?9Hl9{ST(Q|A~(qB|3Mn?&zMSnD-Z zH>6nh&!LqNd|jvv?f{3nu1w?`CHZ$mUXw50YO3$MUZOAD0J@AsFDLZ%Aza&PTH#}q zcz?ow!o-a1-a}Zcnz8a%Q+0!K?|H_NZKluNl&QBf>Smc095|M+Ei#Tu@JR71S>P(w?;Ed57Sk(A>h`;I~uP zc%qBAEy+&~1j*wLqP>Vm=nkVt###4vq(waHs%sok@1+lqAn&En31Jb|Zb~Q-;iH2? z4x6fX{L~(Vn8r$CX^gj2wX-0hq4FHy0H339U&zs!Y2UR&7!4F_(Itg4v@{A^jJ>|) zR}aHBWB!&KvQ2(1n*(h3&_YRf>eNC%?Mul+O95D0E%RkX{-J4~92A37fGCC^0Mdr= z{@JEBcsOB0wrK|5A*+#Ns#WfJEb)t;V-FFwojZDzyXBa?LYQ_0@wDh4#h%~TmiR{J z(x_xL?xnVi9%hX`P0tH{%eUp2s+9YRXe_Dysjw`G%#NE@IogGG{9%qMz~_DI0tQ-) zWQMdkc$2zfE^Httm+^_YreUQ!gG?rjL5LfUn#yv+5m>rzMrU+AVyb3HNjgnEW)|Og%GAweN2@|mWnSvE>8J8_zr|``Gx*}gIE&stySAAKhEw_8(Y-dU0K_-M@DOQve>Lx2`phYJXfD$P4{ z`KwE&y5%YY9w)n5tR*=zcF+aNxHE5d+2j*o1bHhS&chufo3hz5yS3)l}Z|3=-IvqSg3iJKEX%1$^mMQw{G0 zK#8Z0fxU9rf!(=)U%QH8K3|&;x@Kx*Si+ZHGu5YU3jNgK#jl&{DgT7=Cf9LtemIPe zy^hxzzULdSCO1vBl#{ji7dNp@TFRFIjF+L0(oZw~?xv}Zxl}E1 zswd%DyuL_fo)RXt2yyM#k};+}69M*^0J}H@Ju8R1eRsk+ORRY6h2r zTcb>nvKGe9ILi(aLAq4qu@7)9r~Ym}`GKjG`@9-RO=kHuNQ0H@^fM&HkvKE#i1k5InV8fgNw`#DKDD0D*GwbPk3VUq9m>vie22}{uGQ#u zU{M;nofhXG<}VPhot1V}yFsF?I|yuRBRaad8*nj}Wt4ZS_Ue=={2F`rskLDycYS0^DA{5Sky6@k`InDS_@nrDk4)VI zJ`QnqN;lY7#8{WPNp&(;J1E};al>OgtucBBk9>^d%a6bF|2;NMst{2HxsG{Dm9tFG zL)TS~MCsLe+b5=RWzJTj%vc#QO*^HXuf$J1F;#U545SRvcTion;@gN%O$$a;TuC6= zg#JXT?3Vovwo|Vu9W`)K>}!deFlNlFjHYP8ABh!JHA`S`Xg3LLjcB-ne$4xqVT<&w zXcaDX0n&0RXXHOMRWhlZl^ug4*)@DySu6{ecWb7cE*FVrE zc|pDqk?Z*JBT% zfBFyfldui50b1T-{?$j5uh(9Qdq@>&O~{qFFZimDIJ^ISUB>&5rX~i14{z`Z3!3Zh ze9R|P59Rh6e*6V&k zwi4wxYa@bKMD;2oK)Kp(w}m!Vqg;yulBCmGtVEuMVXlUOEmWSYo=XiK}X4W`td2q;owltrvFyAuYgCNSX!dsZY z0bkEIDr}lEYXuK-hK#x^_*7?B3l9C8oLSAf2V6kyy6*y5q{}N6X_vI2rG@-VZ5yz$ zgEAzqgmkKlKyE6=Di|8@>cv=~w+jel&Xe*q51A}a%Xh(>KFlw$WHO|3P$(M{o|A&W5^nH$LjcpO;{GN{DwV$%Y!LW@MIRJq=!)(dNKc=TA?} z^)S9!kx|`+{bcaU$CUD4WV_`Y$Y#8ZE9<1*7LxlKSMw3B%wMUpfPd@C65am9OhF3z zV$?%X9O1QG;7$|l#&#$t=JQ)_td_ETK6fjFW)p<7Hx$T3oRvB3g0~RXW+()YID#-(TxeM8z$Efx`jmVmgw??)@_nA30+yD_e^#6BJLlj0I%1^5W*MZ z2gwUJw8R)*32{T2I z_W27pyc2R=yW-Wf9xe;ADS4Urm9C)&#st#w%-8(5J8PQova>)$6VK=^+IeUN3DL*t zX81`;fs5k3f$ZUujnNry^dWysN|T`o2T)0*En`hm~X1YE}>geO(@)6!7d` z{04N0_(k+Yo^}?OFArYmsQ_ zQl*tbrOodpn@nP}Qj6rj_^_}_>qUYx@tJt7Fyvj7wW({`G*I+3S_Ptqu_}M-!$NBJ z$Gk=07%9N$CIv_x)3r6Yi%Qx;Ww+>b?b$ot)tCKH`lq&}jszR=BB(E`WEh%Jt~{$| zsD3K8umXM_G8Tr;e4$hi&Eaq;3Blp+`#e4usLYq6w{c-d_nF543nCkxp%Nm#udgB>huq zQ301pyq-tT!o~5G{%npib0!ZCV15;b&Lnh$n4DSU7)t+i_YKVP+Gnr$fak0%pBTX0 zlqxg%w@9HBpGikLo_ucrE2Z2?k&&Ae$&!)Mi8MUx2SCdNtfDeGh1ajZqC?I;n|2b%(h6suFNf5#U zR0FR+86d@hL>5{6zUP}Ovk=3m zjJ(Rs!;moj3xU;!KB7`d@(-jqKuU!alufyxh6kdgx?{rKh9#xeEDJ`e* z5mi}jrOY(GtSUQL;$sSlx~{#q^DnBgshz5gH^C1UIc(Vj8^HcCO^hKyCZ{A%_&}8e zD#o(In~EvzGEN#{=It^Ji04TK2s#fn-=?@!8DI5kSe?~~dSXNF^&-|W=Ou{jZdg}s z(8@j(1+-FT^w>9!0)qYXPi+xjRh?BSmrOL4)Y_t^949t+?fhzW7U07k6fn@bNoK-! zI**qPVq*FCy8jf!B9u4xctj1XC35faku_Kk)98DOlkJ@oFI|~c z=g(`f0m_ZLJRz8cAtdr|8R zCdg8j_A@V0i$$3X;YNHX>=y4_i^Uo^Us{XRNalAz^YtNm+_31zN-T8eX_E+xm1`6G zW4qmMbDRlbfvO{dwKxcIz%#WN9bQ?h54aXa8lW335WlpZ-k^=RBY7^NV-rppEij?cqK+_C}0O^}|R3;6O-cCzxJjWP*{-CXQw#d1N@ z!)2}vyBz$Q&ke(zKZEZGV>R(Q@U1ZBU3SV0x!gdqGSPb|Z8R@cn^mN5+}CC`l`9+g zXSG?Z*Hx_LXaNJd9>$tz1Be(o-&LDciR!k&=tR5Qe0wu6Fg7%kCOYZuuO(@F$bkz5 z-X}d>`21(?fm`0N{d@5Wbyy!I^A|q74hzOtR5sRO`xM)BKD;gqcllwx(W#Ydi)SQo zHQ!d3m3RLFpiEqm!NZ_f81-}+Kb@~^z`SZOtV5asw^KeEiWOQ<&$e)TBbOf6&_5yg zEIk_P>C4O4Lsz?2m&ey*k?y~&6YS`zQetP^K8>%a$9&zV0FJqe#1Jc&+0*!eM$ETb z2f&NGOX~R<%BE0+c$d_(<~nW&XXQ991D8`?)ZtC)L(tB({}yz3suW}eyr`g`r%FKs z052+N6c(`m6%?^n3UV{EazQG-K#w+c6))W6q0yhy)*k-^A0IcP+eE5{9#nljjm}ef zrzWgMnLTU%rH)H3U(o=iI}hL@+ITLV!uN$^Lpm7nBHH*T@>^zBR*780KT%Fy8lXtu zgz@GLSUJ~$DD?t)k0%Rx?(0|oTi*XBQ?>cJPX@fGz*&=pz-sLQFDkItWJmT7I^KWv z4_+;$S8j-M{uIiaHe`M!_6X@cjn5|Wfel$b_Z6%DCD^!mk}UVJfEN+$k~~S4dmF%u z2=?p(*S7zbd*xNK+=ClI@P}HwWg}GZZ!7;TXzxTJs7xy0MFq|0S&dlvsyzWNDrX34 z>A!N!KZV0gv6a^jXWp*I(EzAe(NTLEKTMFt`umE133bVuAd0oj9KeeR_56|V4TsR) z02dMJKWu_SXkn=~;{T9T4_>P=^DZ4WzKGOMykip7*90iH6eg;JX=mv!mhgq5pavgUu9^6G5alovzaf-*2jZFVjDw>H^HQk(AqFRIPr zB&p3nz>8`#8ZL4F)u!1psf}w>Rt~nf>P?xy8i*E1MPH1^H)ZwRw=eydSmXY2vR%yv zyogwr1>w&bx zVvL(DSXnH}IMagtV({tO4l}yd91@GA{0bcC!(uo9AgzAJ{8ns;qWtk$#@p7cgaK2A za}2A~Fz6`O%qfRieE6fJr}9v8!ZBJ-Pn%~i8}sHe_|FlZmRM15$c74JG_OYH&79R8p!TO8;qloIEB$hQq? z$QNgoR5epm_^Nhna9DRqMIJj^I|^IG$)|KSZx0y&h?864eICK<$Facj41}>MIVhWt zNl)V})8^2ZH$-J9{L462slPpemSE2Zb-`-&ip8ztfZcFWk^^~%D;opMgtXR($T$^VXHm6JD-zF18_0n+!8$bkKxw7;cm zH4YOxPdHm^?mDd%HvzgGl8EE9j;AG$JPduOgLeR|-9}>V8vV(mKYs~cawT&{(B04s zHTjS6EHHTk;&>|`ur{8I7wM-<ym>(sY+3@CQW>?_Pf)<_%koFbH!@Iu{ z-ImZ-CAwK8(48~Jwr7zB6cM}AtySGh2mnl8Z&I6=59YkyOGcsQmmG%;Q zh(n$D!;b8TZw*QRH%{z#eeNT~o@@sCcL(^1PRy)q>d1?AW{DLVb);oF{)@pCj>twa zHF9yP$;Nb~&#`-!@0hWmGn-&gR=3Y6)s<~CD1XQCy$P6W_s8+43Cvqr7su6Zu;N6= z@!{QA9mPG4FYU%gDr?*E65ZKwCA?imQg?RDpp1;=EqcPtQ$Ch2=*hY(f5z}fJy~!6 z!7(Hsmn|~Vu_?55!I99+v#tvdAg{488AEz8Tnjwdn%h5P2bE9JJiRxoq+E{X=X$f> zl`7Hv+deG1?6p=R57{%WqK)9Ll<{~ge!CBwuY}{{GJV-}WoJwNsxRB>HaVUedq`GV zn@CW7Y{CE6k5%fwDe{`(-7psZ=e zOAcl=l!Ob>%x7-!zmpP>wg@w}-OEVRe6@vGI*o1;2Fh&9av^YGk=C4hY7F__R++C-L1e zH`>Wpsiy({}8uJUEv);<& z#u@d8vAe~Tn~~gi6nm*Gub<&InmsM1Yz@mWjAgx@%XJ7b;jAVo*19(k^&e~9QJYK; zL-@GytcqbA|9L!XR_dDB*=dYBjaDzr8F$9Bx5W&fXI!1aRvQePGLomUelE(%a{T7E ztX!#fv(T$j(ROjon>$-rF+8nKq2=tkU3_miWP{{(c zlibeaLE?|!7N*9u^#NYp-?8$1o`scGN|)m+Ev&t=&x=2^FprWwh?QkoAWE30r?CM% z@jK?Cgn9Ag?^tcaLZ1E|tEOD=Wxl=EF{BrKB5J|tmbz3(SDdM3w|GJ znug&301FCU6%kqRo<$HwCr>`z$^w$JW|GsWrCXToSN&#wXqL@MkD^5~_j?qH zn+IS1J?!8Y%JRJLSx2SR0UkV;RZ;qu&FDCny)_vYXT)+g%Q@LP0V8X5ZR$jD5yLCh zqBen`WwfCVC~9%jc%pzUieT1=SFh;DybSiET~DRc?R*%gv~?1FL87M<`Z;_7(TR=$ zq|K7(42kad+zt3o5`UKPcw=}8YhW167cRjjb}c`!gjtk_U-9-!VLSMhr!8fz48!@! zrR;N0x0h0(@=(k+T{^`Zc-v(d(zkHSGByBj>OEe@#`cISX2K=6rd7o24)A!2!O_`x z9P``aaN>^V4?-Mx9L3?@8X&$7h`3aF91!M!h`W@#0Ks*`%;oHx8e^Pw?qnXbRYd8* zr=t&mbVuOpoDG1I?J@r=jyDu)dH5Az(sj(fulU*(Y^w8jiiuZV$y%9hFu2){V3dYi zpl|?~1+%m*Q=a7K5ps(N8~qf~OknwWeu^($$rdEf29^e+8{>olOTWSDEY29si8}I6 z2NJ`8lkGnKq9S|f$aM}R6^8AIL`LbzISwS1i*1WU2J6TX;}rewE0vLLuEfTnQIJOM z9Z7{7>==oxgjt-BbsR|H^O_)$*J1D@q=y43d|n$#XoL_CfNMt`9IdrVT zx36aI$qjWmu?}i+J#LhwuA(DD97u9}wMm~rv#XADbs)vXxOy_>>z6Wb&ryQHt5{r$ z^ODF1!WR8TJ3q!z6LfC`W;Id6=X7kQBPo_FPBxm5QT>N>1@fV5<5`Gwvt#=COrO~>?0ttvyKUNVEhQvDO*gwZFPw*4y3sH zTp!8II_AY!q5w>p)a~r_6@ow=bBQp##ah5KJcCEv35!Nz{7vRT)JXI2LFY9V^9{n%P7AQHcS6*Z;63VjAb{SThLnDU%UO1 zO3bCnTRwIZtLM2|!?1M=-z$qrOA$m?P&|U@RZ-*>rN1_?&;-!h*bt4Fz?R)Q%WZTvOYLfyHIq zE8ci3%T#u~k`uLZa9n7E%ew5Mn57x$@l(+K*8Eit;KW(zi!tv25m%SoV zC&?rxZF~2a@aKSTv+=M@R=(0V0$UqEI4fB_Q$)0`B&BOPU)cD_Oy;Xpd&uWxvKh)+ z8!xeweXUfqaqCW2PkE&AKXa-x*Jm9g>w%&3a}20!>G3-bNyv`}s*cDB2A$<4q|C!pMcj@)>V^$Yye z9=q1;S*OvAoNOb}XfaFFzd=P!u6({*`OJgJdCn>R+d<}Sh{`y6kR3FXs&LIg;j)?k ze3(^s^*>G1t;6FmXEa`wkTVFX25pPRz4-C&;{62Z(9*3;>IDfS6^o07$#c ztLCzLaqc-%^dIrm?a5i*T>dW!S|dP7C2)g3(O{!*CIERiTO#TE44@~M@-$QpX+M^) z&1J#yOLdCKLXPXsN{U*A9KSNy?#sb-v`+6<$g!->@t-(e{s^n#Uri@D8dsoXn9edI zjt@A(%KE*fEh&^(EJ;YOWte0MgbNx+u-z+miElc>sw6MfDI*J+twYNv%316fIIQiegYLSS$hgY?pUb)0rNL0v`nNCrMwbG4+4EFK(49J$>q@MU#5T) zEr_(Wt))((#BM%C@VDnU~LcaiTF(3rC9b_Tt|PYmhiwJaXOscIH7o_A7GcuU45#86?YCX@Hn=|7+q>;3lAVM&ks6SYsk7(e7Zk3GSv z#Qk$X!dFRB8WM`@tiKl^F(iyN*xCrUdGjfq{;NXP<8;<|4dVo>7V?!|6JatrUTK}x z6vGMqv)z`~-$(5@v-{?l0u980ujBGMWw0lJlvqlL=&M8w6h>e)S?h-t)O9(9U6 zQKp^cbxt$yNouaE4H2JROtNf%DAE@H5Xl4|`Un z{ruy3xYYc>D__90oyemvz{J+@IG=EVg?4(3PlXWYgTy)QhHzgW(vbA0%fVVN1PT2d zXzgqKal9;sU-E-Np{E{0#aqqG@zck4HzMQp1*~6`2}gOuJk~RLsP#pHJxppUWivHc0|Tcy)Us=$s6jE zN{7IaP9DBTO!&4uNV*VjnJzqQeno2C{PjONY8T&|DR_3tYV_UpuR#F2nezJZ`|}Uu8!OUi`Cb ztYOKO>ydJq8Gpv{W!KnqX8#r8mGu=K_BkHd&$Mq%Y@$h5yuLN+;9uazPojJ=q=VC#<;n+SUr}x83IPt5ds>(JZZSi$~ixT zz!i&2gt=#ujG|0XBez*iV_I!wopO%%2bPrZ;RkNBVR$wz_zp}azw$5dpfK{n`1(6I zSn9_AzQevSl;OSavV5iIUfk_~74_8~zVjXn4LZN4xD))i78=hX(C6UL(4x=##iT{- z065$D#~$u_pH)cyehNO=lZLN%r6Lex**$~SdK6ER@i~t85E-B6h$qPSN=H0e#uqu_ zW*JX+#H-5q21nde#FKYtIsisVkmZQKnk*E`b;R$14^HBbK-#{^exe;Q z#?ak-#shdaM#yOC-F)Q(Y&_F<@kEVPbQ_F8#g&3NaaGeP^to){GQdx`9 zX%_$9&K6V{k&35r4aIFOu>f$bAVv^OCVT&YdRCWxfAGjhtajCz6b;W!!s{6>6A`wT z4xbo^@JNEYw$DY10e|p?kFa+e^#?!jh*j~kRKxK5ga$J40Zlx~v^xep0!tCAq-fgi z-xMf~9mEc6O$FbfO3UeEs{x7v=fWZ!wn`3d>{b6I102r(hr+ouw#lP7O z5_Dx;cc@co2S5Ip`PEFXNTW%?}$S}UsK=ut{j)5%BGDXZ&eRW;mIsfh8BbYTt?Px;k$NN8Lp-Mn{#=Pg4&RJJTqSHN)EjJL_swG|nCUon$X>~CKjn4P(& z`tp=F%&XYmf@sDY=2tpuI8sug>^`=80sPh*<`a09U{>=I{N#Hsq=?1kemDBTWm(Z=y096&fDr9~&jQZOfTpejCU^)Bf#Zaow@bbvWper~Uj5%<#1gsKA;gJm1 z2iNn>Z`s7;-zX){1O54WQ07>%la}tYp8=h-D z1y#fs4n7qTh#7PY0v}wXw>g809mSuxqs72#2a=(I<89pZI7JJjl(gN6xGvaHyUX`H z>tKy=HqobEf&Zb!Jch!S2xqWIZtqexVef(5r5tL+-TYJ^ZhFtkCI?Hh-S`37Qjnpk zPa`17#c2GM2&YWMFb1wKh6=9VN%B#GtE7vSbTcH~K}k1Q(nS}ct0?JcbO*oW1(L9- zB=j#r_>M4XDKzYZFh$aNNV->4S-OH}B;86$_nD-7gdgPRL=n1;l8&Yc@T({3k`GD3 zbwvojmV`8afUu+_Tq^0N6`|`X>1bX7-JKz#WX4Ilgd%ixBpppRpj#s8;v`*its;b~ zB*fTEgbO5L6-j6;LU)TW_)v(X8zJdF;s-gthCbscnVpi3<}L7RDe2Bhx*d`(Ow{pw zNk|hK2ww~qIoTo!twk6PAHt(Q!q>jh5I+7R3k>xV>16$vkUU~Af$8^*ZfL>c@}0y# z9Lx`WWc8Kp>-hVRY^D;rj?egqwZpJ={2%61c6eFnjg1?=;X&_$;u2LnfBO#$P<~oV zbTxU)Pppjc-ko>*#45X=cNf&Isl#Y1Y%x||%PpVSc%K(P0gj80_}OKJ`5RNubA<6C zj&DAkMu)87vH7ff`9F|6Y9Es3+so)a2J$}g0e8MTpVh%K|8YL6Z!j;cq}J`tKrMYY zJ~{kax|6nPeg5Uwl8d6$=9DG{a3}t+G6-0f2}cl&mPkX!%qJ~g;4oD4D#g@TrTKFHWii!T!F%Ru#ncg1juJ6#cHr;Qo3wkVwF4-|BAx+& zxiVez;oe3yB-tC-Sa{{fhuQR0o zX#VRGGwvrUwlO^4sCp)El1U16Xa@j%V5||Z=YpBlyc$2d{*jIT1H#&LN@0z-DA6f| z{v^>J^MJ<5JfSlsdN85s3#RBY&V-Hxy2$#SFDS0ohnH4vakXO2`6bbR@()LcznW@~ zvkvm6d_4qpk^FWtJxmoRDpDz2`i1>2Ilf7CbfLcew578vQb-0 z(`WSb7VfeUpdA49|E2+61|$x)g*d2p{MgUCDqz-T@j}C z{j@j=@^yCBIDDAf+*$`-1`z(;uMZgn*sOyOI>4m_xUvr3!Vi{I`zL2X5^-z^D)EAf zymg{~tJS<2X!{KaSx*7@KAG20-~5qIPT^}ZtWx-l z3_DTyunfORLwKhQKcVne8NN?pY4|i>r|?1vzd&KE!64Vwe3By5BqBSFZ*x(-n{F=# zSU(9{3p`cO0>JP>XL|vS_-l@6Mh|AAos7mwFM8iZfV6`~?p#`3s<`~fSCv+4CTB{z zVnlZkXPThfN_02Djh@yL?raMr(T!Ok!d9Dzt|6@L29dNkS&Zn%j(K(-+zUlw?W|G3 zejTm)XUSn1ao8?7EFlicq+;@_Rex8tm=d#q4|T<8Fpw{CRcnUe+>`nY&4+PmNm=`I zsf9R4oce*Lu%s+49Vh5CE@OOb$)CHbjRp)w3hKIv^lP{io7R`vBjVr=YMU%-F(MrK zL}jL?7G^gTAZ>s%MoBV`+>SB8pHyxvmV!UcZLyXB$RL-^G2G&Yq2S$ozQawe)$Bd8 z0B%oag4=qTD^9sOs_2D>1FneklIRE#t&l`H2;&Wyz%pue?;-O=xw+1y3m`ETSG=)+ zCHqAS?^i}`*eT0Nkq)~xU{uVBK1~B4RMKetP6sZ=5|5il@mScn*yj>AJ)gk{YmD!8SA%_DK|Vy1F3WvPoPBsOJw zAdXvViD5+U8`tySi0Yb^pYc%Z`S$q7;MC6As%j*hi6i0h*eJ;+mo2M!fTtSZxAYwZ z>RV0^&_-FjVlC|%Hap#{yoaaiuQass37%@WA&&p%sWvM;>Z1Y8?TwZLVyzqh$5Z`Y zIrf2Dz0~oBC|<0b`fG(!Ai;zZ8Iv}aktcM*BFa*UH!|UOxJ#;5s-z-4EOc~t5XaV2 z-fDf{18*7o77xj5aTfE>-*QiHHPq+lWKo9JX@zCj03dBR@9nKtuXTy^2lsj(pw6FQ zfz}r-lY~sh-fARXJ^kBToq_M&j`2|&di(_jG%aD7*KLfj>HjvF@AXlC zD&eoQ9`ue6sHB$ViN0zDzn!mT{}Zn#B{OX!;&O|iP2`(>)f?XX@Q#CcQ3}}qAuO;@ zw6FQ<@@j6W>o0}A?G35Ie!AV7_DIgM6?>Y~NNu{V4~fLaq5sd<3vpB#y@j+zslAN6fKOoNAK9!au+ z%jQXv{|uy~6_E3clFk4x*1^LI!C3&$(ZQW{aIqQf4D|R*k`8Yy;dCd_{J9RRCSl3r zh}b|iIRToGf=@-;z$a%v7;p-PFHux+xN_ANX&-3YKmn!b)U3J=S}H(ErJ{21#yX09 zG3_I*=8`&kS|M6E2cZcN`q{)d%I5>EZz0D!Ff$OSU11X8*%d33AhPpo^okMCj-b=wrIR9)#9iuhJ*T z)K>*s=(|=29eK$+R8&0&rRniag&e$y105S9$6xAbOCh=(q0=O~tBxKi(JIQz8)!Rq zjtCvsk>^xY8@gU-M57uUNUp%lkpF_aR8lMACFQV6s<)33u9QfTHWtIz8|jsqk70lw z1+An;D6TX3ib`rD<$N-KpP<&@N@aCW@|!1eaP9OA!{E)QXzO^a1C!<)?JzJD5q5-K zq0m(OMu7C!L21^=)>er0_hX##R*H2RQ^1wqSi|8@&)qS^pU8>GEe};aRtSp)uA-ipjDOx)*w0$Ne3G! z(TygM3Ob!iyJLqs_-I2+jmzkql-YwQB+At_B(&v;wIg&qg&6X<2zlg-s;Uie;B>00 z8r*OY;Pw!$Gg#8XjJ7Ww0itUUCA6l1Y7Glextdv^ZfJ9P?P_X-+dmYwOwqqv^z;#* zR!#jOV1R&b&lAP7o0fQ54-HaaL&(v7;I*o&)$xs;gz9P~<(-{Rs;-ugyNX=IhkwGN zH|bfNb^9Eo!5R;hgI)G(?YfO}l&@*{#pw@W<keJRz*RJCZ0Pzgb<4!v}So~CbTbGnoGLqd&dqPXhiF z3LSzNvD+-6La|#~2U~YYx-9rh)$eLeL1Dip+VO6YvvO2#DudM~ zuB{1WN!Dw!5wBNM9itq3z<;T!4uLPXONd%2bO0)pn*Ip<{;Qz`K|vDPgAjT{nZx^p zsMVE6_xY?4HPo_qpg-8DieU_5eRmXTI!T?T~IPeKZoj6I$@C6swM9gss$;a1GD=L?6@t^Ccjyb9jrWYfK(SW!0 z5kE`xKYf9|BGH!#ed#KH+yH~co6CGaLv@&85SI$ehlpSg3&K37t zQEJb%*B5x{M(QZ96PU~><2c{#3>Uz0@hD^eFZldMYVA_91?~n-z1{eUMrw##2ZC75 z$NEEERJt!$!&N_J_HbT3Ts13?F7m$NY6B%VkN*^|29)0b>Z0b{tb!?MH9r-uR;)hb z5(&SdW#iXgjLhh=4l=dQNNRV{qJYqv@^Xz;|CT}jC$~yEn_>9HpxcTD9{3#zC|&c+ zqT%+D_8~80LSwa|LHRV4r#Df(hTO-gQuYjnfy#iEktbprs?PvdV{z54!0`r~(_4K+ z6|1BaS1a z)jTJ5K$XM|?Ha~h!0@)s2_nKjP^~AblAwySM3fj*oSd0lXMO`*tc44ko24G877MkE70Sg7L=M6~#JILXgWry~257Ws zGLWZ4s#S;m=t`qTBZ$mE1!L)NBS98Pmi8Q!V+tsiONty&*m0frP5$W_5myFK;hBUv zy3aw)8Ms16i*EE16fUQEL72KLQ4hR)lsK@%;ID%xV~Fo5@qQp=Sd z2m+js%Xh}o_{=CZ#P!ffvc82LnD7$_pZ4ebL8xBnPkNj2>GK;=YBS}^XO^xsiTyG75VN~YL9>j zY@ThSun}sr!-k|sQozWw!hUTd{+>0Q0x(<+uwR{wpycwigV&vP^wQX!r_DYi_Z;IXh5@6<*O z8C*|C%@-)w)XiuIv6j9;Xwdb+DW-`x4(q6uT8$wTM;60hxX#4O5d^B}AQ>v-J~E!< z4wY&BfJ?~!uX^%ZkkqnLVM`H{5%+x^*3i;gaBo(S$$Y+2~ z}daBD36dVZHJe_%tgwaidvO3xC$NAt` zHO%jhPBKN3P=U14LBsgASk>3>e|j{!kV!cmRF~h2Rcm{Uq^LDPgBAa-Y%F(<@fvN_ zSiiTeq@GLB%ydhWK~I~;zi+FS^UKjGh8I$djFE}D@dIsDk5Z?qQoE>!rhlS4ztvU^ zbobqoqWC zE$OL{X10-_(E{X}8h|lDPXkr_A49WtYTe|W@J_;u1a$X+jwHq1gOvz?LyKt1!AVMV zK^=;tRAB8bfIr<{;%^=yC6gR@OyW3g1-`VNK1<+TQxh>7=sc($K#8W^_?S2~*u#xn zq?)vBlyupaB8CkACzW}6oa*7XzJ-+0R`3uOr)|2}hWuunTCPiyPEoFq!nd_dRLr4R z_N*)jbh4QQbK~U7qLn8$SG5CrC9m3E4R(zO^ECfnJ2ebeU-9Z z`I7c(d5_m!pis_?o1`TEVcNLcg&zf`agM~0SD2330Z6N~#6S>jLNKjI6@*q2nf!`V ze#an`?A$KALI*4p2LO?sB9>BscIm=D>wv~rM?m%EmVf0g+|ogRtCnwk}p_CCoMGO9U|44Qd6w-qz*6kI5!dG#XAt8+o@L>`dZo`XEHqZ%`C zOFmx45t|E`(IUJAv;3s?P{UV*uf$k9-6|G$BIf?H1#uHF-wxv{ruC}_9nAll5%H`O zF)v*Q?r_VT(2i~QF_z-fxeq(yoMulTFVRU2Z(z(4yA4_+pMz#-kzgFr1xYdQjts;) zZFEN=mHkv(H4680J=nA6{s}>5WA5%S}h*h zS?%U=bvLc04>iK3*5Q9soiFOFRxDxIqf;*D2Ro}htF+z^b`@!FL}s;NP3au{y_Wqa zohMB-V{3F@tIQ+2s4d-Zz_w`b1a$*D2z8?Z_`)t~(@GZ1yV`d^M&;+kq#O*y8op7h zCVNXky)l2H*mk$)FS@9e+`HqlVvHpv5Y0qxH5T#eUDc}UashYg*`6nKRh#*2q%i^6 zib7jGDea=`0fHaOmab}m`s+^OVjT2=|J7AB2PaY#9(vebY=#PQLoi2+0vM0$hXABi zr$7JO$Ll7jmEwEEi}sS#20VJpFsit-pR8izk@wUdsQtXc=!Pl|pyF|~pSnnZby=M& z;#0)9B?r@J~ynb0<)VGpd$0`^ZYhW8E1(BgWWPg?E)%?%%l z#oKrIpWcI;+e`JsdD)g;YI)rI&+Vm#`#58iKw+F}jLNu2z253F3_}|^hkd5DR64ZD z==Yi0-%w^gRzEM|TthxY9e99e==_K9)4f%1k6{2~$$!^1^v;-*b&imj1pcwN>R-0} z)M9WaqX*%lU0b{X!lfz?>7)8pe>A0-eAg+;wZ%oiJ>Ot)QS@OaPdqx8GlhTIN8RJI zO171C4WV8DN)hTcqjBwP-m0%UUU}Y<@9C?0m+7A&Y!&+=C6K54?en| z8W40hf{5N|-w2|lkuW^OpmShp3Pj;|Si1Ay`>B3vJccbSHF1B72-7I`bs2pWd;64{IE`Xe zkQFrXuy|x%ws8z&1CWB+c$Q6pAT5n}Vz-YjkY~pzZf;SA&+M-@cUkZo5cn`5dO2=W zp6(9^5q!R)_W-q7@(&n9z`j9!)XE&W6rKZ+=Kep@Pd@aNCSNeoKV#QlrYJ@!(qxJ+ zD8)?($xiJ68DQx61`t^H$I3+;0K*avC}vRKZ?cV8?+~ty+-IN~hA)!E4pajoZ~aPj zoQF-|Ze(m9E$+w%p$RlhJAllOp!|^;=QucI8-Hwt+@lOjM^Z;J&u+ng8K}02>;*1h zL54>Mixds(SnPM&Sf@wH@G;p;Gu)mat3GKdXO5|{g166PCIBx z_|El@9p%L!4zpUTGc=kbz=4(;e-%|I?s2$7=n zE?*?|se;tUM781mR|ryLG^eC4yy;*R)srm_!MVu2BcC1xNhivr8?;N1k!@T>4FyG2 zzM076kCA-wV6{@%ci@5=LAH+~rPdCKupq@rZ}beRs8I#zTA@IPaQk4ji!wTbM-IX4 zetQcaKSb?i7{kvDQG@Zht&c<0fR>?~(IwKJfhD5?#ob7~8izM>6v_^iJMxmLd4Yen z@j5a9H0(o#SeTTtBwvET4)^QNj z9tva|y>zDYnh2(9GhX#`HLOApMB^=gkO?5k7+vRx9YzR8Q`IhR-x=IZOR%tB)3hQ+iz-tae z7Vg*D->d3;i297MG$)vg3LrBav{tx-2atR@+R`a^1JZ%`x7>_Z2aF{x)%Ajzv za0y#h6y`mU>K*(&#E7Qy_Xa*{xLPgw2mrP-L;+(UHT+Bf?6|BPVo;bEbiaeJf_248 zku(Tzlv(_Z_jsKVYNh7CkXAT@D?Ou`(>OIIZATACwg+2WKGvbSn}#!#y#(49DeB1+ zy@v+#VQ3=x#=JWGhY@Nk^+E$`UFHWyeq)4Mv*BtP-EG7=v-LTwGfN^f`}z|DJje%T z+A}Hx^jF-5h!`iS*BN=#NOf?a2~o?8L^KP#A2RvYOwnN!bwSi-=7&eBb;AA-;Bf@5 z3Ggo>I?<76k%&%pBuX>$s-x7}-rWIC*^ejBQGhOKvnc@iQD#1B6wXHd&3yhStb~G! z@odCP9;%P))-j;fzU41QsU6g|YoRojg!Os5(dy1(8|vXoZOI9yTHO4lnoz6~MQWU)y(||G8d5LQCiU}c*6CNFy^#AC(4zQ?>uFbBlaz_y? zh+@MIRxBhE#Evcs#@?}fme?!A?rIQq-K=84!4^yGny68%u`6p$(3se35*=MzG*M$z z{`bt>Etvc~4|`|MIrGjrXUd&BcV=!$NAgrC3!4FfO-@+FQ2}yQdA(8E$-wYsr27GS z>=e%oSeRrhwK$vyj@CZ5e777d`++@Rne7>eN#gevUZjW@)q#_$NZXVY35!EsB%7=^ zVz44@P*Nl;7JHK-HWOAeMM{@amJE^>W4vg#q)<`TlT3_IVF}k)g{1|Q5|D5j1n~W)*SIKwZc>2wGV2F` z1^=>}|B0I~O*p0ad))kBH(x5SQSsBkwe?Ykb(6}`+BQl zZcq)bng`)n+oaMVG}6M|EqbLA{E`IwcnJ=Kpa>*sdmN;#DdFT@TP^ZKJCHHoUA+b> z^f+WJUML5dt*_0=hXWEnGcWpF8>LO7ieNJ>sm_ZeY4clVAu2bYM^urzl?_3b<5iY- zK@#sE@E^H70vWkEP%%|*cK$3$o2HeJRnSzo8lUjFwlcH_qOsN%#bRxBE0B^a5pSH? zBE%4DYgW2VtSzzhKyy??hc~>*Sgoq>)7qqiUq_xaR%>FswV3Z6tCbG!vxMqt3ic`B z6m^URQnF+yQ*_{ejMa)+@+tl-H(yC+igIAY+v}G`5}iqJgMV4<4JQIsYK7?#c~tbW z0u#|n__Yh$ln@FPt(o+Ca4eTVmS-+ziW18dn^Y<{!xXMkiBA|K4ZLA6bH90ACD)!RK9n-HpH@oaAc#TvJhdAM4KuGs@^Ewd@2~B zDgJ9*^fu|{Q{LhMJWXxV&Hu#BCy$6zioeIjw}@c30GTE>DuHyDfOvzDQji=XtcpKQ z@ske>1kp#f#8>EE(aZiCjApS-HH#`m(JaVusQ)V~qyE>U);2&QV$LG_St2htQLCeU z3{bSRHkEnLiCRqL+B9kk$5mrfsAUOtmyU|QU%W$-7~9a&=-6FVRt)3UCu*GyfxPY{ zZA?H0P`sGy`0hzsf~7L>@SJH&YXim2HhVZ3^&@Fymxo&Fx8<~vL_aWJ(gSe2#;t%R ze#EndUZjZk<)F$^MerY_({C z!3`8Y+|5^#bQIZ5i&iYSwBm=l`ARYq8-oI-YDFv$=aD+{Tu$6A5mJ&j1wJI0gAq}b z)Y+=U$5BB_@em1oQ(Vr$h_gNssX36vk1DF*Z;8VynxNFVCAO=$(*BlMt)j}}cTTYti$X|Qk;86wM$&_pECqAv*rRh9j0 zH=i;W#TDPz%^&RMldj@#WQOztVk&)0H=k@1R}}xGi*FHC+ydk*u~!Lfa0v+J=958U zvEt7HALG_*A##LS@)O&lnF%VNOECfhStfPm_5T z{6@QF(@D0Tl)WY9lSEm`bBGlEIvH1eEyRH=l4d&cWg2MfS)L@uA9EpjMcf8oT*Hrx z`I?v)!F+HCNh~h%Ga?_PG}m1u*?Dg!(k&NBcGu~WBxVxw7xXike#YVF!8AZVn1mlO zh-m%rqrBilzU_sCJKCBF1xP!$u!DgP@jSy^rJ zNR?b9SyNuj@m5?`j@vm%vOZYO<)Fzbuuqc2Mqr9H^s|C~me5Zc{iNbYStV~?-aH>e+KCqxl)M z!>;LC730l0{O)w^TjTX0K0if!*{}j;(`i`T1rIZ!GE5VYrJW}A8YS(NYXb5}VfoDZ zkhGC=?3jOiUxGVkXr(_6q+F5@G=ddAiTTa86benPhc_&EpgsB!ZL8y6JP;p>!83AU z%uq`DriZ-bOzjI}ff9V>Osr{qI-8%GiB&24gZYb@n1(t1fLEKPwY9V^h@}Glu7}@5dh4P4|+Hr*P!rf@$|KP1$jD1RW zOs(JI?A52?8NgZ76CXwKUY>X#{&toY7W@sejiE({PcVRfmLG!_{}z8xrx?r|TD4G1 ze{cS`Ui`~e)TZp?PKSS)sTjuq$><)GEJ(Ntbh6D5(56jbURMnjrtj8AyxBo|>bUhePYc zd*EEaPz~j7MjSY%sU&Exco@_;|7N#wQngmb|40Gv;tS`+ZCdGef1yBJWj6@oauPq@ zdOOZ$m&LbqHWlAFtR|#RxS=ZLH-cpnntG*N+DC_;$Gwlp?7IqdyeWZ4W zX}+i#l!xBBOG#H-6$wkx1L1TLK%PfUN#w1*)M72gfaq#)C1sczTp`Rk$@iAlW3C35 z;U^p1O(b_WxaWa1Z&eIcx0&@ezSbmAf?i*15*p=|d?17hL^ebUxfs z>?rAoT)f*HE!;TxSN`Q3tx4l|3`L!f7H*FGLV4a^swGTxmGtOBo>m$P#l#DED$`UE zRgQ{zMd984b2VRs?;WH@?aIx|q+*JvJpUwBD^W04vUDRBTc*ggefWS>ty=TjQ$eQ5 zY3753QMoX^89 zZ#pXg$G)Khx`7bd&RqTj#PVIs>vCah{?9zEw54!ikN@sNR4$B-w_-b&W> zqI}4O{R61Fz88MqqQ2V>P(wvs!?*4Nq^`21iZ;;m!2r}{D1e;r1j!aumh|v%b{TJG z2pGXE}< zRK82$7MiDRtJ=;UQ3V8NmMBg#vN;_VK}0$r^0-MSMQ$Q(5-((G)#(110umRuK@!&_ z-60zCsSC7f4Si^_9$wWqL#@rN!f0M^Zm8yE8udLjFPht3o<}yXOyuOA+r3(VI~Qn? z##se;rG;9E|MKHMSZW(q zfZi0oOp3#uwK~GM??IFDx+bNT>~}TvOq0~)DG=QaJr@%A@DmHQxKbZuIHuR?JSauj zb~Yv@d)#UmIMt8WNYg?vJMd|mR;*!+pR&CnSg|UM92;KOZQr%eW&7RSGRJ)G9I@V_ zj2~Z@rj@s}f;ic1+XS00n)~9z4Y`kC6-IIR`~!or-XSADWqB$IS^Di&VPv`Wn#$6z zlFX7;i?vf(PNb5P!-w-&;(-J{Nrh@1#@=jO<9g<*jl<2pM?@~U|3U!TDptgKiU0h4N0u z!-i`|hwJD-n`w1E<<92Fdz}g+?`2nYcNTW%-B9H{5HRklG$|jCrZg@gdHjzJ1sW5ChbXm9Q zqunll{tt=pioym|w@oWFUTHB#Xj>-8!f6=e1s3ZDYv=;owaW!|7w-T8md_ikjKFnSXns=(T!VMJzS*aLY0@*)C&c%~*LiIwz)j}+Z*XwKa{!Kvt@JT;cHiDWkP)*FBYtXXK%vdEYue)p|(X33}#A# zS4m$VGv?usR%q3Y*Us}2E47foQAXtLm`e4Y{@}7+5NWegE0L=)0>f8o7jq3Zs8Ue*xF#$zN4gA2*hOnIO`>IhD994@zSi&M7UF59cW`x|qMa?bmoJHr9H_C$H5G(O{yQl0nU!s+LdM51gcaJp`PX>Dh@>!0z{kx7?tG6_2FE zp2EXGEwL2X<=*fwH()E=%=LWh2HZr3ujeN>Xde~q32o#<2=?;<4PRvP->5A#YJc;! z8@2EfmtLd7X^m}P%|bQFO!$pf_R6Q zyxSJ7rP22#U$;f8neWpRFcPP$nSS_--`=8C3EDY;rV!=r-1-_H3kDze{}a6IH(HqQ z&Epiiew?@aMr+r@_b;j?*-4GiPV>t!wNoQxibZBZ#;ZJ0J(B+9k>6?~O0N7L)eCik7rOc( zpUHlwEGp~67yS6QT6yD%7yQAun!Wf}Xc4Gu-R_~{`>3^+OFIy8bHw^Z;Mr zfDD#+r0@${wJ_uM=ltDPtxTboJ*gGX=z`8bE$5uaYi!fX)tUpk*_N~cY0^cnUa%^F z#(tgqf{gb1DP0Nfw>~6is{Wi$+oqK&a}ZG{CWJ}%AqNJ++In$@S5R&(?mc21KL8=) z-JbmVHqC4q-J8Y<@1}Kr8GT z%f$KtXGv2Je%%E6XV4~nST@q+S}`wA72p6ThE6=k}U z#&U*@3cz}EUp3OX9f{j$r@T4Wc!33-Y^T<|++nGA{SxNn`_Dufh{WknmewO!5{+6G z&qwdn;=|_lKx!n-c8D!VLI$ZPSNcZ7P%u#+XcW|#i=A2r+OV6URsQHT7+xC?Gw4y* zy$A8BELG*Q9|>N08SjXeWMC5tli6VRy88k7f!Kf~&bu`Fpb3E)GTUd)hGI0)hdYP5 z=6gsXXCKj>$WZ$j<=Kwzd!G@-ESlgVqUom|epITHViGwH4JwsrCFxmL7P}ZjX!^P( zIfqbyf4U2IT><(3vTiTsbc+&gbw98WZm^!0w?%tM3>$o14>ZY-(6%w&GhM^vF5MF` z(l*^vVf1!C?^C^Z;6pE9ck;nZiySr6DSKS>Rsxq8BV7FG%n7)tK*o_?~ZJfO4 zZf$w!Kb@3`fA4>Xv7c!*W_@$q<7qLLxZ2&o%T&r6$m{LV8W!pU4YAiP8I!lT2Kc4C z<+d!XHs8NTD`1$(Pw&yf3jGdGc*|(I%*T77L-A|8<6hj98}VHGumimmueJ}j?8}|_ z(0v%BXLRCe`?QvZ$^7;{tx~bi(fgULDe2VQQ2YMMf}VC$n=8EZe(XEj-I3e(Yf(Wf zVFrCiUdh?V`BFQ;;-y>rwWM6v+ZyxpKh zA0D0hQaRt{`47Vnd3e*qTHoT!+MvDKC)NYof4T#js?}PD+VMk&wJyfAJG}T2t%Mc~ zG2G%t-Qm$kw6LB&cS)5@F}pRYy?s&zGH3%4=+j>+>=5S>c77z!MU&ruK;fOdqtdnW zO1F*DZCB|KcHo=$$B$^Ow6bxOrD@S^UhF7_4=-Bt&POp^4!zB19MygB_5R;z+uFyboQ<@I`bf$IoPHODwODk34xmbziCC z9th6g#LuuvWZ?&N^!Lu*ioZc*62G&^P%PAdZDI$Jnu*mq$$HD_FVgggO|AV}gD5&K z`L5~kO(>1N=q70S3D%}wZB66P99m@6$}N;XisOn7UEbXBslVprkH_)WM@e|>QvN8# zJDk+2G^}ye(eq*!LNwJCBe zRqGO;d`9caK7nI#V|^`_^V?^%Vg|My+XS*>*7$Y0#OFKr0Gt@)M+;cd_2 zOfI7=npeV%tFRCZ+DH_x?i-4{Vd!(2#ndfB10J<_Y;zu0%tuaX=8+^CC%;{zkCVqF zlNmHev*Mhz{Ul)Bu@m&EN zmWp}nBBDSuQczCm-!StQ=d?<7wvx}FOGT7S9fi{mIL<|QyEqQZw4+Na;>SdshEgi< zGhca5D;o3?GZbEfaJhz6Y>(mR&f!t&m~HLI*ZruKZjN&@y-S(qJ%3juU|(CM*YI|3 z>?sO+Jv4LI@ckD!%hH+|dtTsevb3TVniGJ1e+X{zX8R88(8Xy#koUf&C-d1^m~?+| zo}b9lh6PMGPj%Pz7&!DiZ*pF1TCp;yAK)}sf>y5TB#2`O5;OI=f5E8(Nm*O;K!O+>f?Ac= z*11B;h4J)2*M7zB?ia9L%=qSq-8+BM{P9`pNPglXcCc?a$MgJ*>u2Y9i=VNLu*Et4 z+0R<7fXmTTqb6^o3T+anqxpuPwXpgta!}_{3~lrhuA)E#MC{S4(0`x+Zydt&UU6S? zpWh;o#NKFi_F>2`TJB*p&r+kB{wewO?BMKdy6s~BDuhiLV4=r2NSDZC{?qf~Zj5(! zUm>pES)j)WHCs(FAY@KG0v{Q~sTbsx=-MDnJv0(Hc#9}z`AcG{vwYq!T3C_iXG}h^ zj+wW?q(6w?KEr?b1?YJbFVy)SDZ|Z14&pBvC(F;J0i;7-| zOJWFZbCZv7`>}(F9;qVh!+eKnFu3$b-nkit)))B<_f7wlJb2bY`E>>xHkj#Rq5Z0O ziqE^B6mW){ZF#fX;Mh#RI>fb&C#LmSVgODz&7PM|m^Qy+#Bwy2Dv9=yxT)D9a)U<0 zdZ>fbKczZ2f(m&Nrg2^&nCN%M`vZ6Z_${<@?k8*(EKkyX9kb4;+Q7=N{M= zG1omX-Zntx0c`ngjeGdslijo$z_4jET^y!#SB_TGDnC4`{z9^d z)w_@Vs?9M(cb_I<{0je>3!eMu^FBaoS`Rs!G*IZe(LtINjL8y%!d2)QWDwm|gU|V0 zTWpBtq4%_o#{18?<(?K;Aap9FE7VPCXMEuLxOiPndcc%lF=5Zy8ocCvZ18+kjd!`P)vsz+s3!^a`X5UF zf@Vk4dQ|IaFQq&y5nNLze(1gyu-vIy470*tP;Qeote=Z&74IgQ>LY9GuqLB{jazuB~ zHKa-)H=0s->Laa4ksg(mOGX}+{)#y%!o&EWCt8()5ba9dOCW!O-iE(;r0q5Caq@2; zZsUUQR#I^F_FK84}79U8CLK$Pqa?;F6@&1@h|JpACHq=tly6yiSH^9J$i>??b8d_M07162k69#N{GJ-%1o*dt0xJqpHHcEO^D3is@NTgjm>(*ZcXf3AqL(ch>hLt_b0Zr8Nc|3 zSNlu*v*JaXC9i5aK^Gj`;y1ykbCH{%oSeiQjGFSUexCXmOL1Ht6KyTL1rQp=3l zi6R+>&CDr+`XYMt2L&65k5FME619s;;e+vO8xO2ulPo*RqWnSSqYB7>!#5=kYh!_1>XLS4}YVT_iG9Y zILDE~G?st$Mk`skd2v+(y1{Wki|4wUN zZ1GldJ*_Q?`QGf`)`gbsW!Oy(YrWt#$;jQ1}z`LUS%zI45rmx{I-)r@aSJ&{m z*;q6)a4jE@jfa(0Uvqml`hyc+^HbT{{u(`y`R9e;i;8yvk1yO;tOVaUeJPZfL6m@D zE5Uw*K!h#hJNB8$cxyc`k%u)j-n+nieIM<_4>Ay5w^>=(QHZ5}qmfQO}IUslynn{V}H9~sv# z;rD%6edB{A_#gobGfr8`+i0w!ara_AR%4$SuPx>~G`6lpKEe!|inXPQ1yKUyHoz`e z5n%e|2cF1S1ip;2iLvsAUb}x_tVAw@KY!@Qni~RmtpaRZ^{>B@zQwl*aH^^DZ4f24 zf&Rk3$5D77g_eopYC=6Lz$zO)=I;xzn)PZzqOpICOh?RFvVJt9WWgXhi#5YN!$$$1 z{h-hubmsm2S+eo?eE!g%b&lS=0M*?ZzY?{4gA8Vx!+tOyd16d{Pi-V_p=75bXi&j) z|2dVgDhea6fQ5WY0Q;X|`tCacY_!3!mUk$`EQVqHVjXOnV2Tu@q3T zXAt|DV*f_$|0(tk#r}-ge=7Em#BQqC3l-Z;>>?Opq4zpX>& zCdKchlEcY9`-&X{mn2+Mk zQ?dDq_m7IbRWZAY&B4WyG7eoPh6d27)BzCUI@mK7f`pOhrxdkU949Z;3*wSWeJmxQ zW)gCMy62vzZHuW16--G4U2Mil%oLdqw4Wo-fB^#<;=$=Kv63@$o2?NU2s(?>W4%2sl^}hd z!yH@0a?nxnr4FQUahbhRlysC+w^0#OX7DzpSTRcv4`N#{M3NHA{*Z`~I-;_!t0`l@ zxhXf z-DdeI=}n-KS+jsZ8dTCXpt)2yX93tb%^L5)8%}t-S#s`m?^EfjPCDjPmsuC~DzmmI z(1!m4t>^&__5vk&(O-cYNhAu39QcZ|D6Km*ZKcfm1woe@vSyZ5DGq|}U1Fc;T5MBV ztOq-%X?73h7haeoFU~!YrVZ9HJ5rI6zwYf(rtMUq?f(TD=>e_b1xoT_yaFvsB2f`R z$*RQu`oeA6Lj>I=7OA9v0nNL_+UXkXQ5qZqJEvJ|J$Oq9PcJc&7r#H2W__V!x=ZZ+ zZe`Z@3UtW7K-+pin|pzhyqKpzYmi7(M9nW$iTQa_7`;-+5{p+U9%6dU+q40?7KfD< zKY^Xow7oPRr)t5s2)axodGYrnY1$$k(?YgUi51d8k1J5izd-wYK)Vqr-dj_rNAhAV zKpk@1t?bQh5tnIVEh^#)F7D1#XB{AxQtrE{CJVHqH?sD$lLVBYv|FN*FQw$3zD)Ho zvKJZe0U3^<3xgy?afQ)IVRX)kp(?1vh#&!r(xi$P2#JWFozhK(KzF2^1x0Ef6`>KF z>vo`9^w{FI=q`dTWhj-fE1c6L5*2XZmqn_-lm=ZP z0p;Bv5OjHkB*b`yv136kakne7Ea5+Xu@j0WA2auF20hz1eb2L{D^ zaGH4GTzqc<(BlWvqADa46;YLv|7WGsJRt8Udn}Sgv{7N)105Lmaq+_NDxwo4puDn= zFf4lQmu1vnfvg6@A>VH57A+>Y4=kGO0UAv}I?iSCOeuxajYOg%x>IV8L0&}^Ihp*g zw5j0*BFpFsR(9cjT~vVt4=#G@@o+3I0f-=a0QI+0| zzVQI9_5z}0B3=Q_CXuKJ8zuiwiw1cxdU|0L7ers0BgO%Oekh z&S2pDH4!v>8l@Bw3hNlvF&pCr$Z^NKp|h zCMr`Vds2<2RAfpY1jv-NDn&O$z3)c{x2e0L04k80f}Qhz6b6c}NofS7CVoV-r%@Ua zuW*eZB@nPGi>{^tI`AU}?40l+@Oe;l;7kN1a0b!rX_Q9%^}9526Op1KHcwC{T0NfTC+s1wpAvaiZDND2?c^YtR@IbtXf2~r%_5_IsdCPpb}_~6xv#IX1tg9e7Qt0SO9qT*@A7W)ty5vj?JvVMbSZcPIi1su}Dk2rw9FQC-v^GH&X;efC z2wq^)%0ZM)Db^D~i4{*YdzxI;@c6FOzX?c=lp-F*BFFyM{oVss$OUFlV3bs>QeZDX zCotN$t6TXP1TV#8j^{zs70X0WR@e-p+0*2f-;VrjH5P37mPk<%TXAtC)Ty(89 z1v_W=GQxw`kMMMtki58cTUrvQV^Yr|pPJupQlOyNB|M-3UZ5l|Yzp*sqBQI8 zL}k_`iYl|tBIq`&m6F~A8kt46Tk16j>Kas28mt04r&%*Sc#{ZEH;d%Ot6S2nemW-I z6Qo&1bkJ}G8vifQIv&taFHn*fUn|f65{Zf^n5dq?o{n*w^&5gNHDrbBrBa*)-H}ok z7_xFjMH~acU2!GN7O~!gv&;*JQi&jiGhO1)Y_EkWVUQjb)!BIf$}@pIZNgYD0)dBM$qLFk{3}5 zbAyiQE}1SmXd?wW?_Z!}J)lFqKuKQQzah=)Kq65Q9Y?E@spClz>Xky4%-1SK0O*dC zX28%rniqr*OU60MZO&~3UFJ|K(Ou!31l^Hh_TU^Eh0*bcKwo=+(g_G-L+YC-nJ5e( zhkRo+G+U#NtSAX%1_Kv2j?VgLq+6k92$DiiffGkKXRb?ETpo%1y?cpA>r|n&fe>Ji zAYINvTkio~<^@XfVypt44!ZaFBVEVrpfHE!#I(SO_8w5P7bqnVwph)X5 zLq{;K2NvDpd>$MhFC0oGu3eKBixD}y#kZEV%HkeMyYt}Zw0JitdNtjGpv#RUFJ>yx zg*vEf#I-@k?5!{-{|mFP2h-BU3zQOwItsKA=#G?rpy(EVOfVlb>$M3a(@i-HrPTl z#+N{H-Eq2Pk1Cdoag&VUoOPuP}RqZgHec0!g>H zGr@ddag+xr+zW_OiF^vE80e&&>#kT$DHk&pZ8$eU=9nLUIMi+Jt)VpcQdss39jA%| zm!$(wLxA91rF(O=v^8cr;C6wR6KIsB?b{bI$SE?E`ym9 z?jR_7<=BCsGr#gys+gzXic)syf`_Wxah$kPX>SJzKfvf zI-EjK>ad4s_KwR$RfV_>aaG~ks$rd3hg=EBWnktkkuNB@GHWb?5^@01>>XE%D?dxK zdQveF<5hW#D59HsiP-d+GOdjxZ>hNfgTZcOcN*%I@ zX79L0G*yTv5hujyGzUnd)!sVf20-ovGbiK{O0LYBiy$_a*`ucr&E9c?_~Rn({4rbf zCvblvM@0nS;#GCzQ`?^d-6#(bl!{%p5YgUoi`WbpG`K9p9WmSVx^o&aFBmEDEu~P( ze2t)lnNKu($8Dm&LYa-YBW9Wd|CGVE=o}@ijBSgY@fQ6wq+-9{yh;7x zgqFWScQ1}_X{%eOr8Th*7xh_1?87J!jWrW*&htjmETYX=%D`$GM;Dt(BTK9%X^`Di zekrZ(lLJ0loV!HEK|?K!X@h4CR2~2+{}uJ2Yd?(pzim57LdDRNBszwj8~0 z4nW&ZScZq{(g3LE0jTZ;aFPJb3Lvm=UYUE*zFah7)$88+pF7Ws2+}~G@;pvSo}QzH z1_3c^u#hQcCGv<_gH=I)a30&3MMM=AvDbLYMd|4A#-1w>&-`#{&Hn#4^ zZ#Q8L3*I>6<3k0SfxTw5K%*xQZ_0{WrU4g*l%yR=))e9)_I`kj2^sTyPkU{w_Gc8c zrud?!Vo6Nnev%kKF{?ZU3B($T7_NwBDQPMpiE^VRo-si4BNy0BjOhGiQ&!se1ZCEg zl{H@Iix05jx~ng*(Ts%}7Vyr^SeRiXpGKE>zPTAI7Z52o1j`3KpuFwOZyy1T2|C>&QmrOMac!@H_9vP#LDB^7k8m$BS-y$xNG&?QD;=A!aW>&ay zJt;nw#2t-7LKH@MZ(@a1WkvySVkq^u$nEd0Jj2W?MqR)F5&QZSOtoIUdaVTWfQOjb zD4fJMds8~#JDSmpyYj$TR@HL67s?J_QbPsa7nc{|^_$Fk6Z7rP)Dc``95vP%faC%W)jEm@%U(JAtU-)CL;r!85rfEbdpN6e5H z)66b>Y)ckq=^)c)CbGz$Ar@@jdgd%=O}K4##83f|iB+Y1$J^p4Zi(0daIG_qyhetK zqi=Cg+$*%WINL~UPmeeX#vYW3&x5Z8#mlr}c?Zrx zQra{^+fGjjIkP)VW(fQ{1{HmcZR72Ai{p;W9x6vY6Gt%$Wwp|5O) z?cvzIZNKv#TPvbJB3ygK2zfD$?9Au1Vs(slI`d<#ScQ0#BzL?lmK~G3G!Aqk%f^Lb z!^U?q82vRa=*dlIDnh4^j=iOxZFfiPnXuu>$CTocpU{cdjbkARp$_ty-}dI{!NcJ^ z^QW@<48`uceOY*u30@3H!VI}XCv!vjmzk2fA_kf!AHl{+UpnO+D)<2%AJmrnJjgGm zihO$H-F7SknkBVm{3#^yKt}%b&{$=XPm&UVjZIGmOff5&yj@8zqPkQnEtyxFo zPqTU7)~tgzc{bs7F-7vjtyvANxgIt0SFKr0|BLNWj`(xn^J^-ET_5XRLAxm(K@1~S zsmp4**rTJq!ig&OQXRJbm0e4gZEMs+r0dne4bj6B?An2^`ji#6lvG0JJe<>>pu-ik zN4EjGf_;ce0N9Bxp2IZG6HIaiF{)qgO0h?!u)25-(+pS8;xHw?uw1gCg{6FtFko?wzEINB2&;SO3%LtSBebo^46dmKlkd-k|^4pW9F_?;)X)f3#} z32yWR*Li|pdx9(7!Q!Un?yzZzC&=4zTRT=fq8b!Q)&Hm;MIV&$HaGf>!{|5iCLGgg ztGs9r(sy5J$0BkS?S`i-vn?j8rM-SVT=%rJ*R3aFc!f?F2$kgVoiI*%x1SH|#LgD2 zxnGJxM2aE_M1%RKompt9Klb_fw6Zm85Zcn(EGo2>y?%Xw9hMTm?BfeNvlBu75K%dJ zC4aM*x9Q9(mpZ@q|4qMZFW=dPHLu$PBGkb(!c!%7xSO-FqdQxa1GTaL!Cwxd+ea^r z6E!6p$BW>olRdmfR~BRm(o>Ft1_DRNxNkO%JG7_Wy#RfJRB`q(zI1Q8E5j2*2;vt) zvrh~c=MZpzFxY*Gq9AB=bYwa44k7WHe#lg{*Iqo| zt*nnjRx$-%@a^W7Zmg)q0TxUM5eHSkS7cDYdaHZmo(T^&A}lw>Ltr_=adf8_R)U{S zKNgvXv}cNBzY>>ql6|7D7_29`E)Utj$F33{3;|mmxgn2O<3+l&YUSHuMgd+!u5@5c zZN%huzxzqQ#M`FGEr11h-|nn#=-(O2kX*Z1L_$0jZS)KRc%L4ug3-B)=k3lS%g_GKTfvct zeW>6Z{%a5Hv54dO60m0@l-EpPG1VSzL$YKCzFhe>>1mv8qA%^@ltp@1S2Yv1Qptzx z;!`W&>xvA9$>RcWXa!^+iOmpn{w`+VBBtTT%XhMN0$q&Z4-;5$wH`9JOyu^8avOu( zUaF7jwNtsdYUqO+J1Muof*|L}tOW1Wla(&{XzPEd-9WaPojkQCE0PfWVaolm18Oe9 zMa;vGUddBY0%YDpkf@*0kNT)Pf#0)6(1p7Zty8uY?CJdF9@$dA>{Akrd|xI7ACRt~V?5NuVxXMqVA_4zTGA zfqT>rH$WPnD)o)cK9~5j^RYYuN1UPD&x$Yj`Q9uxUllhX_6PN0l?wLJtr$W;$fr6! zJjjZb5~}Vi^9 z!-7gpf;`oAf^_S<6hhFq9XIp^By&R!md3dtYw3_tI^?^p3iA6+{{eXor3c9Q{77H+ zeLlxGE-gRiv-+`+Lb>(AEg-KBk@YR#*N+94ngu~PrxB@_=_;Ac-y-z^@jw1YKXxVm z1h*$2e#3w1&%z3RgG6NOZb}SW$Mb*ztVpSP(tDW_>zu1-x|1fy$$JCXy*%wJVYuwe ztAEBmHWcIiKV#=|HQ4OK=iKrQ%DVy&I^xhK-hLqKYDnkb48&%!SL?WQAggX@!;23> zS6_?Q8^p?i*?kbE3tlSX7DJ&0h)9Emnh13@7 zrGtE9BX8m>yMg$yNjiP2&4z=|?jU1POy+_24f=5zXE*S5gIUeGlfi^#2L&iMfH_&gpt{i&EpIQD3P z<8{RE5Eb9A=beVITEPd_fq~9cP6Sx#bRTRnv?c8kYxt@mtWx<^#7{o<8ny9tZ1&kC zL8pgNG0q|e9bbhGa|jF1)4MF719`|$7G*5Ep5LCz0?Q2lT53%v3d+~#gnJOaQ%$S} zpEnde&BuJxP!?Px9rW1b6DW`;a9JVrPwKO$3z3gFgP%BCRRThf{}h{LpATgN}XSRL`!-MExdg?wpE~TlziPEL(;0m|S z8^*>PTJn6uS!=@x-fcK5Y4GJ^hqLg|BV-na6m;xPh6;z+Vx~5>Hph1V_*t>m*2U=7 zyM+HZoCV>VlJ|#W%iE=uyx<5_!!%xZ1gjHfaAUj|f322yA7ao+y;oQBtl_L^(W4}S zZiA}pH#iqZzOY@(zaPPp%CrI$z7r&8#%z)3=5tir@x0AQ7HpWrhm1t-QGCuwRw-i3 za_NbBs(yN$y$0EcYxptS;txhLe7lvnsGU{mr;@X)D6a@&Ca!dk(Ul8E z7S5)sSnz?G4x?B|tw+mT*`fd%xC=l-N-BV=gk+CSFNB(uwhEdIlYZZEG6(F)g`H~SZ{*7vB! zS4yW(z*gr!^P9a{{X(mkxEjPm2&0fk(!C+1AR^Sx z_DckZn7l&u;~gLleMaWEHFSp%`f>D^iL6Q=N{Yei?D*TV_aY;y18W3J;^+f%ZkLH5 znMpRj-AB4P#IMkihE=%#;`=U$IIjapT&B<^F&7tM#t-iutOfEriL5ug)Fg?OEAciR z0JvSoMqW>P8fz=Y(jT{5M3UXn ziR}nM?K0cjr;-c164{)=@#8CP+aP|(C4$?Gw(4_D_CF)E#JU(5>gmlIeal%qD0eQIdVyG~@i%`X5< zDc(^AWSlsF5NaAV!g~uqypEeXEF05=I6x+n0DC$VlNt3!-H zE6S^!@Z3YC;S}o^DNPPfV%73bm|X z-u`(#VI-j2q-Zx?SXMB4N_-`Lr*d+NizEY}VzUQv7Q4EWW{Ed*h(R@XOe(7FNy;HN3ELB>3X_Mf%;)ng zxStf^8!dR(;=Eeh_q;ct9qOg=OF+y4kI^E#2Z{m zqNEO21Xt0AZ~KC+H$I-jyC$ID?JfoX(mU&(7xMQt%Eena8ED zl(PIw;MwC!HbPgA|Ks@2Y-CRUJ0xi#%w37r@jQi%EE7y>;tRdh+1VOGfgHE~)_mFw z-1^({Wix=R@yr>lM$|x?k55Z`vpPUKhX2>~k{)=Cwt6C8k%k{Ps%HSugx8$OBI-T? z0G#0rrJwQ`0NhXn(U^3m0RZ&-X*S%_T=`r)xAF9utYqW{5Rr9Nch>qK;8*h8F`$s= zekU}0bi21ust!g=$@ZSDwv2DOth_wQLQfbFy$lM5irR7UGWBj!&M=T8E9F31wyNegGKM z+D5Uq&-L03Kaezy?XOSgf6ithbY4>kaVNWxFNt%iHN8ph2wR=3LS z8OW%w_z?igHL`DSDGn-vC?~COh~H-Nbv9PIPCDd2aF`t0ONZ@1B68Tzdg61qVvg{Y zjg{?P5}t;^bQiA!4Dw7+3SBef4D#OoH)43MfdHM1d_?l(MBew3@!B=JL8B@{c_Afk ziFnH7`V4-sm=?t6e#r_Lv$3)HOIFdc2l>QW^U$Cy)^I%Diqod-lP}8*UW%>L$sd*I zWM=B*!d^gedP$_g;5Y-!=_x02mOu={lYpGL<5`2~uwfDMkZ-T#E#XijG`Q*l zo7!3NMhB<4O0o;cm|%0?y9&wpEAbqLh%ib6Jms5X!hksLU3*gaGCLb@d_041UBrTT zjX5lz@ze|+HHQr~bmtr9u*s!5Llq2(@F( zkAp9KA+w3KeH3TwpA{SSr(RNxrt`0su<*hKNR1y&PN^_-N=Ew$9z`RXvI0L zi&-GwW@m-)T^`onm_3c$RL*#98V_5*h8u?P^ab$HXBL^ke|hM_R9<@_YZ5ePs`L=f zHu3V%{VCkG5FSc?3T3BG!B1ztZz22Kc)`MJr?JSq{;xqhKAE4L$HMs1H0D?Q;^hCf zr3pWh#_AL`)GGsDOm`_fc5~}XU#1anxPWyr zo|()SFJO~OIwtZ(Gd%m+RRCy-L-60n`WMWR)uZdfb1b z4&cj|uv&%cQl{QDY97D61m$iTN7@$VAxqikxn_*@;iG=_EzGwpW#xkAjDfw@V~zcH zy~6+n9Z8(^Z_#%w->`~RGyMM*PEptagC3_kz6UMNKp2E+A0%ykAld|-I)E#Jnpm5@fS~^uV(9MZt%q?+^ zsP=@DnCD2R_zsHWdO%61E9uE3P3PEek>WVhb~yrZoULHx`}L(DI?Z7!iJn1qJ%a>L zow;aE*PgJ)l}k8@B9~885}dj&wh5~p{rPk@!}}nauptEvPW^tDonAjlttOH7m_GuV(p*r;=N#%ai3K9^eJKw6Hb*aW$)Hn8LGHk4<6^ z#bClvGUELZ89uDb-#`@asJDAj)#KIVQ^G*QwdA+nV8NnLs?qOpb>lpxmD)3>p? zYC<~3wy1blMIM!mM8y)7EK5%|-V+rTf^f}yG~%_^v9P?CZ=l`(j1|@EuwGDpRD=yE z`q^L~AFMT+MQtM{{I^4)boU(2{nxW{;eli&$`-G^JojFE^z1MfB4$ka>O0~;K)ejsHzejQaEd>#O+g{i^M zwvkzl@A~nyjci`=PJ;;cIBr<6)*GHOI5C(v+=SWP zw!G&i))FUIZ`_16lB@ZTn^?of4eHB<3q`L2uL^bC8P;mbw6@z>jxbo$vxB&kn zK6?wRU^z%@wd~QgV7e%W<^?NuC{lNfKe7YAqKMVBUeS6;+H6Hzs%YIL?Q=z&OPA#6 z5xCHj(B$Z0GE^9$Au^=FfBq?JsSNCyUoh^^ z4lJ%{rNPkz6w^TCOfsJ?Y$=|Y)#-Hl( zrQfnIjY(a2*j85F*rp3_y_G#N_U_CZY-9GoY2m6r{kw*|HpcA#`x^ZAHXN&#T!ZJ^ z&T1J)*5(bivj*jT{ztYrQ2)eVRebOz%ZJUZ_vrT18ENVIV|Bh_J1Z5?t{bVBSOK+v zQiX85vN^k*l`hn&CsBJ;2sbA?i{;Eq z%*}t$7MQ8(h+)@3!=}bw{8~I@2P=}_i&BvH-hnB7d~Rub~M4*67c;PZB}DiyDguqW+kJ#86ETeFAC2BWZ@EU2)bg1G30 zxZZ=a3|6}ImpL&^-uX94Q2B3Y`9c0;`S1)@rF6-hY10;a>3%`iz21ju^UAdNEC&lL zm^Vr4`afOQW-7muhht@a9%bithYL@nVo2@712R~#O7$r#kL4NZDrbrI9Kz&*i%Nrf zZ9YUR<3a1FUfVyyDp=D70#UUFu?y+^z;0I3^0C=ABn0s_lIt#~QZmO}N#!V~ZhDUO z$qt$wPe8SEG(~%Y+J`Xi3J@7)Y0`PO|EHs6IFI7HV+J%NJl~m-RuspN{^@0N^3%}l zigP~VxoJ6nGd#C?sfusD`j}0#0B^RoXanso5y7oHUR6T(3Kf5nRJYD?6t(6`!l2@o zu*#Nc8p(N{Jy=8fa|@~8QMZ0;x7u#nem~^ zw@H`(-uwfTc6P~W(m13QvG~zVs_(XGbDEb;Um@~un|yPaPF22uzje-8B{slhF&;lTs>I8MJaiwcQ1@&nSZZWtsf$`>IWC{srzsi=i`NZ)jn3GN@R?55S}5>Q_)D;mlW9O z?x{u*5HPS9`LBJfcK9D1^{mYFVR2+dFNohtHT2v*MhaJM_pvsAKXNO&kdNEXs#WWa zI*iVJmpiwbNa;kGK?&=UjQCHXaMJL%c6&6h;s;6tjr*Ue2 zk5@SnQk9A!l*pch#|F${Ps9s~X5<(8I)$mfgr|CxOPnIpY^K@^_?6Rm8}*fn-kOiZ zw#m3nNht`t!%BzmS@{wpiDPX_%kUcE+Q=4zUo!+O^Lg7dtVo=-JyzfczL+P)+K5zT z9Bx|T60mhOZjmOUhvYpKqdhV=+QBtf;SEX<%6lEa5vcp;@regm@dj66Sn})Ki7&bz zOQjV&DA_XlebkQZlJdw2FtRp4G{e{xKF5cX^?Olae&GQ7Bw!rr^c!n$lPzdn#{-_BktWF_FS8Wbpt3LC>m;ZG^@~t`c5oWZ=A{_4&hPZTsuDg5UUil7M_9Zy}2#} zFiU3gP35}|;T_@3x#axHy!2tlE!{sQ$-p*qNc&qDG|fRs+^6(tE+OREY7{}0+b6G2 zMV7qEaYcEgj8s?MKYH!L2|I-t`D;)N?beCCfvI5iXYL(Qt}35_Ub>h@x%86g?`{>Mch6A8+ph zR#mmVjjz3BBYO*LZNLjR6%-XODR@Ow14RWh#k4ZB009Lty>rg7xOh&#=lB0V-}7xB*E`2M#=MPrU30C? zmg2JTg7{Ul!gEC`$?p3Ehy7JD2K|I}^ve;)9P9T}DCup-MC-?i^q05MoxWU4$KJ-N z{z|(1wxdI*Y*bxrw0C)kqP$)K???UYc2ehe9HDr_V)Q$Xkboez*{5vql@DQacq-++ zgT3M9H3wgO$I;Hx;UqkA$C|8guVXP@FcFP#VKu#b)NvFKCsx1f*j)I{aGBYLbbS3l zlvpm?uVROCVVY#nbC0me55KiASu0FVO9m#-vB{p6CYi!yn`!cpG$}DQk$2o*d*ioT!+K!;SqN|b^}eq>n#M_r@B9aEFN&3mi$8j?nqIX zH4i!CA0yW>N9V#XQO8MZc7~TBsf0%_1tbi=WIVPAu9p4Ey&DA(W!GfcJB<5U(^lcE z=y13bChp0cn-qGt5Jc9aAYaH74}(PY+C__Ig7==q&#yPN(?4+J=vxh6eEF4f-%r>& z2seJDP^9BVFn$ew8q3`v$zR+d*xJikOFV}Xo_PnQlf(t_cf8=E8Awi*Ge(qii%0^6 zmV&E=Akr^}T!?YBW@NZb2+{W;I=fgr!*tK&C{h^GLJ;v2AYYgz>)GM_c{5+p>D}Gf zR0_Xii57(rf{5oU;*CRj@w;Dv;ZFCtp{gT`FK0>ziB(mWf+O* zTw7&oVhopXD8~IbLO*;c-F>@EtfcUbCRYd2$>WaBU7ts%j9=3RZHiwAasM3>%5ouA z6a$mkI`TT{?{{E>NxW&45ERFmco1l%4(EVQ{-XxZ%K0?n5 z*_&nGM`)BAhf!nb`q%h+<3|ru*Kg1WdmpAz-@q(uj7ny~!}PF_`>`DLFuft*K2E&qCFPRWJ&BU^hjwMnMnk8o1G2Iob%W4iE+M07Ca)eB2* zl5=j7|8|r7!AN#Z8^ef6ANdqg3vU;(F+T2k{8}2-?+84 zz2zqT?@Es=JK_7AB7AX^eC#Iq#apROtz*7Vi$P#ns7CYQ;dJ)j_%(vPE;T5!hO0ia zz@-L7KbE6hYEWE?=3bqz2F31ZH7NGxt3fe~^(pysQ22((1=)DIO3Z%7fvR`t!CFVL z^_4g2-?cd2TJk3KIE803ac|PhQ;sBS?J#=%6b|~{97gr09Pw7y8;Zw%PVzqvrHYRo zORar|Qom0eT|3-{1qgFI+DrW8uv0wj7(JAdKf$hX*AUwMi6f$8_3QsRjb;s@i=Q~o zw101~*bm~#gFG3G!{UExFxCCTk>*T>CU*Eae8I!H@H4k#@WDx+;s+kAg9cK1oukoe zucoBaczAj!68YoT+p+H*^7%0;Iqe8+d+He0wM}iV3`&%uwS@@usgpWbvom?b=tns| zeH7ZpTs;`_xud7$_8EOdJdxF^bMhCy+y{CygFW+LXn*KEr@wlQQ}C+R-tUUA?fy&; zd=np!{_)dq2XCQuXC2*dnJ%I~x-2|Gb2&u95$?oW1amYQk6bY8$jsEkg~h$;3q%=k z@oz9#wG4-^uy=~PDmWMv0+`C-*IwlJf) zP#^W9g%eBszy)S=5Uv&N-sHt!Nd= z-M5v9G}`wf$CnsfZF|vxFCB$`&!M2ACp@mu_S{bgtG>hwrrDEwa8a9jf~U!#C-g!1 z!UMSLG0%9oInGvrAJoh@_Y~WfG!O02AXFP&WZkHTU?`g%4<+1sZ%RIoZ_ynLSI$ck zf004BM10d)d-Db^?)Ra)FdhnVnyUQurl5M6w(b{%cpZ6T z;pd)MElMs}ZnFOY^v(8oK>7BCb3JL&CC6hkf4eEd2d(A!RWi^SkcpXfr&}e#=8NX^$TC*^iEB>$)D~`;((%|Bs;*d;I$l??QKM z^;-14(+{+E=J5yUuAdx)O_^3Mv$|@XnKR=&UPb1McKZ*@51>|+S}lWwI+5y zc(uV%ZnaM8OwawxG0E!eOqc$JFOhs4a)_0<^U-1G$7=FDAtNG<1^10?1ZQVPa?zo0`vlq~uYmUX% zUjyjNYmS$?ES~0}JvOUyB&viZ87D--$I{o{;duS~h2I=Ix*WXof3R^;>UDg-W5J!Y z?mFJK96;sQ@dHU0JJOHW9f{UAr&7%Cj=`4MwD5OFf_3y%s)A_!sssJ}J6@T1g@*p& z7(rEkIMS`ZWKypijtPE`;m^2Ryyd$#*9dM)qSG633pwq$^ZA|VY>WP*TWwvPDwKH_uC=Eb4+Y=e|VPXH2W zNRfua@qADj_En*YNb3`!d(ZZUf`bCw3%dS#qiEC_gguRw)rcZ)az(M+%i$Z8xdD%X zRGE9C%tfP0Qs@tlcHR0sE20~ZJ%Q>N2Rb|gsc6(af?GG{#=<@$y^P1pL~3hnV`-wT zovZzNcm}K8N-UDRFLi%Tr)=$}Sl*{@p6$9PKaz}$26>}QQ&36w(6$&NkE-G0{-It( zc7?l7H8zSs@O!piFxP(Ad()N#Chobc_z7d_jZvld=l zB1Sjvv3J@<@kZIlLtfWm#lNqL7CH&7d#M9)-$DL(Zob2UKdNz1g`X4Q7gcyG!eTOb zr{&aNf(GAerXQ!$e`TCZzne;bS)s@v2pJ&jC#1^e#3}tTnSc%j7 zbwe&Z2{>l^5g_%)Y`>YoRklk)iDFzhhTKpTSu?vTqL(P{@96SK%R_{JK)4V^yjFln zt8Bk&;L1cRuNh?%ML{~qeW}QO-H?C4=t7=KwM88f{ji8GJea299*=8^J6grvD&h`B z+#5YEA>kW6ew5b+TnlgXxFUtC^7^y9Hp=TYdA%;Lf5@wbwz<*6Dz6^$>M5^Y^6HRR zZ+X?_)hVxj^4eZrJIZT-yatJDVUG|gbdlFkdF>{z;quy3UL)kSkG%Gi*Z%SvDX#nd!d5x3T!toN~<#nRGPLkKj@|q~GcgpK@dA(a+XUOYJ zd7UM%56Ek>yv~-_GPhPX+HCtYD3nk>qt4m%N$!meUE|u41^7@Fp7Rl=h zd0i>5kIQSZysnnlweq@NUQ6WlNqOBQubbtyR9?4^4-_rBO$yJ+Yni<681HfLpN@8R z2WHnv>pJfp?xDp_yU}C&@PoH>YUlBEn<4!N)3V*|!k)NmcRT#|K6uw|pWEkI>S=pN zeScM)0i6!i z2Use}9t3dH@F3_KX;qM(V-4s<*Mjs1tP#CwX0X2AQcf3w^+H6+4ACc8Lr2lR5NKn@ zkY8uLKN5`Xtart$fGM5zVOFoPw6(L|#Tq}B4hd~FUFfXOwG`3RE+|+7ofl9;3%a5V zl@u7NceMtT(}+<0Zq#LCsD7KJgla?e#VArjm_EQ7{~9d~)8`<{`7p#xrUl&qC1mL? zVPkisgVKj1A}Sw_62!Ejec|xM4Ei-(-{6S%!B0rzQ>)RgHF^;3?t!0hucFI6^n0vc z?P+39{Uv+>=W;V0kXq-s>dsOIhr0`cc z^@-AlT4Ty-b`*YSyNb3)!C#F6l6C^+i6|2KGq)kUlhMXVU7HW#theoS~6(KaCE2)IzC)a z0`wjsr6nVfWii=r1=P_N0nm*^jzv^E64hv=z)`TQrp!@#4$63X6pHLMfJThgCqrB{ zT7Lq4!1p$EkPKRN8@g^Oy>=VODvFH(2`NUur?<1cQO2JpI}>5L`UM_-Iy}Ea~!-jYqrGAFLgZ zq|xmx@fb%n6c!JgV!AgTNf*;A@o-B6U5>}V4Bbz&6ELW9X=4JEC3HRkC2pXwiHKNF zu@j}VTuIv}>Qk)R3A!>-cB`n{^`vewCsh%}7Ov%IC4`gkRpd$9cf0<$HNKWcOu{IR zuBDli|K><;}ck*|=7$TAs?)IbX+qa(+EM!P19D4)>@ff1k4#mQ*2QW`!5 z#;DH}{bhU1x2gaYR6AMsw~4kug%eSkDq0|*fsQ1i7Rh9p3b&(=Ox2HBBfV($o%$ZT z=KXKktPNxtZv@leY5Fn@!M)RD?Q5s$&)c<7Wif$#BlV&5>U4c7QnuVB-MtZD4ZVlD z-Ho1JMSJfCi$Qr0=Ez3ca*s|{=gTy3hW;FswKHUy!tRwWO}Q71P)e^rvPSHsi}#`t zW>DZvC{eMQXue`vHB&ZJ?M(F8n1eL%K8)(jZI+xPd*3e-ewXImk4W0PwBvr+^^V_a4StXU+qGw8Jx)D#ohZ2e1g=-#QQ8U|>p{ye0> zG2g+mEgi$H0e^J?&`8JUAZhqu zE?nysN*CuM!$w+?0k)j%nQ%}kC1mPPSYt-esZ9NGWPI;DxtN@uCzhH7n)o0*QchbQ z#I%QI&(dEN?#j{!*kh)vax~J^OiX5nr{e76mihYRj<&Bcgq_}Py-F}QhNBOO|0Vb~ zzTV4Tez%I$pHi~*V4Dk?3OWxB@`%dTyW5U}!6KLqrj(Wo24CVuYy^9uCX&HlrNr2+ z1!K?P^m@^e94sHfIeG^aGb#sTG+r9}gkzw`u$3Q8-KhtAP68EuHyz5+N81ZnwSp7K z)jQj(TXXjbF5n(jaJG8}9Gvx9E*iOxRj+aq3t%2ULzy#opWrH6bI*XYM%+t>7N8iV z_bU6(=*R*+4BwE~J6fY>(uh1P zEAsX6_R3kx{$8>q7#->Rd_4@^%#_9M5-h$2bbN{amS6E!Wz_@yMOqoOb*WsbE-b}ZtEA%( zVRT^+^blsq&=;uNG7R@*nz~HiU=Q7;;=ZkV_t}?3?|xXn&lJ%2|-x&(+b*K zgh*vnD0?+nuycJ3<*uQq z$6;Gf3m!*8#q`?a`Z0TG6n-BZpAIy2wjM_xJfS~@vZfTHAX+r-DAr#R`^$-|#Bviy z^HyPA%B8)lWQ}T9AssJDS*=G<>}pU|B0K@^dIgd-egd6Xt#@e?5dRD1PWJAwd5!J` zoqY{-UJ2A^jot-f!WygzWwd<_`Yt@X7HfXM-85n?TCbck*FsrCN7m|}p)Izo(|>}^ zs`c<-IlZ=C_WjH2Q723?8$^j8qPz{F{;Oyo5GB350UI7HZ6%n8-E^S@F{>$XBP8s5 zHX=)GfdSTlztM;%F=Zyxktbz(mZwmk3JQA)Hr2H1Da0tH(@(*hnA|s^1p>~|nN3)~ zuoZq zThXUx(1NX)Z{u(qj*YxvGH8o{Tsk74KBmSV(&%v){Y;Sk3)s)cx1p^s=#SfDJX9@Z(!rjGJ&>^nP28#Xw8um!RUU2cYILT} zJM|#{nl_3Xubh7o`-S*kH18#S zynh)d|AslmzWgQKK1UxzwwEyvS5v~vm`jWMs>JNF=63P0(1o)ZxUw9($O76@uHWMy zX(*#^d{f}{>KF8vzNl`GSM+KAb-qe_C*p~Z*OD)2%_}HR3Hk2Q7yB0sQ2Gor_J%L$ znO*v0{)q#X;tsPSPz?GD6pfvf;xV)2_k2O`|3x2%5$wAgDOLt3J$C6T$G6VXGrJK{ z8>AHYC8lP@E=+`r|EkB@>jIVbcH!@^Fub3p>0YA7X;`Z2hbZNIGtDQ?Qky+Ua|Xxy zhgp~@$ZU^3&c8nBrcw-$rFfWfU)2*aD2}`;$Hw_rF>_>4UM*!B)47yf{O-%nxWw@3sReC=U*sY+<*N}oWODUcZh3#&eg&s1K zYF|UK(AN(kp$r->ppq6G0$GZ;zya@3tsoDP{dG)+*nz)}q+<4}q-4Fj`>aF-2DYMX z`#}}c<=2tIYzjLpGnp!24lNguMtcGF!4)c@_EdXa5BGo*?j+wgP@BlrH1Q48#=2G+ z^rg#hpcN|Kz>@njz4iv0wA%){C`|foR3;AylfJeP)ay>V_e~TtVzbh4jaNd0PWdLX z-%OWbfcEJ9mV}hIK$g=+0iV(lL7t#r0XP*=X;jkG19~8>uF`c%uF}2i<^Q1w>-dii zEoT0~aDiBEus~HIJ)9{Ch!8Ny=Oh%39q{5LK0U~C1O*qh|BkjFL8hOwrk&D=sT~9V zZA34j1Oe!x0$#$~x%wd6bTsm65gcaE=4J>}F^@tsgHqmsCZ07FN;4SFz^$s#?DJRU zm`59rVu}rVU-z@^hI%a}9L2EQMymk!h<(Ztu_}HVrg0Zp-bH*b*4!;MKA%9-lc_y) zL^t}{ys^I5(<{O_?tn_>-?aB#y{}yPqhF(-_i)5fLQ~&EesgG}0Nmy)@I8i_UZR9! zsw@IF({TZfWUmICp#*@{`y{Qd#%4)((g}f)PI4T_-OJ9CDo2MZ_&1oOi)i_ABzlov z6Mz%F<1)3V6M#Be0I>JrSZ^q&oH?O)L4KTO_{ttgwA&+&&GKSC0Z({KSRX~9Q`>iMOL zx{Ff26+H*1IoMP}gAo~312{wr09KEmXrC47pcGibSx@N%nwtz`KOu$E#0_VJ5V z$~`FR7Yy3)PMX%QKvDgKL&E6dXL?6_z!Jp;D<|!Q*;R_ILpsxGxd2Sj0@S#-$UEaV%Y?fNVWBdhDqi; zAeUm#qqw=WTtFfn0azpProdNdmpK&s71(lG4zPACptr%=H7<6Hm_i0nOf`-oLc|ua zB|pe#RtqXv`DYU-H^qLf-)j3r2A6-0x_ILl1NXe&pkq2Y3EWMHrDG*DjUBC&xdbyh;iG1Y1C>0M>{SIw9B$vi}J7 zFeL~$K&u24({Ta2$^H|Zf6YVno^&zg4WOc*up0=qDK4Bg+KhfSOhJxk>FuA8;4-o_ zAnqQD6|jqz3wV!?2slfY%YZW!3$S~>sJdV;+Itesy{)6B{YXcI#-m(m?voRYPY5QM z&{q`on`*3iSFk>vrR7(UTpk?}@C{l11t_Oj0ngEL0hM$_Kn+>00ya_XRoS$FLAJG+ zX>hwPm_I}2tfS$+mnCSZ_lQn03NqXmLl zORxNld}h-H0c)xEFL3pw3Yzx|Jc9=m5CIniR8sFoD5L#pUZZIJjxhRloa>l>b_`Pn z&x>a7>*?JM^J+8={2fW{rv<+wDX-zQ>vz;6m(Kr=c)Ka=50tCRXcg~<^X;ui$0CL_y=c;3Fsp1mBS^Z*Y<_%SbYo(`zdrixvpLUqBS_2wfJCK~Zf0MYI55jhRdP+8Cn|<8m7$nXz0YIb2OxBYTT| zM~VkD#jF{oCSE9Tgarkr3A;@uo1kEo4c6KQmCjeH+h4R99kHHUJfZR`QL5Q$QlHcp zE?|mOMOK0;r&m0U+wAdAD&su5d==9@6eSea)(*SGqGZf@y zjJD6%uA&(#k1V`}8p)Km%)@ghD6j2w0@j$6>~`f`fIWJrirYyUuI-OOyp#6XVTc{5 z9hu?q$AQc|_E4S!nK|E75l)ERM^F0Fffg*I%P>KequK$gXhA!q-1v?%yqEU=uJ^M= zBH^R-RXZdRb3$nf#gu}n>L7NRBk}5U5v6#evXefht==$OU8l^x;NAm^$kU3#7goh0 zF~SFqUO`iRP@nH;l@Bt1la33hC%bM8b2z?N_T#$Iq0@SQiadnXZWi7{b=uWLl_GBi zJU4+F;h;mhA)fSJMpSg8-Ug~YzCX<~jM3KdgJ_pPuV}hxzzTryLvblZ!Kagt@cIiGCJaGOt98%qdvYye>5w^UX;8a z!>V|ziZqxO?8l8Z&SU+IDfS5Q^+0^4(neq0x=cq}ID+&?CNZozDsBb4iW313pZ!p4 z&zd!C=Dz_Q!@PrV%ifiWpV$36K8?qg|MB<N9gQGN6uzC%=s55cP(d37)Ke|M&-uJ6!7C~` z&FdIV3^c-MY=F@N4dokXbnmzy`>{l}J8s%7R(9_QyK349JKJEiA#QmCjb8qxH=Ezc zquT?Gadu~!%FlEwY!;QE^j^F$s!7-Ec~$RNYZ9?el5>bO%pCF-~m zu|k}y@`8+^e&x*9n7lt73c|pN*sh9@s$A41*a-5U!CW{zEk0g*7SQd%#x2(P z9ketUEfu*#Me0iOvEVzwMhANUbHfz(Td>i??rd#0HpJ-Q-^htK`#4urYn*=~8}G%| zTIO1sD^4i;(vKl1Q{_&TWhXmpkL6NuXX7#dhNqOGk6GC77tr52BR%(SrMO}$@Ta}n zburShkS*wfjyQw1bTJmA>3zEzc^!YpoYT~;2O|%xKUyVOLNRQ_@zyN-#|qum5RZz$ z4zpJ7p`b}df4eZgF0~6l+pERbb@7R$z5VdG4Vl+4qt0ikAr#G5$=py1>t+N{V3=W8 ztM^bJ{EVNqY7gBRW(>8~A%h-9A6pz8(nv@ghd12}KTj8AuUBbixY5URC&B@*QcyP( zB;r+-bExVP6`get&+E{KvTZ-ybwqMloSnsRcrC(-96pS2GKViAoGa7r;}e2jU&QHm zQR5Tu{b>bEdD4+cR2ED!GwQ6|XA`LS)|5Ctk7SB_Z(_LNWkoemB|P!t?Bg&TqB$(? zZ{j(;6X6*sRu9+~Fek=KvE=%A4o0~ARoWGcxevk0SLqB)?bV!RwDNfGo`zwsW3Jg_ zHW8(UQw_mmmU<40AJ%K&@G^ukwq*nCM%c;Wvj~S)h{ovSz$bjOc|?R&i8CXIF~*sM6Ndbx=jEsc?*sjMmg7P>D<#vh6%w zpzs35#Ry~*+Dhef6e_PKD)v?s98AMIsw}#QAL5Ohs=Ag;u;)ln_`Vh<^6o-g$y#$R z2UXFE@)Y-LgQl8nDJ0W-UOE1|+68?0=*5p1B# zeNfa-5Ue|*is~b?v|R^7eRb zQ_DbAwW2(Cf+~MWISav(*4nF}3SOdlgGB#e75DVG_V6-OZPjo96UhvBqD`OznvB$N z0Hrl0C445%7y|H>&B4e-dqdU1P*Y_Gw8mk@c&MooOidFb6I=O4$e4K=du z8tcqq5ddGjPD_WOoHehvZfU40TT{zGl`+MGZzrhYCQ43$U~-!n;Y`n~pk}!3ngQ{gz-@nTB7wBRESd$d-R%>}BF zE)T=xxedYk*Hr!5$=38d3aa`w8gVNMQNb#wP=$DhpbpINa2W}zh^e*|)&W%qlfjJK z5@Ps+N@U7cW@6h9Dqa@Bb{0X*zr&1hTW5?Iyc0gszz;srb}-&x+?A?$SSrP<7Rx}@ zFx5fzFaJ^4pjR>{m#0o7wue4vVOz%hFu5@bz+{g{>RLa5Hx8#NSR1LdSSf`z95Awl z)=Sk4OliwpP%}_POtJg+gUYC+KBGk&wo>_c;B+gxiINLHn9wFh^Z}pgpuAd9wq>9i z$oE#PP&*NYUC#w7pvmg8m_@;8EsZ+ichWJ7y7#NJrpZ??X!cm63p$~1oY94zA8Yis z#{Zp8j5WsNybRnu*3!Sr*~Fso(7V)Da1XavbnAod!wVM`U z!lIMhb9r8cS?QBB@OEROXIx*j-cz&!VV_M1o0a&KYR96Sjkg;io@c>FZW8yZ1AW?K zSVwPCrR7cYNKm0ol$@Zzcrn9sPBCWt%o>2sz_)YW%3HY6wt0ZArXy2~zFjRSOZ^T*6X<4aU~JrB z(B2H64pS>$b4Y`#N`)F@*&!uw>TXy1_m!Oc2HPP~@rn7wQ|gr@=AV>Aqti4kPvu)x zs<3{m!Xn0G=99N5UNe!?DSJloWg8VHZ#5`tkxw@&`mWs|-_iw~8sl1unrh4~Eb3sB zWpM_ylNiJp&e)$ZnsE%{B*wcLlNskRx)>i}EM|O?aht+I?Ik8E7!NTXW&Dt_j`1tT zON>7=YJsW(4#xJ3T^J)62QrS7Sg2{^nMh=u$(Y8N%~-&=f^jY5X2vqcU5xt~-(;+2 zJjHlcAoAC~W1@la8ly*$s*%nZz}StkALCHQ7{-Z=(-|LN%wWu8Tm~!@9sO}8N*K2? z?quA~uYuh-4hW7{@r7aR%dT#w^A~ zj75yA88iN5)1*Ylw>H&DfDKl(7%vV8+pm@r-ve z&MM@<9L8M6rHm^X*E5zf?qJ-_c!05r@dV>1jOQ5Lj8_;7uX7;r12rc(8O!RGTy#cZ z3G)pnS^iLA1ztrKeMmd6F!7v1C+q7ueq;^ndG2fYhm;GohFYZv7|~WHv}2UQMJxwo zGrxd&mc2grlJ?)6Q&_-q@>fc}TCZ@$8HLqfD=a&$utf40e_r>g+4=FA3J`uf zue01`m6E@CRAFL~!pdZYR|*t5HY)sZy~1y{DlE=YxG7WN$XyOK{%5?Xh+XRxUVB>M zoF^3?eoEo`EzFa`-S;cpRIV_FYj()wUsn7qE=c(-w%ev~t#+Mi?7!NTXW&Dt_j`1tTON>7=YF$)C z9107y_DpnPj9?tdIFfNZV zj{`2oM;MD4pJd#|_!46U;~~bQj2|-AF@D8(iScJft-Gv9q2^EltvzEG#t6oNj3XJx zGbS?5WK3hsW-MS_!MK)jGh>-VjK5t>>}PzFv6}G|<5|Y<7#kR`F?xinrqLM#7`rj{ zV;ssDBT$V0iA+pqe1I{7F^_Q>#nK#>0&7G1f4iVf=>i zN5)1*Yfn`XZ^n*{p*?y0_hDi%<7mcs#yc5jG0tJkWn9X*l5ss_DdP^t-HZnqs|q=A zg7Fi^bBu1rD~#6}J$tDdIT?c(!x{TCMl+6KoWyuHV_`A}<}tb$A7LzJe3Ee+<4cSc zjE5MHGJeRI>`(=*VszH2?RRpb+}IRqxqnqVoT^t9#BhjKG~><3Hc8j04zN0+GtO1LRZToc`z zpU8H9W?%Lv_Q#kvbat_v z)5;!;!#-C0Sg33h;+-s)3oohkwEwx3>12>CxdM zTJzs>{71mc#mP+Aj6gR55zRgl-diR7XCC@r@apE&Oz?jZK^{o_-?Qh%>HmGk z?7705T>5{nX8*gUDKs-Q@&BZ=h{B}zpJsCpF)hkKdW&@sY|c zvvmGPUX1^nGr%9rl)c;>mfl=5>!0u++{ByXyYi;_U;=y0;PgcLa{QZ$#y^SRHL`Vq zO#6^O;e%RaAPz1~`+omvuf~6$KZ!8>Cf@W&{7t-B8iT{ro)4S!eOTK&B`BUx!BjEOkN0rN?A6M9sar2W( zE@ymZlal|&IDofgxs0oyR=XeZvg{_ju8!waxrO!9)IRVkBi_i8pBRfu3@?VtPx5o> zd6s;S7MH4Ll)h%0!hmNL_GdH;6~pqKjCk^Vn@F&xOxoi>S_QP#J5&PYj9p()azDm& z#x(oNbr@Hlzsj#g`YBh&v>2D$zEPBkp?gF!FzM$ef|I21-@I;21%$V ze*bfP*xq_Dex(uWLj_xm(BL9|U|7>$JqrxwXMznKY%)1955%lcwUB51kbRdc*($1v z&t%r)bzu3K#j@F453@bq%axx8S&uhq<)?sU@z$*PtYv$=ysC-(wa3`NE(Cm@V%ecY zZ7W9*FQ16d4%T}sQ7dP?56cHw)>+=m_IT$|ex|Y>{Vziju}t74KKVJq5xle;cz&#D z@3J0m#mUb=mc=V=;?tdFyniM?IV|H%F!@PYE=NP>Lgyycvp026PdvWqsBl&%g->=- zSmCem5MyH)^TQR6?ym3@%O}E>yrQQ1DA zL33!G950Of#pj6?htqS8_P`H)tJvf7t zFDpKRecZ#3MIQe@edpxW#W2wT*e1%1qB?f7W3KU_Ma(D@sk}R8zu7$7g#B2_1vr>O$ z{`H&m<_u%%kKCjeGl=+@`uA?qn-jzd#dp)bxXHjQ=l9HCqvd3DDl|pgbE^1edGV5@ z{FwT7tT$&)yr?HXPUczEBKh@>3Vt>edQdHdn1!YPO*<}*Lo2eFehVu#dJQk(_~x^v zlK(lrR>sjIX*r^cuh5qooo+Ato60qLze2O}RcvjRB8Fc+sbKpUwm0=ve{-mljpU7n zR~yZ$MnI1ej+Xp)m3$3j8Aq@A5IlXc#ppyM7Wo9y{Tq#TVO3UDQIWP>Z}^26bM0xM z<6DfbVWDkWo0$$V&0@&D)#%zLw~Z=Gn;5(wgK^ts1Se-cv@>7lF&$rLF6wNT$Pe)< zDrFTrQ`J_(IkAdsX1)|;z9eL(QgzeIKxV3DyfS{NsN@g@J!AANH2Eri0$9dR220ou zKVsUdTQqX%GWabZvmxqPuIGj-Vox`)jE0iwgfbtg&`&d4zKq=(L$%Kso!V5L@sVEZ zzfH|$H6N+?&YITcspggZdDYFS=5)JJH*aaM5h)1u?WWx|=APJGuUzLnvC}3_o0MGQ z5u0qp7Paxpt+L)Zo{@sAI+ge5LT!W5%u4#^0%nmouIQR~y|eB{ZYjsIW8~v>!KiTF~M5oG?Zr z`VS|Jb+r9`qrF#gx2iT?B_T0%;y1r;5k=K`C7olEri__5Hfijn*lE*}!jmRXo94>P z%$}ntTP6g<=~jC!9mBCT~r)bAu-uj@a`&uJ^FUkc^&k$&xI z+bBPS_MJ3xtTijC;iS=>B4hnLXnl;Ix2>pr8O#>M_;sd&4-A*3j{f<9@uk&WNTIbz z>Mbv4TdMd6tQ$WxmiW~C$lFazS=H0jwblp~7IFS@QR5cjecFpn+6vaL&h#7NQB-vO z5B$j#qbEh$ol%tN?ORqvU22UPZ5ma~4z#}3=x8l^ zgo;F|-mQhBi&jwLhems6vB_FlwD}Wb@xdz}8xPsgQrV}C>uAIW&lpj7U-Q{Bc#X8;;NdgIG>cF9znh9y z{xp?+Zlu$cSDfv9ifWo`Tz+uNS!0mJQgZNvFO1(r(;v4uU+^vcXH(SrO_nNaB-L1) zVZsrE51z6*C))5;A`fnGq8=d@u`DF-p<4}_zq{S z;VdbvZX>(pnDn`s*;U@*fm=*D;rwpE5g3CQ#2zUdrMz3+K#9mo9KCa$L4))nF27B9+D_i5***S7ej^ z8K0h>HajKt!RDljS&^BYnV%t@#6;FYC6}_Cw=my@nMXKCF%#0WMe_NADsQ5=(Is19 zvbt+bP7WG1Te_~2%KV(|3#&}6Yg}HQYTD{1YJ65sic4D8G;s;puJpO-d6KPbVtGbF zA?g(upP8*XlM*Lo&dqS$k&~95l_^TvV6ugvRZ}*aRCf9_S7vUma(t1e^w^}#?DTsw z(_GRw#mwcTq$Q-KXS*_8OQg%1={r&|K^Dl~R%)_yGgGJJO2;V%HtBhpT~e}C_T^11 zVv2$a(+aBbN5835y2C%UUuwC)>>2ZL)=uo-$`ne!6sNgQ>`kb)lYexHL9#s_voyMQx?; z648a44U3z}X3G*q-kFjQpCsnw4^*yWwy=qoH7L8uc#t$MXEr@0t+`uQG?UT|vOiXu zWU5QXtZJ5J`qieKotB;_oPi@yc$3*27urq6t5eLh)Re4rIpFGB5YobJ5}En=s%tke zlaZ2^E_1oBGh=gd^3qi0i@c=sK#uE@y+{_UxQR-bpE5UnV4R3t;zc()I6K~6 z%F4Wy?EEYgQI@FeCQ^Ayk$0!(xzY;;O1p|y6dmd4^eC-i$t|S z@Nzvh2yl}T>=b_yb%20IniA{`y`{*%LrhX`_S~d7dFe8#Vw0Gg?n;`S=}Jly8w@!) z_|vvvXW)p^mKK>}@<_@^7b~JLD*I36Vsoi0r=NnI9kVK0nk-7m%9JC#(qyp)W#!1- zT$M?o_opRcn!Vt9aLqq^j>GD5m0T4$|#dPQ$zpk?Cg*Gp^z@lDD;rYU7TU)A-P?gebGYz9jNk> zUwaRf2|u{d#o5hT)Perl#TjlbmKfSr?%PT_P)b*4n5QUvsSNk*RMsINCMjv=I}f$f+f`HzxhW$50P`Vkb-gJ(>c@` zZm%<=Qp~^nBI(Ugr^8;~#1;0!y_euT+~BCgyfucV_Y>a>)E)Ny0QeVV98D{|ntJ8z$sB>1wHyu2j@S>B2icVZwbOfoHw znM(RG%-ILk3+(0$#eL2QKvIoqmXnn~PAzjS8M&g>w`An%=x)i(6WKMGO#Z+W*ErcO zji9J&gI}nabc;ICxo*xLqDO0GX z$R5t_=<9$0%Ie|t!r*+ihck#a^l+IoCCG?|y@$0H4qC5IJ z<2-5vxAhmlv4X3kvwfZS1=fRulcu?b<8EVqW;XU|(hqJ*?B^6elvL8sISTX7hy9$> zMwSH&wX>LMe&;>(+gd4f-34FVq#Y)omecT zEZkzsN;XNVmQplJQzoTc9P+ALl1RLNFIN$l`MLX|AZT2)CQzc4i=TP9nrOYA04|in%iFG8X6OERq!|XBk^XQMd{r<~FrKr4&tXRY|GIS=C(w z>8&W-G1bsnz_2>0#@G~FV%%_~CE=7!rdt0W8nMi#Nbk65z(CvqG|=RM&OqR!<8Put?rFI?;d_+ykLg^e*>{{MZ`C$4I+%#mR zyJX!fprSWI{X0?BAZ#j64{`?6{ezr#EDdu9IXj2dw2V4`Ax;sJ7G|3Rq)tTc9p)e0 zQ?%)SSf=2_sHy&LVLJ=917O?G(l#es9h^3{B=gg=WTz^^Y4u=dP+@VHs69GmhB_xM zVOjR}QXwqBJ)gSA5fU1}9Pi~y7W1QQULoeEW~8SjEy+yJQvF(T=`P$P2$L!n)v_v> z!fHu1wOEZ3rI+hCEX!Ljh3w|xHwgEig&jZAsO3PMNEHImM;@Iwf6u zF>%8DIoj#OJG3|M%GKVzd%CvsMaz7xYOOU*D_d{P)4pDB&DUPoU`^3}-(by=doS%u ziFLukOw41ACDu9GA0^gIZRbX7MoRuPY=6>ow2e<%Z(p1?e!fd9eCjFdK|*9h4WY07N0%dCLud>eAZm;!sBf;kNFu`qta6`0pK(N2by`>Y0FIQ zD@#&tUJj-)tb|YgZk?NjA6K@D$IorFveEdPI8j=)HCo%}F;u%~8>ao@Ib1998mOHO zo2BMlC2%PVoB4!y<(X;c~Iuutf+zW(IK}y zWz`%5wJX+0?TS50JLWq`JL5lCJKt%T_GW0b_H_7g?WvwawOtWIw4=i{Jxg2dK@Uee zgA%{BEzC{BK2Ceho~qS*qhD#8L+5M%?4PfFG9(R;ShP*UbG7$I=4l&ZGqV?Ad!$aP z6X)Qr^28kp`D5`ABxnBgCAm0K-}<y%UtLJ<}C)wYH((d~NG3#Jv-bP*tXu5 zfsL&K%ZsLJnomkL4qfmZ<%;L**>f_p(h~>I#~GUTbK6v{!W+xN zTV3XByZbN7$wDa;)21bff7%;^V5)5xlAD5q#r4q{+WrZyl+3K${O0n0Iw60K3sX_z zko?s2Y%|%fCZrYSEyiG(H!m@DsrJ#Nr72lyOSBbJ+GsU*p){zsc%0Il%%!%mOEa;K zWF!tvMX!OMerubhT@K9GmUqt7%DSX!JNwSp4&5SZzAq|A`*LV*idH{tPD);CR%UL7 zwkkFbjTW1inwg0W*F3Fi(+Epa()7h~v*+SoFHk7$b9YfSkb@a$v-EfeIJW*JuI5)5vsV=89d^WJyATI$#0cU zruNr<9$Nhhyg=iN7CX^yp;qIQs$F&FYc-wIv>ScqYv=k=j=x_(p*s>;kH_gVp1Nja zW@=XkF3riAug+8qMg0@!B&KPP$7|NHvQMymrnbv_LSD+8%z~x_ zWMAGt)0+$*qfd5LX7+>Pv4rWl>ytIBsLfze z4Q~HUI?i~($g3<}V|x#+>J^cam|e>Cg;yU)xOnZ3o^~7&eWdkJ0UMKO(kAv z4yen0$6$GHUEg2&s(j6n*PMNZw#g7Vn;KbwANqP|6@M26$ibSl&q&E_*W4Q?@@5K*L38I-C z8;OD?$6K*7#PTqDlF79rc3;CXV%8yp6gg zCj3iRv(mGqZ!^&!^X*ucdzU>q%wwdGc2lut<*i&xh zTiu&Eo_p-dFHl%JNa3r{KN3y5_~%&@n>n6!&VIZj8w^yfv8d`=Bf-5Sy9(O%J^FamlaC#GG8PkP85!ZUlZlP3=*OcTf8byf#LrIa zesjl*zdH6*hOB;;U5N4*y36QS(QI%9D=t}=uu$F3vYNGUJZH?X;`lSi`nHAR#fM#Q zr_0EYvtLd>jSP@e2Ymk7YSb;rH>}59AKFPT`Oh%5 z<-{{o%N(!eqDd&>yGd~)F`4;m)n136sp1Uhkb*`?^Mzw$9FIRW_}TxWt{`Stajdp5mx={(Z-b&OU$Qc97#B`)`x%8iC!5 zXxvf~vr331;~c>(U2~C!XH-Qk;~&!{~s(gY{(guNujO zUpJ6#FI&D>qkjEn@82|p7B#LD=3M2*tI?A8T76mfwnT&pJvc!VCpUj(rGDu6^`BS0 zg)X5M_?C%X;xoHza-!~ClrAQFC75rE?F_re{JL{$CiYz+rIB-o{ssl1fT0Vk#Wi_{ zKXg1FHF&>)o`L$&B`FQL|FZS7R*qkOIpepr^$mZyroMsD7g$b}7_e!<**WtZ)*@Mj8D~N%W#NcY8 zca6HQBl^}`@Bi8HD`S|I^k>I2W^P;SyO*Q>=XWa>qqg#IV5mbaMVy8@l!q6+%F1K< zpTV=qI{#VW|tUahm@V_~KuU{p_Y4u9_(~`L}jVR|od$Yp`jD%J?Cp zIq%GGnd7>pVx2s-YaH9gSS^a9-(?qH%K!XH%3ql`?Qxu4-QGk0)h-H*(L@>aT5Sc) z-z(p)5qZ;ItB$-}7~$WkM(nbVe}rb>tabk*$J5VU*@tNH?Iy*IOY0G#ioU%Jo|W=) z)Gv)CR=d`Yryj^dS1Y*BN@?wQ(Y*S)wdU7RPdI;g9csrvM6r1|5X2U(eP(xT0|`|I zlJmP%$*qS-!-vAav@XOXYk!x@ePm(2P$EpLCuZ~1+TH3TIsLm2KWQ0Oco)4$cL|lb z_dVpqkLNI7eCLS%Akm9!u#i1)pMjX#uCi{N{f3Sl_~@$-9_-piZoWqJ+#+Xa&r6o< zdIPH!+}#1zQVe)}I9~+czQcj02n_jezmX_s|A3;40l5?V=d; zzX^YaLfDMQ=0EH>K~0_{d2c4lMZ`-G6xfZUYoyDjH#sOi-#|FY5w20?>YE)fhZv55 zAKr}V9HKd~46^t4(7#f(_-qqD3T=mBSZDnnTZTddu_URiPXlBgI}ht@=JttQLWx2l zlH5b$`A$+XMDc0DdhHxfzBZ+ym>tvNzf8`TPSOQiEoTdPpXe<8F zs9%_e>skMJH0nv`2_%5KTp2DA19w7tZ`I>eM+14F8D=e#WM1_9=zya1N4LsA#VB{8 zIeJDS*5@E4NtV^;xFku=z_vhtoMU-_{EYr`mEhBjXzJoqpZb)>lT&h40Uh(}w~%cR z_q%@vLxK~Cu8BmyhZq2c!$|&V_3cpdxgWzhS9#|Xy$gvx#Q;8R0DX%7wbTJiDHN!m z*|?M1SV5~nf|8BIfTFKhUAC)>Vo=e0$l4i;*`1{gYIa#ss4)2T9U`}kj*(EQ zKGvW^W?Glf%czF%=K}fMSIEcWu04h%8QQQqGlsawf!lqX+& zN*gxnl$wePb7PB-cU}9Wvz37foeoTC}W6Z!9NWM*R<3%fSgIO4SFgF+&ia zoZbMNb7DH9DH_zMg=VpsUKuiZHs)-)retEJwO0d~IM9Swt8q@z#ON!Obvw(PM(Bye zzMsI>@o*bN*BYASUZ>`j6FozJPM0}E&tanf22MAc?4UPF(0!O1cUh8Wz0n?v2xA*p z_foVO*oy|`iV)5_WXYPw?9JGHI;3L>%Cg7hAZtIOcL>olmgt*+ zvm++=;?@t>pWWnEDZZt65cc=H7>PBtc*gsdL~hu ze-^ccJ%-oCO*)`!m6@&;+AYj!xMq_tm`ileBL)_z1yvOm>Z?9YGrD1$W4O{u5^v%k zA+(X~u1#c*PfLo8O^~@w;eV|P<3v-~vPD_2L60>L%NaU)Hka(f&=#X&?e@@UL zIO?U8^e)&Woh9p9qsMB$$eq>@{1l5umls+SI%2cKWNUdx$IDLNpJ17X1_Y-QT?L98 zXlrZP_Qwtw6jX=!&L4Hq6OMrLKcdf2O}&AL^=(&Oexi3L(YKrEE~Q2`GH9)P#75Uo z#}asY59=SFIDYcdvOgtv#h4rUEt~-8{x4;QIjnk4SwlW?Je_duPh4=sdME1JUa=o_ z;jR`xiiEE`3O;x$h`uYtV4uf;u6{U^i(*i-_pbSU;oF*UvJ$E08cbH7VsNna(5H?U zBgVhe)%y9Tjwc>qAII~Kg(mrl_qs?PZC*}Xt9!~Ll=cKGcM)vk9qzw)EvHS9g6 zzBL&doqgV|&7i#~6u+SAbq`VPiawy-tG4HCvSjJINtjgE$(k%FLjedXqkE_dDf)q8 ze*M33*iVc3<}5k!ZZ9cg@g7FP|2nG(ect=J>Ny(*28L5h&`orW{E?Q~@AZ=TVDgSq zEmeY>YAD%GC8H_s9!oBrfx-H02CTUJo`(9XOsBry1gpOGY)*=xHx412M)IljpI=#x%aiA?Igrq-qjdXQdcY&3jHX2}aY$NPm%}i|7Aw5Q2kDj*T|LS<^k+PPZ z6S=w(Edvf3)*lbljeg|Fyzwk!OdcZp^BP0FaW6n0xGY7s!SkIT#{lf#7V;aky z{npR^=4kY%;AtZ=(lr;v$VM{c{b*V0Y%Hg;4K#`UuR@pITF!gFMSE0tO)=k%3MnCj zJKAKV_3hsrFPXU?STFs|@mUw~QdJPD5Y9X(G_kx^V;)=lAEalY5p5+Wknry1%I71x z*AN3ch~B;Wyq_36pf0C~o-_JtPoFK4g2-Y!&Tp^S14$CNj7RHqBMdS~jSG-Iq z8eVe}*P2)P=uaPzqkl#itSOa`KJkd0f?#0E6ZCYdwfygnSAIYDBZg4&+cIeNJKJqm zsM$G+mdmwKx%U$T2d$rffzHLHk2sg5s~NSj{&m!~fz#ylmf`9H^q>E0R`6pSIo%ONV+`i*AE1n+~@Yv9_5>*}!bYT%vmp(X&vofar6s=Z|+I zF}R7|bdJf2?HGrYy49vj_|e6&Z83S=+sM8 zx%s1%C~e!II%%97(4B&?f9x-5&tk=WME@0{_olksCc69J+dF1-C`EJHg(ui1w}WKA zr^wB(CO?RMcPXiI04wqZBe6(+@s639CU^TrB=da8(S8x#_2E>%h)$W#jEvM2**PEr zF+L2k5%x0r)pl6KFZ(!728~89*EIr^I@pGY(chHVG}$rI)Ye~yjxu|xuGRe_ddLxK z@sc+psVlZ;tcy)XZx+2#IX~RQ0U=l=L++YrYGiwCec2rgZs$mv?cW2T$PRKP_5;cO zxP>hFm@^w35{mz0^snPHu>3E5K1LUL>c@~O1G|};kxJJIaBYnRlzkus&yhga1s~Jx zFx>p*bmJn9>f7>hkFQBOLG~+8af%>tjW)RYJk95!#Nar5Z77oSX34OjOOqyXGNQe1 zt*Uyr+NaB;_FZLZdwAiYkY^f%XzRavc2<#v?P2du(A_ggfxThMR#N74fOk%k9t^wB z=wQzk$*7JQa@RI%+Q{f(PBXn*$M{Zivo98ElI8j*p$(UdKSl3cw_+82 zs)txse~Kn9S^9lyyUzbqB3h$+FB3j($)(b%pXy1)*`FdD@C0TmdKT)lqH77Cy@kYJ zksa1#Cv@)lG)X3nL^*RWhijb%a_`f|F|nw}K5iv>pEYTp*s&*iI#Tjkv@HC4eHs0Q zm>{>3VV_|}qm@iZ!9-(BOvkt^nf6&{`yux;Xj@62JisdY+AzHsbT8>Ih>r{pv=Gl< zns)4o^PBKWH<|DGOSByPixub+_39S~K5r<;KF7v%^aN1PmcxSjF*5Hj(J|ut3(NDe zzu1&hUm$GiW0sVJ+-|_lw~Ds7S2KL|uzs}QuhGCuf2%L^Ket?6qFyj}e{Ka6954QC zCiXvI{Kuisch<9=9ltfFer|n{81vMr)hI zbnMA>D9qy5kLx5y;iY%Ax6v#%OWH$P9ukV_xu{vEn^keb#r4u@=#)1j;NxW6ZK zs(PD3mR;(-VnES*f$`E%_Wiw{Z2QMsvNlX&^0ULf zzk%)S4#?jNoD_7k#Z4GeNmfX0X{9h0|` znekS=`BC*AlifZewD++Xz1fG`%0TOpZjRs8$xiGb!>qTvIhy}u`a~mEzMp7V7f#e| z{ZvJXk)S6#Vr2I&BMFa5F)`6nxZA1#^Ur;Ij4$E#CEYgevHq0g_}yR6aGQwk@T#W~ zGOcs8%z<+9yZ(25d$%F4`X7axU(H6)h>OLB|@XCPoiC-8pO%v?$K8KmyRc_ zb_=5(dG=;^1aTsx&OM1SpH6M*wBk;pHMKD63tYMI#g`}=)4EzC2H=F!(2BZ0i?~u^ zSPRSQ)@zooKI@^7$`Ztu*Fz<7?U8QS#NSHRc1=o6kL!dD8#4J8b|-`jPY2oDEmqE1 zhSh9C)LX1N49jt+Zi5Kfw#u6OFUJ?=-X!be%lL?s>{!j@Iexe9^FMDE7bEwRqGj5@ zkWr>vUAjB|VobDFcXzz~;4my6MEY{Ah-AmRW>I(R?W=V^{o85;&Y#P`1?SSPP=v+A zWOa<|&?rvMcMnBamCId7xRy{Npy*%0XU`f+SH)mKa*Y^#bwx5n){@P;nQSeZwQbk7 zRf}e=<34Q_^I6=-pET>(N`6ST-buk`kQ-bj(;5`BR>N_|2z4o*#-tz$N}xl0Kz0mi zqAX4UD;{Fm{ojkaS06T~w8uLtAN

NCs@los@U)p?pIY8@s&rO>I#1(%Cu}AN4cm zf>hgw*pEW0r>D}Ns@LyP#C`jz&3jn!c*v$sRV;fcQT=)d65cc94W13#U&Y(IM=k9G zo1v!goFlXUJej*Nw94HP75;)Aool@>0eA3%nsJ%jt>kPED>EVLW#_1`G#F|HB1Ge6 z=vQI8!Y<>!f@#+%@48KN1CbLxXx=gP!`2;S=~uvxsd$$I&}YveXup2^2ZM=$p+vWvLKxfk;&QpnPHWY%Cw6T0=+;vDtwdgE zAq&%5%496M@}S-7G!C&hK$CYQrE^D7`h!F%Px}zZJi8~_J7i+tM|^ckh`|o2nb47f zj#WmYF2{d_3H2^AGCcz;FBsEt+N=X+MWry08ze?kr)w&8(w}+h^s^9CR~M&~;+;XP zm<4)d!xnNY-I_Y!;a@oKejSY}2d(4>X{p#4y}G|q&wg))ZJ#o004xZF$zCIrNf{rb zsYpyqL4TZ+*|#Sff{m?9Gn&ZGjA*%>ffhcqsra&5%D`So(e##Btb?gAW@|=sxt|GB zX46QlVr8T{(-J{ApAnPNnS6L&mK7<>$N($5VgFDfr+9cWK87iV(9X$hq@ronlq~UP z){}zF1}$Yq&q%3+c%*Fmp1yQQ%;;dxg{!IIppL@7+kJq zcIcMLhaX_+=(*H%YCh>a@T4}vhJ5R*G)$sig7D!yvW#3w7Luhjiy4CYwQ`=?3mrUc zEKu*ils@PoU?zki`BFxyCD?|DmoMddPcgsjBtv_4lG_Z$I;#85Z@+%?>&VC( zDl+XhL>`D1>wn*pivMjQ1HNe}hrVfw>!Vvr-~R|casDoLOrH7Q|4-V^`sV*g+j-yA zlSSVkZ9U(joZhERHFK5go91%+n@A~j;k%`FuF_29`KCHk75qC=PJj;i-p@%Kk{mz( zojLY?Q!8^EgMZhQF_bqheG|$X*WusVKGaw0rlOj8!~1W`)eQTb&O`ddXz~mjM;^@_ zd;VQ3b4>bAt;})n-!(GF@Nb!<;mmRQTg@DUzQqs&ipwWZB{RqPZ{;o==6UE)Gjlky z9KZbMfp34DIZlIl@JyPOGgl*I9`TH#e>0vVy@!aqfOh5>@gHUm>p#dGO3B6tVv#xc znOKwEhIh@GPV3_Vj(3_mkqwkM?Ie|Y&Z>$U^~ru`t=gt^upxD#WwP~NPjoVaBaFA? z#t5THt8}f5;OT11Xd2#wj@Ofx)w?IQ?;LR%4KRMnOzfB{0|pxPTBWzgtYN#9?q7px ztA*OIkX}cyGv(@S{8iU2gVx;RAY)6gM#rI$)!X0zA$OqBrfZrUooWbn*PrQa z)R&R{jHYsCxY1I!jW!y8qPt?D{Mu-vp|ra-+Gv70xFc#Xlw?^p#&`?$jD7J-OcIwT zhFS);d|HP51dD@x3`~;tHJY|aO~#5?qV&s#2gipP^<>a+sGDM?_Hw-9>;t9kF}81) zBH5$SH*A}l@kx)4sV!02!sGHjMhj9q;6j4Lv=&(DFm#n)HjZKCU(T3jh41QXFtrBs zh3hJ~M#@3bB0`#X^fkCh)+!~oJGSG8s@%2NM)Nf5v9BG^Id`~G;kRy{$=RGwdN6Qj z`WkJdg6eLlCpBq9`Wal(YlDMi8V{qvm2)26MMl@~MUM9~>eYN7+}~(W^L@?`qrUxa zWo1P@_WGeAFrrqP_UmsTzGE@6!WLS3b!Qa3@}0LTVDzg+{q492+0jYv4Fokd*}>3yyU_XKW`KJ-~=YLRMv_PO_hwDgOfF z(SJ$vbWnpQD+VBe4>HHz9tr(xMnNNG<_!(^8XL>h7wT&f*#fI`?9=#EuRQ z=WY!!YIw7O-rRudNGZDp56qAtM&bi8sm`VzBlUDS#bEclW&QLU$1@LA4ulCM*>=Ew z^ThXqVaJtmVDP%0haz8Jd{f!9zRkpT-WA!%=O{1fda)A*nN6!YEydZ8*w>#DqX!fF z1ej*r6q&Gv1&YBS)guahYM8sS!7d|pJ9UxmZn2PwHO_nfv~B|-{Tx!yQDi2H!rhHzJgUJPU38nAbW@d3_JR5>!?_PU{H zmtPg_VsKj2ECAL}L(6tzhNcqo%iUoH>YRU|?CY)p=9r796wH<~eJE0W^-w%B4i*p9 z7%3VG_3ph?@83tONZVo4_L6oGu8~q)O0T1(|1d;P@3B_1o{ndqxQ=cAk3I3^ac+&eP))XxHPF~&R&S@{@mDvyYu0t>48(Mj8(MD!XvVed7l4C@ zSSy{5cg#H_th-J})3?f~Vf6495Ej;>aTZTx!T48>bV`8@9~xFb+^_Kb<;j0Nj}{4s_m0(Mr8F{+EB z^~VWOj+bwbh2OKLyad^8Q;C(+UxI)3K^@wAeT-o}oF4VuqrDmU(X+^tzk@v1Yr`F} z&U0gpA4mBm>Ks}0E9%6j&Jl*KM7QE(#gVt@vL_oKFGtweF3(0SpbzuukH_d`Tg2x*i>#0OI=V$n9A{YW%DV4I$c>fO{l1Que^N2d zh?X(q4V~ApJpCMzzdtn|GZ>!5Fhmo4(0EPo;o~*I%SkK%5xuH>+G=^O;oIp)$6Lk( z><`8q{q@P<=(B_zZpG{ZBpSCa9|Omh78?;Cifc102spEMJBhv1^aM;*cxLji%{wxa~=%_KCn6@{50rdive* z9-~8Ma&mmy*DadI#3m%$ne@{A_Z$BSllI4qR8 z3yr32qAYyG9O4C{%1M>TtP*<7P>3p6S`yOq(|?h}Y_ zE@EfbJS%#TihZ%lFGPd1c=UBPI)xBkujM(hHkV&#GlDTH3LN!NTEL=uL$p z53hG;A%EL(s`r@PoMp65>Xa6r=KNYJ=0c5UCxcdlBWHGq<7?T%Sr;D}(4wxHja2>b zoZY7YIq2{Tq)P+lL2b)r3UafZ3Axh1L=G-N+M)y?|FhQ^)607Ef1-XKabt;L%^w%_ zbmAQxXEPf}ey-8*eHNT7E}rbmg=77ySAVWiO*@VeSj~MRfBF7{D z!y3IRG)B&&w3ybprcr;2sQBKn-g_(RHRlqR!je4{4(y|F3=Z$%c8wu(3|*nhV|ED~ zBqgpr?%)YcqGNUqbIhEu-ZaPfZ`bym*4#daqQ|H@_Y~u=O`@K;NK5VYwe+3K4sWJ2 zAp;i|;(g9Mg9#W)v_`To51r0Nn3-X3cP?`Guz83FWP3X%FqfJS4fd<ba`#eRwK)Wl@XP#Ep+4G<-{{m?zfHrNi zO`AcQ`yy$pfXF#gFb{VA;Dw27=r`JY+sId;dN9;X}prVPf|lD0NP7-!DQ? zVQ5w8u_S0{?NwbrEJ8^}bR}fiaVRI&L#Blk&o>%%#MbvtP_rrzJzs`76hNKOUyo>1 zlS=1nu#V1$NyR~$eg-H`C0?_Mw@B=FnZ!aM!lhRX#>6&l+rfU6S$Rgy@$WR|jtZ3+ zeT8y!ZV+ALUIrFVA+GWeT~mpNr@suXHB;@K8+qD`*4&On9ZZFLW{^3UOZ4Uuw=JXK zkGlpp7FfR>?)doGu@u_4oQ&>OWNcx_mCW`Umh0Q7$Lp+JjIwD3{vGvn)K#z10*jH; zHyJu_SG38n9{D!vnYYG_!;0$YO%!Jm4qB-3b$TIG-z%XjdreaoYOiS~T_^kLVcucu z4{pbk4GxeJ9gTHGof&Qx+X~Zta*T|HhshW$C*D%*cM%FBtmMm&K3_))78@T}m#;kf zTkFgVQ85t{^KttA#i%!(xeS&25~aLB;=G$QBfb~*kmEi(?UAuny0RGE@E^HVE{5#U zTa-F_hgfot=)X_g;(7(>?n|6JfEXA`%pOkkP9g>;|NRxT`c+MbT+8QS=y=>IP+)XS zW!9D5g+}vK%pfH5{k!Ip>-} zuy~avQPwUoIITK;88U1aOr*-5C5B$#a;y+8p#(jJ8Zx_pil?iU^N1@J5N9nSmJ|{z zR}uZIUoqG=%T^XJRJu}7fGPtAUgNnp_mH!|uxB~scmcd&kKlNuRiB1E+P5~h^`g% zmI;2f&}i1ZJD$?zI~wa+Lkj20WLS|An!}ONwAH7y z5`7bhZV%Bll^A$Ve|&jLUQArShPZaytAg6X(TfSfI>TK07BWpPBGa(D#FBxp z0lj00zMR(}hGn$+SVec7{nI$)Z(6N$94|yHUt?G+=0`Pn_%=5T z_?6H4=lrN&J%4?T5!;p{Db5}DUJI&g4ucz*OFPHT!TyP3Ypr=X7#0oPX;`UqqJH_a zg*41PpPqeW#UHKvOY76)b)zEItTXub+9R9R(US#K6I^J0JkIf^)3+Y8#pRog#!_?+ zy~!KrF#6eyZ4PqtvJo$9uN$e7bHl(fuNy`}DnqdTCi?0#?_vIW=Y7mP@9mAQfscpqqnp zix@B2=FHk{%kJ3=+1w(^-T@*7C|ifAWsRNSc>2wNDqpV3fi!RNrjT{l{S;n7+JIG6 z9@nW4;i=YF6CA%Z$6*Q(<7o$aprffM+ehYnpS5s;;};K(+l(l^S7L3O;D~)>6vk?% zb^}grM#^}j-g$5-Q?780l`_%stBAQ<4D0^=x=%;Q=`EIbqT`ikk8Lr+c_CWHZ3Ay$ zEiH_dz*g(>L`PbkgC)#UsU=ardTi9TDrOn79lY-K)}%>}hUS3n){9dx9s1^usFxgp z5>Wgb7?x-`x81Nx?$>=S;a8U!tf`Y7Pg?E%9ra*jw5;4|HJjpSQTLpm zJtU+VHkw?58B-TWwMp!~)2P}zF_3Yz{s{G#o+jp=CHl@0uay(;oF{rN5wBb(&bUb& zTS=UFix{{~^xsuREvc zbsv6i4^w;21Tr3;NGzE|96U``V|wEcR_av8PtE*2oa2b*%LC8iu2!tv+`|?VPc5O0oz?s7Gpt8fMLlW# zdz#~+-(=G;U!kgBMjW|{xPDDYpkbU8hXs7M>OSNwp+NCEGTqxiEZssJy+d&)am5~> zcuR@y14Q3JqUR9NujoDs4uk2Zm!qR%+P$F3+1(Y@n4KQj#!+$ zm+1!#>!n;rbVN(E3zw!ln*96}4<>MfQA=#=H!Q<^`0aWv<=k<&vZ-+$)TU<8QN)Vp zEW#AoD7^>n9YhZY*W2mFq`il&sWTkk;soalGaa2H#vC=QclXw9-)-0#=x8bBhm9tk z(KSg+<6=5mw5qnmTQUR3; zp0(bag@Z*mj~G@;DvpBQJwj81990k2 zst4s%;y+K3ma^}tf$MK&IO0nu7%$kI!;k4;We(3+_$pqPW_IxU9<$@^7=w3#dOR12 z?n?|Lr=sT_)A3B-(<)z1c~=m`2gW|)c;Q*u6)>{u#ihvo<3cT$RCcY zahFfgX1YdL(RCf29+VBIj0W}Ntgj|T#pxG-B6giNEdL{pe>wXwF)6$El*-Ql$c9kD~tuWwK=QX+xKBXVA0&93fIkbloBYE_>GdXW`?v(?%nw z=3l(rA$t%k@aL`yDf$)N_tZr(a9?@){|=l!OK}3xZ{wujVJ~+}*>MJQ6IafJ>eo0a zIE$u#?OCHf?$O4a`KmJX3HFs4Z#lcBC4P;26S0E`i!NoMEO6$G#zn;$O~MstQBeI; z;0gM=P>)foxXvQ}WeisF5ai)!*^tJ`95T9=lLbp`AxAGAy>LYDSkb$Z&#u+#;`D(# z0+Sc<@c-0VqYIY4@N~BfyC_4uTNj^$0?!)oVSDPVvTzXAml=)p0)DV>L2&Gw`tjl~ z!|X8zD7@UBEl!-wJ_j>HeiW7&>G3j%I=#^8L>-48*rw=O%V#$b8zSt>x)EEK%Z%hC zes%}i`>Hj{=2@=+*+BGcq)4)MOX6heIm6zM+ezk=-?vG5iiv^EDz%gRJrnArOX)c@ z13RN0`|Zr$$n&^+F}5f6ah}6+6`o>0>M3Wr|J8E{0ueoBZaM0*<@h4dIhUDdrLNG10d_fJfEPE~8V{8lytR=Y9j9_Hlp{AO`V|B8&aAOJ zcEovu`(<_KIp(vk;DgILdodN=Nkq%+^US^~>N-qO&TOo-Y3oRH!xf=WT3j6NMm&2S zL-8&dwT#~Z;~&7fU4`+b46QJ_%cu(6+c>Vm=p|DsjIU*81&virPM`kx6@!ZIBlN-x zL^NVpRAKatk(^7|W7b%XR-h+5@&Y=+%PycKlwUyQyZEBf0N3Wi&>>(lc_k5UZ~SORAz^DY?Oc}z`T@-KiTp!Ni9 zd$4~a9(#+R63n=T?t+nmt2O97I}0N(f!}qM_Pc=+f!PXOluqWBPML5~N9kDhfPAt{ zy=WvjG3Oh8pG|{2D(zMDDf+3&tdAX^3A(KrGsf!Kj*AHVUdjZN0}MPPn_<%v#C6F? z{Hmr?62muuCI(fp>lh18q#Hl*7swc1c?mIm`4VDyT#&Bd4FgWW@GN?kPhq!!Iutn;OAAX~58+9l`oC${7TUPc85(V?tF7lOb7F z25ZIeWvUD)e~|p)818Au@U37dhFRj0#dR42B1W+NI)K8ifF_TkC*ah)hL`^|*bp?;Wg<<;tc7nmNXXi2u2AVIS{neO&Pm2LzXMkcT+Nmx$4Ct-)peTG?s^B& z4mI##Hk)B)u}UkPBChxK_zMaJKXmUF+j(2zRd{2+7iyqJA|Z zs0tcmC>Yi|Pxb=1LgcAg&d5 zw9B>YMtyz48_r=RA{MUw05UF)?b1EIYdYRX-Y~c!HW{N>ylwdbD+cJ%QEeKv&Anl? zSGB!ssNHhoztrCPk=n9aYW+|f=~JybvhRJVw=ccXINsB|)y09I;rnRde zSZ(b-s`abdkdEUa9bpwRpr!)3T`ew$DIBON?5R`(kdLYj7*bOn5wxnZrU56YBUn?% zNn1xv6(fJ7qW2FqRg_VM>l`CI6o&U^{qSGWoAVuc$_NF8qIK)wK4?Uy_1y@L#1 zP8Xk|UooH<1Y)~l!?+e`KzV^;oL-`I?iRXXcWz^*rSgtpd6znV`O=EJ##=qo@O>m6 zT8!x~x9=i3TFCWVs8Lql#WEK;df??QYbt>A8mp}P#cXC(E6 z4KYbF?w-*j^f-lLZipc+D}HBifpYU*IC<5{Pkm-}lw`^Ey9UZ?l{KR%9#Gz(qU!=x zxfMMZ+GvXCi{B<(q=7hQ-aWp1k8bfO6xF?_%6mpSwx;OKsS8l|eD_*D zZ6PU-!B_665bl~*}^ihjj_ zVo=c)RBehLMQ_k@Eyt}Bz58Ivq*r3+TE^Y$1C2iLNuAE(?_=WlXD#}bK4xk{?LJ*) zA2YsFIxg!UXyWuP4mY*O=@PWiY|>W_L}QVdP)kKcA2TVddv~Xd#pO1Uezhr}7*up! zro|qh_VRN2n&{~JK z8zGO*h3W9fDVNCM0S$RbInebTG$3qivJ4*py5FYbo?UQN5`#ZbTO%06NM1a^jF#Q> z9^JZ4lxFWNHd(e)+Iw3ifEd+7qDDGcajP74oc#ux(P8&cfVBHJn0do@htj^giuZ^C zAUg7$(qnqb!ht5fff3WImnsq{WYh488#z1BY#X|z=>j~IF+}DCWoEXC@0jh(#+=089KVc`zJtu_@Akw<{tzp5wd1M3Y|X|+NcIgGQwGD= z0L9H8&s`x;Wmk`_@JR_k_j>pZ3LoAmMQ&rQ3m{zmWy5BfsWEmM;{ixJuV}JEv05PcO83@lF zh?na_%+M81QsKtUv}mS~*CXO^Z7xpX$n1Xh;r(F02%g~|)*l-`;M{+xnL+Y!J$aw~ zhJjSX{m?RIK!p4P2XSg20SXK??ZZQ7hMKkZ^Jfn;Yn~n&HViB0{z1RTfY~+t_lS$o zR>3~Q%+Qwn@#N?~i5x=}i#&YxR1$qlnjlCxzmO&ShnexXH(iblGjZ)X@0Pnc-0Xqj z6K)5X??!TU%IqrNjWko!aRZoGJOiCw{5Et90ZIfeYPvqm{8~o$k3gP6nCtXNLcB~G zZnD2Cy+@fi7b^otnOX6;*sDKo@5X8BoBgpO=cfFFxDg8r&*|xM8ouHy0Nt_7d3m_$ z%#ht~6Q_2QlDkUT2n_q>+(^tHAY#iAzuj@rhYOj57^xmLC!pxrLem0UXU9-u_v+s)}%lR->Q9)XkT^zTk6m-L~(QVODbE05d%$unI&oS~fP zP)q-jT+5gF{UY!IM_Ek!W-7}p#o3DaWH%H3#noN!NRwX5U78eBLYfnqo3zciMj~0t z?jR;yeu{+LPlghsi0a)+=_c1lVyoJ1=8ze1!S(>ffMQUwa3|eed#I_#SS)`j?}2gf=b4?fd(GzxXq$wrL!V;ZL5@Q&OZg}TOp{?px2EWszCIC6+$4iLBU zr=`i@F=j`(jpHG;1_>L#=)RgE##7F?ZRdWyBbv+L-VvXrbWWGyeInZ7QaWefz7d?S{~(bch7Nr}^>W_``C0S$ z&T@VXI;Zo;nxCZnD+T+9rEDzba)()FTUlz^+rDJLc(a`h8EaW+zln$bppo5$9~njBO>?xUt8lY`8Yv!q-H$j{^(pfTYg$UM5*+;Wj61`37UL zGN$52Tr>qZmXj(ayy?#l&uKW~PF%A&a^A<8smS2o@sRPWC8a0dcB1{U4_J=LF=M#( zWNIkEZ!y6$Q##8|3ItT3Ofg6gYcz0MbE+D+u~*Q*-MWGXF83Pq)v4N?*Q+@fT?XgY zt4^@4C#(0gMrfzjIyol8$C*9b$h_VtA3jhJN~I&~$h>hTw`@7B{bq(OLxQHICZMk5+a5+-DHt$A8&K{t#h_xPqU#JTvOl;zW4xJ} z(XLB-9{ZGYjF48q&4+iW#`7kD?wal1cwrSU~RlBw_XM)+bnRwND)o|EyY{tjQv4KcmU1w=kwxSy- z4>ZMRm1XcmM0UwUv%ajFY`#U8Aydu9_~qFHnV4rbp<7?CDGxSFNXW$gkm8AEbDkZ< z%8duxO!;|BxiigtUn&;x8c``NGTZT9#TYre!fgK+G$?X+EyGJse_EgF7EUr7@psr1 zR95&M<1wT8dvGeMEByAEZr0A=M$emXzGaPATK6&ZGfG!u*KRWo zsJKZfMhYjJ&G`uo`)$uuR1>?Vp@8y}yI1C#F%q0_Vh2u2#`~Bbijl2e?!%Nj3sHS6 zU1IXot7e=S8Bm1aNX{~o+hXt)cNwq(E90wIAuSS95|X;N>k%W5?_6(ws&`Y%@vR7B zF2fjUMgJq}sb6GIu>)~ujvcrq^UOxpf(3PP?b8qW;PsOCA#84m|2hWOJfrE1kwJ)2 znKnbuU4|UdmmNRfj6n(4i(sWVPdAQYxdO-GFyDpCOi>ME6O>*N#nHk^1VgSZHLE_? zE)#ra2W{H*)9X&~%v=u(3Sj{Xk)}&?%=_}}CO)o!pigBDiO-GcJ;X6`2Ju#9KrRiC zOI~>5U23+mZ~Mhvcj8-)__=^I=ciftnRqE*9gZwnxE|Z7><_3rWyuEkUQB-X zP2Mq5G8J`*E0AulO3dDc`Wt;5~LSUHmZ4254qQA%wO zwU9Z}%oyAp^ggD%Vr15Qn!4X>M^I*`tQfgcU^-jNkfN$o4Z!Nm^lscw79*!t(@MEs zjMc$$TTxY_@nRfc_%zML#}U)y+DsFj4?FJ!=EC~`Jr46Bv2IM`HL{_!*)qeHx-|=& zWol^ftkbr%c)HnAM$XfiwXgTuxIpvb#)Zgu_!bMJbHYjlS=O&LJ5+s=K1SvuE@l6E z)cY)b5}3}fhG4gRB0hR6{pXkx87iuSXP`cW)iJn*$u0shGIoa3jt-RW8L+BSt#XxV z?V#ue+Nwi2Jx12s;i$@D_rHf^j&gg*{jj}DeCX>D0)#B3vmJTUkfLe$C=;uX zo!*fDGN}MrXmg>79r8BYL4-K9hn*66t4*BSVz=u&Q(JF_jgcvui)3dp!rnkiW||FU z<7`vvbc&ISGtrq5?<`zqAuDE?I9E|T%S3$aVbtepkQ(4K6p(nc0wNDEGt8M~cE(Qu zgz&Q#Qurx>7=8}UGBYJG%e01;;36F~2^!jfVwqx)N+aO)HT5oUHoaRmn{ld?2AMKq zw%IUK&g3FN)BlB&9^WHN(c|3N>Qz2O@aCh8>zt;5qFXUX(F2rr4B?sC4B^CFhA=mm zA{?wXp?PTSZR?19yCIdV&=wyk0vTmsL%>PexZFXR=D=AgsLPpL~_ zPN+5u)o%z;#a#3KL}y}W&WLKAh$0fu(ibZ;7NgwtRu8r7hH5ZP)@(tJi*AY5V~$xL zKR4#U-(vOAPah-oFi>JITpMY7x*k=g)@Swx_72R-D12IZ%gMzkf*H7e4COZ~v9q=T zu0@mL*_9p&TCo#F{VPZr(Kg6`F3Qe@C8gry=z1HewgRvxe>GS=)DsalaoBuptm)E)&CKZe zX1LDr%m-txdh1mzRP-qp10^C{DOaUjImAkl`!mXjl;(@w0)ht_zw;6rFDU$x-wGvCb4~`OkSVNI1C=0=c$(bs6fn z5TwB*c}a?Fj*0EW_!M}sj6IT!#ICrl7Ll^ZzPwUjjx5%4JevKVLiNrE#FVFfb{yG@*J>vj*+KQd0ZDYih#Ty->$Z#4{8Utw6KIylNU3-@i20+dXcjj z;%*hpQS_*INFr0#EH*LJz_Br23xIAIhO=gp6Y@J3Yh}8h^0_MMRV-BbP<7ZLBdJFZ zOyNp)KI5^vdK$9?Vm=ipra(kY3T{x(NBxR#(dC<3C1ICre#qEW`D*NX#+CmfMtX=c z6=Q@P&({E*wRtX(C!m_k6oZPDWLLgHOH|izTHv~*F$T10Gi};j(%eeRQS>MWF3DVi z0c^jePL{gFUUnOK1Tq=y10Z@T3m}-QB3@gBoBi)X%I+}CSc<+5N~2Z4JYQVEJin)a zEmnY%g_NnoFs%gxeoDZBpMzk*&zS-<6AfKOfni!R_u<$x5?=js1vMCoZlHv!a`-fP zDI?zQCC^z3KXR1IqnN9_NwR*aenv%afR;r?2pxK_oY}(nua;tsx+)o6q`~rwOKG|D z`$8!6+DfV-Nrf4Zz2CcjK4AD(0x}Bna6)g=bJ+73qqy$KDN|}=2)9q!9y1eBe1=UUa zERwSvS9$hgN}`fm&a^{1O&w%*uOLK*Vda!MnLDw1rv4e3?|NGJ8lr+Fq)X z0mU-Kpt6Qe0?NeItn87@NLlh%f@(c?2w!e8!z{b1S&nKK=^EoVSJA6j2$YEWa(gv~ zDc5`$bR1q|S5;$t42j>zkR0|gean0dPe@>%4+5jtK;VuFARf0s0zdnlkiqW>2;t`< zrHa)vzhbFkK(S0Qs8|WqN^;m5`dqk%KCfRxpZ#l~!*yNhP`L;U_*n%G{A>XWe)f?k z8$2i;ZvIG)qDL`T(W_VpRF7_`N1nCxXx>_SRLJ=spG}9(@oUwiS>V9iBCz0R6?uwP zlV7n^F`!rmMD-lhmr5WeSn>IZwI(J$((GJ&dL1%JxNmxSZB^g&CiS{*XbAw??ZrAq z#hP`raQix1cwim4+%_G?EC&OAHh}{_yTOtb6Dh~mnbz}1&|gM{s|tOJ#fp9kvjqR3 z%I{NQsZs)A%Bc19b{r=y%9IjRtWaGmiru|*po!}*V}Er+}Mn4hlRxrs1ZkPMBci+5qaz6COb1O*g%6WZD2gy-oSVm zxDj6EQahx!Qfe`!CTxOKUyQmV*_t}fK@PvyA&8&e8#MukQq-$f7ApD_ixvHfrHTQd zCd2%V^nU+FdVdm1Uq^$2?qQe0SA80+5TML=mb+xRaGjwDmCg9-HJJi9-w+O zW)nSHy@?)e-9(S}Z(=xYI#d^d0Y4kSfuEgV!Os!$cvVxOqEE575)(M6VDzO_F#uGL z?y5(#i|NtgVtTZiqn9$94xLkr)uZ{~z*`Ym@UwwDLDf{L==wner07=6QS>P0I`P9k zNtPTh)-K*`q_U=@dOTq>?XKeKzgcu}m?jSP8Vp%mcRQaCXQRaJz14(118+#51@$yq>?s ztQt9=WHyEY%eT;g{S`F8t1KL$U$uSvfgE0?6)FuO8@iQ> zM{%asr<7u)*b}(kt!DVvMtnsL6E*c^%~s_7W!sSVr){@O%pq#P7LjeVZQ3@baNahk z^HUKdu2AA2CDy70Om=V6B%cToyyZd$Klv0YRT~0|Wr{(?N=4UgjVYjJtvyhKDFS-z zl)>BS^VscB;W6CC)husy1@J?h7qm{L_0P%KjnDpo4G?r4Y<-HJIt-ln&@L?`&|G3-&l zJ~Na8im-c!y?PMz+Y{{-JMGni8N2M&f?2!m)r7@+ z?A3;wdu_daPuP0PPhjS-Mb&KX_8pv#3XL`A95Acy@RYnGoXY$-GcgVQ@2RIuXRCA> za~i?TrTGunSWPJPWAs|Jf^f=j4}I+}NV(shjJ1b3*Y?>HvT|!*&Gm})`@=Kl?4a-4 zPm{!Z05{9Yox>ddvW3IM5h{(-SVK(W$CG45Ic)K2gcd6L6pLxygRxS)GprL=YC)lR zO3hl+{Tq%#RkE#HXIxI}0)*X|BA=Z+%~1Udi)39 z*@d1*R!!Va-c>y_JbFJCLPJB4E&CC)P|S`w#T=wYV~iy?cl~%4@W>IfxlB4{I-6mW zLKZ%o6B>veI~JPYlXEArelqYZblR&j_Uek9<}kauwi&s*y0)2i)UItjyP4l-?|}&c zn(#oa@%4y3y08d04yBz~^F8ukbBbG!njPY?j~KU~VKV_H9%JP6vCw>FXlZur1*Rt# z8>?mjw$n70rm1_h*v_W~Wv~D(f?{PjLhO0PvwO^zDF1uyl{Y!G7wZG@_^vi{irhoO z$`PcdOhgJrur|E6N9*2xd(8%T+kOn%U3aw@0`>AunYy>;G-2)$L`b+FRkYU(&-~>d zfv8)BoS{{}y?f2~?MbOsO!6Ehh87$y@3r$chCKT~^N?1jh0H^?mt{xTm5xv7ri&WL z%j?f*KEG9FFLR`INX<-3K>UqmKI0lk)j~wcJ~J&5A7)Ani!MD2?YYp75}^Lu#p~j} z(A21(mK7@9SA#xme~5mV^kSv^l^$M&S_F>JDpVnPN|h%-#6VGBf{K-ju6r7(if*7b zVZr^(R5vtol;TlJICe*uR`pWHmoga2<5eE4c9ohbp)8IvxD)xbPH2ja^F@`VHRjk# z4scc?G|O}FK#d8v{YOGk%C=|fImSgu{on&;iflfK2(FrYn{mL@qDbD^EQ~%is8}V! zb8W>3%-@G6+x8p)lV6!i6$6T86!?j*;O;mWTJ(HV0*CGD`1~=un%)qwtM4_(?dtr} zX?vY?%NetY_3p{2XIi5_coyrd>gqIfJq9w; z-Q(8rGMruVp9oil?Rr#G&z-JSOdGb%#CZ}d5!I>!)*rSb2X`c64KkB)boW%KpoAv1 z;k5sBXq4ERU)ttQYPnX%?FFnkTj7Hrsi^v}5Ol7VkFxh1(t)+Wp2J!T96XGO4Qgao zGBTAGv}rd;bKTeM2IO2HSKQFOnRcXla%%t4u&GUR*Rf1_m8DS8r;OUUI-=Fd53CG|mF6c+tD3P#l{WFHRy7A$)s!krAk5;FBAaKm z&9j?4Wy%v&tWf!|MpN&Tc zoK18NN`S^a+zsX3tMvJ&!o3uGQU&WCwQ_z#S%8wJ*?Z|p&HC%8)2qG}D*Dt3CFJ!2YhTa+$7IEi#8ffbX9+G;aTg=OZe6V zca(&gPM*bf^y(hKIVMh_8st+fR`jd-3>jT!rquTR82e3jT7;+N3d2be?moXu8l?66z* zd8|rdXiG<9_4%|>-D&{JB6$^p%-hZCnpNIE%&cpkW6`?X7fC>aLw*oqR zSIIZv})m(jHpuIR5TmMI1mD}fk|vq3#rVK(4zpu%j( z-!qhR4bb#bbOSM)i2ai_v#k4qiBVQYVlO*$$;Jy1%ONX5H}3*MH{v2f7r2PfZ3v>a zM)T{tz$pIV0*B`PFM{6_;y+COx#Yh^{_)BWTdqO?KldSlpYJG=t2TLwPASwEpJFjk zeVKWYz7$`iFMBBBw<*xD79{+XfQFxgB$ldhK(S0Qs8|V9b=Op#b&2YxUxI{dpi(%? zGvbo!n*<{CeNSpOsK^qEIf@>|Tp%vxlI54M!K5$hp-66XIdDl+@R+UOG-z1pI8QxZ zl`2&9DHc}E z2f%{y7)YqrNeQZ|N<~+;W(P&LVh*r55)ePOP2(@CO`gkY(yYtSkgJ?t#X?0N*>Ps0 z92;00%be@sGaDza*E+MY=#HM*c*%XG>dc09i(6u++u|kIDce{ouZ6`A*A_3d#b3Kt zEk3#u;vN^>t{Z*h>0c_j7%wcJ9~rpWi>fzka=5 z_iXR$zV5?wKhHDI%seGlo~Iq!S;b+>F%dZC$t-Nh`KiF}Pllkjs^7eD28 zvCDK3Ei-GGbZ;{+N_W{eP`tN0`v!__=0)%Emul2~b*S!ZQx|#J^bvP&ql?}C%x!Ha z6-(~%G(Z0NJ^$lPELD5V5+-8r@9{J{ck}`ZJxwWoRQ$M@+QW9Iy$ zZ;WXue|0g{kvC$&YEQG;Gyl>0IQ4R;yV)J=4R@OEZFGtGf6~2;EOr0y-lpB#$eV7a zdmCM5{vUU5)8^KRl$+bQdz*aM{6G1=+33OYTLAg2jQbCmz5bDjSGl((8TUuE+{$ln zvaj8DQU`dAmU(5ov-v>nL$BX=zCIw&ljZGt?cJ(vro+f;)8TaYHtOD%%B_>vO`f*6 zk0>@Aluu}$*0@QRzYZRH;ws;j^3Or8=<=xA{E5A5?{3a}kvi^cgvRIm&d1cOio`D3 zFK=vI8>ti9Sm&5y!>NcoB#_53&f^E&2mH>x5~x$-VT1GTK^}RT_8%V1J~lL3b5JH8 zcPCrs-X_eHwu^KYci zAou+eFPjHsGu^~eH*w(DJ|k|Ezo;&}r;a=zyY=c(?k_i;P3EQZaW_Lg1TJ}x{7qQ? zer`SxZnddNyFFC7w;A`g+RZ)uk;J+~oT>NLIrJ&7@=3H|_j8_|2hE29Mk?G%ySH)o zHtpUf*O(2E4-k~+NB7o|XLNF(?0($fT9coYTlvJdP|2ZB82nC>SSkSE-3 z?k8@pdz*G|BkN2goOXYtQ>GKM?($k;=h>+;%GjYop z&288zburhyjk~wcySHU>YyLR#!Zi6%wwL9tV)720xFnV|9mQ(0juKR1<2-O6(J z_8s>&>E5oATc`Gf+vH^fe@v@0Xy91q2eUyHv;F2(MC;s~lzW?YZ!_*~WR975)V+E5Q?+qB%u*TFj^1ZQH_3~TDOO71ps9KH_ld1}R0P_V?#;4-sz3QGgHC;Ut2cpI@oyQ17yS0flt-ladLyY1P4#3=T7hroVrnLmSZ6SXH0!JL0+Bd z4v-qBmKT($=c(n}lU@5pHgNL`>ZmjL6db%y>o3OXS?XRm(IU^0PZ4sQK?FRNd2WaD zt|51T^l$2iu=jWMlQ@Xy;s|~LC-JK|jhAKH@5~Fk117T_?9>6);5godJ^yOGx2{ew z;52mG_r}QvYWWT|cLOrVs-MBp*^!xGvEgGxj2Mx!cqJH4x}_+4i$F=B6x8& zAHRdcDFUAo$l!0Vf1`HzOSS{N7pHK820EZ;ljb+Y0emtJ;r8mVlOS*r2^oAP_I;)u z_Rn^JN8kjWlx>gi$DYr%{Y>n|&$*WK&qp9Zf**f?1NaLZ#XE2c|AjqY=ztCLbVUNV z8II#qjm`O&CU6c3zRlV}cO1gk;shRyGk7fae5vj4!2uk_QT(hpZ0^ZY1YXT{P^lff zo9zIv%XWaj#tHljPU3wyg_|_g6-nbO8p{2znLviXBodr0I)Ue~2XDY${3G_^f3P37 zYs4SqaY9#yLOdv$yVG<(vWgNvn;5aU9tnCvxfRp$EoWdKh_bc7NqryjM2R;G; z?8lGe0A7QG_+K2tR~)JRMQ|C8;)6Jj!^4hZ3kb}`N&FE`;VPWQ&5zdh8SKZ-R^7re z*n{U{uR83!Odv?Y3LL`gaRPsjGx!he`&tLA=g|p;@G&@vUQaU5?omOHD+)W&-KcM3ERmw=oMPHS9spZYdjI8FTw_TdfK zkDX(*zW{E7i}8(k`n1M+{f`rflkh2a?$-f#<5D@F%mgoNq63uUF*t=6;WYjlXK#V;|m*D<0MSrt+}aEUELDx(hDC!*LJ%C@u*TSV~|r{t8dW zwVLYyrTBDQh6m#$o`X~PLtKUT;c#A5XM9$n4iLaMW9MpDJFyQR zd93yynyLAhspZAA1a2cCg&)F|_zmpDw1aK9HSYW$9iSLb#uc~>XYe}La{iT%S1eRN zgq=m|Rk#Q{Ew%puzNDqO{>y+xFKGkwB-D61o@gu&B8p$t`gu5m%dqb?txsV;uEa_F z6ZR%Fzn(X&1NaDdyy{-~EF8q=;w0{aQ}||__qq;nC-&l(a7Vl$Odvqu01n|(Pt*zd zmuiRIaS#V^kGHh`797E2aT-5`hb+_l@cRVf1lDC6;62zW*9Hf01Rr~nPB4nk!Et;I zPU3OczryV=?8FEpG~mdW0=oM=jeo`&-27y%KZv{H;M=SKj^l@M8ZW{-T+9CdkU*<< zw1Z8!E&d*#i_JGInS;`g58@y`u@xKeuJ(5^4!*Ar;CTHb^!h)5fHPhKj^m8VHrS)S z2WN&J>DJH1lZUGl_)h#Gj^HnGyhQVV$~u0eUjO%G1C^Rk=M>!n?-uouID(twG(Hjg zzta46#_~|y*{be_tMPTX``1V6^}mEb{A+D+2VRFC#jU^5`uX@iycDPL$JqO==6{8Q z_&5Cdw_#0iPSqLvzEdBAAHk>M-MAA@rZvA8e*Sy)0Nin#dMqA~r{PxNA2gwqK=`RW|AZ4E7`jRU3$vE8PXAPW3pbx$r&%`(3 zy#H!`2;YgP;tZDW=W!<#_(k(y#Ov@o>ag=Gfej=S{;CbO;mLRpuEcq*b%3JZH2*kU ziqFE;xGOIHUGw|6mh-=yz;F@@cWT1zxDTF@bw=ysxabeHd{d9R#YMZ+tML%L1*h<@ z#`60o$M>f;I7mVi7o4sgX7DMvVz=fO;nsW9SKyiWW?Y5G;K*N^f1fyP4w{bD8i|+Fi2$7|YWq?_l*Q*oV8}04~8H zJP}85DUPbc&I$r?628U>?6lPhq;M;o!98(exK3~&PU2}ejbFgt5|c0ce+2=b8*rTO zaSwa|NAPK9>4f6=N}R;~u`@y^6v1BnJoX#Q_|9qqK@zHP1UG8OfcPj}I!*^T0ekOO zpJ^;lPy_d=JK<|^PaG7xzyH6Uz(5j);X!yJ9*pldmM5qsxD>CzFX4Nq=>*=vRd_wF zfB(^X{ZA9vbH6s&g)e(RUDu}rMsYK|4Y$Vi9@PBvaUA!;&O=&%GcI~4tbq`L6bVyt z$-`Pd3s>M5vGa)5zk^Hhdi)_y<3I2&d}4Tpb|9~&a+mZP+zj7?TjRy}eEcQug^zwz z`@0!;!Xcc*Q*n55lr1FiE`AZ8`#4*Oi|~45`6WUc@54?^^Sx(Fy*xz^$}jxPgW5B2 z2%oDCI}rk1Nr>XXIF6^|1bzu8@j9Hsl{k&J;SB!W_&?_Sdq!v2u!9MR=c#?h@*LiW zM`J&J9~a}M9W_6QPs1hnW*owg8;50p2!RhsD8+m6Tzt(rIzSu`!)16JPT+{KJm;@? zPB$oq*Wo4Nut`V}SWm*|_;=%$^7Q-&kGl^G&Q<&I*|-P3760$u z4o)%&@`O{qnaJJZHpWfln7Bok@NDBe$MN!9?|eFp%+U_JU!V0X^}sNJ zIDwH_(_j+z(c!(u@@tiJo=)(gtl4Ex;3_=FST^8Bt`#riAP$!kC?-&0Y);q*^e>xV z;KcpvA8`o(f&Jtkz&`357HNO+D*e;#DLApmb=c`bAW1@h9P#LY6L4yPI-Kp`RrL%U zU7~&=+x{B$n>h4|dUduwZY=x%GXm+9CTzt){uG0CqClKTn4;yjlYHjdc*7fuy6uYo@ zvbs)ZT@l}6olp}Tls}1@lc@tvROqX0uE5doAPtldh;LO-!^yw(Wi<=1XOH@Q?7LZA znYFyM!3_8-PMoVRI%#-;PB7C=`#%wf;|H`s7Xp!in$REnTh(zp9EAg&w8JN}^=RcbzOOlY>ryXp>kvX;8dh-ct=AO~hRqwlNcTp$%=!&(# zfz9gkv6ojq^}^{hbizZ7&Es$HtJ>fW5)${QpTNnDI-%#YW(hyQ-lMd`tvEVRSMVR4 z*{|)JT%;3Dwbl*lC=Qz~N;lVp-Xw%7wS!x*ueT2HC{DCd$Fun?=@RUf7yL-K&L->( z)&Vm(E+3L^>W}iv{jZroq*xQq^y>@*6Exv+?Cqcf4#%nQb(h_d^{JXa14oY04SEjy z&erzJaPSrN7yhsg82m#U?8tV&S#c0MA837xt}LOv+Qe)@d+cec_Y;?6KdrRU6!g9eJIc^y@6hHh4;x_(SZI-x-_wZ*WT9+G1?(V9bQPj!v-7#p+NK zb#v^0({vY!{`nY%;n{}z~(+jbunNH}MtUpl?!@;%cJ8)u+I*L>B$vI|)UdPTcvchuwIiC^m zUZY#IE8E~ob^awxaE)$(d`hCb#EE9Q+4^p6O0_8yRaqp$Ri%JBAv0*UtX+k0yh%KT%*1V``YSR zFc14b()zb>e2dnv$LYt|h1vS3x@ixcQ0fQGZ-)b6?x`*z;NjvE#Gy~MgD`fOKq>Yu z)%xW){f7E0oQmjx`?KvgX??SvI-zjzY7KND;9ss=I0&aN)g|1DqvxsXU9KG@uHr1g zk@ec&wKx#a{w84G8|ry#`TPGt4J1j(Ox6y5#Qwcnzdu`lrq(x?drWgjlVOWG;Phu& ze-#dXt{#Cy?`wY#xR&!T&OtOUo4^v6qR5(72aQ$XTERo`VC8 zwf-9HKSO<+vAO>Hx#yZrLg;#3(hQDL-}p+lm&rb`uHS9=ebAVLuIyy;=wKZ`BTpaFmDL*W)C+Y&?#5wExF(f(?HCYOeoL z0-LfO9H|q?U{4#}LQjBQ@SRTJY@BAruERe0T4}R_lW>yub8ra19nL14rUQJ7Jv`ZT zuF(Ml9CWR4RK7;r4A29oS&@-Ak=FVracG@x&`O--;uZddKwMr?W;)!9gFFJt>&-sK z&2Z#o%|8*R+UUipEl!-J?t+7I4Ke*+hofHimL2@U~$koX-cm$_|dOd$O z+W{}9UxGv1HUDGm`ANMM`!81S!vWW_|2@~TPtVpZYMu2wUBXT{A)gUqCe#c2s?^0e zDsSL7^+U4t^|ik-*n5gPfee{bOnn|s z7OF4B-go8wFJ=M*2>3edjK|<~RDCawRH$Rw4z_Fim$LQp=`^OlcX1%CPT}Z;2!L` zRqLO|-cjl|vAa4@b=OM?Q!Z_b$1+U zp}VXP_C2E$8iJD->K0GNP9tsqBu@OS6Mn_moPW`;HSjSBzJ=Q0E1cn1D8JxzZJoeg z9N?^I&{wxGbEMWc#R1N$lW~HxpuO0={s-Bji%3XuN7Ebo{o3JBoE)YH*%X}MS1ylb z>qlt)3)uUXI*EO!=mvk?m+OD(UESinBzQ(@gA?RfF!x-6%XFW%$H`vmOK|!Mbzkfo zr~{0{aqbBp#DTYUgBJDU`k(qn2YiPF|D`&i6pp;lL4`x3G{1g-mYiQeG{>o?IzVe2 z+N%@ljsx7WT^lBlrh`E^$#2ERU{5FQ@O~VstrM7^&F8dx6DKV(3l5eRJ74llsIOLU8`!Olh6U=*InZ$2M&9dZ1;ckET_OK#^O9gh5=-icRja2<9| zy-8ox_y2At zAOl?WUtPl4cvV^_^bHNt(m>MWQ$n)&THoGSI`mV219lc_{@vsc%V_><_>C%l|Nj+% zZa-;4gF!k#o4?dujb(r&0|c=bo8O|l_Y*(r2E2-sTeSTKT$d-Wf8%>kmDgX_k}dj= zT*XWWm&)7LjQzOLY3kv4Z7cPBTz!oCQ@pN;dJmpSfB8eS|Lx7RzAb(t+(HAr2&|yP zTU|$-!t=F$22ONRzd?TWiRwz~H&Oo&^f59;y?FoU87E!#6h9z%2v#!{GC{6g$4Md#8ZR(>+bin`0I}y!* zMXn>xChCLKpBd2lhjD6%dMO@sx%w+Sj1_1;Li#t?|6*ozfeF--C1!xWIFAWVG;Shq z&l#_$)x)?5$8it*CN9QnaS8rT95x-65!g*aIc|814p@O(;7Z&9SK%JGbb?OsCR~lj z;`l_Zf8Z8#{g(+OCuv|V2`T(KPUF=$gSX;poWaGDwZD3|>IRhH|KKv*?$$6%Iz=0F zC&6>Ox-TxiLp=%y@l-q+m*T{on*Rz;;ty~Ne;Fo_Ch#lH;94Vff@%Id-yHi`^0wHI zFUA4f7YDI?^{l(%Asn7YU^0QH@pSw;F2x_?1pWpm@t-(_8{WnS;FdU}4m;-%s3zeG zoX0^k5EtS}xCGC@6?g$I;~-m(Q+NZeb}jq=M*emFq6KABSf*C#qRU} zO9%w+(xbLN4&pI5gde~W{0xrbB{+`P-~|2-C-36*e|rd|NN615pus2N3_cG#ck6_& z#D#b;F5|4Z9arMVan(3p|5rx9%R#sjNAPAG#lPV=t~-WfgoE%n?7?SaFYbw3hY17; zMDQd$9Y2aI@eA0`QT-0?fj`5g_`liqxYk&maJUCYc_D!^+z#h)R9}h<@lCij9*-yE zhj9u&hb!@Nb=XN0NRd#5ci;^E7dxC4O~>gL6yY;*4}38$#{F;!9^+chzsUrqlaRvC z;!6A$uEw9@JWk8)IL@_TKQ88=Ef}v8F2Sc8hvn)wnSh^!>G%d*iscih+&hvoJPnuQ zxwrx^!IgL|uEO7o-Rplffoc-+IE{{;paWKO+MI?Rj{1vm9=-v4@M!GkpnU*G@v{@S z{+AJ0MnVE_z)8FV2RMok;$qx#Ma4BAi zz1h2F?87^;!!gldl1?xWw+s^q5I7G{$5-PrJRFzfZ2JlvqrMWqhO6+$xH{YZI|6xJ z9R9|I_~^+x~v!_yIgw9d@22Fr9>DxD;=|4hQKDoQDr$KW;un zC)@+K!xbFFJ#ff9reyyQB;asR+>Sl?N$kaou@8TQ6Zl(Pj{nAK?73YhSY+;L&Gr9u z0s-!NyWwKo9|!SxT!Lrd5MGEUp2E)Ha6G)Vgi#%D8WzQ$#^lIj#uMSoW^B%A1=pD?$HTW;IqW;^}mup4-%^I zAY6^_#1RgvS=h&tzm7eu&^la*cVI8BeJ|Jl2y^m4;RxuynsNEzz4Vre~A3#=I)MxwHp7$IMW^BE zxCqB--!p4>JlX&K2_%`oZ8(F&I7Ek!;|N}WqxemnUHTL2OIP`}84QLMb$p`(J39Q1A1L`9m*8ao3eHyr!K$L_j zI4++g=x#x_9?!=?Ca@eQt2F;h?D?;HXSSa9^&fF3AfNw7-~bv6%^>^Up zBJJ-foOnz9nz1?mqD#&Y;ORKT3U+fHaUxCiPq}yE z^h!NO-WJQ>|07OtsRpV@h%Zq$jhX@EFCxp;ci?2X`g`o5!w!#YzC-=}IKD#jw_qRn zXFg%B|1yAgnI>HM1OvXM9)*4HsAD+zw)$NhrTq`sPy2>XGNE@hza37!r|$Knx&E6i zBruSKI4iO>+u%)YaP3UZ58_X7k`=4Qe%iNuivFp;6sOi}e=mgzcxljQmNxL=y*NV$ zkH@q=LjB35>NNFlVlVrA3r;oBW8eUef1ncxcbaWFaGV19ut#&A_ragPs(-bbif4YT zUXNRyr1{O}X#47G)E#iY37Q|kUGQyq-V1Ji*qKY9iF~}JnLs)A(cm-OW3hJl2k!T> z`sk;10s-6>M_Q4AA>Wf`FPMiJO-vo8o>9br1e1e4O`o>`A z9sG5^*8hm_V*(8p=oXjb)3L9)=HHD^qQ6h@T2|-}*I{`Kcab(|{Jd_#@ptJKoZ?zu z6+?YjJo9ADA7w00-z&!IbG^B=FB_-r%W>lq$)(#uKpj&kFh3eC? z?xMZ~PrpnZ#J{ov_uvr6L>w2LrTHuIS$Kokef?jH)|&7g3BHJ)hP4*zgla#g?ts_L zQeTX_%urwJT3%7{mG0xg_^mDKaX4?Qdg?-+|EG9iIrfnMIj*37C-&h+ zWjbDf{8PdNf&~0Hiu>bYCO8p$*uuxLp8*zU%@%%)dr-d(=h1)o0D%Yt6uzi4jN=Y? zI=%{*;So5B@52fFEH1?>aJf3{d_f@17X5+q*n%Tp(g_t(e>!eW{Utcg77oM-d>1Zd zi)Z69*RucLBoL*+MqJ1Mzu^id(C}rQke?1u!5Qki;?~sPgnf7lj+pUeeCH_w)0x2Q z*#>m@DNeG4KjSp6w^%1sfltI$_(Gh*eQ`$YKL4LUppt~gaW#GgPsVGq^=#2kxP%p} z^NLO=j}7qRBJwZ9#bKWR_ajhF!lY~m3^)^)Q2%SWW^4s z!%m36@vrNQs%X#|XK+7U#DG(91@*IW3NObF`CE)-f^jCe*R7ZHFF?YvZ|DR{m{2D? z8TZBBsd}xLf_->4_T%L^fVbcv-fL`r{~sc7>=K>@Y2~@I=FTjNyWH;ebR_tzEhM&ddIGJtF1i#0vaqTy`{udEA z`AwZb58NFWD%AND{aV zSKtx2HCy;F_Tg7>5#E5KOn4Wrm(QsTM8{l65XAlUoMN4oo-hu;k zcrcrfPkmPhoKF6gIKq-o$kyX$vh@+J|Emd1rh}b0gbUx(0a6Uu6$e;>TW|&SGjS1j z-7n%&yb@R4&5XVvP)WkCIF0M9lnKaFwPaeKyEVme+!{x=X?^FctJDGPl`jyHZk(Z6 zhridr9oYaKJc$$EYW>2j`BI6ua2l_{5xfO^QriA!oZ|a}_Tq4mEohk3EsT7p4UWfU z4fVO*S=fjD`1gFR?~OS4I#DxE+$>#NnJuEX;3y8+r@1qqn}`c|urt|Lz9pf>mk zNAW*+Sfx(z_z!eKb??#@xd>miLp=nSk5fNsY|g)TKGncVHzDE_(cxCyho{fgbdda7 z^T&Ls12$Pthpr<|UGkSwzk)Y5ZKnQF`u|hxKL78&Ksy+;N;{lfs-A|^J9P_};MTj; z+gwMS=K3!yP)VS0wN9Wnzw7OY=kdGS zYw%*~C*XVe#p7K3#9W==Ufk2>zlFVcTQ+}})>mWaFLnMpoj?S)!Es|5-|0>uNy0!J#CK;q*r^@9h(mZ|HXrZ9 z>EAW~xKDKg8GHebO1^vj?@Pc>2X|sGo{xQa6%ODXIK=>s*Xw{D>f2%m_rdX<^8H(K z_BoRX1T#A0=Wqb8#eV!JPSRnM4LV>7pMyi>_r_j40Xvzn4mgK^hlF>r52ta00qUk$ zQhX{-<12B5_8}Zk>z{64&(?>((!eePUY78PjVvKP6-Tev2|tBXcdPf~_$2k?nbwE>pX&q$5GZ(78!W_`by~j#r*6>t3qEHBiq+5HKp*vAIL!nGf1&vi z&YH<;dHzpe1_@F8CQjk+a2z+<%mg^?PQodCK8~)}4H%2Z57!BDzQpsku;IaWOz|3iHWw|G$NyME2z|F=)oz)}gAi$@@$ zZvKtBJV*n)YqEMFZuGdi_P3fJ+@>3FGhY9w*1vm&WFT}%Q>c8>go76*o(DpOS)Jg2;ENK@F1SKhz@ap)AWTO zH9zx(uE?mL@J4m*9qN1e6HFXe{GeOhwo2>cJVp$EKp;wk;-59)M)uiiTy~>Q=(7K6 zy??HH8NT2db=P0C-pdv)$N$j&>|eG1Om*0Kfk4H4?V!PLn(*&)>Z#a6hri+T>9F{B z&42z5&3^;uanQEhsr9SkT7L&_cBirI|Lp{P4A3K^4LZ`nD>%7FJNO=_aN!@CAIH6M z1V4hkT+WwZpRtVZd_%xPLcLwuVG5ssJ%8&2&cRXYuf!P!EWv3!6$i*)j6-7g`d>jH z$X(|**#>wQcDNWd_){mC`AsKuJPt9z^KqPqbj3J8{UjXyo9q890vQtC%qDR8ti=8y zI^c-iI)TVw^#|BDOzqvH_369RA)FjfKK2aa`d{xamVA&V48+b*8sOkSb%Vb(-_LWw z0vuqATjL=09kZtXavTqnFpxlqgh|-Pl265c{1^`4r?c(xA{@m_a00KyVIKo-CJ@2D zV?VB6ty`RA7q!H3j)8VKg}Y|!$?uOH`kSDZ*ZjKh*h>eiaf$)H$3grL z_T$36x&aB?(Y0LvJ#-i#Awt4PoW|2}kke{GHh;Gs6Dx5BZ^a(+_h29Oo_#VQbN!DK zIKu?Qei~eigVf)MQ+NUnksrlT>K9{&`U>pB+r;kc{{l2PKtd81?$-$gXwU(NsJ|Mg z@vYcP{xs~TK8_Rk?fqQ;eKT|k|0KcFPks77GJ)pu5{+5c3U)?$mM;_4n8F(7` z;W;LdC%@VJO20B`k#QY42>G(vw{Q|~%euC9c$r*8O}@8|dNz)bzZ}Q$x7e@k!_IZJ zv;&6@@4#_7JfgPN2kD?44pD!ZvFws4E4bLTo8JL1R~e*Zs!z#}9)WNhvkc>ppKNAWxy z!HaMRCyZOj_k2C41HOYx7N|Gk)(iOk{|^LwB>aWFxRHE@o9QrskH^LMTpYx`a0m~_ z5quw>93~JaP=sH{)A7f+2i}bR_;=%49$9Stn6BE5#V5%@%omw7Hf|z+{T`+((8Sm) z_y5oH)hox5@WOCSXhp(g>f7S+_*`QdAj$w28JnGbnLZJ@jQmH)zZy@+*Bc)%=U;N3 z4tS#p$P696Mr*jSEV+jUBaLOiAOnuY0X*6Gc)25sYkzkdOMgcUQBTDS*}#Wg%lVf_ z!V@HWV3tVrM%U6D1|*HgXG*!=xJ$ON{UfNXJwqjtNo z?4!A?z^^!t|1_5N^BM3U?c*F1jgQd&s_}8S5c|aL-~T-Xx|5KH`(g**W-J3ncq7qW zxQx^Gaa_$kV;raPtJqKbcaD(zKht3)9ehH9kA$r_LH%#I9M?KhClnz6Xk3C@Vh5jr zi|_?U%JUyHfe?YqNT^_d{AdB#C7wzC**H#p zcN{4*6O!}4H-RV(h8W9|C7Hk&>Z_=a;52>^d)dOrjpe-eKCb8a0-T+}Di1UyV&0(O|d!#GI? zb8v|KB{)d_2ROk5x8MN&4ac9XufPAVm9JZrX2wmihXGH)ksi7QZH;9YE#jW92rs~0 zjpbV5B*^;3-N$^dosQE!-h5BU#c2R}*s zGTP6^Gs6VpCLmiBVGCX|mZRKD{Ts%zMM*j=r-KUWSJFO8`;T!3r;JaOXHw)>8q2{J zjy|JHnx;XV20M&pi`UU0Lw$(Lb2avG@v2oI`LY2)>KhqL`&G0rz~#8PI_xYZa6Ab~ zd?HTZQ}JSa27U+ojAa7PvjLrOD<*UiJ{4bLe3G1h89E4%;AM;Y8_O0%sUMWB=kdWv zW7$PM>c<(&E{d>=?!X~DmG)zp;A6(-`d`kBVkRK_B*9VsEFC21U;!O?$bZFHCU_?k zcoRqPySNmu!uR2IV)yU=-j@2HagzjO{5lT*c3M8ld}B$C`co`-Rv(#d-&@@<>tU8B z9wYaE(&3?m$2FmTw!w0>Jm1xS-&(HQ#GTNg{C1WvSIg5|{Wr|=v?g->mkAt7c)?2e z$nsB?_gJpiG`GKImRnoCAZ!J$u{_%H%a*GwH)@tUfwq<}wj2&wfqN`JW;t&8Rm<;K z{@C&_x$frFYo0sfmX_OCKF6{@wq;g+%kT|Wc#`EgmRDF_Yk7<1pDpjtwS13dVeWRE zX!&f*msl>gJks)=mS^Nx?#Jfm1RUor%d0GZZh430U6v16Zggyo4K|Ns3Tp<$r&#V_ zxr^ogmM2)AWqDZ*oAI5h8Uf?e{!?Ry;_;T>wfvW5|8cqPZ@2u!VY}lyiw+0O_|9_6 zpIH9Qa{c3T2W)A%gXPN(S;u!~916JOJ8LcPw%qW9+yRR%kG1@~QGc@>0v6xIR3gy{^ND181IOCUDq;EYEiB9GdXUR{bWce!Erw ztL5Fs;Ti+(w-Rcf?5@ZW*%fM~K6KST+j3|1p$T4R)epAnCyK*0ui`VTgn5>iT3%!M zTg$sF*J_o!i;rqm%YAo!UM%!Xeqq?B)rYpPi{*gj!Ino^o@{xlajzp0i}MgP5Ld4uJzE&pk`&Z+J$bo+DffRCvWFhd+~xs7GN<*O{;WO=yd35R{D zyGi#S4!C~S@?y*HTiy_xJztLHU#;+7%e7BCyy0dXkL4DY&$N8L<;!zj+qogu>AC!t zwMNDInjh0*Q_PRKvDxOwlGyU+^4rzg9Q)a5XRP6Z{FeE7`mHvHc5TyGCkfX&GuGGq zxHvY&{OA{(ZGMc2EngrV%#8hP^h&JZ^RmgStqwl3oVNUjdAa3JEPrEpSB_71oCa-jx8QioZDT!OkcH}Lh5K0^Vfl8;4_kiP@++2C z7OtjOF@gX#U_p&^|@;J*6TApY5m0WjoKFSR^ zUs?X$^1qgw$NIh~k7wGSmAiReEMIB4f9%B<^Dhd|u)^~!zh*gUd6VVsmjASz*Dhz8 zVdsS0fYa7;SIgI19%6a2<%ccL$+29|7v%)x7Ydd?vi!N_A1v>(T+5fU#m;dxZ5~gY zQ!^mG-0}d+6D&`&{FLQ{<&SFEeLVA3jexn%@3j24<+|;2x2TEb7M4%9+~KhG@l2P) z0W-eS!}7J3Z?aqxdvUQmItW|gM`Bwf-2N3Syu$J-%Nt{-zan=GRaSV9<+^8Et6=$L z%N;CVXt_tO&vts}2ArELkF-1`wpZqT;!G?2yyYdaPOr-5t+&EkEN`>?i{;&x4_I#0 zA$NJ2=Q`}P$_+T}EqAeeh2_4MhglwP`Q99NaGb|;0`j5}%S$X*#2$Q29!Y)|d(r%; zj;%32@?$@nA8ld{6SAlmcg)@BfaQUfZ?QbZ@&lGkM zD0c-LTRy?^X@~8O@3cQ0FylKHSng%HpXFODPq7@e{Olp?_|9900`BAJK0E}ef5TM4r*msx&0cKR~80&a@+ zG(UchjgTL;ozAg`mgTpo)jam1Fnm_$+~eQ^%a>a&wmib}9hM)lJTKQ_=e68`leE0i z@^_Yhv%Jr8-UYcE*fhs-be)_Nke5?f?qc~0%Y7{mvpnANJvD8P?#F5d#B(jbWON4@ zA~xIn_$;>E{P;Dt#r&uj+iQNbh#mX3{5UVxNq&R_T@DXw#<i*w zljVIiZH}{} z{kdD%%5o>my)55sd5q<$mS@%QP_xfptPwDN+wywL+bsWOxlz~L4LUKl{9Spn*~JQ9 z75iDj;h|P|g5{~|+Gb|v-&2-fu>7XwwU)onb=axS4LC=4bEki3z?POfTE5is4VH&n z4&_*mfO~TS@-)lxe9Ny{UT%54CZ@H`GfaT$qZ@2t#4V&?u zc{KvYuUKAbdA;RrmjAR|w|njeHa%>2eCOoD0W-eSL4D|tkew`dw|u4L>nz`7dFUbQ z_|9#I0`BO%{+%kn7EVXD=lAZxu4}hv7bMZ zvw5NwzT5KsmLIh|%kn(Si!3K{9d?%I2AmHpueH3%@>iC>v;3pw-*PN_b5Bk{R`c@Q zJ=w@|f#v^LKH2h_mOIw8Ihro484zD;`D)93Ef2DMty~i3$eqbNDGQ`5lJcUIm!-TW zWr>ufQr?sjULiLhO8H32S}EJ4Y?t!0l>bWkP0Alq_DcCj$^j{j8_ApVq#P;b7%5Gq zG?#L`l#`^KETxr{Q>2_PrI!@>{0B!qzsI>n%B@mHNeRijSe>!*ANjBcCnDuuDbu7p zEJZ$<#)(OJR*HN8h$9~|;w+FN9}wfnXR0a{!eEIKlQsff}ok}TN zr1X)w^p(K2nNf?|z!!{B-$TNN1pw zK~lbu_M4?#FaMVhtaRiQthDsS0JMd}#f>WN9|IU(OV^U^InIq+CDRZSfBW0eH z`O9AaQl6BD^W?>l&x$ulm!C_yL8e|TrJoeDnes2PSzaRgrIZ6w-(HqMzNEp~CO_pH zJe+P)>PWn!x|SFIC$ux%n6}Twq4PPagz@ZA>)r9U1Li{HOCk~x<AZ+aM~oXgVHDHtG-BMi(9ppn z2C^j5eo~FaR32JQ!y4=7(%mt)=$@16!&}?SRM%M1q7mV7lWJ_Qsjji8E)TD&VU1-q ztg$*W?MXEjwpY>k(K%fUYh<^VLu&Z=lCjH%f0sX}ehbsr;e*1RW{sT^d%dvWnBKk2 zYRfTJb8XF_Ig5L?1P-sRy99E8O**`~X8*`mxa+%Y|Bv~999J(iap2f-%l=!dpi%wU zn#Kj|mhEa#@I{^2Q%4kheAHFNUB?a|><&0IT#O}V9Sm;t6)a0 zWg~qBXVq@n>5`E{Ck`AxV)UrMxq}DZHgxPjnRM*Rzw?ia)$33&WZA3^1+|}vy}q%a zF!n>L;KF6yHx^uz-*7-^?C9a6Bz@Tv+X_ysU2o{9i6h1?du@BcFZJt>8$WRDcqi6> U_0cCRTlP!AwDaV>#E$cS0Iu> +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. +Preamble -For details, see http://tetgen.org +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. -============================================================================== +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains +free software for all its users. -TetGen -A Quality Tetrahedral Mesh Generator and 3D Delaunay Triangulator -Version 1.5 (Released on February 21, 2012). +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. -Copyright 2002 -- 2012 +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. -Hang Si -Research Group of Numerical Mathematics and Scientific Computing -Weierstrass Institute for Applied Analysis and Stochastics -Mohrenstr. 39 -10117 Berlin, Germany +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come +about. The GNU General Public License permits making a modified +version and letting the public access it on a server without ever +releasing its source code to the public. -Phone: +49 (0) 30-20372-446 Fax: +49 (0) 30-2044975 -EMail: -Web Site: http://www.wias-berlin.de/~si +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. -Distribution of modified versions of this code is permissible UNDER -THE CONDITION THAT THIS CODE AND ANY MODIFICATIONS MADE TO IT IN THE -SAME SOURCE FILES tetgen.h AND tetgen.cxx REMAIN UNDER COPYRIGHT OF -THE ORIGINAL AUTHOR, BOTH SOURCE AND OBJECT CODE ARE MADE FREELY -AVAILABLE WITHOUT CHARGE, AND CLEAR NOTICE IS GIVEN OF THE -MODIFICATIONS. +The precise terms and conditions for copying, distribution and +modification follow. -Distribution of this code for any commercial purpose is permissible -ONLY BY DIRECT ARRANGEMENT WITH THE COPYRIGHT OWNER. +TERMS AND CONDITIONS -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +0. Definitions. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +"This License" refers to version 3 of the GNU Affero General Public +License. -============================================================================== \ No newline at end of file +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in +force. You may convey covered works to others for the sole purpose of +having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +a) The work must carry prominent notices stating that you modified it, +and giving a relevant date. b) The work must carry prominent notices +stating that it is released under this License and any conditions +added under section 7. This requirement modifies the requirement in +section 4 to "keep intact all notices". c) You must license the +entire work, as a whole, under this License to anyone who comes into +possession of a copy. This License will therefore apply, along with +any applicable section 7 additional terms, to the whole of the work, +and all its parts, regardless of how they are packaged. This License +gives no permission to license the work in any other way, but it does +not invalidate such permission if you have separately received it. d) +If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your work +need not make them do so. A compilation of a covered work with other +separate and independent works, which are not by their nature +extensions of the covered work, and which are not combined with it +such as to form a larger program, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the compilation and +its resulting copyright are not used to limit the access or legal +rights of the compilation's users beyond what the individual works +permit. Inclusion of a covered work in an aggregate does not cause +this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +a) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium customarily +used for software interchange. b) Convey the object code in, or +embodied in, a physical product (including a physical distribution +medium), accompanied by a written offer, valid for at least three +years and valid for as long as you offer spare parts or customer +support for that product model, to give anyone who possesses the +object code either (1) a copy of the Corresponding Source for all the +software in the product that is covered by this License, on a durable +physical medium customarily used for software interchange, for a price +no more than your reasonable cost of physically performing this +conveying of source, or (2) access to copy the Corresponding Source +from a network server at no charge. c) Convey individual copies of +the object code with a copy of the written offer to provide the +Corresponding Source. This alternative is allowed only occasionally +and noncommercially, and only if you received the object code with +such an offer, in accord with subsection 6b. d) Convey the object +code by offering access from a designated place (gratis or for a +charge), and offer equivalent access to the Corresponding Source in +the same way through the same place at no further charge. You need not +require recipients to copy the Corresponding Source along with the +object code. If the place to copy the object code is a network server, +the Corresponding Source may be on a different server (operated by you +or a third party) that supports equivalent copying facilities, +provided you maintain clear directions next to the object code saying +where to find the Corresponding Source. Regardless of what server +hosts the Corresponding Source, you remain obligated to ensure that it +is available for as long as needed to satisfy these requirements. e) +Convey the object code using peer-to-peer transmission, provided you +inform other peers where the object code and Corresponding Source of +the work are being offered to the general public at no charge under +subsection 6d. A separable portion of the object code, whose source +code is excluded from the Corresponding Source as a System Library, +need not be included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its +conditions. Additional permissions that are applicable to the entire +Program shall be treated as though they were included in this License, +to the extent that they are valid under applicable law. If additional +permissions apply only to part of the Program, that part may be used +separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +a) Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or b) Requiring +preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices +displayed by works containing it; or c) Prohibiting misrepresentation +of the origin of that material, or requiring that modified versions of +such material be marked in reasonable ways as different from the +original version; or d) Limiting the use for publicity purposes of +names of licensors or authors of the material; or e) Declining to +grant rights under trademark law for use of some trade names, +trademarks, or service marks; or f) Requiring indemnification of +licensors and authors of that material by anyone who conveys the +material (or modified versions of it) with contractual assumptions of +liability to the recipient, for any liability that these contractual +assumptions directly impose on those licensors and authors. All other +non-permissive additional terms are considered "further restrictions" +within the meaning of section 10. If the Program as you received it, +or any part of it, contains a notice stating that it is governed by +this License along with a term that is a further restriction, you may +remove that term. If a license document contains a further restriction +but permits relicensing or conveying under this License, you may add +to a covered work material governed by the terms of that license +document, provided that the further restriction does not survive such +relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public +License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + Copyright (C) + + This program is free software: you can redistribute it and/or + modify it under the terms of the GNU Affero General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public + License along with this program. If not, see + . Also add information on how to + contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for +the specific requirements. + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU AGPL, see . \ No newline at end of file diff --git a/external/tetgen/README b/external/tetgen/README index 34b6f2372..bc5cfa04c 100644 --- a/external/tetgen/README +++ b/external/tetgen/README @@ -1,4 +1,4 @@ -This is TetGen version 1.5 (released on February 21, 2012) +This is TetGen version 1.5 (released on November 4, 2013) Please see the documentation of TetGen for compiling and using TetGen. It is available at the following link: diff --git a/external/tetgen/example.poly b/external/tetgen/example.poly index 9394d8dbb..e92495666 100644 --- a/external/tetgen/example.poly +++ b/external/tetgen/example.poly @@ -1,139 +1,84 @@ -# -# example.poly - Sample file of TetGen. -# -# A .poly file describes a piecewise linear complex (PLC) -# This file represents a compensated magic tee junction. -# -# The source file is from: Vali Catina -# - -# Part 1 - node list -56 3 0 0 - 1 -13.716000000000001 -5.0800000000000001 0 - 2 -13.716000000000001 5.0800000000000001 0 - 3 -11.43 5.0800000000000001 0 - 4 11.43 -5.0800000000000001 0 - 5 11.43 7.3659999999999997 0 - 6 -11.43 7.3659999999999997 0 - 7 0.95105651629515364 -0.37999999999999989 0 - 8 -0.95105651629515364 -0.37999999999999989 0 - 9 -0.95105651629515364 4.6200000000000001 0 - 10 0.95105651629515364 4.6200000000000001 0 - 11 -0.95105651629515353 4.6200000000000001 -0.30901699437494756 - 12 -0.58778525229247303 4.6200000000000001 -0.80901699437494756 - 13 1.2246063538223773e-16 4.6200000000000001 -1 - 14 0.58778525229247325 4.6200000000000001 -0.80901699437494734 - 15 0.95105651629515364 4.6200000000000001 -0.30901699437494734 - 16 -0.95105651629515353 -0.37999999999999989 -0.30901699437494756 - 17 -0.58778525229247303 -0.37999999999999989 -0.80901699437494756 - 18 1.2246063538223773e-16 -0.37999999999999989 -1 - 19 0.58778525229247325 -0.37999999999999989 -0.80901699437494734 - 20 0.95105651629515364 -0.37999999999999989 -0.30901699437494734 - 21 -1.5874999999999999 -0.37999999999999989 -2.9160938800395356e-16 - 22 -1.5098022196185561 -0.37999999999999989 -0.49056447857022928 - 23 -1.2843144785702287 -0.37999999999999989 -0.93310908801430126 - 24 -0.93310908801430081 -0.37999999999999989 -1.2843144785702292 - 25 -0.49056447857022878 -0.37999999999999989 -1.5098022196185563 - 26 1.9440625866930238e-16 -0.37999999999999989 -1.5874999999999999 - 27 0.49056447857022917 -0.37999999999999989 -1.5098022196185561 - 28 0.93310908801430115 -0.37999999999999989 -1.284314478570229 - 29 1.284314478570229 -0.37999999999999989 -0.93310908801430092 - 30 1.5098022196185563 -0.37999999999999989 -0.49056447857022889 - 31 1.5874999999999999 -0.37999999999999989 9.7203129334651192e-17 - 32 -1.5874999999999999 -5.0800000000000001 -2.9160938800395356e-16 - 33 -1.5098022196185561 -5.0800000000000001 -0.49056447857022928 - 34 -1.2843144785702287 -5.0800000000000001 -0.93310908801430126 - 35 -0.93310908801430081 -5.0800000000000001 -1.2843144785702292 - 36 -0.49056447857022878 -5.0800000000000001 -1.5098022196185563 - 37 1.9440625866930238e-16 -5.0800000000000001 -1.5874999999999999 - 38 0.49056447857022917 -5.0800000000000001 -1.5098022196185561 - 39 0.93310908801430115 -5.0800000000000001 -1.284314478570229 - 40 1.284314478570229 -5.0800000000000001 -0.93310908801430092 - 41 1.5098022196185563 -5.0800000000000001 -0.49056447857022889 - 42 1.5874999999999999 -5.0800000000000001 9.7203129334651192e-17 - 43 -11.43 7.3659999999999997 -5.0800000000000001 - 44 11.43 7.3659999999999997 -5.0800000000000001 - 45 -11.43 5.0800000000000001 -5.0800000000000001 - 46 11.43 5.0800000000000001 -5.0800000000000001 - 47 -13.716000000000001 5.0800000000000001 -11.43 - 48 -13.716000000000001 -5.0800000000000001 -11.43 - 49 -11.43 -5.0800000000000001 -11.43 - 50 -11.43 5.0800000000000001 -11.43 - 51 -11.43 -5.0800000000000001 -13.715999999999999 - 52 11.43 -5.0800000000000001 -13.715999999999999 - 53 11.43 5.0800000000000001 -13.715999999999999 - 54 -11.43 5.0800000000000001 -13.715999999999999 - -55 -6.6628777242868491 -0.9950764223207208 -6.66287772428685 -56 4.2067426959563958 0.1964514680670118 -5.7864151699132815 - -# Part 2 - facet list -30 1 -1 0 2 -14 1 2 3 6 5 4 42 31 7 10 9 8 21 32 -1 0 1 -7 9 10 15 14 13 12 11 -1 0 1 -4 8 9 11 16 -1 0 1 -4 11 12 17 16 -1 0 1 -4 12 13 18 17 -1 0 1 -4 13 14 19 18 -1 0 1 -4 14 15 20 19 -1 0 1 -4 10 7 20 15 -1 0 3 -18 7 31 30 29 28 27 26 25 24 23 22 21 8 16 17 18 19 20 -1 0 1 -4 21 22 33 32 -1 0 1 -4 22 23 34 33 -1 0 1 -4 23 24 35 34 -1 0 1 -4 24 25 36 35 -1 0 1 -4 25 26 37 36 -1 0 1 -4 26 27 38 37 -1 0 1 -4 27 28 39 38 -1 0 1 -4 28 29 40 39 -1 0 1 -4 29 30 41 40 -1 0 1 -4 30 31 42 41 -1 0 1 -4 43 45 46 44 -1 0 1 -4 5 6 43 44 -1 0 1 -6 4 5 44 46 53 52 -1 0 1 -4 6 3 45 43 -1 0 1 -4 47 48 49 50 -1 0 1 -8 3 2 47 50 54 53 46 45 -1 0 4 -17 42 4 52 51 49 48 1 32 33 34 35 36 37 38 39 40 41 -1 0 1 -4 2 1 48 47 -1 0 1 -4 50 49 51 54 -1 0 1 -4 54 51 52 53 - -# The following facet is a single segment with marker -1 -1 0 -1 -2 55 56 - -# Part 3 - hole list -0 - -# Part 4 - region list -0 +28 3 0 1 +1 0 0 0 1 +2 2 0 0 1 +3 2 2 0 1 +4 0 2 0 1 +5 0 0 4 9 +6 2 0 4 9 +7 2 2 3 9 +8 0 2 3 9 +9 0 0 5 2 +10 2 0 5 2 +11 2 2 5 2 +12 0 2 5 2 +13 0.25 0.25 0.5 4 +14 1.75 0.25 0.5 4 +15 1.75 1.5 0.5 4 +16 0.25 1.5 0.5 4 +17 0.25 0.25 1 4 +18 1.75 0.25 1 4 +19 1.75 1.5 1 4 +20 0.25 1.5 1 4 +21 0.25 0 2 4 +22 1.75 0 2 4 +23 1.75 1.5 2 4 +24 0.25 1.5 2 4 +25 0.25 0 2.5 4 +26 1.75 0 2.5 4 +27 1.75 1.5 2.5 4 +28 0.25 1.5 2.5 4 +23 1 +1 0 1 # 1 +4 1 2 3 4 +1 0 9 # 2 +4 5 6 7 8 +2 1 3 # 3 +4 1 2 6 5 +4 21 22 26 25 +1 1 0 2.25 +1 0 3 # 4 +4 2 3 7 6 +1 0 3 # 5 +4 3 4 8 7 +1 0 3 # 6 +4 4 1 5 8 +1 0 2 # 7 +4 9 10 11 12 +1 0 3 # 8 +4 9 10 6 5 +1 0 3 # 9 +4 10 11 7 6 +1 0 3 # 10 +4 11 12 8 7 +1 0 3 # 11 +4 12 9 5 8 +1 0 4 # 12 +4 13 14 15 16 +1 0 4 # 13 +4 17 18 19 20 +1 0 4 # 14 +4 13 14 18 17 +1 0 4 # 15 +4 14 15 19 18 +1 0 4 # 16 +4 15 16 20 19 +1 0 4 # 17 +4 16 13 17 20 +1 0 4 # 18 +4 21 22 23 24 +1 0 4 # 19 +4 25 26 27 28 +1 0 4 # 20 +4 21 22 26 25 +1 0 4 # 21 +4 22 23 27 26 +1 0 4 # 22 +4 23 24 28 27 +1 0 4 # 23 +4 24 21 25 28 +2 +1 1 0.4 2.25 +2 1 0.4 0.75 +2 +1 1 0.25 0.1 10 0.001 +2 1 0.5 4 20 0.01 diff --git a/external/tetgen/header.txt b/external/tetgen/header.txt new file mode 100644 index 000000000..7f2e7d323 --- /dev/null +++ b/external/tetgen/header.txt @@ -0,0 +1,17 @@ +/////////////////////////////////////////////////////////////////////////////// +// // +// TetGen // +// // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // +// // +// Version 1.5 // +// November 4, 2013 // +// // +// TetGen is freely available through the website: http://www.tetgen.org. // +// It may be copied, modified, and redistributed for non-commercial use. // +// Please consult the file LICENSE for the detailed copyright notices. // +// // +/////////////////////////////////////////////////////////////////////////////// + +#include "tetgen.h" + diff --git a/external/tetgen/makefile b/external/tetgen/makefile index 42de4f9a8..b3b131cfe 100644 --- a/external/tetgen/makefile +++ b/external/tetgen/makefile @@ -19,12 +19,9 @@ CXX = g++ # -O2, -O3 ... to find the best optimization level. # =================================================================== -#CXXFLAGS = -g -CXXFLAGS = -O2 +CXXFLAGS = -O3 -# PREDCXXFLAGS is for compiling J. Shewchuk's predicates. It should -# always be equal to -O0 (no optimization). Otherwise, TetGen may not -# work properly. +# PREDCXXFLAGS is for compiling J. Shewchuk's predicates. PREDCXXFLAGS = -O0 @@ -39,14 +36,7 @@ PREDCXXFLAGS = -O0 # down the speed of TetGen. They can be skipped by define the -DNDEBUG # switch. -SWITCHES = -Wall -DSELF_CHECK - -# SWITCHES = -Wall -Wabi -Wctor-dtor-privacy \ -# -Woverloaded-virtual -Wno-pmf-conversions -Wsign-promo \ -# -Wsynth -Wchar-subscripts -Wconversion -Wsign-compare \ -# -Wcomment -Wimplicit -Wmissing-braces -Wparentheses \ -# -Wreturn-type -Wswitch -Wswitch-default \ -# -Wswitch-enum -Wtrigraphs -W -DSELF_CHECK +SWITCHES = # RM should be set to the name of your favorite rm (file deletion program). diff --git a/external/tetgen/predicates.cxx b/external/tetgen/predicates.cxx index a4b77b69f..33817d799 100644 --- a/external/tetgen/predicates.cxx +++ b/external/tetgen/predicates.cxx @@ -125,6 +125,13 @@ #include "tetgen.h" // Defines the symbol REAL (float or double). +#ifdef USE_CGAL_PREDICATES + #include + typedef CGAL::Exact_predicates_inexact_constructions_kernel cgalEpick; + typedef cgalEpick::Point_3 Point; + cgalEpick cgal_pred_obj; +#endif // #ifdef USE_CGAL_PREDICATES + /* On some machines, the exact arithmetic routines might be defeated by the */ /* use of internal extended precision floating-point registers. Sometimes */ /* this problem can be fixed by defining certain values to be volatile, */ @@ -378,286 +385,141 @@ static REAL isperrboundA, isperrboundB, isperrboundC; // Options to choose types of geometric computtaions. // Added by H. Si, 2012-08-23. static int _use_inexact_arith; // -X option. -static int _use_static_filter; // -S option. +static int _use_static_filter; // Default option, disable it by -X1 -// Static filters. Added by H. Si, 2012-08-23. +// Static filters for orient3d() and insphere(). +// They are pre-calcualted and set in exactinit(). +// Added by H. Si, 2012-08-23. static REAL o3dstaticfilter; static REAL ispstaticfilter; -#ifndef NDEBUG -// Counters for counting the number of calls. Added by H. Si, 2012-08-23. -long ori3dcount, ori3dadaptcount; -long insphcount, insphadaptcount, insphexactcount; -long ori4dcount, ori4dadaptcount, ori4dexactcount; -long o3dfilterfailscount; -long ispfilterfailscount; -#endif // #ifndef NDEBUG -/*****************************************************************************/ -/* */ -/* doubleprint() Print the bit representation of a double. */ -/* */ -/* Useful for debugging exact arithmetic routines. */ -/* */ -/*****************************************************************************/ -/* -void doubleprint(number) -double number; +// The following codes were part of "IEEE 754 floating-point test software" +// http://www.math.utah.edu/~beebe/software/ieee/ +// The original program was "fpinfo2.c". + +double fppow2(int n) { - unsigned long long no; - unsigned long long sign, expo; - int exponent; - int i, bottomi; + double x, power; + x = (n < 0) ? ((double)1.0/(double)2.0) : (double)2.0; + n = (n < 0) ? -n : n; + power = (double)1.0; + while (n-- > 0) + power *= x; + return (power); +} - no = *(unsigned long long *) &number; - sign = no & 0x8000000000000000ll; - expo = (no >> 52) & 0x7ffll; - exponent = (int) expo; - exponent = exponent - 1023; - if (sign) { - printf("-"); +#ifdef SINGLE + +float fstore(float x) +{ + return (x); +} + +int test_float(int verbose) +{ + float x; + int pass = 1; + + //(void)printf("float:\n"); + + if (verbose) { + (void)printf(" sizeof(float) = %2u\n", (unsigned int)sizeof(float)); +#ifdef CPU86 // + (void)printf(" FLT_MANT_DIG = %2d\n", FLT_MANT_DIG); +#endif + } + + x = (float)1.0; + while (fstore((float)1.0 + x/(float)2.0) != (float)1.0) + x /= (float)2.0; + if (verbose) + (void)printf(" machine epsilon = %13.5e ", x); + + if (x == (float)fppow2(-23)) { + if (verbose) + (void)printf("[IEEE 754 32-bit macheps]\n"); } else { - printf(" "); + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; } - if (exponent == -1023) { - printf( - "0.0000000000000000000000000000000000000000000000000000_ ( )"); + + x = (float)1.0; + while (fstore(x / (float)2.0) != (float)0.0) + x /= (float)2.0; + if (verbose) + (void)printf(" smallest positive number = %13.5e ", x); + + if (x == (float)fppow2(-149)) { + if (verbose) + (void)printf("[smallest 32-bit subnormal]\n"); + } else if (x == (float)fppow2(-126)) { + if (verbose) + (void)printf("[smallest 32-bit normal]\n"); } else { - printf("1."); - bottomi = -1; - for (i = 0; i < 52; i++) { - if (no & 0x0008000000000000ll) { - printf("1"); - bottomi = i; - } else { - printf("0"); - } - no <<= 1; - } - printf("_%d (%d)", exponent, exponent - 1 - bottomi); + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; } + + return pass; } -*/ -/*****************************************************************************/ -/* */ -/* floatprint() Print the bit representation of a float. */ -/* */ -/* Useful for debugging exact arithmetic routines. */ -/* */ -/*****************************************************************************/ +# else -/* -void floatprint(number) -float number; +double dstore(double x) { - unsigned no; - unsigned sign, expo; - int exponent; - int i, bottomi; + return (x); +} - no = *(unsigned *) &number; - sign = no & 0x80000000; - expo = (no >> 23) & 0xff; - exponent = (int) expo; - exponent = exponent - 127; - if (sign) { - printf("-"); +int test_double(int verbose) +{ + double x; + int pass = 1; + + // (void)printf("double:\n"); + if (verbose) { + (void)printf(" sizeof(double) = %2u\n", (unsigned int)sizeof(double)); +#ifdef CPU86 // + (void)printf(" DBL_MANT_DIG = %2d\n", DBL_MANT_DIG); +#endif + } + + x = 1.0; + while (dstore(1.0 + x/2.0) != 1.0) + x /= 2.0; + if (verbose) + (void)printf(" machine epsilon = %13.5le ", x); + + if (x == (double)fppow2(-52)) { + if (verbose) + (void)printf("[IEEE 754 64-bit macheps]\n"); } else { - printf(" "); + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; } - if (exponent == -127) { - printf("0.00000000000000000000000_ ( )"); + + x = 1.0; + while (dstore(x / 2.0) != 0.0) + x /= 2.0; + //if (verbose) + // (void)printf(" smallest positive number = %13.5le ", x); + + if (x == (double)fppow2(-1074)) { + //if (verbose) + // (void)printf("[smallest 64-bit subnormal]\n"); + } else if (x == (double)fppow2(-1022)) { + //if (verbose) + // (void)printf("[smallest 64-bit normal]\n"); } else { - printf("1."); - bottomi = -1; - for (i = 0; i < 23; i++) { - if (no & 0x00400000) { - printf("1"); - bottomi = i; - } else { - printf("0"); - } - no <<= 1; - } - printf("_%3d (%3d)", exponent, exponent - 1 - bottomi); + (void)printf("[not IEEE 754 conformant] !!\n"); + pass = 0; } + + return pass; } -*/ -/*****************************************************************************/ -/* */ -/* expansion_print() Print the bit representation of an expansion. */ -/* */ -/* Useful for debugging exact arithmetic routines. */ -/* */ -/*****************************************************************************/ - -/* -void expansion_print(elen, e) -int elen; -REAL *e; -{ - int i; - - for (i = elen - 1; i >= 0; i--) { - REALPRINT(e[i]); - if (i > 0) { - printf(" +\n"); - } else { - printf("\n"); - } - } -} -*/ - -/*****************************************************************************/ -/* */ -/* doublerand() Generate a double with random 53-bit significand and a */ -/* random exponent in [0, 511]. */ -/* */ -/*****************************************************************************/ - -/* -double doublerand() -{ - double result; - double expo; - long a, b, c; - long i; - - a = random(); - b = random(); - c = random(); - result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); - for (i = 512, expo = 2; i <= 131072; i *= 2, expo = expo * expo) { - if (c & i) { - result *= expo; - } - } - return result; -} -*/ - -/*****************************************************************************/ -/* */ -/* narrowdoublerand() Generate a double with random 53-bit significand */ -/* and a random exponent in [0, 7]. */ -/* */ -/*****************************************************************************/ - -/* -double narrowdoublerand() -{ - double result; - double expo; - long a, b, c; - long i; - - a = random(); - b = random(); - c = random(); - result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); - for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { - if (c & i) { - result *= expo; - } - } - return result; -} -*/ - -/*****************************************************************************/ -/* */ -/* uniformdoublerand() Generate a double with random 53-bit significand. */ -/* */ -/*****************************************************************************/ - -/* -double uniformdoublerand() -{ - double result; - long a, b; - - a = random(); - b = random(); - result = (double) (a - 1073741824) * 8388608.0 + (double) (b >> 8); - return result; -} -*/ - -/*****************************************************************************/ -/* */ -/* floatrand() Generate a float with random 24-bit significand and a */ -/* random exponent in [0, 63]. */ -/* */ -/*****************************************************************************/ - -/* -float floatrand() -{ - float result; - float expo; - long a, c; - long i; - - a = random(); - c = random(); - result = (float) ((a - 1073741824) >> 6); - for (i = 512, expo = 2; i <= 16384; i *= 2, expo = expo * expo) { - if (c & i) { - result *= expo; - } - } - return result; -} -*/ - -/*****************************************************************************/ -/* */ -/* narrowfloatrand() Generate a float with random 24-bit significand and */ -/* a random exponent in [0, 7]. */ -/* */ -/*****************************************************************************/ - -/* -float narrowfloatrand() -{ - float result; - float expo; - long a, c; - long i; - - a = random(); - c = random(); - result = (float) ((a - 1073741824) >> 6); - for (i = 512, expo = 2; i <= 2048; i *= 2, expo = expo * expo) { - if (c & i) { - result *= expo; - } - } - return result; -} -*/ - -/*****************************************************************************/ -/* */ -/* uniformfloatrand() Generate a float with random 24-bit significand. */ -/* */ -/*****************************************************************************/ - -/* -float uniformfloatrand() -{ - float result; - long a; - - a = random(); - result = (float) ((a - 1073741824) >> 6); - return result; -} -*/ +#endif /*****************************************************************************/ /* */ @@ -678,7 +540,8 @@ float uniformfloatrand() /* */ /*****************************************************************************/ -void exactinit(int noexact, int nofilter, REAL maxx, REAL maxy, REAL maxz) +void exactinit(int verbose, int noexact, int nofilter, REAL maxx, REAL maxy, + REAL maxz) { REAL half; REAL check, lastcheck; @@ -705,6 +568,24 @@ void exactinit(int noexact, int nofilter, REAL maxx, REAL maxy, REAL maxz) _FPU_SETCW(cword); #endif /* LINUX */ + if (verbose) { + printf(" Initializing robust predicates.\n"); + } + +#ifdef USE_CGAL_PREDICATES + if (cgal_pred_obj.Has_static_filters) { + printf(" Use static filter.\n"); + } else { + printf(" No static filter.\n"); + } +#endif // USE_CGAL_PREDICATES + +#ifdef SINGLE + test_float(verbose); +#else + test_double(verbose); +#endif + every_other = 1; half = 0.5; epsilon = 1.0; @@ -740,9 +621,13 @@ void exactinit(int noexact, int nofilter, REAL maxx, REAL maxy, REAL maxz) isperrboundB = (5.0 + 72.0 * epsilon) * epsilon; isperrboundC = (71.0 + 1408.0 * epsilon) * epsilon * epsilon; + // Set TetGen options. Added by H. Si, 2012-08-23. _use_inexact_arith = noexact; _use_static_filter = !nofilter; + // Calculate the two static filters for orient3d() and insphere() tests. + // Added by H. Si, 2012-08-23. + // Sort maxx < maxy < maxz. Re-use 'half' for swapping. assert(maxx > 0); assert(maxy > 0); @@ -758,18 +643,9 @@ void exactinit(int noexact, int nofilter, REAL maxx, REAL maxy, REAL maxz) half = maxy; maxy = maxx; maxx = half; } - // Calculate the static filters. o3dstaticfilter = 5.1107127829973299e-15 * maxx * maxy * maxz; ispstaticfilter = 1.2466136531027298e-13 * maxx * maxy * maxz * (maxz * maxz); -#ifndef NDEBUG - // Clear the counters. - ori3dcount = ori3dadaptcount = 0l; - insphcount = insphadaptcount = insphexactcount = 0l; - ori4dcount = ori4dadaptcount = ori4dexactcount = 0l; - o3dfilterfailscount = 0l; - ispfilterfailscount = 0l; -#endif // #ifndef NDEBUG } /*****************************************************************************/ @@ -1953,9 +1829,6 @@ REAL orient3dadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) INEXACT REAL _i, _j, _k; REAL _0; -#ifndef NDEBUG - ori3dadaptcount++; -#endif // #ifndef NDEBUG adx = (REAL) (pa[0] - pd[0]); bdx = (REAL) (pb[0] - pd[0]); @@ -2304,25 +2177,35 @@ REAL orient3dadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL permanent) return finnow[finlength - 1]; } +#ifdef USE_CGAL_PREDICATES + +REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + return (REAL) + - cgal_pred_obj.orientation_3_object() + (Point(pa[0], pa[1], pa[2]), + Point(pb[0], pb[1], pb[2]), + Point(pc[0], pc[1], pc[2]), + Point(pd[0], pd[1], pd[2])); +} + +#else + REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) { REAL adx, bdx, cdx, ady, bdy, cdy, adz, bdz, cdz; REAL bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady; REAL det; - REAL permanent, errbound; -#ifndef NDEBUG - ori3dcount++; -#endif // #ifndef NDEBUG adx = pa[0] - pd[0]; - bdx = pb[0] - pd[0]; - cdx = pc[0] - pd[0]; ady = pa[1] - pd[1]; - bdy = pb[1] - pd[1]; - cdy = pc[1] - pd[1]; adz = pa[2] - pd[2]; + bdx = pb[0] - pd[0]; + bdy = pb[1] - pd[1]; bdz = pb[2] - pd[2]; + cdx = pc[0] - pd[0]; + cdy = pc[1] - pd[1]; cdz = pc[2] - pd[2]; bdxcdy = bdx * cdy; @@ -2343,14 +2226,14 @@ REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) } if (_use_static_filter) { - if (fabs(det) > o3dstaticfilter) return det; - //if (det > o3dstaticfilter) return det; - //if (det < minus_o3dstaticfilter) return det; -#ifndef NDEBUG - o3dfilterfailscount++; -#endif // #ifndef NDEBUG + //if (fabs(det) > o3dstaticfilter) return det; + if (det > o3dstaticfilter) return det; + if (det < -o3dstaticfilter) return det; } + + REAL permanent, errbound; + permanent = (Absolute(bdxcdy) + Absolute(cdxbdy)) * Absolute(adz) + (Absolute(cdxady) + Absolute(adxcdy)) * Absolute(bdz) + (Absolute(adxbdy) + Absolute(bdxady)) * Absolute(cdz); @@ -2362,6 +2245,8 @@ REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd) return orient3dadapt(pa, pb, pc, pd, permanent); } +#endif // #ifdef USE_CGAL_PREDICATES + /*****************************************************************************/ /* */ /* incirclefast() Approximate 2D incircle test. Nonrobust. */ @@ -3393,9 +3278,6 @@ REAL insphereexact(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) INEXACT REAL _i, _j; REAL _0; -#ifndef NDEBUG - insphexactcount++; -#endif // #ifndef NDEBUG Two_Product(pa[0], pb[1], axby1, axby0); Two_Product(pb[0], pa[1], bxay1, bxay0); @@ -3973,9 +3855,6 @@ REAL insphereadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, INEXACT REAL _i, _j; REAL _0; -#ifndef NDEBUG - insphadaptcount++; -#endif // #ifndef NDEBUG aex = (REAL) (pa[0] - pe[0]); bex = (REAL) (pb[0] - pe[0]); @@ -4153,6 +4032,21 @@ REAL insphereadapt(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, return insphereexact(pa, pb, pc, pd, pe); } +#ifdef USE_CGAL_PREDICATES + +REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) +{ + return (REAL) + - cgal_pred_obj.side_of_oriented_sphere_3_object() + (Point(pa[0], pa[1], pa[2]), + Point(pb[0], pb[1], pb[2]), + Point(pc[0], pc[1], pc[2]), + Point(pd[0], pd[1], pd[2]), + Point(pe[0], pe[1], pe[2])); +} + +#else + REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) { REAL aex, bex, cex, dex; @@ -4165,9 +4059,6 @@ REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) REAL abc, bcd, cda, dab; REAL det; -#ifndef NDEBUG - insphcount++; -#endif // #ifndef NDEBUG aex = pa[0] - pe[0]; bex = pb[0] - pe[0]; @@ -4222,9 +4113,7 @@ REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) if (fabs(det) > ispstaticfilter) return det; //if (det > ispstaticfilter) return det; //if (det < minus_ispstaticfilter) return det; -#ifndef NDEBUG - ispfilterfailscount++; -#endif // #ifndef NDEBUG + } REAL aezplus, bezplus, cezplus, dezplus; @@ -4273,6 +4162,8 @@ REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe) return insphereadapt(pa, pb, pc, pd, pe, permanent); } +#endif // #ifdef USE_CGAL_PREDICATES + /*****************************************************************************/ /* */ /* orient4d() Return a positive value if the point pe lies above the */ @@ -4336,9 +4227,6 @@ REAL orient4dexact(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, INEXACT REAL _i, _j; REAL _0; -#ifndef NDEBUG - ori4dexactcount++; -#endif // #ifndef NDEBUG Two_Product(pa[0], pb[1], axby1, axby0); Two_Product(pb[0], pa[1], bxay1, bxay0); @@ -4553,9 +4441,6 @@ REAL orient4dadapt(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, INEXACT REAL _i, _j; REAL _0; -#ifndef NDEBUG - ori4dadaptcount++; -#endif // #ifndef NDEBUG aex = (REAL) (pa[0] - pe[0]); bex = (REAL) (pb[0] - pe[0]); @@ -4731,9 +4616,6 @@ REAL orient4d(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, REAL det; REAL permanent, errbound; -#ifndef NDEBUG - ori4dcount++; -#endif // #ifndef NDEBUG aex = pa[0] - pe[0]; bex = pb[0] - pe[0]; @@ -4821,30 +4703,4 @@ REAL orient4d(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, } -void predicates_statistics(int weighted) -{ -#ifndef NDEBUG - printf(" Number of orient3d tests: %ld\n", ori3dcount); - if (_use_static_filter) { - printf(" Number of static filter fails: %ld\n", o3dfilterfailscount); - } - if (!_use_inexact_arith) { - printf(" Number of orient3dadapt tests: %ld\n", ori3dadaptcount); - } - if (!weighted) { - printf(" Number of insphere tests: %ld\n", insphcount); - if (_use_static_filter) { - printf(" Number of static filter fails: %ld\n", ispfilterfailscount); - } - if (!_use_inexact_arith) { - printf(" Number of insphereadapt tests: %ld\n", insphadaptcount); - printf(" Number of insphereexact tests: %ld\n", insphexactcount); - } - } else { - printf(" Number of orient4d tests: %ld\n", ori4dcount); - printf(" Number of orient4dadapt tests: %ld\n", ori4dadaptcount); - printf(" Number of orient4dexact tests: %ld\n", ori4dexactcount); - } -#endif // #ifndef NDEBUG -} diff --git a/external/tetgen/src/behavior.cxx b/external/tetgen/src/behavior.cxx index 45f34952c..0be7e850f 100644 --- a/external/tetgen/src/behavior.cxx +++ b/external/tetgen/src/behavior.cxx @@ -11,20 +11,21 @@ void tetgenbehavior::syntax() { - printf(" tetgen [-pYq_Aa_mriO_S_T_XMwcdzfenvgKJBNEFICQVh] input_file\n"); + printf(" tetgen [-pYrq_Aa_miO_S_T_XMwcdzfenvgkJBNEFICQVh] input_file\n"); printf(" -p Tetrahedralizes a piecewise linear complex (PLC).\n"); printf(" -Y Preserves the input surface mesh (does not modify it).\n"); + printf(" -r Reconstructs a previously generated mesh.\n"); printf(" -q Refines mesh (to improve mesh quality).\n"); + printf(" -R Mesh coarsening (to reduce the mesh elements).\n"); printf(" -A Assigns attributes to tetrahedra in different regions.\n"); printf(" -a Applies a maximum tetrahedron volume constraint.\n"); printf(" -m Applies a mesh sizing function.\n"); - printf(" -r Reconstructs a previously generated mesh.\n"); printf(" -i Inserts a list of additional points.\n"); printf(" -O Specifies the level of mesh optimization.\n"); printf(" -S Specifies maximum number of added points.\n"); printf(" -T Sets a tolerance for coplanar test (default 1e-8).\n"); printf(" -X Suppresses use of exact arithmetic.\n"); - printf(" -M No merge of coplanar facets.\n"); + printf(" -M No merge of coplanar facets or very close vertices.\n"); printf(" -w Generates weighted Delaunay (regular) triangulation.\n"); printf(" -c Retains the convex hull of the PLC.\n"); printf(" -d Detects self-intersections of facets of the PLC.\n"); @@ -34,12 +35,12 @@ void tetgenbehavior::syntax() printf(" -n Outputs tetrahedra neighbors to .neigh file.\n"); printf(" -v Outputs Voronoi diagram to files.\n"); printf(" -g Outputs mesh to .mesh file for viewing by Medit.\n"); - printf(" -K Outputs mesh to .vtk file for viewing by Paraview.\n"); + printf(" -k Outputs mesh to .vtk file for viewing by Paraview.\n"); printf(" -J No jettison of unused vertices from output .node file.\n"); printf(" -B Suppresses output of boundary information.\n"); printf(" -N Suppresses output of .node file.\n"); printf(" -E Suppresses output of .ele file.\n"); - printf(" -F Suppresses output of .face file.\n"); + printf(" -F Suppresses output of .face and .edge file.\n"); printf(" -I Suppresses mesh iteration numbers.\n"); printf(" -C Checks the consistency of the final mesh.\n"); printf(" -Q Quiet: No terminal output except errors.\n"); @@ -58,20 +59,13 @@ void tetgenbehavior::usage() printf("TetGen\n"); printf("A Quality Tetrahedral Mesh Generator and 3D Delaunay "); printf("Triangulator\n"); - printf("Version 1.5 (February 21, 2012).\n"); - printf("\n"); - printf("Copyright (C) 2002 - 2012\n"); - printf("Hang Si\n"); - printf("Mohrenstr. 39, 10117 Berlin, Germany\n"); - printf("Hang.Si@wias-berlin.de\n"); + printf("Version 1.5\n"); + printf("November 4, 2013\n"); printf("\n"); printf("What Can TetGen Do?\n"); printf("\n"); - printf(" TetGen generates exact Delaunay tetrahedralizations, exact\n"); - printf(" constrained Delaunay tetrahedralizations, and quality "); - printf("tetrahedral\n meshes. The latter are nicely graded and whose "); - printf("tetrahedra have\n radius-edge ratio bounded, thus are suitable "); - printf("for finite element and\n finite volume analysis.\n"); + printf(" TetGen generates Delaunay tetrahedralizations, constrained\n"); + printf(" Delaunay tetrahedralizations, and quality tetrahedral meshes.\n"); printf("\n"); printf("Command Line Syntax:\n"); printf("\n"); @@ -92,22 +86,24 @@ void tetgenbehavior::usage() printf("Examples of How to Use TetGen:\n"); printf("\n"); printf(" \'tetgen object\' reads vertices from object.node, and writes "); - printf("their\n Delaunay tetrahedralization to object.1.node and "); - printf("object.1.ele.\n"); + printf("their\n Delaunay tetrahedralization to object.1.node, "); + printf("object.1.ele\n (tetrahedra), and object.1.face"); + printf(" (convex hull faces).\n"); printf("\n"); printf(" \'tetgen -p object\' reads a PLC from object.poly or object."); printf("smesh (and\n possibly object.node) and writes its constrained "); - printf("Delaunay\n tetrahedralization to object.1.node, object.1.ele and "); - printf("object.1.face.\n"); + printf("Delaunay\n tetrahedralization to object.1.node, object.1.ele, "); + printf("object.1.face,\n"); + printf(" (boundary faces) and object.1.edge (boundary edges).\n"); printf("\n"); printf(" \'tetgen -pq1.414a.1 object\' reads a PLC from object.poly or\n"); printf(" object.smesh (and possibly object.node), generates a mesh "); printf("whose\n tetrahedra have radius-edge ratio smaller than 1.414 and "); printf("have volume\n of 0.1 or less, and writes the mesh to "); - printf("object.1.node, object.1.ele\n and object.1.face.\n"); + printf("object.1.node, object.1.ele,\n object.1.face, and object.1.edge\n"); printf("\n"); printf("Please send bugs/comments to Hang Si \n"); - terminatetetgen(0); + terminatetetgen(NULL, 0); } /////////////////////////////////////////////////////////////////////////////// @@ -176,6 +172,13 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) nobisect_param = (argv[i][j + 1] - '0'); j++; } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + addsteiner_algo = (argv[i][j + 1] - '0'); + j++; + } + } } else if (argv[i][j] == 'r') { refine = 1; } else if (argv[i][j] == 'q') { @@ -222,6 +225,27 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) optmaxdihedral = (REAL) strtod(workstring, (char **) NULL); } } + } else if (argv[i][j] == 'R') { + coarsen = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + coarsen_param = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + coarsen_percent = (REAL) strtod(workstring, (char **) NULL); + } + } } else if (argv[i][j] == 'w') { weighted = 1; if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { @@ -229,10 +253,71 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) j++; } } else if (argv[i][j] == 'b') { + // -b(brio_threshold/brio_ratio/hilbert_limit/hilbert_order) brio_hilbert = 1; - if (argv[i][j + 1] == '0') { // -b0 - brio_hilbert = 0; // Turn off BRIO sorting. + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + brio_threshold = (int) strtol(workstring, (char **) &workstring, 0); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + brio_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + hilbert_limit = (int) strtol(workstring, (char **) &workstring, 0); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + hilbert_order = (REAL) strtod(workstring, (char **) NULL); + } + } + if (brio_threshold == 0) { // -b0 + brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. + } + if (brio_ratio >= 1.0) { // -b/1 + no_sort = 1; + brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. } } else if (argv[i][j] == 'l') { incrflip = 1; @@ -272,7 +357,19 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) } else if (argv[i][j] == 'c') { convex = 1; } else if (argv[i][j] == 'M') { - nomerge = 1; + nomergefacet = 1; + nomergevertex = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { + nomergefacet = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { + nomergevertex = (argv[i][j + 1] - '0'); + j++; + } + } } else if (argv[i][j] == 'X') { if (argv[i][j + 1] == '1') { nostaticfilter = 1; @@ -374,6 +471,26 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) quiet = 1; } else if (argv[i][j] == 'V') { verbose++; + } else if (argv[i][j] == 'x') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + tetrahedraperblock = (int) strtol(workstring, (char **) NULL, 0); + if (tetrahedraperblock > 8188) { + vertexperblock = tetrahedraperblock / 2; + shellfaceperblock = vertexperblock / 2; + } else { + tetrahedraperblock = 8188; + } + } } else if ((argv[i][j] == 'h') || (argv[i][j] == 'H') || (argv[i][j] == '?')) { usage(); @@ -390,7 +507,7 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) if (infilename[0] == '\0') { // No input file name. Print the syntax and exit. syntax(); - terminatetetgen(0); + terminatetetgen(NULL, 0); } // Recognize the object from file extension if it is available. if (!strcmp(&infilename[strlen(infilename) - 5], ".node")) { @@ -440,36 +557,41 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) if (diagnose && !plc) { // -d plc = 1; } - if (plc && !quality && !nobisect) { // -p only - // Create a CDT, do not do mesh optimization. + if (refine && !quality) { // -r only + // Reconstruct a mesh, no mesh optimization. optlevel = 0; } + if (insertaddpoints && (optlevel == 0)) { // with -i option + optlevel = 2; + } + if (coarsen && (optlevel == 0)) { // with -R option + optlevel = 2; + } // Detect improper combinations of switches. - if (plc && refine) { - printf("Error: Switch -r cannot use together with -p.\n"); - return false; - } - if (refine && (plc || noiterationnum)) { - printf("Error: Switches %s cannot use together with -r.\n", - "-p, -d, and -I"); - return false; - } if ((refine || plc) && weighted) { printf("Error: Switches -w cannot use together with -p or -r.\n"); return false; } - // Be careful not to allocate space for element area constraints that - // will never be assigned any value (other than the default -1.0). - if (!refine && !plc) { - varvolume = 0; + if (convex) { // -c + if (plc && !regionattrib) { + // -A (region attribute) is needed for marking exterior tets (-1). + regionattrib = 1; + } } + + // Note: -A must not used together with -r option. // Be careful not to add an extra attribute to each element unless the // input supports it (PLC in, but not refining a preexisting mesh). if (refine || !plc) { regionattrib = 0; } + // Be careful not to allocate space for element area constraints that + // will never be assigned any value (other than the default -1.0). + if (!refine && !plc) { + varvolume = 0; + } // If '-a' or '-aa' is in use, enable '-q' option too. if (fixedvolume || varvolume) { if (quality == 0) { @@ -482,7 +604,11 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) // No user-specified dihedral angle bound. Use default ones. if (!quality) { if (optmaxdihedral < 179.0) { - optmaxdihedral = 179.0; + if (nobisect) { // with -Y option + optmaxdihedral = 179.0; + } else { // -p only + optmaxdihedral = 179.999; + } } if (optminsmtdihed < 179.999) { optminsmtdihed = 179.999; diff --git a/external/tetgen/src/constrained.cxx b/external/tetgen/src/constrained.cxx index f4db241f5..cabe2f083 100644 --- a/external/tetgen/src/constrained.cxx +++ b/external/tetgen/src/constrained.cxx @@ -5,138 +5,77 @@ /////////////////////////////////////////////////////////////////////////////// // // -// markacutevertices() Classify vertices as ACUTEVERTEXs or RIDGEVERTEXs. // +// makesegmentendpointsmap() Create a map from a segment to its endpoints.// // // -// Initially all segment vertices have type RIDGEVERTEX. A segment is acute // -// if there are at least two segments incident at it form an angle less than // -// theta (= 60 degree). // -// // -// The minimum segment-segment angle (minfaceang) is calculated. // +// The map is saved in the array 'segmentendpointslist'. The length of this // +// array is twice the number of segments. Each segment is assigned a unique // +// index (starting from 0). // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::markacutevertices() +void tetgenmesh::makesegmentendpointsmap() { - face* segperverlist; - int* idx2seglist; - point pa, pb, pc; - REAL anglimit, ang; - bool acuteflag; - int acutecount; - int idx, i, j; + arraypool *segptlist; + face segloop, prevseg, nextseg; + point eorg, edest, *parypt; + int segindex = 0, idx = 0; + int i; - REAL sharpanglimit; - int sharpsegcount; - - if (b->verbose) { - printf(" Marking acute vertices.\n"); + if (b->verbose > 0) { + printf(" Creating the segment-endpoints map.\n"); } - anglimit = PI / 3.0; // 60 degree. - sharpanglimit = 5.0 / 180.0 * PI; // 5 degree. - minfaceang = PI; // 180 degree. - acutecount = sharpsegcount = 0; - // Construct a map from points to segments. - makepoint2submap(subsegs, idx2seglist, segperverlist); + segptlist = new arraypool(2 * sizeof(point), 10); - // Loop over the set of vertices. - points->traversalinit(); - pa = pointtraverse(); - while (pa != NULL) { - idx = pointmark(pa) - in->firstnumber; - // Mark it if it is an endpoint of some segments. - if (idx2seglist[idx + 1] > idx2seglist[idx]) { - if (b->psc) { - // Only test it if it is an input vertex. - if (pointtype(pa) == FREESEGVERTEX) { - pa = pointtraverse(); - continue; - } - } - acuteflag = false; - // Do a brute-force pair-pair check. - for (i=idx2seglist[idx]; i 4) { - // There are at least 5 segments shared at this vertices. - acuteflag = true; - } - } - if (acuteflag) { - if (b->verbose > 2) { - printf(" Mark %d as ACUTEVERTEX.\n", pointmark(pa)); - } - setpointtype(pa, ACUTEVERTEX); - acutecount++; + // A segment s may have been split into many subsegments. Operate the one + // which contains the origin of s. Then mark the rest of subsegments. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + segloop.shver = 0; + while (segloop.sh != NULL) { + senext2(segloop, prevseg); + spivotself(prevseg); + if (prevseg.sh == NULL) { + eorg = sorg(segloop); + edest = sdest(segloop); + setfacetindex(segloop, segindex); + senext(segloop, nextseg); + spivotself(nextseg); + while (nextseg.sh != NULL) { + setfacetindex(nextseg, segindex); + nextseg.shver = 0; + if (sorg(nextseg) != edest) sesymself(nextseg); + assert(sorg(nextseg) == edest); + edest = sdest(nextseg); + // Go the next connected subsegment at edest. + senextself(nextseg); + spivotself(nextseg); } + segptlist->newindex((void **) &parypt); + parypt[0] = eorg; + parypt[1] = edest; + segindex++; } - pa = pointtraverse(); + segloop.sh = shellfacetraverse(subsegs); } if (b->verbose) { - if (acutecount > 0) { - printf(" Found %d acute vertices.\n", acutecount); - } - if (sharpsegcount > 0) { - printf(" Found %d sharp segments.\n", sharpsegcount); - } - printf(" Minimum seg-seg angle = %g.\n", minfaceang / PI * 180.0); + printf(" Found %ld segments.\n", segptlist->objects); } - delete [] idx2seglist; - delete [] segperverlist; + segmentendpointslist = new point[segptlist->objects * 2]; + + totalworkmemory += (segptlist->objects * 2) * sizeof(point *); + + for (i = 0; i < segptlist->objects; i++) { + parypt = (point *) fastlookup(segptlist, i); + segmentendpointslist[idx++] = parypt[0]; + segmentendpointslist[idx++] = parypt[1]; + } + + delete segptlist; } -/////////////////////////////////////////////////////////////////////////////// -// // -// reportselfintersect() Report a self-intersection. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::reportselfintersect(face *checkseg, face *checksh) -{ - face parentsh; - point pa, pb, pc, pd, pe; - point fa, fb; - - pa = sorg(*checkseg); - pb = sdest(*checkseg); - fa = farsorg(*checkseg); - fb = farsdest(*checkseg); - - pc = sorg(*checksh); - pd = sdest(*checksh); - pe = sapex(*checksh); - - printf(" !! Detected a self-intersection between:\n"); - printf(" A segment [%d,%d] < [%d,%d], \n", pointmark(pa), pointmark(pb), - pointmark(fa), pointmark(fb)); - printf(" a subface [%d,%d,%d] in facet #%d.\n", pointmark(pc), - pointmark(pd), pointmark(pe), shellmark(*checksh)); - -} /////////////////////////////////////////////////////////////////////////////// // // @@ -163,14 +102,14 @@ enum tetgenmesh::interresult point pa, pb, pc, pd; enum {HMOVE, RMOVE, LMOVE} nextmove; REAL hori, rori, lori; + int t1ver; int s; // The origin is fixed. pa = org(*searchtet); if ((point) searchtet->tet[7] == dummypoint) { // A hull tet. Choose the neighbor of its base face. - searchtet->ver = 11; - fsymself(*searchtet); + decode(searchtet->tet[3], *searchtet); // Reset the origin to be pa. if ((point) searchtet->tet[4] == pa) { searchtet->ver = 11; @@ -194,8 +133,7 @@ enum tetgenmesh::interresult pc = apex(*searchtet); if (pc == endpt) { // pa->pc is the search edge. - eprevself(*searchtet); - esymself(*searchtet); + eprevesymself(*searchtet); return ACROSSVERT; } @@ -203,12 +141,6 @@ enum tetgenmesh::interresult while (1) { pd = oppo(*searchtet); - - if (b->verbose > 3) { - printf(" From tet (%d, %d, %d, %d) to %d.\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(endpt)); - } - // Check whether the opposite vertex is 'endpt'. if (pd == endpt) { // pa->pd is the search edge. @@ -248,8 +180,8 @@ enum tetgenmesh::interresult } } else { // Two tets, below horizon and below right, are viable. - s = randomnation(2); - if (s == 0) { + //s = randomnation(2); + if (randomnation(2)) { nextmove = HMOVE; } else { nextmove = RMOVE; @@ -258,8 +190,8 @@ enum tetgenmesh::interresult } else { if (lori > 0) { // Two tets, below horizon and below left, are viable. - s = randomnation(2); - if (s == 0) { + //s = randomnation(2); + if (randomnation(2)) { nextmove = HMOVE; } else { nextmove = LMOVE; @@ -273,8 +205,8 @@ enum tetgenmesh::interresult if (rori > 0) { if (lori > 0) { // Two tets, below right and below left, are viable. - s = randomnation(2); - if (s == 0) { + //s = randomnation(2); + if (randomnation(2)) { nextmove = RMOVE; } else { nextmove = LMOVE; @@ -296,8 +228,7 @@ enum tetgenmesh::interresult } if (lori == 0) { // pa->'endpt' is COLLINEAR with pa->pc. - eprevself(*searchtet); - esymself(*searchtet); // [a,c,d] + eprevesymself(*searchtet); // // [a,c,d] return ACROSSVERT; } // pa->'endpt' crosses the edge pb->pc. @@ -317,8 +248,7 @@ enum tetgenmesh::interresult } if (lori == 0) { // pa->'endpt' crosses the edge pc->pd. - eprevself(*searchtet); - esymself(*searchtet); // face acd + eprevesymself(*searchtet); // [a,c,d] return ACROSSEDGE; } // pa->'endpt' crosses the face bcd. @@ -348,25 +278,22 @@ enum tetgenmesh::interresult /////////////////////////////////////////////////////////////////////////////// // // -// scoutsegment() Look for a given segment in the tetrahedralization T. // +// scoutsegment() Search an edge in the tetrahedralization. // // // -// Search an edge in the tetrahedralization that matches the given segmment. // -// If such an edge exists, the segment is 'locked' at the edge. 'searchtet' // -// returns this (constrained) edge. Otherwise, the segment is missing. // +// If the edge is found, it returns SHAREEDGE, and 'searchtet' returns the // +// edge from startpt to endpt. // // // -// The returned value indicates one of the following cases: // -// - SHAREEDGE, the segment exists and is inserted in T; // -// - ACROSSEDGE, the segment intersects an edge (in 'searchtet'). // -// - ACROSSFACE, the segment crosses a face (in 'searchtet'). // +// If the edge is missing, it returns either ACROSSEDGE or ACROSSFACE, which // +// indicates that the edge intersects an edge or a face. If 'refpt' is NULL,// +// 'searchtet' returns the edge or face. If 'refpt' is not NULL, it returns // +// a vertex which encroaches upon this edge, and 'searchtet' returns a tet // +// which containing 'refpt'. // // // // The following cases can happen when the input PLC is not valid. // -// - ACROSSVERT, the segment intersects a vertex ('refpt'). // -// - ACROSSSEG, the segment intersects a segment(returned by 'searchtet'). // -// - ACROSSSUB, the segment intersects a subface(returned by 'searchtet'). // -// // -// If the returned value is ACROSSEDGE or ACROSSFACE, i.e., the segment is // -// missing, 'refpt' returns the reference point for splitting thus segment, // -// 'searchtet' returns a tet containing the 'refpt'. // +// - ACROSSVERT, the edge intersects a vertex return by the origin of // +// 'searchtet'. // +// - ACROSSSEG, the edge intersects a segment returned by 'searchtet'. // +// - ACROSSSUB, the edge intersects a subface returned by 'searchtet'. // // // /////////////////////////////////////////////////////////////////////////////// @@ -374,18 +301,12 @@ enum tetgenmesh::interresult tetgenmesh::scoutsegment(point startpt, point endpt, triface* searchtet, point* refpt, arraypool* intfacelist) { - triface neightet, reftet; - face checkseg, checksh; - point pa, pb, pc, pd; - badface *bface; + point pd; enum interresult dir; - REAL angmax, ang; - long facecount; - int types[2], poss[4]; - int pos, i, j; + int t1ver; if (b->verbose > 2) { - printf(" Scout seg (%d, %d).\n", pointmark(startpt), pointmark(endpt)); + printf(" Scout seg (%d, %d).\n",pointmark(startpt),pointmark(endpt)); } point2tetorg(startpt, *searchtet); @@ -398,14 +319,13 @@ enum tetgenmesh::interresult return SHAREEDGE; } else { // A point is on the path. - *refpt = pd; + // Let the origin of the searchtet be the vertex. + enextself(*searchtet); + if (refpt) *refpt = pd; return ACROSSVERT; } } // if (dir == ACROSSVERT) - if (b->verbose > 2) { - printf(" Seg is missing.\n"); - } // dir is either ACROSSEDGE or ACROSSFACE. enextesymself(*searchtet); // Go to the opposite face. @@ -413,29 +333,28 @@ enum tetgenmesh::interresult if (dir == ACROSSEDGE) { // Check whether two segments are intersecting. - tsspivot1(*searchtet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(*searchtet)) { return ACROSSSEG; } - across_edge_count++; } else if (dir == ACROSSFACE) { if (checksubfaceflag) { // Check whether a segment and a subface are intersecting. - tspivot(*searchtet, checksh); - if (checksh.sh != NULL) { + if (issubface(*searchtet)) { return ACROSSSUB; } } } if (refpt == NULL) { + // Do not need a reference point. Return. return dir; } - if (b->verbose > 2) { - printf(" Scout a ref-point for it.\n"); - } - facecount = across_face_count; + triface neightet, reftet; + point pa, pb, pc; + REAL angmax, ang; + int types[2], poss[4]; + int pos = 0, i, j; pa = org(*searchtet); angmax = interiorangle(pa, startpt, endpt, NULL); @@ -457,53 +376,10 @@ enum tetgenmesh::interresult // Search intersecting faces along the segment. while (1) { - if (intfacelist != NULL) { - if (dir == ACROSSFACE) { - // Save the intersecting face. - intfacelist->newindex((void **) &bface); - bface->tt = *searchtet; - bface->forg = org(*searchtet); - bface->fdest = dest(*searchtet); - bface->fapex = apex(*searchtet); - // Save the intersection type (ACROSSFACE or ACROSSEDGE). - bface->key = (REAL) dir; - } else { // dir == ACROSSEDGE - i = 0; - if (intfacelist->objects > 0l) { - // Get the last saved one. - bface = (badface *) fastlookup(intfacelist, intfacelist->objects - 1); - if (((enum interresult) (int) bface->key) == ACROSSEDGE) { - // Skip this edge if it is the same as the last saved one. - if (((bface->forg == org(*searchtet)) && - (bface->fdest == dest(*searchtet))) || - ((bface->forg == dest(*searchtet)) && - (bface->fdest == org(*searchtet)))) { - i = 1; // Skip this edge. - } - } - } - if (i == 0) { - // Save this crossing edge. - intfacelist->newindex((void **) &bface); - bface->tt = *searchtet; - bface->forg = org(*searchtet); - bface->fdest = dest(*searchtet); - // bface->fapex = apex(*searchtet); - // Save the intersection type (ACROSSFACE or ACROSSEDGE). - bface->key = (REAL) dir; - } - } - } pd = oppo(*searchtet); assert(pd != dummypoint); // SELF_CHECK - if (b->verbose > 3) { - printf(" Passing face (%d, %d, %d, %d), dir(%d).\n", - pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), - (int) dir); - } - across_face_count++; // Stop if we meet 'endpt'. if (pd == endpt) break; @@ -571,9 +447,6 @@ enum tetgenmesh::interresult enextself(neightet); } pd = org(neightet); - if (b->verbose > 2) { - angmax = interiorangle(pd, startpt, endpt, NULL); - } *refpt = pd; // break; return ACROSSVERT; @@ -588,16 +461,13 @@ enum tetgenmesh::interresult if (dir == ACROSSEDGE) { // Check whether two segments are intersecting. - tsspivot1(*searchtet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(*searchtet)) { return ACROSSSEG; } - across_edge_count++; } else if (dir == ACROSSFACE) { if (checksubfaceflag) { // Check whether a segment and a subface are intersecting. - tspivot(*searchtet, checksh); - if (checksh.sh != NULL) { + if (issubface(*searchtet)) { return ACROSSSUB; } } @@ -611,19 +481,6 @@ enum tetgenmesh::interresult *refpt = NULL; } - // dir is either ACROSSVERT, or ACROSSEDGE, or ACROSSFACE. - if (b->verbose > 2) { - if (*refpt != NULL) { - printf(" Refpt %d (%g), visited %ld faces.\n", pointmark(*refpt), - angmax / PI * 180.0, across_face_count - facecount); - } else { - printf(" No refpt (%g) is found, visited %ld faces.\n", - angmax / PI * 180.0, across_face_count - facecount); - } - } - if (across_face_count - facecount > across_max_count) { - across_max_count = across_face_count - facecount; - } *searchtet = reftet; return dir; @@ -633,180 +490,80 @@ enum tetgenmesh::interresult // // // getsteinerpointonsegment() Get a Steiner point on a segment. // // // +// Return '1' if 'refpt' lies on an adjacent segment of this segment. Other- // +// wise, return '0'. // +// // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) +int tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) { - point ei, ej; - REAL Li, Lj, L, dj, dr; - REAL ti = 0.0, tj = 0.0, t; - int type, eid = 0, i; - - REAL diff, stept = 0.0, L1; - int iter; - - ei = sorg(*seg); - ej = sdest(*seg); - - - if (b->verbose > 2) { - printf(" Get Steiner point on seg [%d (%c), %d (%c)].\n", - pointmark(ei), pointtype(ei) == ACUTEVERTEX ? 'A' : 'N', - pointmark(ej), pointtype(ej) == ACUTEVERTEX ? 'A' : 'N'); - } - - if (b->psc) { - eid = shellmark(*seg); - if (pointtype(ei) != FREESEGVERTEX) { - ti = in->getvertexparamonedge(in->geomhandle, pointmark(ei), eid); - } else { - ti = pointgeomuv(ei, 0); - } - if (pointtype(ej) != FREESEGVERTEX) { - tj = in->getvertexparamonedge(in->geomhandle, pointmark(ej), eid); - } else { - tj = pointgeomuv(ej, 0); - } - } + point ei = sorg(*seg); + point ej = sdest(*seg); + int adjflag = 0, i; if (refpt != NULL) { - if (pointtype(ei) == ACUTEVERTEX) { - if (pointtype(ej) == ACUTEVERTEX) { - // Choose the vertex which is closer to refpt. - Li = distance(ei, refpt); - Lj = distance(ej, refpt); - if (Li > Lj) { - // Swap ei and ej; - sesymself(*seg); - ei = sorg(*seg); - ej = sdest(*seg); - t = ti; - ti = tj; - tj = t; - } - type = 1; - } else { - type = 1; - } - } else { - if (pointtype(ej) == ACUTEVERTEX) { - type = 1; - // Swap ei and ej; - sesymself(*seg); - ei = sorg(*seg); - ej = sdest(*seg); - t = ti; - ti = tj; - tj = t; - } else { - type = 0; - } - } - } else { - type = 0; - } - - if (type == 1) { - L = distance(ei, ej); - Li = distance(ei, refpt); - // Cut the segment by a sphere centered at ei with radius Li. - if (b->psc) { - stept = (tj - ti) / 100.0; - iter = 0; - t = ti + (Li / L) * (tj - ti); - while (1) { - in->getsteineronedge(in->geomhandle, eid, t, steinpt); - L1 = distance(steinpt, ei); - diff = L1 - Li; - if ((fabs(diff) / L) < 1e-3) { - break; - } - if (diff > 0) { - t -= stept; // Move it towards ei. - } else { - t += stept; // Move it towards ej. - } - iter++; - if (iter > 10) { - printf("Warning: Get the right Steiner point failed.\n"); - break; - } - } // while (1) - } else { - t = Li / L; - for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); - } - } - // Avoid creating a too short edge. - dj = distance(steinpt, ej); - dr = distance(steinpt, refpt); - if (dj < dr) { - // Cut the segment by the radius equal to Li / 2. - if (b->psc) { - iter = 0; - t = ti + ((Li / 2.0) / L) * (tj - ti); - while (1) { - in->getsteineronedge(in->geomhandle, eid, t, steinpt); - L1 = distance(steinpt, ei); - diff = L1 - (Li / 2.0); - if ((fabs(diff) / L) < 1e-3) { - break; - } - if (diff > 0) { - t -= stept; // Move it towards ei. - } else { - t += stept; // Move it towards ej. - } - iter++; - if (iter > 10) { - printf("Warning: Get the right Steiner point failed.\n"); - break; - } - } // while (1) - } else { - t = (Li / 2.0) / L; + REAL L, L1, t; + + if (pointtype(refpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(refpt), parentseg); + int sidx1 = getfacetindex(parentseg); + point far_pi = segmentendpointslist[sidx1 * 2]; + point far_pj = segmentendpointslist[sidx1 * 2 + 1]; + int sidx2 = getfacetindex(*seg); + point far_ei = segmentendpointslist[sidx2 * 2]; + point far_ej = segmentendpointslist[sidx2 * 2 + 1]; + if ((far_pi == far_ei) || (far_pj == far_ei)) { + // Create a Steiner point at the intersection of the segment + // [far_ei, far_ej] and the sphere centered at far_ei with + // radius |far_ei - refpt|. + L = distance(far_ei, far_ej); + L1 = distance(far_ei, refpt); + t = L1 / L; for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); + steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); } + adjflag = 1; + } else if ((far_pi == far_ej) || (far_pj == far_ej)) { + L = distance(far_ei, far_ej); + L1 = distance(far_ej, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); + } + adjflag = 1; + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); } - r3count++; } else { - r2count++; + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + } + + // Make sure that steinpt is not too close to ei and ej. + L = distance(ei, ej); + L1 = distance(steinpt, ei); + t = L1 / L; + if ((t < 0.2) || (t > 0.8)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } } } else { // Split the point at the middle. - if (b->psc) { - t = 0.5 * (ti + tj); - in->getsteineronedge(in->geomhandle, eid, t, steinpt); - } else { - t = 0.5; - for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); - } + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); } - r1count++; } - if (b->psc) { - setpointgeomuv(steinpt, 0, t); - setpointgeomtag(steinpt, eid); - } - if (pointtype(steinpt) == UNUSEDVERTEX) { - setpointtype(steinpt, FREESEGVERTEX); - } - - if (b->verbose > 2) { - printf(" Split at t(%g)", t); - if (b->psc) { - printf(", ti(%g), tj(%g)", ti, tj); - } - printf(".\n"); - } + return adjflag; } + /////////////////////////////////////////////////////////////////////////////// // // // delaunizesegments() Recover segments in a DT. // @@ -824,29 +581,18 @@ void tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) void tetgenmesh::delaunizesegments() { triface searchtet, spintet; - face searchsh, checksh; - face sseg, checkseg, *psseg; + face searchsh; + face sseg, *psseg; point refpt, newpt; enum interresult dir; insertvertexflags ivf; - int loc; + int t1ver; - // For reporting PLC problems. - point forg1, fdest1; // The 1st segment. - point forg2, fdest2, fapex2; // The 2nd segment. - // Does this mesh containing subfaces? - if (checksubfaceflag) { - ivf.bowywat = 2; // The mesh is a CDT. - ivf.lawson = 2; // Do flip to recover Delaunayness. - ivf.validflag = 1; // Validation is needed. - } else { - ivf.bowywat = 1; // The mesh is a DT. - ivf.lawson = 0; // No need to do flip. - ivf.validflag = 0; // No need to valid the B-W cavity. - } - - searchsh.sh = NULL; + ivf.bowywat = 1; // Use Bowyer-Watson insertion. + ivf.assignmeshsize = b->metric; + ivf.sloc = (int) ONEDGE; // on 'sseg'. + ivf.sbowywat = 1; // Use Bowyer-Watson insertion. // Loop until 'subsegstack' is empty. while (subsegstack->objects > 0l) { @@ -855,13 +601,9 @@ void tetgenmesh::delaunizesegments() psseg = (face *) fastlookup(subsegstack, subsegstack->objects); sseg = *psseg; - assert(!sinfected(sseg)); // Check if this segment has been recovered. sstpivot1(sseg, searchtet); if (searchtet.tet != NULL) { - // Check if the tet contains the same segment. - tsspivot1(searchtet, checkseg); // SELF_CHECK - assert(checkseg.sh == sseg.sh); continue; // Not a missing segment. } @@ -870,8 +612,7 @@ void tetgenmesh::delaunizesegments() if (dir == SHAREEDGE) { // Found this segment, insert it. - tsspivot1(searchtet, checkseg); // SELF_CHECK - if (checkseg.sh == NULL) { + if (!issubseg(searchtet)) { // Let the segment remember an adjacent tet. sstbond1(sseg, searchtet); // Bond the segment to all tets containing it. @@ -881,7 +622,7 @@ void tetgenmesh::delaunizesegments() fnextself(spintet); } while (spintet.tet != searchtet.tet); } else { - // Collision! Should not happen. + // Collision! Maybe a bug. assert(0); } } else { @@ -892,145 +633,30 @@ void tetgenmesh::delaunizesegments() //setpointtype(newpt, FREESEGVERTEX); getsteinerptonsegment(&sseg, refpt, newpt); - // Start searching from the 'searchtet'. + // Start searching from 'searchtet'. ivf.iloc = (int) OUTSIDE; - //ivf.bowywat; - //ivf.lawson; - ivf.rejflag = 0; - ivf.chkencflag = 0; - ivf.sloc = ivf.iloc; - ivf.sbowywat = ivf.bowywat; - ivf.splitbdflag = 0; - // ivf.validflag - ivf.respectbdflag = 0; - ivf.assignmeshsize = b->metric; // Insert the new point into the tetrahedralization T. // Missing segments and subfaces are queued for recovery. // Note that T is convex (nonconvex = 0). - loc = insertvertex(newpt, &searchtet, &searchsh, &sseg, &ivf); - - assert(loc != (int) ONVERTEX); - if (loc != (int) NEARVERTEX) { + if (insertpoint(newpt, &searchtet, &searchsh, &sseg, &ivf)) { // The new point has been inserted. - if (ivf.lawson > 0) { - // For CDT, use flips to reocver Delaunayness. - lawsonflip3d(newpt, ivf.lawson, 0, 0, 0); - } st_segref_count++; if (steinerleft > 0) steinerleft--; } else { - // The new point is either ON or VERY CLOSE to an existing point. - refpt = point2ppt(newpt); - printf(" !! Avoid to create a short edge (length = %g)\n", - distance(newpt, refpt)); - - // It is probably an input problem. Two possible cases are: - // (1) An input vertex is very close an input segment; or - // (2) Two input segments are nearly intersect each other. - forg1 = farsorg(sseg); - fdest1 = farsdest(sseg); - - if ((pointtype(refpt) == RIDGEVERTEX) || - (pointtype(refpt) == ACUTEVERTEX) || - (pointtype(refpt) == VOLVERTEX)) { - // Case (1) - printf(" !! Point %d is very close to segment (%d, %d).\n", - pointmark(refpt), pointmark(forg1), pointmark(fdest1)); - } else if (pointtype(refpt) == FREESEGVERTEX) { - // Case (2). Find a subsegment contain 'refpt'. - subsegs->traversalinit(); - checkseg.sh = shellfacetraverse(subsegs); - while (checkseg.sh != NULL) { - if (((point) checkseg.sh[3] == refpt) || - ((point) checkseg.sh[4] == refpt)) break; - checkseg.sh = shellfacetraverse(subsegs); - } - assert(checkseg.sh != NULL); - checkseg.shver = 0; - forg2 = farsorg(checkseg); - fdest2 = farsdest(checkseg); - printf(" !! Two segments are very close to each other.\n"); - printf(" 1st: (%d, %d), 2nd: (%d, %d)\n", pointmark(forg1), - pointmark(fdest1), pointmark(forg2), pointmark(fdest2)); - } else { - // Unknown case - assert(0); - } - // Indicate it may be an input problem. - printf(" Short edge length bound is: %g. Tolerance is %g.\n", - b->minedgelength, b->epsilon); - terminatetetgen(4); + assert (ivf.iloc == (enum locateresult) NEARVERTEX); + terminatetetgen(this, 4); } } else { - // The input PLC contains self-intersections. - if (dir == ACROSSVERT) { - // refpt is the vertex intersecting the segment. - forg1 = farsorg(sseg); - fdest1 = farsdest(sseg); - if ((pointtype(refpt) == RIDGEVERTEX) || - (pointtype(refpt) == ACUTEVERTEX) || - (pointtype(refpt) == FACETVERTEX) || - (pointtype(refpt) == VOLVERTEX)) { - printf("Point %d is on segment (%d, %d).\n", - pointmark(refpt), pointmark(forg1), pointmark(fdest1)); - } else if (pointtype(refpt) == FREESEGVERTEX) { - // Case (2). Find a subsegment contain 'refpt'. - subsegs->traversalinit(); - checkseg.sh = shellfacetraverse(subsegs); - while (checkseg.sh != NULL) { - if (((point) checkseg.sh[3] == refpt) || - ((point) checkseg.sh[4] == refpt)) break; - checkseg.sh = shellfacetraverse(subsegs); - } - assert(checkseg.sh != NULL); - checkseg.shver = 0; - forg2 = farsorg(checkseg); - fdest2 = farsdest(checkseg); - printf("Two segments intersect.\n"); - printf(" 1st: (%d, %d), 2nd: (%d, %d)", pointmark(forg1), - pointmark(fdest1), pointmark(forg2), pointmark(fdest2)); - } else if (pointtype(refpt) == FREEFACETVERTEX) { - assert(0); // Report this case. - } - } else if (dir == ACROSSSEG) { - tsspivot1(searchtet, checkseg); - if (!b->quiet) { - printf("Two segments intersect.\n"); - forg1 = farsorg(sseg); - fdest1 = farsdest(sseg); - forg2 = farsorg(checkseg); - fdest2 = farsdest(checkseg); - printf(" 1st: (%d, %d), 2nd: (%d, %d).\n", pointmark(forg1), - pointmark(fdest1), pointmark(forg2), pointmark(fdest2)); - } - } else if (dir == ACROSSSUB) { - tspivot(searchtet, checksh); - if (!b->quiet) { - printf("A segment and a subface intersect.\n"); - forg1 = farsorg(sseg); - fdest1 = farsdest(sseg); - forg2 = sorg(checksh); - fdest2 = sdest(checksh); - fapex2 = sapex(checksh); - printf(" Seg: (%d, %d), Sub: (%d, %d, %d).\n", - pointmark(forg1), pointmark(fdest1), - pointmark(forg2), pointmark(fdest2), pointmark(fapex2)); - } - } else { - // Unknown cases. - assert(0); - } // Indicate it is an input problem. - terminatetetgen(3); + terminatetetgen(this, 3); } } } // while - } /////////////////////////////////////////////////////////////////////////////// // // -// scoutsubface() Look for a given subface in the tetrahedralization T. // +// scoutsubface() Search subface in the tetrahedralization. // // // // 'searchsh' is searched in T. If it exists, it is 'locked' at the face in // // T. 'searchtet' refers to the face. Otherwise, it is missing. // @@ -1046,17 +672,13 @@ enum tetgenmesh::interresult tetgenmesh::scoutsubface(face* searchsh, triface* searchtet) { triface spintet; - face checksh; point pa, pb, pc; enum interresult dir; + int t1ver; pa = sorg(*searchsh); pb = sdest(*searchsh); - if (b->verbose > 2) { - printf(" Scout subface (%d, %d, %d).\n", pointmark(pa), pointmark(pb), - pointmark(sapex(*searchsh))); - } // Get a tet whose origin is a. point2tetorg(pa, *searchtet); @@ -1065,8 +687,10 @@ enum tetgenmesh::interresult if (dir == ACROSSVERT) { // Check validity of a PLC. if (dest(*searchtet) != pb) { - // A vertex lies on the search edge. Return it. + // A vertex lies on the search edge. enextself(*searchtet); + // It is possible a PLC self-intersection problem. + terminatetetgen(this, 3); return TOUCHEDGE; } // The edge exists. Check if the face exists. @@ -1076,8 +700,7 @@ enum tetgenmesh::interresult while (1) { if (apex(spintet) == pc) { // Found a face matching to 'searchsh'! - tspivot(spintet, checksh); - if (checksh.sh == NULL) { + if (!issubface(spintet)) { // Insert 'searchsh'. tsbond(spintet, *searchsh); fsymself(spintet); @@ -1087,11 +710,13 @@ enum tetgenmesh::interresult return SHAREFACE; } else { // Another subface is already inserted. + face checksh; + tspivot(spintet, checksh); assert(checksh.sh != searchsh->sh); // SELF_CHECK // This is possibly an input problem, i.e., two facets overlap. // Report this problem and exit. printf("Warning: Found two facets nearly overlap.\n"); - terminatetetgen(5); + terminatetetgen(this, 5); // unifysubfaces(&checksh, searchsh); *searchtet = spintet; return COLLISIONFACE; @@ -1103,47 +728,39 @@ enum tetgenmesh::interresult } // dir is either ACROSSEDGE or ACROSSFACE. - return dir; //ACROSSTET; + return dir; } /////////////////////////////////////////////////////////////////////////////// // // -// formmissingregion() Form the missing region of a missing subface. // +// formregion() Form the missing region of a missing subface. // // // // 'missh' is a missing subface. From it we form a missing region R which is // -// a collection of missing subfaces connected through adjacent edges. // +// a connected region formed by a set of missing subfaces of a facet. // +// Comment: There should be no segment inside R. // // // -// The missing region R is returned in the array 'missingshs'. All subfaces // -// in R are oriented as 'missh'. The array 'missingshverts' returns all ver- // -// tices of R. All subfaces and vertices of R are marktested. // -// // -// 'adjtets' returns a list of tetrahedra adjacent to R. They are used to // -// search a crossing tetrahedron of R. // -// // -// Many ways are possible to form the missing region. The method used here // -// is to search missing edges in R. Starting from 'missh', its three edges // -// are checked. If one of the edges is missing, then the adjacent subface at // -// this edge is also missing. It is added to the array. By an incrementally // -// broad-first searching, we can find all subfaces of R. // +// 'missingshs' returns the list of subfaces in R. All subfaces in this list // +// are oriented as the 'missh'. 'missingshbds' returns the list of boundary // +// edges (tetrahedral handles) of R. 'missingshverts' returns all vertices // +// of R. They are all pmarktested. // // // +// Except the first one (which is 'missh') in 'missingshs', each subface in // +// this list represents an internal edge of R, i.e., it is missing in the // +// tetrahedralization. Since R may contain interior vertices, not all miss- // +// ing edges can be found by this way. // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::formmissingregion(face* missh, arraypool* missingshs, - arraypool* missingshbds, - arraypool* missingshverts, - arraypool* adjtets) +void tetgenmesh::formregion(face* missh, arraypool* missingshs, + arraypool* missingshbds, arraypool* missingshverts) { - triface searchtet, *parytet; + triface searchtet, spintet; face neighsh, *parysh; + face neighseg, fakeseg; point pa, pb, *parypt; enum interresult dir; + int t1ver; int i, j; - if (b->verbose > 2) { - printf(" Form missing region from subface (%d, %d, %d)\n", - pointmark(sorg(*missh)), pointmark(sdest(*missh)), - pointmark(sapex(*missh))); - } smarktest(*missh); missingshs->newindex((void **) &parysh); *parysh = *missh; @@ -1154,43 +771,22 @@ void tetgenmesh::formmissingregion(face* missh, arraypool* missingshs, for (j = 0; j < 3; j++) { pa = sorg(*missh); pb = sdest(*missh); - // Get a tet whose origin is a. point2tetorg(pa, searchtet); - // Search the edge [a,b]. dir = finddirection(&searchtet, pb); if (dir != ACROSSVERT) { // This edge is missing. Its neighbor is a missing subface. spivot(*missh, neighsh); - assert(neighsh.sh != NULL); if (!smarktested(neighsh)) { // Adjust the face orientation. - if (sorg(neighsh) != pb) { - sesymself(neighsh); - } - if (b->verbose > 3) { - printf(" Add a missing subface (%d, %d, %d)\n", - pointmark(pb), pointmark(pa), pointmark(sapex(neighsh))); - } + if (sorg(neighsh) != pb) sesymself(neighsh); smarktest(neighsh); missingshs->newindex((void **) &parysh); *parysh = neighsh; } } else { - if (dest(searchtet) == pb) { - // Remember an existing edge for searching the first crossing tet. - adjtets->newindex((void **) &parytet); - *parytet = searchtet; - // Found an existing edge, it must be a boundary edge of R. - if (b->verbose > 3) { - printf(" -- A boundary edge (%d, %d)\n", pointmark(pa), - pointmark(pb)); - } - missingshbds->newindex((void **) &parysh); - *parysh = *missh; // It is only queued once. - } else { - // The input PLC has problem. - //assert(0); - terminatetetgen(3); + if (dest(searchtet) != pb) { + // This might be a self-intersection problem. + terminatetetgen(this, 3); } } // Collect the vertices of R. @@ -1203,56 +799,86 @@ void tetgenmesh::formmissingregion(face* missh, arraypool* missingshs, } // j } // i - if (b->verbose > 2) { - printf(" Region has: %ld subfaces, %ld vertices\n", - missingshs->objects, missingshverts->objects); - } + // Get the boundary edges of R. + for (i = 0; i < missingshs->objects; i++) { + missh = (face *) fastlookup(missingshs, i); + for (j = 0; j < 3; j++) { + spivot(*missh, neighsh); + if ((neighsh.sh == NULL) || !smarktested(neighsh)) { + // A boundary edge of R. + // Let the segment point to the adjacent tet. + point2tetorg(sorg(*missh), searchtet); + finddirection(&searchtet, sdest(*missh)); + missingshbds->newindex((void **) &parysh); + *parysh = *missh; + // Check if this edge is a segment. + sspivot(*missh, neighseg); + if (neighseg.sh == NULL) { + // Temporarily create a segment at this edge. + makeshellface(subsegs, &fakeseg); + setsorg(fakeseg, sorg(*missh)); + setsdest(fakeseg, sdest(*missh)); + sinfect(fakeseg); // Mark it as faked. + // Connect it to all tets at this edge. + spintet = searchtet; + while (1) { + tssbond1(spintet, fakeseg); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + neighseg = fakeseg; + } + // Let the segment and the boundary edge point to each other. + ssbond(*missh, neighseg); + sstbond1(neighseg, searchtet); + } + senextself(*missh); + } // j + } // i - if (missingshs->objects > maxregionsize) { - maxregionsize = missingshs->objects; - } // Unmarktest collected missing subfaces. for (i = 0; i < missingshs->objects; i++) { - missh = (face *) fastlookup(missingshs, i); - sunmarktest(*missh); + parysh = (face *) fastlookup(missingshs, i); + sunmarktest(*parysh); } - - // Comment: All vertices in R are pmarktested. } - /////////////////////////////////////////////////////////////////////////////// // // // scoutcrossedge() Search an edge that crosses the missing region. // // // +// Return 1 if a crossing edge is found. It is returned by 'crosstet'. More- // +// over, the edge is oriented such that its origin lies below R. Return 0 // +// if no such edge is found. // +// // // Assumption: All vertices of the missing region are marktested. // // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, +int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* missingshbds, arraypool* missingshs) { - triface *searchtet, spintet; + triface searchtet, spintet; face *parysh; - face checkseg; + face neighseg; point pa, pb, pc, pd, pe; enum interresult dir; REAL ori; int types[2], poss[4]; int searchflag, interflag; + int t1ver; int i, j; - if (b->verbose > 2) { - printf(" Search a crossing edge.\n"); - } searchflag = 0; - for (j = 0; j < adjtets->objects && !searchflag; j++) { - searchtet = (triface *) fastlookup(adjtets, j); + for (j = 0; j < missingshbds->objects && !searchflag; j++) { + parysh = (face *) fastlookup(missingshbds, j); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); interflag = 0; // Let 'spintet' be [#,#,d,e] where [#,#] is the boundary edge of R. - spintet = *searchtet; + spintet = searchtet; while (1) { pd = apex(spintet); pe = oppo(spintet); @@ -1266,7 +892,7 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, pa = sorg(*parysh); pb = sdest(*parysh); pc = sapex(*parysh); - interflag = tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); + interflag=tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); if (interflag > 0) { if (interflag == 2) { // They intersect at a single point. @@ -1274,14 +900,13 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { //pos = poss[0]; // Go to the crossing edge [d,e,#,#]. - eprev(spintet, crosstet); - esymself(crosstet); - enextself(crosstet); // [d,e,#,#]. + edestoppo(spintet, crosstet); // // [d,e,#,#]. // Check if it is a segment. - tsspivot1(crosstet, checkseg); - if (checkseg.sh != NULL) { - reportselfintersect(&checkseg, parysh); - terminatetetgen(3); + if (issubseg(crosstet)) { + //face checkseg; + //tsspivot1(crosstet, checkseg); + //reportselfintersect(&checkseg, parysh); + terminatetetgen(this, 3); } // Adjust the edge such that d lies below [a,b,c]. ori = orient3d(pa, pb, pc, pd); @@ -1289,21 +914,7 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, if (ori < 0) { esymself(crosstet); } - if (b->verbose > 2) { - printf(" Found edge (%d, %d) intersect", pointmark(pd), - pointmark(pe)); - printf(" face (%d, %d, %d)\n", pointmark(pa), pointmark(pb), - pointmark(pc)); - } - // Save the corners of this subface. - plane_pa = pa; - plane_pb = pb; - plane_pc = pc; searchflag = 1; - } else { - // An improper intersection type. - // Maybe it is a PLC problem. - // At the moment, just ignore it. } } break; @@ -1315,11 +926,10 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, if (interflag > 0) break; // Go to the next tetrahedron. fnextself(spintet); - if (spintet.tet == searchtet->tet) break; + if (spintet.tet == searchtet.tet) break; } // while (1) } // j - adjtets->restart(); return searchflag; } @@ -1333,7 +943,6 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, // #] which intersects R in its interior, where the edge [d,e] intersects R, // // and d lies below R. // // // -// // // 'crosstets' returns the set of crossing tets. Every tet in it has the // // form [d,e,#,#] where [d,e] is a crossing edge, and d lies below R. The // // set of tets form the cavity C, which is divided into two parts by R, one // @@ -1342,13 +951,6 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, // in the top part of C, and so does 'botpoints'. Both 'toppoints' and // // 'botpoints' contain vertices of R. // // // -// NOTE: 'toppoints' may contain points which are not vertices of any top // -// faces, and so may 'botpoints'. Such points may belong to other facets and // -// need to be present after the recovery of this cavity (P1029.poly). // -// // -// A pair of boundary faces: 'firsttopface' and 'firstbotface', are saved. // -// They share the same edge in the boundary of the missing region. // -// // // Important: This routine assumes all vertices of the facet containing this // // subface are marked, i.e., pmarktested(p) returns true. // // // @@ -1359,25 +961,22 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, arraypool* botfaces, arraypool* toppoints, arraypool* botpoints) { - arraypool *crossedges, *testededges; + arraypool *crossedges; triface spintet, neightet, *parytet; - face checksh, *parysh = NULL; - face checkseg; // *paryseg; + face *parysh = NULL; point pa, pd, pe, *parypt; enum interresult dir; bool testflag, invalidflag; int types[2], poss[4]; + int t1ver; int i, j, k; // Temporarily re-use 'topfaces' for all crossing edges. crossedges = topfaces; - // Temporarily re-use 'botfaces' for all tested edges. - testededges = botfaces; // Only used by 'b->psc'. if (b->verbose > 2) { - printf(" Form the cavity of missing region.\n"); + printf(" Form the cavity of a missing region.\n"); } - missingsubfacecount += missingshs->objects; // Mark this edge to avoid testing it later. markedge(*searchtet); crossedges->newindex((void **) &parytet); @@ -1386,7 +985,7 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, invalidflag = 0; // Collect all crossing tets. Each cross tet is saved in the standard - // form [d,e,#,#], where [d,e] is a corossing edge, d lies below R. + // form [d,e,#,#], where [d,e] is a crossing edge, d lies below R. // NEITHER d NOR e is a vertex of R (!pmarktested). for (i = 0; i < crossedges->objects; i++) { // Get a crossing edge [d,e,#,#]. @@ -1394,14 +993,12 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, // Sort vertices into the bottom and top arrays. pd = org(*searchtet); - assert(!pmarktested(pd)); // pd is not on R. if (!pinfected(pd)) { pinfect(pd); botpoints->newindex((void **) &parypt); *parypt = pd; } pe = dest(*searchtet); - assert(!pmarktested(pe)); // pe is not on R. if (!pinfected(pe)) { pinfect(pe); toppoints->newindex((void **) &parypt); @@ -1412,11 +1009,6 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, spintet = *searchtet; while (1) { if (!infected(spintet)) { - if (b->verbose > 3) { - printf(" Add a crossing tet (%d, %d, %d, %d)\n", - pointmark(org(spintet)), pointmark(dest(spintet)), - pointmark(apex(spintet)), pointmark(oppo(spintet))); - } infect(spintet); crosstets->newindex((void **) &parytet); *parytet = spintet; @@ -1432,10 +1024,10 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, // spintet is [d,e,a,#], where d lies below R, and e lies above R. pa = apex(spintet); if (pa != dummypoint) { - if (!pmarktested(pa) || b->psc) { - // There exists a crossing edge, either [e,a] or [a,d]. First check - // if the crossing edge has already be added. This is to check if - // a tetrahedron at this edge is marked. + if (!pmarktested(pa)) { + // There exists a crossing edge, either [e,a] or [a,d]. First check + // if the crossing edge has already be added, i.e., check if a + // tetrahedron at this edge is marked. testflag = true; for (j = 0; j < 2 && testflag; j++) { if (j == 0) { @@ -1461,12 +1053,8 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, pe = dest(spintet); for (k = 0; k < missingshs->objects; k++) { parysh = (face *) fastlookup(missingshs, k); - plane_pa = sorg(*parysh); - plane_pb = sdest(*parysh); - plane_pc = sapex(*parysh); - // Test if this face intersects [e,a]. - if (tri_edge_test(plane_pa, plane_pb, plane_pc, pe, pa, - NULL, 1, types, poss)) { + if (tri_edge_test(sorg(*parysh), sdest(*parysh), sapex(*parysh), + pe, pa, NULL, 1, types, poss)) { // Found intersection. 'a' lies below R. enext(spintet, neightet); dir = (enum interresult) types[0]; @@ -1478,9 +1066,8 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, } break; } - // Test if this face intersects [a,d]. - if (tri_edge_test(plane_pa, plane_pb, plane_pc, pa, pd, - NULL, 1, types, poss)) { + if (tri_edge_test(sorg(*parysh), sdest(*parysh), sapex(*parysh), + pa, pd, NULL, 1, types, poss)) { // Found intersection. 'a' lies above R. eprev(spintet, neightet); dir = (enum interresult) types[0]; @@ -1494,47 +1081,28 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, } } // k if (k < missingshs->objects) { - // Found a pair of triangle - edge interseciton. + // Found a pair of triangle - edge intersection. if (invalidflag) { - if (b->verbose > 2) { - printf(" A non-valid subface - edge intersection\n"); + if (!b->quiet) { + printf("Warning: A non-valid facet - edge intersection\n"); printf(" subface: (%d, %d, %d) edge: (%d, %d)\n", - pointmark(plane_pa), pointmark(plane_pb), - pointmark(plane_pc), pointmark(org(neightet)), + pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), + pointmark(sapex(*parysh)), pointmark(org(neightet)), pointmark(dest(neightet))); } // It may be a PLC problem. - terminatetetgen(3); - } else if (b->psc) { - if (pmarktested(pa)) { - // The intersection is invalid. - if (b->verbose > 2) { - printf(" A non-valid subface - edge intersection\n"); - printf(" subface: (%d, %d, %d) edge: (%d, %d)\n", - pointmark(plane_pa), pointmark(plane_pb), - pointmark(plane_pc), pointmark(org(neightet)), - pointmark(dest(neightet))); - } - // Split the subface intersecting this edge. - recentsh = *parysh; - recenttet = neightet; // For point location. - invalidflag = 1; - break; - } // if (pmarktested(pa)) - } // if (b->psc) + terminatetetgen(this, 3); + } // Adjust the edge direction, so that its origin lies below R, // and its destination lies above R. esymself(neightet); // Check if this edge is a segment. - tsspivot1(neightet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(neightet)) { // Invalid PLC! - reportselfintersect(&checkseg, parysh); - terminatetetgen(3); - } - if (b->verbose > 3) { - printf(" Add a crossing edge (%d, %d)\n", - pointmark(org(neightet)), pointmark(dest(neightet))); + //face checkseg; + //tsspivot1(neightet, checkseg); + //reportselfintersect(&checkseg, parysh); + terminatetetgen(this, 3); } // Mark this edge to avoid testing it again. markedge(neightet); @@ -1542,32 +1110,29 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, *parytet = neightet; } else { // No intersection is found. It may be a PLC problem. - //assert(b->psc); - // Mark this edge to avoid testing it again. - //markedge(neightet); - //testededges->newindex((void **) &parytet); - //*parytet = neightet; invalidflag = 1; // Split the subface intersecting [d,e]. for (k = 0; k < missingshs->objects; k++) { parysh = (face *) fastlookup(missingshs, k); - plane_pa = sorg(*parysh); - plane_pb = sdest(*parysh); - plane_pc = sapex(*parysh); // Test if this face intersects [e,a]. - if (tri_edge_test(plane_pa, plane_pb, plane_pc, pd, pe, - NULL, 1, types, poss)) { + if (tri_edge_test(sorg(*parysh),sdest(*parysh),sapex(*parysh), + pd, pe, NULL, 1, types, poss)) { break; } } // k - assert(k < missingshs->objects); + if (k == missingshs->objects) { + // Not found such an edge. + // Arbitrarily choose an edge (except the first) to split. + k = randomnation(missingshs->objects - 1); + parysh = (face *) fastlookup(missingshs, k + 1); + } recentsh = *parysh; recenttet = spintet; // For point location. break; // the while (1) loop } // if (k == missingshs->objects) } // if (testflag) - } // if (!pmarktested(pa) || b->psc) - } + } // if (!pmarktested(pa) || b->psc) + } // if (pa != dummypoint) // Go to the next crossing tet. fnextself(spintet); if (spintet.tet == searchtet->tet) break; @@ -1582,7 +1147,6 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, printf(" Formed cavity: %ld (%ld) cross tets (edges).\n", crosstets->objects, crossedges->objects); } - crossingtetcount += crosstets->objects; // Unmark all marked edges. for (i = 0; i < crossedges->objects; i++) { @@ -1592,17 +1156,6 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, } crossedges->restart(); - if (b->psc) { - // Unmark all marked edges. - for (i = 0; i < testededges->objects; i++) { - searchtet = (triface *) fastlookup(testededges, i); - assert(edgemarked(*searchtet)); // SELF_CHECK - unmarkedge(*searchtet); - } - testededges->restart(); - } else { // only p->plc - assert(testededges->objects == 0l); - } if (invalidflag) { // Unmark all collected tets. @@ -1622,44 +1175,21 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, crosstets->restart(); botpoints->restart(); toppoints->restart(); + + // Randomly split an interior edge of R. + i = randomnation(missingshs->objects - 1); + recentsh = * (face *) fastlookup(missingshs, i); return false; } - // Find a pair of cavity boundary faces from the top and bottom sides of - // the facet each, and they share the same edge. Save them in the - // global variables: firsttopface, firstbotface. They will be used in - // fillcavity() for gluing top and bottom new tets. - for (i = 0; i < crosstets->objects; i++) { - searchtet = (triface *) fastlookup(crosstets, i); - // Crosstet is [d,e,a,b]. - enextesym(*searchtet, spintet); - eprevself(spintet); // spintet is [b,a,e,d] - fsym(spintet, neightet); // neightet is [a,b,e,#] - if (!infected(neightet)) { - // A top face. - firsttopface = neightet; - } else { - continue; // Go to the next cross tet. - } - eprevesym(*searchtet, spintet); - enextself(spintet); // spintet is [a,b,d,e] - fsym(spintet, neightet); // neightet is [b,a,d,#] - if (!infected(neightet)) { - // A bottom face. - firstbotface = neightet; - } else { - continue; - } - break; - } // i - assert(i < crosstets->objects); // SELF_CHECK // Collect the top and bottom faces and the middle vertices. Since all top // and bottom vertices have been infected. Uninfected vertices must be // middle vertices (i.e., the vertices of R). // NOTE 1: Hull tets may be collected. Process them as a normal one. // NOTE 2: Some previously recovered subfaces may be completely inside the - // cavity. In such case, we remove these subfaces from the cavity and put // them into 'subfacstack'. They will be recovered later. + // cavity. In such case, we remove these subfaces from the cavity and put + // them into 'subfacstack'. They will be recovered later. // NOTE 3: Some segments may be completely inside the cavity, e.g., they // attached to a subface which is inside the cavity. Such segments are // put in 'subsegstack'. They will be recovered later. @@ -1669,16 +1199,14 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, for (i = 0; i < crosstets->objects; i++) { searchtet = (triface *) fastlookup(crosstets, i); // searchtet is [d,e,a,b]. - enextesym(*searchtet, spintet); - eprevself(spintet); // spintet is [b,a,e,d] + eorgoppo(*searchtet, spintet); fsym(spintet, neightet); // neightet is [a,b,e,#] if (!infected(neightet)) { // A top face. topfaces->newindex((void **) &parytet); *parytet = neightet; - } - eprevesym(*searchtet, spintet); - enextself(spintet); // spintet is [a,b,d,e] + } + edestoppo(*searchtet, spintet); fsym(spintet, neightet); // neightet is [b,a,d,#] if (!infected(neightet)) { // A bottom face. @@ -1743,19 +1271,17 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, arraypool *cavshells, arraypool *newtets, arraypool *crosstets, arraypool *misfaces) { - triface searchtet, neightet, spintet, *parytet, *parytet1; - face checksh, tmpsh, *parysh; - face checkseg; + triface searchtet, neightet, *parytet, *parytet1; + face tmpsh, *parysh; point pa, pb, pc, pd, pt[3], *parypt; enum interresult dir; insertvertexflags ivf; - REAL ori; //, ang, len; + REAL ori; long baknum, bakhullsize; int bakchecksubsegflag, bakchecksubfaceflag; - //int iloc; + int t1ver; int i, j; - if (b->verbose > 2) { printf(" Delaunizing cavity: %ld points, %ld faces.\n", cavpoints->objects, cavfaces->objects); @@ -1769,31 +1295,38 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, checksubsegflag = 0; checksubfaceflag = 0; b->verbose--; // Suppress informations for creating Delaunay tetra. - b->plc = 0; // Do not do unifypoint(); + b->plc = 0; // Do not check near vertices. + + ivf.bowywat = 1; // Use Bowyer-Watson algorithm. // Get four non-coplanar points (no dummypoint). - parytet = (triface *) fastlookup(cavfaces, 0); - pa = org(*parytet); - pb = dest(*parytet); - pc = apex(*parytet); + pa = pb = pc = NULL; + for (i = 0; i < cavfaces->objects; i++) { + parytet = (triface *) fastlookup(cavfaces, i); + parytet->ver = epivot[parytet->ver]; + if (apex(*parytet) != dummypoint) { + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + break; + } + } pd = NULL; - for (i = 1; i < cavfaces->objects; i++) { + for (; i < cavfaces->objects; i++) { parytet = (triface *) fastlookup(cavfaces, i); pt[0] = org(*parytet); pt[1] = dest(*parytet); pt[2] = apex(*parytet); for (j = 0; j < 3; j++) { if (pt[j] != dummypoint) { // Do not include a hull point. - // if (!pinfected(pt[j])) { - ori = orient3d(pa, pb, pc, pt[j]); - if (ori != 0) { - pd = pt[j]; - if (ori > 0) { // Swap pa and pb. - pt[j] = pa; pa = pb; pb = pt[j]; - } - break; + ori = orient3d(pa, pb, pc, pt[j]); + if (ori != 0) { + pd = pt[j]; + if (ori > 0) { // Swap pa and pb. + pt[j] = pa; pa = pb; pb = pt[j]; } - // } + break; + } } } if (pd != NULL) break; @@ -1806,15 +1339,13 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, // Incrementally insert the vertices (duplicated vertices are ignored). for (i = 0; i < cavpoints->objects; i++) { pt[0] = * (point *) fastlookup(cavpoints, i); - assert(pt[0] != dummypoint); // SELF_CHECK searchtet = recenttet; ivf.iloc = (int) OUTSIDE; - ivf.bowywat = 1; - insertvertex(pt[0], &searchtet, NULL, NULL, &ivf); + insertpoint(pt[0], &searchtet, NULL, NULL, &ivf); } if (b->verbose > 2) { - printf(" Identfying %ld boundary faces of the cavity.\n", + printf(" Identifying %ld boundary faces of the cavity.\n", cavfaces->objects); } @@ -1825,10 +1356,7 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, parytet = (triface *) fastlookup(cavfaces, i); // Skip an interior face (due to the enlargement of the cavity). if (infected(*parytet)) continue; - // This face may contain dummypoint (See fig/dum-cavity-case2). - // If so, dummypoint must be its apex. - j = (parytet->ver & 3); // j is the face number. - parytet->ver = epivot[j]; // [4,5,2,11] + parytet->ver = epivot[parytet->ver]; pt[0] = org(*parytet); pt[1] = dest(*parytet); pt[2] = apex(*parytet); @@ -1839,38 +1367,21 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, searchtet.tet = NULL; dir = scoutsubface(&tmpsh, &searchtet); if (dir == SHAREFACE) { - // Inserted. Make sure that tmpsh connects an interior tet of C. - stpivot(tmpsh, neightet); - // neightet and tmpsh refer to the same edge [pt[0], pt[1]]. - // If the origin of neightet is pt[1], it is inside. - if (org(neightet) != pt[1]) { - fsymself(neightet); - assert(org(neightet) == pt[1]); // SELF_CHECK - // Make sure that tmpsh is connected with an interior tet. - sesymself(tmpsh); - tsbond(neightet, tmpsh); - } - assert(dest(neightet) == pt[0]); // SELF_CHECK - } else if (dir == COLLISIONFACE) { - // This case is not possible anymore. 2010-02-01 - assert(0); - } else { - if (b->verbose > 2) { - printf(" bdry face (%d, %d, %d) -- %d is missing\n", - pointmark(pt[0]), pointmark(pt[1]), pointmark(pt[2]), i); - } + // Inserted! 'tmpsh' must face toward the inside of the cavity. + // Remember the boundary tet (outside the cavity) in tmpsh + // (use the adjacent tet slot). + tmpsh.sh[0] = (shellface) encode(*parytet); + // Save this subface. + cavshells->newindex((void **) &parysh); + *parysh = tmpsh; + } + else { + // This boundary face is missing. shellfacedealloc(subfaces, tmpsh.sh); // Save this face in list. misfaces->newindex((void **) &parytet1); *parytet1 = *parytet; - continue; } - // Remember the boundary tet (outside the cavity) in tmpsh - // (use the adjacent tet slot). - tmpsh.sh[0] = (shellface) encode(*parytet); - // Save this subface. - cavshells->newindex((void **) &parysh); - *parysh = tmpsh; } // i if (misfaces->objects > 0) { @@ -1879,7 +1390,7 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, misfaces->objects); } - // Removing all tempoaray subfaces. + // Removing all temporary subfaces. for (i = 0; i < cavshells->objects; i++) { parysh = (face *) fastlookup(cavshells, i); stpivot(*parysh, neightet); @@ -1910,11 +1421,7 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, if (!pinfected(pd)) { searchtet = recenttet; ivf.iloc = (int) OUTSIDE; - ivf.bowywat = 1; - insertvertex(pd, &searchtet, NULL, NULL, &ivf); - if (b->verbose > 2) { - printf(" Add point %d into list.\n", pointmark(pd)); - } + insertpoint(pd, &searchtet, NULL, NULL, &ivf); pinfect(pd); cavpoints->newindex((void **) &parypt); *parypt = pd; @@ -1924,15 +1431,9 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, esym(*parytet, neightet); fsymself(neightet); if (!infected(neightet)) { - if (b->verbose > 2) { - printf(" Add a cavface (%d, %d, %d).\n", - pointmark(org(neightet)), pointmark(dest(neightet)), - pointmark(apex(neightet))); - } cavfaces->newindex((void **) &parytet1); *parytet1 = neightet; - } else { - } + } enextself(*parytet); } // j } // if (!infected(parytet)) @@ -1958,8 +1459,8 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, *parytet = recenttet; for (i = 0; i < newtets->objects; i++) { searchtet = * (triface *) fastlookup(newtets, i); - for (searchtet.ver = 0; searchtet.ver < 4; searchtet.ver++) { - fsym(searchtet, neightet); + for (j = 0; j < 4; j++) { + decode(searchtet.tet[j], neightet); if (!marktested(neightet)) { marktest(neightet); newtets->newindex((void **) &parytet); @@ -1971,9 +1472,6 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, cavpoints->restart(); cavfaces->restart(); - if (cavshells->objects > maxcavsize) { - maxcavsize = cavshells->objects; - } if (crosstets->objects > baknum) { // The cavity has been enlarged. cavityexpcount++; @@ -2001,112 +1499,131 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, /////////////////////////////////////////////////////////////////////////////// bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, - arraypool* midfaces, arraypool* missingshs) + arraypool* midfaces, arraypool* missingshs, + arraypool* topnewtets, arraypool* botnewtets, + triface* crossedge) { arraypool *cavshells; - triface *parytet, bdrytet, toptet, bottet, midface; - triface neightet, spintet; - face checksh, *parysh; + triface bdrytet, neightet, *parytet; + triface searchtet, spintet; + face *parysh; face checkseg; - point pa, pb, pc, pf, pg; //, *pts; - int types[2], poss[4]; - //REAL elen[3]; //ori, len, n[3]; - bool mflag, bflag; - int i, j, k; + point pa, pb, pc; + bool mflag; + int t1ver; + int i, j; // Connect newtets to tets outside the cavity. These connections are needed // for identifying the middle faces (which belong to R). - for (k = 0; k < 2; k++) { - cavshells = (k == 0 ? topshells : botshells); + for (j = 0; j < 2; j++) { + cavshells = (j == 0 ? topshells : botshells); if (cavshells != NULL) { for (i = 0; i < cavshells->objects; i++) { // Get a temp subface. parysh = (face *) fastlookup(cavshells, i); - // Get the boundary tet outside the cavity. + // Get the boundary tet outside the cavity (saved in sh[0]). decode(parysh->sh[0], bdrytet); pa = org(bdrytet); pb = dest(bdrytet); pc = apex(bdrytet); - // Get the adjacent new tet. + // Get the adjacent new tet inside the cavity. stpivot(*parysh, neightet); - assert(org(neightet) == pb); // SELF_CHECK - assert(dest(neightet) == pa); // SELF_CHECK - // Mark neightet as an interior tet of this cavity, 2009-04-24. - // Comment: We know neightet is an interior tet. - if (!infected(neightet)) { - infect(neightet); - } - assert(oppo(bdrytet) != NULL); // No faked tet. - // if (oppo(bdrytet) != NULL) { - // Bond the two tets. - bond(bdrytet, neightet); // Also cleared the pointer to tmpsh. - // } + // Mark neightet as an interior tet of this cavity. + infect(neightet); + // Connect the two tets (the old connections are replaced). + bond(bdrytet, neightet); tsdissolve(neightet); // Clear the pointer to tmpsh. // Update the point-to-tets map. - setpoint2tet(pa, encode(neightet)); - setpoint2tet(pb, encode(neightet)); - setpoint2tet(pc, encode(neightet)); - // Delete the temp subface. - // shellfacedealloc(subfacepool, parysh->sh); + setpoint2tet(pa, (tetrahedron) neightet.tet); + setpoint2tet(pb, (tetrahedron) neightet.tet); + setpoint2tet(pc, (tetrahedron) neightet.tet); } // i } // if (cavshells != NULL) - } // k + } // j - mflag = true; // Initialize it. + if (crossedge != NULL) { + // Glue top and bottom tets at their common facet. + triface toptet, bottet, spintet, *midface; + point pd, pe; + REAL ori; + int types[2], poss[4]; + int interflag; + int bflag; - if (midfaces != NULL) { + mflag = false; + pd = org(*crossedge); + pe = dest(*crossedge); + + // Search the first (middle) face in R. + // Since R may be non-convex, we must make sure that the face is in the + // interior of R. We search a face in 'topnewtets' whose three vertices + // are on R and it intersects 'crossedge' in its interior. Then search + // a matching face in 'botnewtets'. + for (i = 0; i < topnewtets->objects && !mflag; i++) { + searchtet = * (triface *) fastlookup(topnewtets, i); + for (searchtet.ver = 0; searchtet.ver < 4 && !mflag; searchtet.ver++) { + pa = org(searchtet); + if (pmarktested(pa)) { + pb = dest(searchtet); + if (pmarktested(pb)) { + pc = apex(searchtet); + if (pmarktested(pc)) { + // Check if this face intersects [d,e]. + interflag = tri_edge_test(pa,pb,pc,pd,pe,NULL,1,types,poss); + if (interflag == 2) { + // They intersect at a single point. Found. + toptet = searchtet; + // The face lies in the interior of R. + // Get the tet (in topnewtets) which lies above R. + ori = orient3d(pa, pb, pc, pd); + assert(ori != 0); + if (ori < 0) { + fsymself(toptet); + pa = org(toptet); + pb = dest(toptet); + } + // Search the face [b,a,c] in 'botnewtets'. + for (j = 0; j < botnewtets->objects; j++) { + neightet = * (triface *) fastlookup(botnewtets, j); + // Is neightet contains 'b'. + if ((point) neightet.tet[4] == pb) { + neightet.ver = 11; + } else if ((point) neightet.tet[5] == pb) { + neightet.ver = 3; + } else if ((point) neightet.tet[6] == pb) { + neightet.ver = 7; + } else if ((point) neightet.tet[7] == pb) { + neightet.ver = 0; + } else { + continue; + } + // Is the 'neightet' contains edge [b,a]. + if (dest(neightet) == pa) { + // 'neightet' is just the edge. + } else if (apex(neightet) == pa) { + eprevesymself(neightet); + } else if (oppo(neightet) == pa) { + esymself(neightet); + enextself(neightet); + } else { + continue; + } + // Is 'neightet' the face [b,a,c]. + if (apex(neightet) == pc) { + bottet = neightet; + mflag = true; + break; + } + } // j + } // if (interflag == 2) + } // pc + } // pb + } // pa + } // toptet.ver + } // i - // The first pair of top and bottom tets share the same edge [a, b]. - // toptet = * (triface *) fastlookup(topfaces, 0); - if (infected(firsttopface)) { - // This is due to he enlargement of the cavity. Find the updated top - // boundary face at edge [a,b]. - // Comment: An uninfected tet at [a,b] should be found since [a,b] is a - // boundary edge of the missing region R. It should not be enclosed - // by the enlarged cavity. - pa = apex(firsttopface); // SELF_CHECK - while (1) { - fnextself(firsttopface); - if (!infected(firsttopface)) break; - assert(apex(firsttopface) != pa); // SELF_CHECK - } - } - toptet = firsttopface; - pa = apex(toptet); - fsymself(toptet); - // Search a subface from the top mesh. - while (1) { - esymself(toptet); // The next face in the same tet. - pc = apex(toptet); - assert(pc != pa); // We should not return to the starting point. - if (pmarktested(pc)) break; // [a,b,c] is a subface. - fsymself(toptet); // Go to the adjacent tet. - } - // Search the subface [a,b,c] in the bottom mesh. - // bottet = * (triface *) fastlookup(botfaces, 0); - if (infected(firstbotface)) { - pa = apex(firstbotface); // SELF_CHECK - while (1) { - fnextself(firstbotface); - if (!infected(firstbotface)) break; - assert(apex(firstbotface) != pa); // SELF_CHECK - } - } - bottet = firstbotface; - pa = apex(bottet); - fsymself(bottet); - while (1) { - esymself(bottet); // The next face in the same tet. - pf = apex(bottet); - assert(pf != pa); // We should not return to the starting point. - if (pf == pc) break; // Face matched. - if (pmarktested(pf)) { - mflag = false; break; // Not matched. - } - fsymself(bottet); - } if (mflag) { - // Connect the two tets together. + // Found a pair of matched faces in 'toptet' and 'bottet'. bond(toptet, bottet); // Both are interior tets. infect(toptet); @@ -2115,20 +1632,23 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, markface(toptet); midfaces->newindex((void **) &parytet); *parytet = toptet; + } else { + // No pair of 'toptet' and 'bottet'. + toptet.tet = NULL; + // Randomly split an interior edge of R. + i = randomnation(missingshs->objects - 1); + recentsh = * (face *) fastlookup(missingshs, i); } - // Match pairs of subfaces (middle faces), connect top and bottom tets. + // Find other middle faces, connect top and bottom tets. for (i = 0; i < midfaces->objects && mflag; i++) { // Get a matched middle face [a, b, c] - midface = * (triface *) fastlookup(midfaces, i); + midface = (triface *) fastlookup(midfaces, i); // The tet must be a new created tet (marktested). - assert(marktested(midface)); // SELF_CHECK - - // Check the neighbors at edges [b, c] and [c, a]. - for (j = 0; j < 2 && mflag; j++) { - enextself(midface); // [b, c] or [c, a]. - pg = apex(midface); - toptet = midface; + assert(marktested(*midface)); // SELF_CHECK + // Check the neighbors at the edges of this face. + for (j = 0; j < 3 && mflag; j++) { + toptet = *midface; bflag = false; while (1) { // Go to the next face in the same tet. @@ -2138,6 +1658,7 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, break; // Find a subface. } if (pc == dummypoint) { + assert(0); // Check this case. break; // Find a subface. } // Go to the adjacent tet. @@ -2151,16 +1672,19 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, if (!bflag) { // assert(marktested(toptet)); // SELF_CHECK if (!facemarked(toptet)) { - fsym(midface, bottet); + fsym(*midface, bottet); + spintet = bottet; while (1) { esymself(bottet); - pf = apex(bottet); - if (pf == pc) break; // Face matched. - if (pmarktested(pf)) { - mflag = false; break; // Not matched - } + pd = apex(bottet); + if (pd == pc) break; // Face matched. fsymself(bottet); - } + if (bottet.tet == spintet.tet) { + // Not found a matched bottom face. + mflag = false; + break; + } + } // while (1) if (mflag) { if (marktested(bottet)) { // Connect two tets together. @@ -2172,79 +1696,185 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, markface(toptet); midfaces->newindex((void **) &parytet); *parytet = toptet; - } else { - // The 'bottet' is not inside the cavity! - // This case can happen when the cavity was enlarged, and the - // 'toptet' is a co-facet (sub)face adjacent to the missing - // region, and it is a boundary face of the top cavity. - // So the toptet and bottet should be bonded already through - // a temp subface. See fig/dump-cavity-case18. Check it. - fsym(toptet, neightet); - assert(neightet.tet == bottet.tet); // SELF_CHECK - assert(neightet.ver == bottet.ver); // SELF_CHECK - // Do not add this face into 'midfaces'. } - } + } else { // mflag == false + // Adjust 'toptet' and 'bottet' to be the crossing edges. + fsym(*midface, bottet); + spintet = bottet; + while (1) { + esymself(bottet); + pd = apex(bottet); + if (pmarktested(pd)) { + // assert(pd != pc); + // Let 'toptet' be [a,b,c,#], and 'bottet' be [b,a,d,*]. + // Adjust 'toptet' and 'bottet' to be the crossing edges. + // Test orient3d(b,c,#,d). + ori = orient3d(dest(toptet), pc, oppo(toptet), pd); + if (ori < 0) { + // Edges [a,d] and [b,c] cross each other. + enextself(toptet); // [b,c] + enextself(bottet); // [a,d] + } else if (ori > 0) { + // Edges [a,c] and [b,d] cross each other. + eprevself(toptet); // [c,a] + eprevself(bottet); // [d,b] + } else { + // b,c,#,and d are coplanar!. + assert(0); + } + break; // Not matched + } + fsymself(bottet); + assert (bottet.tet != spintet.tet); + } + } // if (!mflag) } // if (!facemarked(toptet)) - } + } // if (!bflag) + enextself(*midface); } // j } // i - } // if (midfaces != NULL) - - if (mflag) { - if (midfaces != NULL) { + if (mflag) { if (b->verbose > 2) { printf(" Found %ld middle subfaces.\n", midfaces->objects); } - if (midfaces->objects > maxregionsize) { - maxregionsize = midfaces->objects; - } - // Unmark middle faces. + face oldsh, newsh, casout, casin, neighsh; + + oldsh = * (face *) fastlookup(missingshs, 0); + + // Create new subfaces to fill the region R. for (i = 0; i < midfaces->objects; i++) { // Get a matched middle face [a, b, c] - midface = * (triface *) fastlookup(midfaces, i); - assert(facemarked(midface)); // SELF_CHECK - unmarkface(midface); + midface = (triface *) fastlookup(midfaces, i); + unmarkface(*midface); + makeshellface(subfaces, &newsh); + setsorg(newsh, org(*midface)); + setsdest(newsh, dest(*midface)); + setsapex(newsh, apex(*midface)); + // The new subface gets its markers from the old one. + setshellmark(newsh, shellmark(oldsh)); + if (checkconstraints) { + setareabound(newsh, areabound(oldsh)); + } + // Connect the new subface to adjacent tets. + tsbond(*midface, newsh); + fsym(*midface, neightet); + sesymself(newsh); + tsbond(neightet, newsh); } - } - } else { - // Faces at top and bottom are not matched. There exists non-Delaunay - // subedges. See fig/dump-cavity-case5.lua. - pa = org(toptet); - pb = dest(toptet); - pc = apex(toptet); - pf = apex(bottet); - pf = oppo(toptet); - pg = oppo(bottet); - // Find a subface in R which intersects the edge [f,g]. - for (i = 0; i < missingshs->objects; i++) { - parysh = (face *) fastlookup(missingshs, i); - pa = sorg(*parysh); - pb = sdest(*parysh); - pc = sapex(*parysh); - if (tri_edge_test(pa, pb, pc, pf, pg, NULL, 1, types, poss)) { - // Found a subface. - break; + // Connect new subfaces together and to the bdry of R. + // Delete faked segments. + for (i = 0; i < midfaces->objects; i++) { + // Get a matched middle face [a, b, c] + midface = (triface *) fastlookup(midfaces, i); + for (j = 0; j < 3; j++) { + tspivot(*midface, newsh); + spivot(newsh, casout); + if (casout.sh == NULL) { + // Search its neighbor. + fnext(*midface, searchtet); + while (1) { + // (1) First check if this side is a bdry edge of R. + tsspivot1(searchtet, checkseg); + if (checkseg.sh != NULL) { + // It's a bdry edge of R. + assert(!infected(searchtet)); // It must not be a cavity tet. + // Get the old subface. + checkseg.shver = 0; + spivot(checkseg, oldsh); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + checkseg.sh = NULL; + } + spivot(oldsh, casout); + if (casout.sh != NULL) { + casin = casout; + if (checkseg.sh != NULL) { + // Make sure that the subface has the right ori at the + // segment. + checkseg.shver = 0; + if (sorg(newsh) != sorg(checkseg)) { + sesymself(newsh); + } + spivot(casin, neighsh); + while (neighsh.sh != oldsh.sh) { + casin = neighsh; + spivot(casin, neighsh); + } + } + sbond1(newsh, casout); + sbond1(casin, newsh); + } + if (checkseg.sh != NULL) { + ssbond(newsh, checkseg); + } + break; + } // if (checkseg.sh != NULL) + // (2) Second check if this side is an interior edge of R. + tspivot(searchtet, neighsh); + if (neighsh.sh != NULL) { + // Found an adjacent subface of newsh (an interior edge). + sbond(newsh, neighsh); + break; + } + fnextself(searchtet); + assert(searchtet.tet != midface->tet); + } // while (1) + } // if (casout.sh == NULL) + enextself(*midface); + } // j + } // i + + // Delete old subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + shellfacedealloc(subfaces, parysh->sh); } - } - - if (i < missingshs->objects) { - // Such subface exist. - recentsh = *parysh; } else { - assert(0); // Debug this case. + if (toptet.tet != NULL) { + // Faces at top and bottom are not matched. + // Choose a Steiner point in R. + // Split one of the crossing edges. + pa = org(toptet); + pb = dest(toptet); + pc = org(bottet); + pd = dest(bottet); + // Search an edge in R which is either [a,b] or [c,d]. + // Reminder: Subfaces in this list 'missingshs', except the first + // one, represents an interior edge of R. + for (i = 1; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + if (((sorg(*parysh) == pa) && (sdest(*parysh) == pb)) || + ((sorg(*parysh) == pb) && (sdest(*parysh) == pa))) break; + if (((sorg(*parysh) == pc) && (sdest(*parysh) == pd)) || + ((sorg(*parysh) == pd) && (sdest(*parysh) == pc))) break; + } + if (i < missingshs->objects) { + // Found. Return it. + recentsh = *parysh; + } else { + assert(0); + } + } } - - // Set a tet for searching the new point. - recenttet = firsttopface; - } + midfaces->restart(); + } else { + mflag = true; + } // Delete the temp subfaces. - for (k = 0; k < 2; k++) { - cavshells = (k == 0 ? topshells : botshells); + for (j = 0; j < 2; j++) { + cavshells = (j == 0 ? topshells : botshells); if (cavshells != NULL) { for (i = 0; i < cavshells->objects; i++) { parysh = (face *) fastlookup(cavshells, i); @@ -2257,9 +1887,6 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, if (botshells != NULL) { botshells->restart(); } - if (midfaces != NULL) { - midfaces->restart(); - } return mflag; } @@ -2274,10 +1901,12 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, arraypool *botnewtets) { arraypool *newtets; + shellface *sptr, *ssptr; triface *parytet, *pnewtet, newtet, neightet, spintet; face checksh, *parysh; face checkseg, *paryseg; - int i, j, k; + int t1ver; + int i, j; if (b->verbose > 2) { printf(" Carve cavity: %ld old tets.\n", crosstets->objects); @@ -2293,38 +1922,44 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // Collect all subfaces and segments which attached to the old tets. for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); - assert(infected(*parytet)); // SELF_CHECK - for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { - tspivot(*parytet, checksh); - if (checksh.sh != NULL) { - if (!sinfected(checksh)) { - sinfect(checksh); - cavetetshlist->newindex((void **) &parysh); - *parysh = checksh; + if ((sptr = (shellface*) parytet->tet[9]) != NULL) { + for (j = 0; j < 4; j++) { + if (sptr[j]) { + sdecode(sptr[j], checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + cavetetshlist->newindex((void **) &parysh); + *parysh = checksh; + } } - } + } // j } - for (j = 0; j < 6; j++) { - parytet->ver = edge2ver[j]; - tsspivot1(*parytet, checkseg); - if (checkseg.sh != NULL) { - if (!sinfected(checkseg)) { - sinfect(checkseg); - cavetetseglist->newindex((void **) &paryseg); - *paryseg = checkseg; + if ((ssptr = (shellface*) parytet->tet[8]) != NULL) { + for (j = 0; j < 6; j++) { + if (ssptr[j]) { + sdecode(ssptr[j], checkseg); + // Skip a deleted segment (was a faked segment) + if (checkseg.sh[3] != NULL) { + if (!sinfected(checkseg)) { + sinfect(checkseg); + cavetetseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } } - } + } // j } } // i + // Uninfect collected subfaces. for (i = 0; i < cavetetshlist->objects; i++) { - checksh = * (face *) fastlookup(cavetetshlist, i); - suninfect(checksh); + parysh = (face *) fastlookup(cavetetshlist, i); + suninfect(*parysh); } // Uninfect collected segments. for (i = 0; i < cavetetseglist->objects; i++) { - checkseg = * (face *) fastlookup(cavetetseglist, i); - suninfect(checkseg); + paryseg = (face *) fastlookup(cavetetseglist, i); + suninfect(*paryseg); } // Connect subfaces to new tets. @@ -2340,11 +1975,6 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // Does this tet lie inside the cavity. if (infected(neightet)) { checksh = *parysh; - if (b->verbose > 2) { - printf(" Found an interior subface (%d, %d, %d)\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } stdissolve(checksh); caveencshlist->newindex((void **) &parysh); *parysh = checksh; @@ -2358,10 +1988,7 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, tsbond(newtet, *parysh); } } // i - if (b->verbose > 2) { - printf(" %ld (%ld) cavity (interior) subfaces.\n", - cavetetshlist->objects, caveencshlist->objects); - } + for (i = 0; i < cavetetseglist->objects; i++) { checkseg = * (face *) fastlookup(cavetetseglist, i); @@ -2375,10 +2002,6 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, } fnextself(spintet); if (spintet.tet == neightet.tet) { - if (b->verbose > 2) { - printf(" Found an interior seg (%d, %d)\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } sstdissolve1(checkseg); caveencseglist->newindex((void **) &paryseg); *paryseg = checkseg; @@ -2396,10 +2019,7 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, } } } // i - if (b->verbose > 2) { - printf(" %ld (%ld) cavity (interior) segments.\n", - cavetetseglist->objects, caveencseglist->objects); - } + cavetetshlist->restart(); cavetetseglist->restart(); @@ -2407,6 +2027,9 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // Delete the old tets in cavity. for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); + if (ishulltet(*parytet)) { + hullsize--; + } tetrahedrondealloc(parytet->tet); } @@ -2414,8 +2037,8 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // Collect new tets in cavity. Some new tets have already been found // (and infected) in the fillcavity(). We first collect them. - for (k = 0; k < 2; k++) { - newtets = (k == 0 ? topnewtets : botnewtets); + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); if (newtets != NULL) { for (i = 0; i < newtets->objects; i++) { parytet = (triface *) fastlookup(newtets, i); @@ -2425,20 +2048,17 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, } } // i } - } // k + } // j // Now we collect all new tets in cavity. for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); - if (i == 0) { - recenttet = *parytet; // Remember a live handle. - } for (j = 0; j < 4; j++) { decode(parytet->tet[j], neightet); if (marktested(neightet)) { // Is it a new tet? if (!infected(neightet)) { // Find an interior tet. - assert((point) neightet.tet[7] != dummypoint); // SELF_CHECK + //assert((point) neightet.tet[7] != dummypoint); // SELF_CHECK infect(neightet); crosstets->newindex((void **) &pnewtet); *pnewtet = neightet; @@ -2447,9 +2067,12 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, } // j } // i + parytet = (triface *) fastlookup(crosstets, 0); + recenttet = *parytet; // Remember a live handle. + // Delete outer new tets. - for (k = 0; k < 2; k++) { - newtets = (k == 0 ? topnewtets : botnewtets); + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); if (newtets != NULL) { for (i = 0; i < newtets->objects; i++) { parytet = (triface *) fastlookup(newtets, i); @@ -2457,6 +2080,9 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // This is an interior tet. uninfect(*parytet); unmarktest(*parytet); + if (ishulltet(*parytet)) { + hullsize++; + } } else { // An outer tet. Delete it. tetrahedrondealloc(parytet->tet); @@ -2479,21 +2105,19 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, - arraypool *botnewtets) + arraypool *botnewtets, arraypool *missingshbds) { - triface *parytet, neightet; - face checksh; + triface *parytet, neightet, spintet; + face *parysh; face checkseg; point *ppt; + int t1ver; int i, j; // Reconnect crossing tets to cavity boundary. for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); assert(infected(*parytet)); // SELF_CHECK - if (i == 0) { - recenttet = *parytet; // Remember a live handle. - } parytet->ver = 0; for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { fsym(*parytet, neightet); @@ -2516,6 +2140,31 @@ void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, uninfect(*parytet); } + // Remember a live handle. + recenttet = * (triface *) fastlookup(crosstets, 0); + + // Delete faked segments. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, checkseg); + assert(checkseg.sh != NULL); + if (checkseg.sh[3] != NULL) { + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(*parysh); + //checkseg.sh = NULL; + } + } + } // i + // Delete new tets. for (i = 0; i < topnewtets->objects; i++) { parytet = (triface *) fastlookup(topnewtets, i); @@ -2545,7 +2194,8 @@ void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flipcertify(triface *chkface, badface **pqueue) +void tetgenmesh::flipcertify(triface *chkface,badface **pqueue,point plane_pa, + point plane_pb, point plane_pc) { badface *parybf, *prevbf, *nextbf; triface neightet; @@ -2622,7 +2272,7 @@ void tetgenmesh::flipcertify(triface *chkface, badface **pqueue) for (i = 0; i < 5; i++) { if (pmarktest2ed(p[i])) { // A top point has a positive weight. - w[i] = orient3d(plane_pa, plane_pb, plane_pc, p[i]); + w[i] = orient3dfast(plane_pa, plane_pb, plane_pc, p[i]); if (w[i] < 0) w[i] = -w[i]; assert(w[i] != 0); } else { @@ -2713,19 +2363,20 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, arraypool *botpoints, arraypool *midpoints) { arraypool *crossfaces, *bfacearray; - triface fliptets[5], baktets[2], fliptet, newface; + triface fliptets[6], baktets[2], fliptet, newface; triface neightet, *parytet; face checksh; face checkseg; badface *pqueue; badface *popbf, bface; + point plane_pa, plane_pb, plane_pc; point p1, p2, pd, pe; point *parypt; + flipconstraints fc; REAL ori[3]; int convcount, copcount; int flipflag, fcount; int n, i; - long f23count, f32count, f44count; long totalfcount; @@ -2773,9 +2424,6 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, if (b->verbose > 1) { printf(" Found %ld crossing faces.\n", crossfaces->objects); } - if (crossfaces->objects > maxcrossfacecount) { - maxcrossfacecount = crossfaces->objects; - } for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); @@ -2788,7 +2436,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, for (i = 0; i < crossfaces->objects; i++) { parytet = (triface *) fastlookup(crossfaces, i); - flipcertify(parytet, &pqueue); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); } crossfaces->restart(); @@ -2801,7 +2449,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, // Flip insert the facet. while (pqueue != NULL) { - // Pop a face from the priotity queue. + // Pop a face from the priority queue. popbf = pqueue; bface = *popbf; @@ -2856,7 +2504,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, fliptets[0] = fliptet; // abcd, d may be the new vertex. fliptets[1] = neightet; // bace. - flip23(fliptets, 1, 0, 0); + flip23(fliptets, 1, &fc); // Put the link faces into check list. for (i = 0; i < 3; i++) { eprevesym(fliptets[i], newface); @@ -2902,7 +2550,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, if (n == 3) { // Found a 3-to-2 flip. - flip32(fliptets, 1, 0, 0); + flip32(fliptets, 1, &fc); // Put the link faces into check list. for (i = 0; i < 3; i++) { esym(fliptets[0], newface); @@ -2936,7 +2584,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, baktets[0] = fliptets[2]; // = [b,a,e,f] baktets[1] = fliptets[3]; // = [b,a,f,d] // The flip may involve hull tets. - flip23(fliptets, 1, 0, 0); + flip23(fliptets, 1, &fc); // Put the "outer" link faces into check list. // fliptets[0] = [e,d,a,b] => will be flipped, so // [a,b,d] and [a,b,e] are not "outer" link faces. @@ -2955,7 +2603,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, eprevself(fliptets[0]); // = [b,a,d,c], d is the new vertex. fliptets[1] = baktets[0]; // = [b,a,e,f] fliptets[2] = baktets[1]; // = [b,a,f,d] - flip32(fliptets, 1, 0, 0); + flip32(fliptets, 1, &fc); // Put the "outer" link faces into check list. // fliptets[0] = [d,e,f,a] // fliptets[1] = [e,d,f,b] @@ -2993,14 +2641,13 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, pointmark(bface.fapex), pointmark(bface.foppo), pointmark(bface.noppo), bface.key); } - dbg_ignore_facecount++; } // if (convcount == 1) if (flipflag == 1) { // Update the priority queue. for (i = 0; i < crossfaces->objects; i++) { parytet = (triface *) fastlookup(crossfaces, i); - flipcertify(parytet, &pqueue); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); } crossfaces->restart(); if (1) { // if (!b->flipinsert_random) { @@ -3009,7 +2656,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, parytet = (triface *) fastlookup(bfacearray, i); // This face may be changed. if (!isdeadtet(*parytet)) { - flipcertify(parytet, &pqueue); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); } } bfacearray->restart(); @@ -3034,7 +2681,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, } // 'bfacearray' may be not empty (for what reason ??). - dbg_unflip_facecount += bfacearray->objects; + //dbg_unflip_facecount += bfacearray->objects; assert(flippool->items == 0l); delete bfacearray; @@ -3053,11 +2700,6 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, f32count = flip32count - f32count; f44count = flip44count - f44count; totalfcount = f23count + f32count + f44count; - - if (totalfcount > maxflipsequence) { - maxflipsequence = totalfcount; - } - if (b->verbose > 2) { printf(" Total %ld flips. f23(%ld), f32(%ld), f44(%ld).\n", totalfcount, f23count, f32count, f44count); @@ -3070,156 +2712,85 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, // // // 'missingshs' contains the list of subfaces in R. Moreover, each subface // // (except the first one) in this list represents an interior edge of R. // -// Note: All subfaces in R are smarktested. // // // // Note: We assume that all vertices of R are marktested so we can detect // // new subface by checking the flag in apexes. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::fillregion(arraypool* missingshs, arraypool* missingshbds, +bool tetgenmesh::fillregion(arraypool* missingshs, arraypool* missingshbds, arraypool* newshs) { badface *newflipface, *popface; - triface searchtet, spintet; + triface searchtet, spintet, neightet; face oldsh, newsh, opensh, *parysh; face casout, casin, neighsh, checksh; - face checkseg, fakeseg; - point pc, pd, pe, pf, ppt[2]; - enum interresult dir; - REAL n[3], len; // elen[3]; - bool insideflag; - int types[2], poss[4]; - int i, j, k; + face neighseg, checkseg; + point pc; + int success; + int t1ver; + int i, j; - if (b->verbose > 2) { - printf(" Fill region: %ld old subfaces (%ld).\n", missingshs->objects, - fillregioncount); - } - // Search the first constrained face of R. It is found from the set of - // faces sharing at a boundary edge [a,b]. Such face must be found. - // The search takes the following two steps: - // - First, finds a candidate face [a,b,c] where c is also a vertex of R; - // Note that [a,b,c] may not be the right face to fill R. For instance, - // when R is concave at b. - // - Second, check if [a,b,c] can fill R. This can be checked if an - // adjacent tet of [a,b,c] intersects R. This is a tetrahedron-triangle - // intersection test. It can be reduced to two triangle-edge intersect - // tests, i.e., intersect the two faces not containing the edge [a,b] in - // this tet with all interior edges of R. - - // We start from the first boundary edge of R. - oldsh = * (face *) fastlookup(missingshbds, 0); - ppt[0] = sorg(oldsh); - ppt[1] = sdest(oldsh); - point2tetorg(ppt[0], searchtet); - dir = finddirection(&searchtet, ppt[1]); - assert(dir == ACROSSVERT); // SELF_CHECK - - insideflag = false; - - // Each face has two adjacent tets. - for (k = 0; k < 2; k++) { - if (b->verbose > 2) { - printf(" Search an interior face from edge (%d, %d).\n", - pointmark(ppt[0]), pointmark(ppt[1])); - } + // Search the first new subface to fill the region. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); + j = 0; // Count the number of passes of R. spintet = searchtet; while (1) { pc = apex(spintet); if (pmarktested(pc)) { - // Found a candidate face. Check if it is inside R. - if (missingshs->objects > 2l) { - // pd = oppo(spintet); - // if (pd == dummypoint) { - // Calculate an above point for this subface. - facenormal(ppt[0], ppt[1], pc, n, 1, NULL); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - len = DIST(ppt[0], ppt[1]); - len += DIST(ppt[1], pc); - len += DIST(pc, ppt[0]); - len /= 3.0; - dummypoint[0] = ppt[0][0] + len * n[0]; - dummypoint[1] = ppt[0][1] + len * n[1]; - dummypoint[2] = ppt[0][2] + len * n[2]; - pd = dummypoint; - // } - //if (pd != dummypoint) { - for (j = 0; j < 2 && !insideflag; j++) { - for (i = 1; i < missingshs->objects && !insideflag; i++) { - parysh = (face *) fastlookup(missingshs, i); - // Get an interior edge of R. - pe = sorg(*parysh); - pf = sdest(*parysh); - if (tri_edge_test(ppt[j],pc,pd,pe,pf,NULL,1,types,poss)) { - dir = (enum interresult) types[0]; - if (dir == ACROSSFACE) { - searchtet = spintet; - insideflag = true; - } else if (dir == ACROSSEDGE) { - searchtet = spintet; - insideflag = true; - } - } - } // i - } // j - // } - // if (pd == dummypoint) { - dummypoint[0] = 0; - dummypoint[1] = 0; - dummypoint[2] = 0; - // } - } else { - // It is a simple 2-to-2 flip. - searchtet = spintet; - insideflag = true; - } - } // if (pmarktested(pc)) - if (insideflag) break; + neightet = spintet; + j++; + } fnextself(spintet); if (spintet.tet == searchtet.tet) break; - } // while (1) - if (insideflag) break; - esymself(searchtet); - ppt[0] = org(searchtet); - ppt[1] = dest(searchtet); - } // k + } + assert(j >= 1); + if (j == 1) { + // Found an interior new subface. + searchtet = neightet; + oldsh = *parysh; + break; + } + } // i - if (!insideflag) { - // Something strange is happening. - // Refine the missing region by adding a Steiner point. - recentsh = oldsh; - recenttet = searchtet; // For point location. + if (i == missingshbds->objects) { + // Failed to find any interior subface. + // Need Steiner points. return false; } - // Create a new subface at the boundary edge. - if (b->verbose > 2) { - printf(" Create a new subface (%d, %d, %d)\n", pointmark(ppt[0]), - pointmark(ppt[1]), pointmark(pc)); - } makeshellface(subfaces, &newsh); - setsorg(newsh, ppt[0]); - setsdest(newsh, ppt[1]); - setsapex(newsh, pc); + setsorg(newsh, org(searchtet)); + setsdest(newsh, dest(searchtet)); + setsapex(newsh, apex(searchtet)); // The new subface gets its markers from the old one. setshellmark(newsh, shellmark(oldsh)); if (checkconstraints) { setareabound(newsh, areabound(oldsh)); } // Connect the new subface to adjacent tets. - tspivot(searchtet, checksh); // SELF_CHECK - assert(checksh.sh == NULL); // SELF_CHECK tsbond(searchtet, newsh); fsymself(searchtet); sesymself(newsh); tsbond(searchtet, newsh); // Connect newsh to outer subfaces. sspivot(oldsh, checkseg); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + checkseg.sh = NULL; + } spivot(oldsh, casout); if (casout.sh != NULL) { casin = casout; @@ -3255,367 +2826,474 @@ bool tetgenmesh::fillregion(arraypool* missingshs, arraypool* missingshbds, flipstack = newflipface; } - // Every other boundary edge of R is identified as a segment. Insert a faked - // segments at the place if it is not a segment. - for (i = 1; i < missingshbds->objects; i++) { - parysh = (face *) fastlookup(missingshbds, i); - ppt[0] = sorg(*parysh); - ppt[1] = sdest(*parysh); - point2tetorg(ppt[0], searchtet); - dir = finddirection(&searchtet, ppt[1]); - assert(dir == ACROSSVERT); // SELF_CHECK - tsspivot1(searchtet, checkseg); - if (checkseg.sh == NULL) { - // Insert a fake segment at this tet. - if (b->verbose > 2) { - printf(" Insert a fake segment (%d, %d)\n", pointmark(ppt[0]), - pointmark(ppt[1])); - } - makeshellface(subsegs, &fakeseg); - setsorg(fakeseg, ppt[0]); - setsdest(fakeseg, ppt[1]); - sinfect(fakeseg); // Mark it as faked. - // Connect it to all tets at this edge. - spintet = searchtet; - while (1) { - tssbond1(spintet, fakeseg); - fnextself(spintet); - if (spintet.tet == searchtet.tet) break; - } - checkseg = fakeseg; - } - // Let the segment hold the old subface. - checkseg.shver = 0; - sbond1(checkseg, *parysh); - // Remember it to free it later. - *parysh = checkseg; - } + success = 1; // Loop until 'flipstack' is empty. - while (flipstack != NULL) { - + while ((flipstack != NULL) && success) { // Pop an "open" side from the stack. popface = flipstack; opensh = popface->ss; flipstack = popface->nextitem; // The next top item in stack. flippool->dealloc((void *) popface); - // Process it if it is still open. - spivot(opensh, casout); - if (casout.sh == NULL) { - if (b->verbose > 2) { - printf(" Get an open side (%d, %d) - %d.\n", - pointmark(sorg(opensh)), pointmark(sdest(opensh)), - pointmark(sapex(opensh))); - } - // Search a neighbor to close this side. - stpivot(opensh, searchtet); - tsspivot1(searchtet, checkseg); - if (checkseg.sh == NULL) { - // No segment. It is inside R. Search for a new face to fill in R. - // Note that the face may not be found (see fig 2010-05-25-c). - spintet = searchtet; - fnextself(spintet); // Skip the current face. - while (1) { - pc = apex(spintet); - if (pmarktested(pc)) { - // Found a place for a new subface inside R -- Case (i). + // opensh is either (1) an interior edge or (2) a bdry edge. + stpivot(opensh, searchtet); + tsspivot1(searchtet, checkseg); + if (checkseg.sh == NULL) { + // No segment. It is an interior edge of R. + // Search for a new face in R. + spintet = searchtet; + fnextself(spintet); // Skip the current face. + while (1) { + pc = apex(spintet); + if (pmarktested(pc)) { + // 'opensh' is an interior edge. + if (!issubface(spintet)) { + // Create a new subface. + makeshellface(subfaces, &newsh); + setsorg(newsh, org(spintet)); + setsdest(newsh, dest(spintet)); + setsapex(newsh, pc); + // The new subface gets its markers from its neighbor. + setshellmark(newsh, shellmark(opensh)); + if (checkconstraints) { + setareabound(newsh, areabound(opensh)); + } + // Connect the new subface to adjacent tets. + tsbond(spintet, newsh); + fsymself(spintet); + sesymself(newsh); + tsbond(spintet, newsh); + // Connect newsh to its adjacent subface. + sbond(newsh, opensh); + // Add this new subface into list. + sinfect(newsh); + newshs->newindex((void **) &parysh); + *parysh = newsh; + // Push two "open" side of the new subface into stack. + for (i = 0; i < 2; i++) { + senextself(newsh); + newflipface = (badface *) flippool->alloc(); + newflipface->ss = newsh; + newflipface->nextitem = flipstack; + flipstack = newflipface; + } + } else { + // Connect to another open edge. tspivot(spintet, checksh); - if (checksh.sh == NULL) { - // Create a new subface. - if (b->verbose > 2) { - printf(" Create a new subface (%d, %d, %d)\n", - pointmark(org(spintet)), pointmark(dest(spintet)), - pointmark(pc)); - } - makeshellface(subfaces, &newsh); - setsorg(newsh, org(spintet)); - setsdest(newsh, dest(spintet)); - setsapex(newsh, pc); - // The new subface gets its markers from its neighbor. - setshellmark(newsh, shellmark(opensh)); - if (checkconstraints) { - setareabound(newsh, areabound(opensh)); - } - // Connect the new subface to adjacent tets. - tsbond(spintet, newsh); - fsymself(spintet); - sesymself(newsh); - tsbond(spintet, newsh); - // Connect newsh to its adjacent subface. - sbond(newsh, opensh); - // Add this new subface into list. - sinfect(newsh); - newshs->newindex((void **) &parysh); - *parysh = newsh; - // Push two "open" side of the new subface into stack. - for (i = 0; i < 2; i++) { - senextself(newsh); - newflipface = (badface *) flippool->alloc(); - newflipface->ss = newsh; - newflipface->nextitem = flipstack; - flipstack = newflipface; - } - } else { - // A new subface has already been created. - assert(sinfected(checksh)); // It must be in stack. - spivot(checksh, neighsh); // SELF_CHECK - assert(neighsh.sh == NULL); // Its side must be open. - if (b->verbose > 2) { - printf(" Connect to another open side (%d, %d, %d)\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - sbond(opensh, checksh); // Simply connect them. - } - break; // -- Case (i) + sbond(opensh, checksh); } + break; + } // if (pmarktested(pc)) + fnextself(spintet); + if (spintet.tet == searchtet.tet) { + // Not find any face to fill in R at this side. + // Suggest a point to split the edge. + success = 0; + break; + } + } // while (1) + } else { + // This side coincident with a boundary edge of R. + checkseg.shver = 0; + spivot(checkseg, oldsh); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + spintet = searchtet; + while (1) { + tssdissolve1(spintet); fnextself(spintet); - if (spintet.tet == searchtet.tet) { - // Not find any face to fill in R at this side. - // TO DO: suggest a point to split the edge. - assert(0); - } - } // while (1) - } else { - // This side coincident with a boundary edge of R. - checkseg.shver = 0; - spivot(checkseg, oldsh); - if (sinfected(checkseg)) { - // It's a faked segment. Delete it. - if (b->verbose > 2) { - printf(" Delete a fake segment (%d, %d)\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - spintet = searchtet; - while (1) { - tssdissolve1(spintet); - fnextself(spintet); - if (spintet.tet == searchtet.tet) break; - } - shellfacedealloc(subsegs, checkseg.sh); - } - if (b->verbose > 2) { - printf(" Connect to a boundary edge (%d, %d, %d)\n", - pointmark(sorg(oldsh)), pointmark(sdest(oldsh)), - pointmark(sapex(oldsh))); - } - sspivot(oldsh, checkseg); - spivot(oldsh, casout); - if (casout.sh != NULL) { - casin = casout; - if (checkseg.sh != NULL) { - // Make sure that the subface has the right ori at the segment. - checkseg.shver = 0; - if (sorg(opensh) != sorg(checkseg)) { - sesymself(opensh); - } - spivot(casin, neighsh); - while (neighsh.sh != oldsh.sh) { - casin = neighsh; - spivot(casin, neighsh); - } - } - sbond1(opensh, casout); - sbond1(casin, opensh); + if (spintet.tet == searchtet.tet) break; } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + checkseg.sh = NULL; + } + spivot(oldsh, casout); + if (casout.sh != NULL) { + casin = casout; if (checkseg.sh != NULL) { - ssbond(opensh, checkseg); + // Make sure that the subface has the right ori at the segment. + checkseg.shver = 0; + if (sorg(opensh) != sorg(checkseg)) { + sesymself(opensh); + } + spivot(casin, neighsh); + while (neighsh.sh != oldsh.sh) { + casin = neighsh; + spivot(casin, neighsh); + } + } + sbond1(opensh, casout); + sbond1(casin, opensh); + } + if (checkseg.sh != NULL) { + ssbond(opensh, checkseg); + } + } // if (checkseg.sh != NULL) + } // while ((flipstack != NULL) && success) + + if (success) { + // Uninfect all new subfaces. + for (i = 0; i < newshs->objects; i++) { + parysh = (face *) fastlookup(newshs, i); + suninfect(*parysh); + } + // Delete old subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + shellfacedealloc(subfaces, parysh->sh); + } + fillregioncount++; + } else { + // Failed to fill the region. + // Re-connect old subfaces at boundaries of R. + // Also delete fake segments. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + // It still connect to 'casout'. + // Re-connect 'casin' to it. + spivot(*parysh, casout); + casin = casout; + spivot(casin, neighsh); + while (1) { + if (sinfected(neighsh)) break; + if (neighsh.sh == parysh->sh) break; + casin = neighsh; + spivot(casin, neighsh); + } + if (sinfected(neighsh)) { + sbond1(casin, *parysh); + } + sspivot(*parysh, checkseg); + if (checkseg.sh != NULL) { + if (checkseg.sh[3] != NULL) { + if (sinfected(checkseg)) { + sstpivot1(checkseg, searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + ssdissolve(*parysh); + shellfacedealloc(subsegs, checkseg.sh); + } } } + } + // Delete all new subfaces. + for (i = 0; i < newshs->objects; i++) { + parysh = (face *) fastlookup(newshs, i); + shellfacedealloc(subfaces, parysh->sh); + } + // Clear the flip pool. + flippool->restart(); + flipstack = NULL; - } // if (casout.sh == NULL) - - } // while (flipstack != NULL) - - // Uninfect all new subfaces. - for (i = 0; i < newshs->objects; i++) { - parysh = (face *) fastlookup(newshs, i); - suninfect(*parysh); + // Choose an interior edge of R to split. + assert(missingshs->objects > 1); + // Skip the first subface in 'missingshs'. + i = randomnation(missingshs->objects - 1) + 1; + parysh = (face *) fastlookup(missingshs, i); + recentsh = *parysh; } + newshs->restart(); + + return success; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// insertpoint_cdt() Insert a new point into a CDT. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::insertpoint_cdt(point newpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf, + arraypool *cavpoints, arraypool *cavfaces, + arraypool *cavshells, arraypool *newtets, + arraypool *crosstets, arraypool *misfaces) +{ + triface neightet, *parytet; + face checksh, *parysh, *parysh1; + face *paryseg, *paryseg1; + point *parypt; + int t1ver; + int i; + if (b->verbose > 2) { - printf(" Created %ld new subfaces.\n", newshs->objects); + printf(" Insert point %d into CDT\n", pointmark(newpt)); } - fillregioncount++; - return true; + if (!insertpoint(newpt, searchtet, NULL, NULL, ivf)) { + // Point is not inserted. Check ivf->iloc for reason. + return 0; + } + + + for (i = 0; i < cavetetvertlist->objects; i++) { + cavpoints->newindex((void **) &parypt); + *parypt = * (point *) fastlookup(cavetetvertlist, i); + } + // Add the new point into the point list. + cavpoints->newindex((void **) &parypt); + *parypt = newpt; + + for (i = 0; i < cavebdrylist->objects; i++) { + cavfaces->newindex((void **) &parytet); + *parytet = * (triface *) fastlookup(cavebdrylist, i); + } + + for (i = 0; i < caveoldtetlist->objects; i++) { + crosstets->newindex((void **) &parytet); + *parytet = * (triface *) fastlookup(caveoldtetlist, i); + } + + cavetetvertlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + + // Insert the point using the cavity algorithm. + delaunizecavity(cavpoints, cavfaces, cavshells, newtets, crosstets, + misfaces); + fillcavity(cavshells, NULL, NULL, NULL, NULL, NULL, NULL); + carvecavity(crosstets, newtets, NULL); + + if ((splitsh != NULL) || (splitseg != NULL)) { + // Insert the point into the surface mesh. + sinsertvertex(newpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); + + // Put all new subfaces into stack. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + + if (splitseg != NULL) { + // Queue two new subsegments in C(p) for recovery. + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + subsegstack->newindex((void **) &paryseg1); + *paryseg1 = *paryseg; + } + } // if (splitseg != NULL) + + // Delete the old subfaces in sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (checksubfaceflag) { + // It is possible that this subface still connects to adjacent + // tets which are not in C(p). If so, clear connections in the + // adjacent tets at this subface. + stpivot(*parysh, neightet); + if (neightet.tet != NULL) { + if (neightet.tet[4] != NULL) { + // Found an adjacent tet. It must be not in C(p). + assert(!infected(neightet)); + tsdissolve(neightet); + fsymself(neightet); + assert(!infected(neightet)); + tsdissolve(neightet); + } + } + } + shellfacedealloc(subfaces, parysh->sh); + } + if (splitseg != NULL) { + // Delete the old segment in sC(p). + shellfacedealloc(subsegs, splitseg->sh); + } + + // Clear working lists. + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + } // if ((splitsh != NULL) || (splitseg != NULL)) + + // Put all interior subfaces into stack for recovery. + // They were collected in carvecavity(). + // Note: Some collected subfaces may be deleted by sinsertvertex(). + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (parysh->sh[3] != NULL) { + subfacstack->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + } + + // Put all interior segments into stack for recovery. + // They were collected in carvecavity(). + // Note: Some collected segments may be deleted by sinsertvertex(). + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + if (paryseg->sh[3] != NULL) { + subsegstack->newindex((void **) &paryseg1); + *paryseg1 = *paryseg; + } + } + + caveencshlist->restart(); + caveencseglist->restart(); + + return 1; } /////////////////////////////////////////////////////////////////////////////// // // // refineregion() Refine a missing region by inserting points. // // // +// 'splitsh' represents an edge of the facet to be split. It must be not a // +// segment. +// // +// Assumption: The current mesh is a CDT and is convex. // +// // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::refineregion() +void tetgenmesh::refineregion(face &splitsh, arraypool *cavpoints, + arraypool *cavfaces, arraypool *cavshells, + arraypool *newtets, arraypool *crosstets, + arraypool *misfaces) { - triface searchtet; - face splitsh; - face *paryseg, sseg; - point steinpt, pa, pb, pc; + triface searchtet, spintet; + face splitseg, *paryseg; + point steinpt, pa, pb, refpt; insertvertexflags ivf; - REAL auv[2], buv[2], newuv[2], t; - int fmark, fid, eid; - int loc; // iloc, sloc; - int s, i; + enum interresult dir; + long baknum = points->items; + int t1ver; + int i; - // The mesh is a CDT. - assert(subsegstack->objects == 0l); // SELF_CHECK + if (b->verbose > 2) { + printf(" Refining region at edge (%d, %d, %d).\n", + pointmark(sorg(splitsh)), pointmark(sdest(splitsh)), + pointmark(sapex(splitsh))); + } - // Create a new point. - makepoint(&steinpt, FREEFACETVERTEX); - - // The 'recentsh' saved an edge to be split. - splitsh = recentsh; // Add the Steiner point at the barycenter of the face. pa = sorg(splitsh); pb = sdest(splitsh); - pc = sapex(splitsh); - - if (b->psc) { - assert(in->facetmarkerlist != NULL); - fmark = shellmark(splitsh) - 1; - fid = in->facetmarkerlist[fmark]; - if (pointtype(pa) == RIDGEVERTEX) { - in->getvertexparamonface(in->geomhandle, pointmark(pa), fid, auv); - } else if (pointtype(pa) == FREESEGVERTEX) { - eid = pointgeomtag(pa); // The Edge containing this Steiner point. - t = pointgeomuv(pa, 0); // The Steiner point's parameter on Edge. - in->getedgesteinerparamonface(in->geomhandle, eid, t, fid, auv); - } else if (pointtype(pa) == FREEFACETVERTEX) { - auv[0] = pointgeomuv(pa, 0); - auv[1] = pointgeomuv(pa, 1); - } else { - assert(0); - } - if (pointtype(pb) == RIDGEVERTEX) { - in->getvertexparamonface(in->geomhandle, pointmark(pb), fid, buv); - } else if (pointtype(pb) == FREESEGVERTEX) { - eid = pointgeomtag(pb); // The Edge containing this Steiner point. - t = pointgeomuv(pb, 0); // The Steiner point's parameter on Edge. - in->getedgesteinerparamonface(in->geomhandle, eid, t, fid, buv); - } else if (pointtype(pb) == FREEFACETVERTEX) { - buv[0] = pointgeomuv(pb, 0); - buv[1] = pointgeomuv(pb, 1); - } else { - assert(0); - } - newuv[0] = 0.5 * (auv[0] + buv[0]); - newuv[1] = 0.5 * (auv[1] + buv[1]); - in->getsteineronface(in->geomhandle, fid, newuv, steinpt); - setpointgeomuv(steinpt, 0, newuv[0]); - setpointgeomuv(steinpt, 1, newuv[1]); - setpointgeomtag(steinpt, fid); - } else { - for (i = 0; i < 3; i++) { - steinpt[i] = (pa[i] + pb[i] + pc[i]) / 3.0; - } + // Create a new point. + makepoint(&steinpt, FREEFACETVERTEX); + for (i = 0; i < 3; i++) { + steinpt[i] = 0.5 * (pa[i] + pb[i]); } - // Start searching it from 'recentet'. - searchtet = recenttet; - // Now insert the point p. The flags are chosen as follows: - // - boywat = 2, the current T is a CDT, - // - lawson = 2, do flip after inserting p, some existing segments - // and subfaces may be flipped, they are queued and - // and will be recovered. - // - rejflag = 1, reject p if it encroaches upon at least one segment, - // queue encroached segments. - ivf.iloc = (int) OUTSIDE; - ivf.bowywat = 2; - ivf.lawson = 2; - ivf.rejflag = 1; - ivf.chkencflag = 0; - ivf.sloc = (int) ONFACE; - ivf.sbowywat = 2; - ivf.splitbdflag = 0; - ivf.validflag = 1; - ivf.respectbdflag = 0; + ivf.bowywat = 1; // Use the Bowyer-Watson algorrithm. + ivf.cdtflag = 1; // Only create the initial cavity. + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; ivf.assignmeshsize = b->metric; - loc = insertvertex(steinpt, &searchtet, &splitsh, NULL, &ivf); - assert((loc != OUTSIDE) && (loc != ONVERTEX)); - if (loc == NEARVERTEX) { - // The new point is either ON or VERY CLOSE to an existing point. - pa = point2ppt(steinpt); - printf(" !! Avoid to create a short edge (length = %g)\n", - distance(steinpt, pa)); - // Indicate it may be an input problem. - printf(" Short edge length bound is: %g. Tolerance is %g.\n", - b->minedgelength, b->epsilon); - terminatetetgen(4); - } + point2tetorg(pa, searchtet); // Start location from it. + ivf.iloc = (int) OUTSIDE; - if (loc == ENCSEGMENT) { - // Some segments are encroached and queued. - assert(encseglist->objects > 0l); - // Randomly pick one encroached segment to split. - s = randomnation(encseglist->objects); - paryseg = (face *) fastlookup(encseglist, s); - sseg = *paryseg; - // The new point p is the midpoint of this segment. - getsteinerptonsegment(&sseg, NULL, steinpt); - setpointtype(steinpt, FREESEGVERTEX); - encseglist->restart(); // Clear the queue. + ivf.rejflag = 1; // Reject it if it encroaches upon any segment. + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, NULL, &ivf, cavpoints, + cavfaces, cavshells, newtets, crosstets, misfaces)) { + if (ivf.iloc == (int) ENCSEGMENT) { + pointdealloc(steinpt); + // Split an encroached segment. + assert(encseglist->objects > 0); + i = randomnation(encseglist->objects); + paryseg = (face *) fastlookup(encseglist, i); + splitseg = *paryseg; + encseglist->restart(); - // Start searching from an adjacent tetrahedron (containing the segment). - sstpivot1(sseg, searchtet); - spivot(sseg, splitsh); - // Insert the point p. The flags are chosen as follows: - // - boywat = 2, the current T is a CDT, - // - lawson = 2, do flip after inserting p, some existing segments - // and subfaces may be flipped, they are queued and - // and will be recovered. - // - rejflag = 0, always insert p, even it will cause some segments - // or subfaces missing, queue missing boundaries. - ivf.iloc = (int) ONEDGE; - ivf.bowywat = 2; - ivf.lawson = 2; - ivf.rejflag = 0; - ivf.chkencflag = 0; - ivf.sloc = (int) ONEDGE; - ivf.sbowywat = 2; - ivf.splitbdflag = 0; - ivf.validflag = 1; - ivf.respectbdflag = 0; - ivf.assignmeshsize = b->metric; - loc = insertvertex(steinpt, &searchtet, &splitsh, &sseg, &ivf); - - if (loc == NEARVERTEX) { - // The new point is either ON or VERY CLOSE to an existing point. - pa = point2ppt(steinpt); - printf(" !! Avoid to create a short edge (length = %g)\n", - distance(steinpt, pa)); - // Indicate it may be an input problem. - printf(" Short edge length bound is: %g. Tolerance is %g.\n", - b->minedgelength, b->epsilon); - terminatetetgen(4); + // Split the segment. + pa = sorg(splitseg); + pb = sdest(splitseg); + // Create a new point. + makepoint(&steinpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinpt[i] = 0.5 * (pa[i] + pb[i]); + } + point2tetorg(pa, searchtet); + ivf.iloc = (int) OUTSIDE; + ivf.rejflag = 0; + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, + cavpoints, cavfaces, cavshells, newtets, + crosstets, misfaces)) { + assert(0); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + assert(0); } - - st_segref_count++; } else { st_facref_count++; - } - if (steinerleft > 0) steinerleft--; - - // Do flip to recover Delaunayniess. - lawsonflip3d(steinpt, 2, 0, 0, 0); - - // Some vertices may be queued, recover them. - if (subvertstack->objects > 0l) { - assert(0); //delaunizevertices(); + if (steinerleft > 0) steinerleft--; } - // Some subsegments may be queued, recover them. - if (subsegstack->objects > 0l) { - delaunizesegments(); + while (subsegstack->objects > 0l) { + // seglist is used as a stack. + subsegstack->objects--; + paryseg = (face *) fastlookup(subsegstack, subsegstack->objects); + splitseg = *paryseg; + + // Check if this segment has been recovered. + sstpivot1(splitseg, searchtet); + if (searchtet.tet != NULL) continue; + + // Search the segment. + dir = scoutsegment(sorg(splitseg), sdest(splitseg), &searchtet, &refpt, + NULL); + if (dir == SHAREEDGE) { + // Found this segment, insert it. + if (!issubseg(searchtet)) { + // Let the segment remember an adjacent tet. + sstbond1(splitseg, searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, splitseg); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + // Collision! Should not happen. + assert(0); + } + } else { + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // Split the segment. + // Create a new point. + makepoint(&steinpt, FREESEGVERTEX); + //setpointtype(newpt, FREESEGVERTEX); + getsteinerptonsegment(&splitseg, refpt, steinpt); + ivf.iloc = (int) OUTSIDE; + ivf.rejflag = 0; + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, + cavpoints, cavfaces, cavshells, newtets, + crosstets, misfaces)) { + assert(0); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + // Maybe a PLC problem. + assert(0); + } + } + } // while + + if (b->verbose > 2) { + printf(" Added %ld Steiner points.\n", points->items - baknum); } } /////////////////////////////////////////////////////////////////////////////// // // -// constrainedfacets() Recover subfaces saved in 'subfacestack'. // +// constrainedfacets() Recover constrained facets in a CDT. // +// // +// All unrecovered subfaces are queued in 'subfacestack'. // // // /////////////////////////////////////////////////////////////////////////////// @@ -3626,16 +3304,14 @@ void tetgenmesh::constrainedfacets() arraypool *tg_topshells, *tg_botshells, *tg_facfaces; arraypool *tg_toppoints, *tg_botpoints; arraypool *tg_missingshs, *tg_missingshbds, *tg_missingshverts; - - triface searchtet, neightet; - face searchsh, neighsh, *parysh; - face checkseg, *paryseg; - point refpt, *parypt; + triface searchtet, neightet, crossedge; + face searchsh, *parysh, *parysh1; + face *paryseg; + point *parypt; enum interresult dir; - bool success; int facetcount; - //int bakhullsize; - int crossflag; + int success; + int t1ver; int i, j; // Initialize arrays. @@ -3653,245 +3329,184 @@ void tetgenmesh::constrainedfacets() tg_missingshs = new arraypool(sizeof(face), 10); tg_missingshbds = new arraypool(sizeof(face), 10); tg_missingshverts = new arraypool(sizeof(point), 8); - // This is a global array used by refineregion(). - encseglist = new arraypool(sizeof(face), 4); + encseglist = new arraypool(sizeof(face), 4); facetcount = 0; - // Loop until 'subfacstack' is empty. while (subfacstack->objects > 0l) { + subfacstack->objects--; parysh = (face *) fastlookup(subfacstack, subfacstack->objects); searchsh = *parysh; - if (searchsh.sh[3] == NULL) continue; // Skip a dead subface. + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. - stpivot(searchsh, neightet); - if (neightet.tet == NULL) { - // Find an unrecovered subface. - smarktest(searchsh); - tg_facfaces->newindex((void **) &parysh); - *parysh = searchsh; - // Collect all non-recovered subfaces of the same facet. - for (i = 0; i < tg_facfaces->objects; i++) { - searchsh = * (face *) fastlookup(tg_facfaces, i); - for (j = 0; j < 3; j++) { - sspivot(searchsh, checkseg); - if (checkseg.sh == NULL) { - spivot(searchsh, neighsh); - assert(neighsh.sh != NULL); // SELF_CHECK - if (!smarktested(neighsh)) { - // It may be already recovered. - stpivot(neighsh, neightet); - if (neightet.tet == NULL) { - smarktest(neighsh); - tg_facfaces->newindex((void **) &parysh); - *parysh = neighsh; - } + // Collect all unrecovered subfaces which are co-facet. + smarktest(searchsh); + tg_facfaces->newindex((void **) &parysh); + *parysh = searchsh; + for (i = 0; i < tg_facfaces->objects; i++) { + parysh = (face *) fastlookup(tg_facfaces, i); + for (j = 0; j < 3; j++) { + if (!isshsubseg(*parysh)) { + spivot(*parysh, searchsh); + assert(searchsh.sh != NULL); // SELF_CHECK + if (!smarktested(searchsh)) { + if (!isshtet(searchsh)) { + smarktest(searchsh); + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = searchsh; } } - senextself(searchsh); - } // j - } // i - // Have found all facet subfaces (vertices). Uninfect them. - for (i = 0; i < tg_facfaces->objects; i++) { - parysh = (face *) fastlookup(tg_facfaces, i); - sunmarktest(*parysh); - } + } + senextself(*parysh); + } // j + } // i + // Have found all facet subfaces. Unmark them. + for (i = 0; i < tg_facfaces->objects; i++) { + parysh = (face *) fastlookup(tg_facfaces, i); + sunmarktest(*parysh); + } - if (b->verbose > 2) { - printf(" Recover facet #%d: %ld subfaces.\n", facetcount + 1, - tg_facfaces->objects); - } - facetcount++; + if (b->verbose > 2) { + printf(" Recovering facet #%d: %ld subfaces.\n", facetcount + 1, + tg_facfaces->objects); + } + facetcount++; - // Loop until 'tg_facfaces' is empty. - while (tg_facfaces->objects > 0l) { - // Get the last subface of this array. - tg_facfaces->objects--; - parysh = (face *) fastlookup(tg_facfaces, tg_facfaces->objects); - searchsh = *parysh; + while (tg_facfaces->objects > 0l) { - if (searchsh.sh[3] == NULL) continue; // Skip a dead subface. + tg_facfaces->objects--; + parysh = (face *) fastlookup(tg_facfaces, tg_facfaces->objects); + searchsh = *parysh; - stpivot(searchsh, neightet); - if (neightet.tet != NULL) continue; // Not a missing subface. + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. - // Insert the subface. - searchtet.tet = NULL; - dir = scoutsubface(&searchsh, &searchtet); - if (dir == SHAREFACE) continue; // The subface is inserted. - if (dir == COLLISIONFACE) continue; // The subface is removed. + searchtet.tet = NULL; + dir = scoutsubface(&searchsh, &searchtet); + if (dir == SHAREFACE) continue; // The subface is inserted. - // The subface is missing. Form the missing region. - // Re-use 'tg_crosstets' for 'adjtets'. - formmissingregion(&searchsh, tg_missingshs, tg_missingshbds, - tg_missingshverts, tg_crosstets); + // The subface is missing. Form the missing region. + // Re-use 'tg_crosstets' for 'adjtets'. + formregion(&searchsh, tg_missingshs, tg_missingshbds, tg_missingshverts); - // Search for a crossing edge (tg_crosstets is cleared). - crossflag = scoutcrossedge(searchtet, tg_crosstets, tg_missingshs); - - if (crossflag == 1) { - // Recover subfaces by local retetrahedralization. - // Form a cavity of crossing tets. - if (formcavity(&searchtet, tg_missingshs, tg_crosstets, tg_topfaces, - tg_botfaces, tg_toppoints, tg_botpoints)) { - if (!b->flipinsert) { - // Tetrahedralize the top part. Re-use 'tg_midfaces'. - delaunizecavity(tg_toppoints, tg_topfaces, tg_topshells, - tg_topnewtets, tg_crosstets, tg_midfaces); - // Tetrahedralize the bottom part. Re-use 'tg_midfaces'. - delaunizecavity(tg_botpoints, tg_botfaces, tg_botshells, - tg_botnewtets, tg_crosstets, tg_midfaces); - // Fill the cavity with new tets. - success = fillcavity(tg_topshells, tg_botshells, tg_midfaces, - tg_missingshs); - if (success) { - // Cavity is remeshed. Delete old tets and outer new tets. - carvecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); - // Insert the missing region into cavity. - j = 0; // FOR DEBUG! Count the number of non-recovered faces. - for (i = 0; i < tg_missingshs->objects; i++) { - searchsh = * (face *) fastlookup(tg_missingshs, i); - searchtet.tet = NULL; - dir = scoutsubface(&searchsh, &searchtet); - assert(dir != COLLISIONFACE); // SELF_CHECK - if (dir != SHAREFACE) { - // A subface is missing. This is possible that the subface - // is not actually a constrained Delaunay face in T. - // Add this face at the end of the list, so it will be - // processed immediately. This is necessary because we - // have created some non-locally Delaunay face (by the - // remesh of the cavity). We have to insert the subfaces - // to make these face constrained Delaunay. - tg_facfaces->newindex((void **) &parysh); - *parysh = searchsh; - j++; // FOR DEBUG! - } - } // i - // Recover interior subfaces. - for (i = 0; i < caveencshlist->objects; i++) { - searchsh = * (face *) fastlookup(caveencshlist, i); - searchtet.tet = NULL; - dir = scoutsubface(&searchsh, &searchtet); - assert(dir != COLLISIONFACE); // SELF_CHECK - if (dir != SHAREFACE) { - // The subface is missing. This is possible that the subface - // is removed by the enlargement of the cavity. It has to - // be recovered. - // Add this face at the end of the list, so it will be - // processed immediately. We have to insert the subfaces - // to make these face constrained Delaunay. - tg_facfaces->newindex((void **) &parysh); - *parysh = searchsh; - j++; // FOR DEBUG! - } - } // i - // Recover interior segments. This should always be recovered. - for (i = 0; i < caveencseglist->objects; i++) { - paryseg = (face *) fastlookup(caveencseglist, i); - searchtet.tet = NULL; - refpt = NULL; - dir = scoutsegment(sorg(*paryseg),sdest(*paryseg),&searchtet, - &refpt, NULL); - assert(dir == SHAREEDGE); - // Insert this segment. - tsspivot1(searchtet, checkseg); // SELF_CHECK - if (checkseg.sh == NULL) { - // Let the segment remember an adjacent tet. - sstbond1(*paryseg, searchtet); - // Bond the segment to all tets containing it. - neightet = searchtet; - do { - tssbond1(neightet, *paryseg); - fnextself(neightet); - } while (neightet.tet != searchtet.tet); - } else { - // Collision! Should not happen. - assert(0); - } - } // i - caveencshlist->restart(); - caveencseglist->restart(); - } else { - // Restore old tets and delete new tets. - restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); - // Set a handle for searching subface. - //recentsh = searchsh; - } + if (scoutcrossedge(searchtet, tg_missingshbds, tg_missingshs)) { + // Save this crossing edge, will be used by fillcavity(). + crossedge = searchtet; + // Form a cavity of crossing tets. + success = formcavity(&searchtet, tg_missingshs, tg_crosstets, + tg_topfaces, tg_botfaces, tg_toppoints, + tg_botpoints); + if (success) { + if (!b->flipinsert) { + // Tetrahedralize the top part. Re-use 'tg_midfaces'. + delaunizecavity(tg_toppoints, tg_topfaces, tg_topshells, + tg_topnewtets, tg_crosstets, tg_midfaces); + // Tetrahedralize the bottom part. Re-use 'tg_midfaces'. + delaunizecavity(tg_botpoints, tg_botfaces, tg_botshells, + tg_botnewtets, tg_crosstets, tg_midfaces); + // Fill the cavity with new tets. + success = fillcavity(tg_topshells, tg_botshells, tg_midfaces, + tg_missingshs, tg_topnewtets, tg_botnewtets, + &crossedge); + if (success) { + // Cavity is remeshed. Delete old tets and outer new tets. + carvecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); } else { - // Use the flip algorithm of Shewchuk to recover the subfaces. - flipinsertfacet(tg_crosstets, tg_toppoints, tg_botpoints, - tg_missingshverts); - // Check the missing subfaces again. - j = 0; // FOR DEBUG! Count the number of non-recovered faces. - for (i = 0; i < tg_missingshs->objects; i++) { - searchsh = * (face *) fastlookup(tg_missingshs, i); - searchtet.tet = NULL; - dir = scoutsubface(&searchsh, &searchtet); - assert(dir != COLLISIONFACE); // SELF_CHECK - if (dir != SHAREFACE) { - // A subface is missing. This is possible that the subface - // is not actually a constrained Delaunay face in T. - // Add this face at the end of the list, so it will be - // processed immediately. This is necessary because we - // have created some non-locally Delaunay face (by the - // remesh of the cavity). We have to insert the subfaces - // to make these face constrained Delaunay. - tg_facfaces->newindex((void **) &parysh); - *parysh = searchsh; - j++; // FOR DEBUG! - } - } // i - // Clear working lists. - tg_crosstets->restart(); - tg_topfaces->restart(); - tg_botfaces->restart(); - tg_toppoints->restart(); - tg_botpoints->restart(); - success = true; - } // if (b->flipinsert) - } else { - // Formcavity failed. - success = false; - } - } else { //if (crossflag == 0) { - // Recover subfaces by retriangulate the surface mesh. - // Re-use tg_topshells for newshs. - success = fillregion(tg_missingshs, tg_missingshbds, tg_topshells); - if (success) { - // Region is remeshed. Delete old subfaces (in tg_missingshs). - for (i = 0; i < tg_missingshs->objects; i++) { - parysh = (face *) fastlookup(tg_missingshs, i); - shellfacedealloc(subfaces, parysh->sh); + restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets, + tg_missingshbds); } - tg_topshells->restart(); } else { - // Search a handle for searching tetrahedron. - recenttet = searchtet; - } - } + // Use the flip algorithm of Shewchuk to recover the subfaces. + flipinsertfacet(tg_crosstets, tg_toppoints, tg_botpoints, + tg_missingshverts); + // Recover the missing region. + success = fillregion(tg_missingshs, tg_missingshbds, tg_topshells); + assert(success); + // Clear working lists. + tg_crosstets->restart(); + tg_topfaces->restart(); + tg_botfaces->restart(); + tg_toppoints->restart(); + tg_botpoints->restart(); + } // b->flipinsert - // Unmarktest all points of the missing region. - for (i = 0; i < tg_missingshverts->objects; i++) { - parypt = (point *) fastlookup(tg_missingshverts, i); - punmarktest(*parypt); - } - tg_missingshverts->restart(); - tg_missingshbds->restart(); - tg_missingshs->restart(); + if (success) { + // Recover interior subfaces. + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + dir = scoutsubface(parysh, &searchtet); + if (dir != SHAREFACE) { + // Add this face at the end of the list, so it will be + // processed immediately. + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + } + caveencshlist->restart(); + // Recover interior segments. This should always be recovered. + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + dir = scoutsegment(sorg(*paryseg),sdest(*paryseg),&searchtet, + NULL, NULL); + assert(dir == SHAREEDGE); + // Insert this segment. + if (!issubseg(searchtet)) { + // Let the segment remember an adjacent tet. + sstbond1(*paryseg, searchtet); + // Bond the segment to all tets containing it. + neightet = searchtet; + do { + tssbond1(neightet, *paryseg); + fnextself(neightet); + } while (neightet.tet != searchtet.tet); + } else { + // Collision! Should not happen. + assert(0); + } + } + caveencseglist->restart(); + } // success - remesh cavity + } // success - form cavity + } else { + // Recover subfaces by retriangulate the surface mesh. + // Re-use tg_topshells for newshs. + success = fillregion(tg_missingshs, tg_missingshbds, tg_topshells); + } - if (!success) { - // The missing region can not be recovered. Refine it. - refineregion(); - // Clean the current list of facet subfaces. - //tg_facfaces->restart(); - } - } // while (tg_facfaces->objects > 0l) + // Unmarktest all points of the missing region. + for (i = 0; i < tg_missingshverts->objects; i++) { + parypt = (point *) fastlookup(tg_missingshverts, i); + punmarktest(*parypt); + } + tg_missingshverts->restart(); + tg_missingshbds->restart(); + tg_missingshs->restart(); - } // if (neightet.tet == NULL) - } // while (subfacstack->objects > 0l) + if (!success) { + // The missing region can not be recovered. Refine it. + refineregion(recentsh, tg_toppoints, tg_topfaces, tg_topshells, + tg_topnewtets, tg_crosstets, tg_midfaces); + // Clean the current list of facet subfaces. + // tg_facfaces->restart(); + } + } // while (tg_facfaces->objects) + + } // while ((subfacstack->objects) + + // Accumulate the dynamic memory. + totalworkmemory += (tg_crosstets->totalmemory + tg_topnewtets->totalmemory + + tg_botnewtets->totalmemory + tg_topfaces->totalmemory + + tg_botfaces->totalmemory + tg_midfaces->totalmemory + + tg_toppoints->totalmemory + tg_botpoints->totalmemory + + tg_facfaces->totalmemory + tg_topshells->totalmemory + + tg_botshells->totalmemory + tg_missingshs->totalmemory + + tg_missingshbds->totalmemory + + tg_missingshverts->totalmemory + + encseglist->totalmemory); // Delete arrays. delete tg_crosstets; @@ -3926,13 +3541,13 @@ void tetgenmesh::constraineddelaunay(clock_t& tv) // Statistics. long bakfillregioncount; long bakcavitycount, bakcavityexpcount; + long bakseg_ref_count; if (!b->quiet) { printf("Constrained Delaunay...\n"); } - // Identify acute vertex for PLC inputs. - markacutevertices(); + makesegmentendpointsmap(); if (b->verbose) { printf(" Delaunizing segments.\n"); @@ -3940,26 +3555,25 @@ void tetgenmesh::constraineddelaunay(clock_t& tv) checksubsegflag = 1; - // Put all segments into the list. - // In random order. - subsegs->traversalinit(); - for (i = 0; i < subsegs->items; i++) { - s = randomnation(i + 1); - // Move the s-th seg to the i-th. - subsegstack->newindex((void **) &paryseg); - *paryseg = * (face *) fastlookup(subsegstack, s); - // Put i-th seg to be the s-th. - searchseg.sh = shellfacetraverse(subsegs); - //sinfect(searchseg); // Only save it once. - paryseg = (face *) fastlookup(subsegstack, s); - *paryseg = searchseg; - } + // Put all segments into the list (in random order). + subsegs->traversalinit(); + for (i = 0; i < subsegs->items; i++) { + s = randomnation(i + 1); + // Move the s-th seg to the i-th. + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + // Put i-th seg to be the s-th. + searchseg.sh = shellfacetraverse(subsegs); + //sinfect(searchseg); // Only save it once. + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = searchseg; + } // Recover non-Delaunay segments. delaunizesegments(); if (b->verbose) { - printf(" %ld Steiner points.\n", st_segref_count); + printf(" Inserted %ld Steiner points.\n", st_segref_count); } tv = clock(); @@ -3968,17 +3582,13 @@ void tetgenmesh::constraineddelaunay(clock_t& tv) printf(" Constraining facets.\n"); } - if (b->flipinsert) { - // Clear the counters. - flip23count = flip32count = flip44count = 0l; - } - // Subfaces will be introduced. checksubfaceflag = 1; bakfillregioncount = fillregioncount; bakcavitycount = cavitycount; bakcavityexpcount = cavityexpcount; + bakseg_ref_count = st_segref_count; // Randomly order the subfaces. subfaces->traversalinit(); @@ -4007,10 +3617,10 @@ void tetgenmesh::constraineddelaunay(clock_t& tv) } printf(".\n"); } - if (st_segref_count + st_facref_count > 0) { + if (st_segref_count + st_facref_count - bakseg_ref_count > 0) { printf(" Inserted %ld (%ld, %ld) refine points.\n", - st_segref_count + st_facref_count, st_segref_count, - st_facref_count); + st_segref_count + st_facref_count - bakseg_ref_count, + st_segref_count - bakseg_ref_count, st_facref_count); } } } diff --git a/external/tetgen/src/delaunay.cxx b/external/tetgen/src/delaunay.cxx index b083d845a..26a81d3ca 100644 --- a/external/tetgen/src/delaunay.cxx +++ b/external/tetgen/src/delaunay.cxx @@ -23,6 +23,9 @@ void tetgenmesh::transfernodes() int mtrindex; int i, j; + if (b->psc) { + assert(in->pointparamlist != NULL); + } // Read the points. coordindex = 0; @@ -60,7 +63,7 @@ void tetgenmesh::transfernodes() pointloop[3] = w; // Regular tetrahedralization. } } - // Determine the smallest and largests x, y and z coordinates. + // Determine the smallest and largest x, y and z coordinates. if (i == 0) { xmin = xmax = x; ymin = ymax = y; @@ -97,7 +100,7 @@ void tetgenmesh::transfernodes() longest = sqrt(x * x + y * y + z * z); if (longest == 0.0) { printf("Error: The point set is trivial.\n"); - terminatetetgen(3); + terminatetetgen(this, 3); } // Two identical points are distinguished by 'lengthlimit'. @@ -201,12 +204,12 @@ int tetgenmesh::hilbert_split(point* vertexarray,int arraysize,int gc0,int gc1, d = ((gc0 & (1< 0) { do { for (; i < arraysize; i++) { @@ -252,12 +255,6 @@ void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, int p[9], w, e_w, d_w, k, ei, di; int n = 3, mask = 7; - - // Record the highest order of the curve. - if (depth + 1 > max_hcurve_depth_count) { - max_hcurve_depth_count = depth + 1; - } - p[0] = 0; p[8] = arraysize; @@ -289,13 +286,14 @@ void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, } } - // Recursivly sort the points in sub-boxes. + // Recursively sort the points in sub-boxes. for (w = 0; w < 8; w++) { // w is the local Hilbert index (NOT Gray code). // Sort into the sub-box either there are more than 2 points in it, or // the prescribed order of the curve is not reached yet. - if ((p[w+1] - p[w] > b->hilbert_limit) || (b->hilbert_order > 0)) { - // Calulcate the start point (ei) of the curve in this sub-box. + //if ((p[w+1] - p[w] > b->hilbert_limit) || (b->hilbert_order > 0)) { + if ((p[w+1] - p[w]) > b->hilbert_limit) { + // Calculcate the start point (ei) of the curve in this sub-box. // update e = e ^ (e(w) left_rotate (d+1)). if (w == 0) { e_w = 0; @@ -343,6 +341,28 @@ void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, } // w } +/////////////////////////////////////////////////////////////////////////////// +// // +// brio_multiscale_sort() Sort the points using BRIO and Hilbert curve. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::brio_multiscale_sort(point* vertexarray, int arraysize, + int threshold, REAL ratio, int *depth) +{ + int middle; + + middle = 0; + if (arraysize >= threshold) { + (*depth)++; + middle = arraysize * ratio; + brio_multiscale_sort(vertexarray, middle, threshold, ratio, depth); + } + // Sort the right-array (rnd-th round) using the Hilbert curve. + hilbert_sort3(&(vertexarray[middle]), arraysize - middle, 0, 0, // e, d + xmin, xmax, ymin, ymax, zmin, zmax, 0); // depth. +} + /////////////////////////////////////////////////////////////////////////////// // // // randomnation() Generate a random number between 0 and 'choices' - 1. // @@ -375,11 +395,11 @@ unsigned long tetgenmesh::randomnation(unsigned int choices) // Searching begins from one of handles: the input 'searchtet', a recently // // encountered tetrahedron 'recenttet', or from one chosen from a random // // sample. The choice is made by determining which one's origin is closest // -// to the point we are searcing for. // +// to the point we are searching for. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::randomsample(point searchpt, triface *searchtet) +void tetgenmesh::randomsample(point searchpt,triface *searchtet) { tetrahedron *firsttet, *tetptr; point torg; @@ -394,44 +414,40 @@ void tetgenmesh::randomsample(point searchpt, triface *searchtet) pointmark(searchpt)); } - if (searchtet->tet == NULL) { - // A null tet. Choose the recenttet as the starting tet. - *searchtet = recenttet; - // Recenttet should not be dead. - assert(recenttet.tet[4] != NULL); - } - - // 'searchtet' should be a valid tetrahedron. Choose the base face - // whose vertices must not be 'dummypoint'. - searchtet->ver = 3; - // Record the distance from its origin to the searching point. - torg = org(*searchtet); - searchdist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + - (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + - (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); - if (b->verbose > 3) { - printf(" Dist %g from tet (%d, %d, %d, %d).\n", searchdist, - pointmark(torg), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); - } - - // If a recently encountered tetrahedron has been recorded and has not - // been deallocated, test it as a good starting point. - if (recenttet.tet != searchtet->tet) { - recenttet.ver = 3; - torg = org(recenttet); - dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + - (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + - (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); - if (dist < searchdist) { + if (!nonconvex) { + if (searchtet->tet == NULL) { + // A null tet. Choose the recenttet as the starting tet. *searchtet = recenttet; - searchdist = dist; - if (b->verbose > 3) { - printf(" Dist %g from recent tet (%d, %d, %d, %d).\n", - searchdist, pointmark(torg), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); + // Recenttet should not be dead. + assert(recenttet.tet[4] != NULL); + } + + // 'searchtet' should be a valid tetrahedron. Choose the base face + // whose vertices must not be 'dummypoint'. + searchtet->ver = 3; + // Record the distance from its origin to the searching point. + torg = org(*searchtet); + searchdist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + + // If a recently encountered tetrahedron has been recorded and has not + // been deallocated, test it as a good starting point. + if (recenttet.tet != searchtet->tet) { + recenttet.ver = 3; + torg = org(recenttet); + dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + if (dist < searchdist) { + *searchtet = recenttet; + searchdist = dist; } } + } else { + // The mesh is non-convex. Do not use 'recenttet'. + assert(samples >= 1l); // Make sure at least 1 sample. + searchdist = longest; } // Select "good" candidate using k random samples, taking the closest one. @@ -471,11 +487,6 @@ void tetgenmesh::randomsample(point searchpt, triface *searchtet) searchtet->tet = tetptr; searchtet->ver = 11; // torg = org(t); searchdist = dist; - if (b->verbose > 3) { - printf(" Dist %g from tet (%d, %d, %d, %d).\n", searchdist, - pointmark(torg), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); - } } } else { // A dead tet. Re-sample it. @@ -490,7 +501,6 @@ void tetgenmesh::randomsample(point searchpt, triface *searchtet) // // // locate() Find a tetrahedron containing a given point. // // // -// This routine implements the simple Walk-through point location algorithm. // // Begins its search from 'searchtet', assume there is a line segment L from // // a vertex of 'searchtet' to the query point 'searchpt', and simply walk // // towards 'searchpt' by traversing all faces intersected by L. // @@ -509,22 +519,19 @@ void tetgenmesh::randomsample(point searchpt, triface *searchtet) // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult - tetgenmesh::locate(point searchpt, triface* searchtet, int chkencflag) +enum tetgenmesh::locateresult tetgenmesh::locate(point searchpt, + triface* searchtet) { - triface neightet; - face checksh; point torg, tdest, tapex, toppo; enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; REAL ori, oriorg, oridest, oriapex; - enum locateresult loc; + enum locateresult loc = OUTSIDE; + int t1ver; int s; if (searchtet->tet == NULL) { // A null tet. Choose the recenttet as the starting tet. - *searchtet = recenttet; - // Recenttet should not be dead. - assert(recenttet.tet[4] != NULL); + searchtet->tet = recenttet.tet; } // Check if we are in the outside of the convex hull. @@ -532,7 +539,6 @@ enum tetgenmesh::locateresult // Get its adjacent tet (inside the hull). searchtet->ver = 3; fsymself(*searchtet); - assert(!ishulltet(*searchtet)); } // Let searchtet be the face such that 'searchpt' lies above to it. @@ -543,17 +549,11 @@ enum tetgenmesh::locateresult ori = orient3d(torg, tdest, tapex, searchpt); if (ori < 0.0) break; } - if (searchtet->ver == 4) { // SELF_CHECK - assert(0); - } - - loc = OUTSIDE; // Set a default return value. + assert(searchtet->ver != 4); // Walk through tetrahedra to locate the point. while (true) { - ptloc_count++; // Count the number of visited tets. - toppo = oppo(*searchtet); // Check if the vertex is we seek. @@ -586,8 +586,8 @@ enum tetgenmesh::locateresult } } else { // Two faces, opposite to origin and destination, are viable. - s = randomnation(2); // 's' is in {0,1}. - if (s == 0) { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { nextmove = ORGMOVE; } else { nextmove = DESTMOVE; @@ -596,8 +596,8 @@ enum tetgenmesh::locateresult } else { if (oriapex < 0) { // Two faces, opposite to origin and apex, are viable. - s = randomnation(2); // 's' is in {0,1}. - if (s == 0) { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { nextmove = ORGMOVE; } else { nextmove = APEXMOVE; @@ -611,8 +611,8 @@ enum tetgenmesh::locateresult if (oridest < 0) { if (oriapex < 0) { // Two faces, opposite to destination and apex, are viable. - s = randomnation(2); // 's' is in {0,1}. - if (s == 0) { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { nextmove = DESTMOVE; } else { nextmove = APEXMOVE; @@ -630,10 +630,8 @@ enum tetgenmesh::locateresult // tetrahedron. Check for boundary cases. if (oriorg == 0) { // Go to the face opposite to origin. - //enextfnextself(*searchtet); enextesymself(*searchtet); if (oridest == 0) { - //enextself(*searchtet); // edge apex->oppo eprevself(*searchtet); // edge oppo->apex if (oriapex == 0) { // oppo is duplicated with p. @@ -644,7 +642,6 @@ enum tetgenmesh::locateresult break; } if (oriapex == 0) { - //enext2self(*searchtet); enextself(*searchtet); // edge dest->oppo loc = ONEDGE; // return ONEDGE; break; @@ -654,10 +651,8 @@ enum tetgenmesh::locateresult } if (oridest == 0) { // Go to the face opposite to destination. - //enext2fnextself(*searchtet); eprevesymself(*searchtet); if (oriapex == 0) { - //enextself(*searchtet); eprevself(*searchtet); // edge oppo->org loc = ONEDGE; // return ONEDGE; break; @@ -667,7 +662,6 @@ enum tetgenmesh::locateresult } if (oriapex == 0) { // Go to the face opposite to apex - //fnextself(*searchtet); esymself(*searchtet); loc = ONFACE; // return ONFACE; break; @@ -686,14 +680,6 @@ enum tetgenmesh::locateresult } else { esymself(*searchtet); } - if (chkencflag) { - // Check if we are walking across a subface. - tspivot(*searchtet, checksh); - if (checksh.sh != NULL) { - loc = ENCSUBFACE; - break; - } - } // Move to the adjacent tetrahedron (maybe a hull tetrahedron). fsymself(*searchtet); if (oppo(*searchtet) == dummypoint) { @@ -711,6 +697,275 @@ enum tetgenmesh::locateresult return loc; } +/////////////////////////////////////////////////////////////////////////////// +// // +// flippush() Push a face (possibly will be flipped) into flipstack. // +// // +// The face is marked. The flag is used to check the validity of the face on // +// its popup. Some other flips may change it already. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::flippush(badface*& fstack, triface* flipface) +{ + if (!facemarked(*flipface)) { + badface *newflipface = (badface *) flippool->alloc(); + newflipface->tt = *flipface; + markface(newflipface->tt); + // Push this face into stack. + newflipface->nextitem = fstack; + fstack = newflipface; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// incrementalflip() Incrementally flipping to construct DT. // +// // +// Faces need to be checked for flipping are already queued in 'flipstack'. // +// Return the total number of performed flips. // +// // +// Comment: This routine should be only used in the incremental Delaunay // +// construction. In other cases, lawsonflip3d() should be used. // +// // +// If the new point lies outside of the convex hull ('hullflag' is set). The // +// incremental flip algorithm still works as usual. However, we must ensure // +// that every flip (2-to-3 or 3-to-2) does not create a duplicated (existing)// +// edge or face. Otherwise, the underlying space of the triangulation becomes// +// non-manifold and it is not possible to flip further. // +// Thanks to Joerg Rambau and Frank Lutz for helping in this issue. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::incrementalflip(point newpt, int hullflag, flipconstraints *fc) +{ + badface *popface; + triface fliptets[5], *parytet; + point *pts, *parypt, pe; + REAL sign, ori; + int flipcount = 0; + int t1ver; + int i; + + if (b->verbose > 2) { + printf(" Lawson flip (%ld faces).\n", flippool->items); + } + + if (hullflag) { + // 'newpt' lies in the outside of the convex hull. + // Mark all hull vertices which are connecting to it. + popface = flipstack; + while (popface != NULL) { + pts = (point *) popface->tt.tet; + for (i = 4; i < 8; i++) { + if ((pts[i] != newpt) && (pts[i] != dummypoint)) { + if (!pinfected(pts[i])) { + pinfect(pts[i]); + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } + } + popface = popface->nextitem; + } + } + + // Loop until the queue is empty. + while (flipstack != NULL) { + + // Pop a face from the stack. + popface = flipstack; + fliptets[0] = popface->tt; + flipstack = flipstack->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // Skip it if it is a dead tet (destroyed by previous flips). + if (isdeadtet(fliptets[0])) continue; + // Skip it if it is not the same tet as we saved. + if (!facemarked(fliptets[0])) continue; + + unmarkface(fliptets[0]); + + if ((point) fliptets[0].tet[7] == dummypoint) { + // It must be a hull edge. + fliptets[0].ver = epivot[fliptets[0].ver]; + // A hull edge. The current convex hull may be enlarged. + fsym(fliptets[0], fliptets[1]); + pts = (point *) fliptets[1].tet; + ori = orient3d(pts[4], pts[5], pts[6], newpt); + if (ori < 0) { + // Visible. The convex hull will be enlarged. + // Decide which flip (2-to-3, 3-to-2, or 4-to-1) to use. + // Check if the tet [a,c,e,d] or [c,b,e,d] exists. + enext(fliptets[1], fliptets[2]); + eprev(fliptets[1], fliptets[3]); + fnextself(fliptets[2]); // [a,c,e,*] + fnextself(fliptets[3]); // [c,b,e,*] + if (oppo(fliptets[2]) == newpt) { + if (oppo(fliptets[3]) == newpt) { + // Both tets exist! A 4-to-1 flip is found. + terminatetetgen(this, 2); // Report a bug. + } else { + esym(fliptets[2], fliptets[0]); + fnext(fliptets[0], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Perform a 3-to-2 flip. Replace edge [c,a] by face [d,e,b]. + // This corresponds to my standard labels, where edge [e,d] is + // repalced by face [a,b,c], and a is the new vertex. + // [0] [c,a,d,e] (d = newpt) + // [1] [c,a,e,b] (c = dummypoint) + // [2] [c,a,b,d] + flip32(fliptets, 1, fc); + } + } else { + if (oppo(fliptets[3]) == newpt) { + fnext(fliptets[3], fliptets[0]); + fnext(fliptets[0], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Perform a 3-to-2 flip. Replace edge [c,b] by face [d,a,e]. + // [0] [c,b,d,a] (d = newpt) + // [1] [c,b,a,e] (c = dummypoint) + // [2] [c,b,e,d] + flip32(fliptets, 1, fc); + } else { + if (hullflag) { + // Reject this flip if pe is already marked. + pe = oppo(fliptets[1]); + if (!pinfected(pe)) { + pinfect(pe); + cavetetvertlist->newindex((void **) &parypt); + *parypt = pe; + // Perform a 2-to-3 flip. + flip23(fliptets, 1, fc); + } else { + // Reject this flip. + flipcount--; + } + } else { + // Perform a 2-to-3 flip. Replace face [a,b,c] by edge [e,d]. + // [0] [a,b,c,d], d = newpt. + // [1] [b,a,c,e], c = dummypoint. + flip23(fliptets, 1, fc); + } + } + } + flipcount++; + } + continue; + } // if (dummypoint) + + fsym(fliptets[0], fliptets[1]); + if ((point) fliptets[1].tet[7] == dummypoint) { + // A hull face is locally Delaunay. + continue; + } + // Check if the adjacent tet has already been tested. + if (marktested(fliptets[1])) { + // It has been tested and it is Delaunay. + continue; + } + + // Test whether the face is locally Delaunay or not. + pts = (point *) fliptets[1].tet; + if (b->weighted) { + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], newpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + newpt[3]); + } else { + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], newpt); + } + + + if (sign < 0) { + point pd = newpt; + point pe = oppo(fliptets[1]); + // Check the convexity of its three edges. Stop checking either a + // locally non-convex edge (ori < 0) or a flat edge (ori = 0) is + // encountered, and 'fliptet' represents that edge. + for (i = 0; i < 3; i++) { + ori = orient3d(org(fliptets[0]), dest(fliptets[0]), pd, pe); + if (ori <= 0) break; + enextself(fliptets[0]); + } + if (ori > 0) { + // A 2-to-3 flip is found. + // [0] [a,b,c,d], + // [1] [b,a,c,e]. no dummypoint. + flip23(fliptets, 0, fc); + flipcount++; + } else { // ori <= 0 + // The edge ('fliptets[0]' = [a',b',c',d]) is non-convex or flat, + // where the edge [a',b'] is one of [a,b], [b,c], and [c,a]. + // Check if there are three or four tets sharing at this edge. + esymself(fliptets[0]); // [b,a,d,c] + for (i = 0; i < 3; i++) { + fnext(fliptets[i], fliptets[i+1]); + } + if (fliptets[3].tet == fliptets[0].tet) { + // A 3-to-2 flip is found. (No hull tet.) + flip32(fliptets, 0, fc); + flipcount++; + } else { + // There are more than 3 tets at this edge. + fnext(fliptets[3], fliptets[4]); + if (fliptets[4].tet == fliptets[0].tet) { + if (ori == 0) { + // A 4-to-4 flip is found. (Two hull tets may be involved.) + // Current tets in 'fliptets': + // [0] [b,a,d,c] (d may be newpt) + // [1] [b,a,c,e] + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + esymself(fliptets[0]); // [a,b,c,d] + // A 2-to-3 flip replaces face [a,b,c] by edge [e,d]. + // This creates a degenerate tet [e,d,a,b] (tmpfliptets[0]). + // It will be removed by the followed 3-to-2 flip. + flip23(fliptets, 0, fc); // No hull tet. + fnext(fliptets[3], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Current tets in 'fliptets': + // [0] [...] + // [1] [b,a,d,e] (degenerated, d may be new point). + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + // A 3-to-2 flip replaces edge [b,a] by face [d,e,f]. + // Hull tets may be involved (f may be dummypoint). + flip32(&(fliptets[1]), (apex(fliptets[3]) == dummypoint), fc); + flipcount++; + } + } + } + } // ori + } else { + // The adjacent tet is Delaunay. Mark it to avoid testing it again. + marktest(fliptets[1]); + // Save it for unmarking it later. + cavebdrylist->newindex((void **) &parytet); + *parytet = fliptets[1]; + } + + } // while (flipstack) + + // Unmark saved tetrahedra. + for (i = 0; i < cavebdrylist->objects; i++) { + parytet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*parytet); + } + cavebdrylist->restart(); + + if (hullflag) { + // Unmark infected vertices. + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + puninfect(*parypt); + } + cavetetvertlist->restart(); + } + + + return flipcount; +} + /////////////////////////////////////////////////////////////////////////////// // // // initialdelaunay() Create an initial Delaunay tetrahedralization. // @@ -803,15 +1058,15 @@ void tetgenmesh::initialdelaunay(point pa, point pb, point pc, point pd) // // /////////////////////////////////////////////////////////////////////////////// + void tetgenmesh::incrementaldelaunay(clock_t& tv) { triface searchtet; point *permutarray, swapvertex; - insertvertexflags ivf; REAL v1[3], v2[3], n[3]; REAL bboxsize, bboxsize2, bboxsize3, ori; - int randindex, loc; - int ngroup, nstart, nend; + int randindex; + int ngroup = 0; int i, j; if (!b->quiet) { @@ -822,82 +1077,81 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) permutarray = new point[in->numberofpoints]; points->traversalinit(); - if (b->verbose) { - printf(" Permuting vertices.\n"); - } - srand(in->numberofpoints); - for (i = 0; i < in->numberofpoints; i++) { - randindex = rand() % (i + 1); // randomnation(i + 1); - permutarray[i] = permutarray[randindex]; - permutarray[randindex] = (point) points->traverse(); - } - if (b->brio_hilbert) { // -b option + if (b->no_sort) { if (b->verbose) { - printf(" Sort the points using simple BRIO and Hilbert curve L(%d).\n", - b->hilbert_limit); + printf(" Using the input order.\n"); } - hilbert_init(in->mesh_dim); - max_hcurve_depth_count = 0; - - ngroup = (int) log((double) in->numberofpoints); - nstart = 0; - for (i = 0; i < ngroup; i++) { - nend = in->numberofpoints >> (ngroup - 1 - i); - hilbert_sort3(&(permutarray[nstart]), nend - nstart, 0, 0, // e, d - xmin, xmax, ymin, ymax, zmin, zmax, 0); - nstart = nend; + for (i = 0; i < in->numberofpoints; i++) { + permutarray[i] = (point) points->traverse(); } + } else { if (b->verbose) { - printf(" Number of sorted subsets: %d.\n", ngroup); - printf(" Maximum curve order: %d.\n", max_hcurve_depth_count); + printf(" Permuting vertices.\n"); + } + srand(in->numberofpoints); + for (i = 0; i < in->numberofpoints; i++) { + randindex = rand() % (i + 1); // randomnation(i + 1); + permutarray[i] = permutarray[randindex]; + permutarray[randindex] = (point) points->traverse(); + } + if (b->brio_hilbert) { // -b option + if (b->verbose) { + printf(" Sorting vertices.\n"); + } + hilbert_init(in->mesh_dim); + brio_multiscale_sort(permutarray, in->numberofpoints, b->brio_threshold, + b->brio_ratio, &ngroup); } } tv = clock(); // Remember the time for sorting points. // Calculate the diagonal size of its bounding box. - bboxsize = sqrt(NORM2(xmax - xmin, ymax - ymin, zmax - zmin)); + bboxsize = sqrt(norm2(xmax - xmin, ymax - ymin, zmax - zmin)); bboxsize2 = bboxsize * bboxsize; bboxsize3 = bboxsize2 * bboxsize; // Make sure the second vertex is not identical with the first one. i = 1; - while ((DIST(permutarray[0], permutarray[i]) / bboxsize) < b->epsilon) { + while ((distance(permutarray[0],permutarray[i])/bboxsize)epsilon) { i++; if (i == in->numberofpoints - 1) { printf("Exception: All vertices are (nearly) identical (Tol = %g).\n", b->epsilon); - terminatetetgen(10); + terminatetetgen(this, 10); } } if (i > 1) { - // Swap to move the non-indetical vertex from index i to index 1. + // Swap to move the non-identical vertex from index i to index 1. swapvertex = permutarray[i]; permutarray[i] = permutarray[1]; permutarray[1] = swapvertex; } // Make sure the third vertex is not collinear with the first two. + // Acknowledgement: Thanks Jan Pomplun for his correction by using + // epsilon^2 and epsilon^3 (instead of epsilon). 2013-08-15. i = 2; for (j = 0; j < 3; j++) { v1[j] = permutarray[1][j] - permutarray[0][j]; v2[j] = permutarray[i][j] - permutarray[0][j]; } - CROSS(v1, v2, n); - while ((sqrt(NORM2(n[0], n[1], n[2])) / bboxsize2) < b->epsilon) { + cross(v1, v2, n); + while ((sqrt(norm2(n[0], n[1], n[2])) / bboxsize2) < + (b->epsilon * b->epsilon)) { i++; if (i == in->numberofpoints - 1) { printf("Exception: All vertices are (nearly) collinear (Tol = %g).\n", b->epsilon); - terminatetetgen(10); + terminatetetgen(this, 10); } for (j = 0; j < 3; j++) { v2[j] = permutarray[i][j] - permutarray[0][j]; } - CROSS(v1, v2, n); + cross(v1, v2, n); } if (i > 2) { - // Swap to move the non-indetical vertex from index i to index 1. + // Swap to move the non-identical vertex from index i to index 1. swapvertex = permutarray[i]; permutarray[i] = permutarray[2]; permutarray[2] = swapvertex; @@ -905,20 +1159,20 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) // Make sure the fourth vertex is not coplanar with the first three. i = 3; - ori = orient3d(permutarray[0], permutarray[1], permutarray[2], - permutarray[i]); - while ((fabs(ori) / bboxsize3) < b->epsilon) { + ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], + permutarray[i]); + while ((fabs(ori) / bboxsize3) < (b->epsilon * b->epsilon * b->epsilon)) { i++; if (i == in->numberofpoints) { printf("Exception: All vertices are coplanar (Tol = %g).\n", b->epsilon); - terminatetetgen(10); + terminatetetgen(this, 10); } - ori = orient3d(permutarray[0], permutarray[1], permutarray[2], - permutarray[i]); + ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], + permutarray[i]); } if (i > 3) { - // Swap to move the non-indetical vertex from index i to index 1. + // Swap to move the non-identical vertex from index i to index 1. swapvertex = permutarray[i]; permutarray[i] = permutarray[3]; permutarray[3] = swapvertex; @@ -940,11 +1194,14 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) if (b->verbose) { printf(" Incrementally inserting vertices.\n"); } + insertvertexflags ivf; + flipconstraints fc; // Choose algorithm: Bowyer-Watson (default) or Incremental Flip if (b->incrflip) { ivf.bowywat = 0; ivf.lawson = 1; + fc.enqflag = 1; } else { ivf.bowywat = 1; ivf.lawson = 0; @@ -952,45 +1209,58 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) for (i = 4; i < in->numberofpoints; i++) { - if (b->verbose > 2) printf(" #%d", i); if (pointtype(permutarray[i]) == UNUSEDVERTEX) { setpointtype(permutarray[i], VOLVERTEX); } - // Auto choose the starting tet for point location. - searchtet.tet = NULL; + if (b->brio_hilbert || b->no_sort) { // -b or -b/1 + // Start the last updated tet. + searchtet.tet = recenttet.tet; + } else { // -b0 + // Randomly choose the starting tet for point location. + searchtet.tet = NULL; + } ivf.iloc = (int) OUTSIDE; // Insert the vertex. - loc = insertvertex(permutarray[i], &searchtet, NULL, NULL, &ivf); - if (loc == (int) ONVERTEX) { - // The point already exists. Mark it and do nothing on it. - swapvertex = org(searchtet); - assert(swapvertex != permutarray[i]); // SELF_CHECK - if (b->object != tetgenbehavior::STL) { - if (!b->quiet) { - printf("Warning: Point #%d is coincident with #%d. Ignored!\n", - pointmark(permutarray[i]), pointmark(swapvertex)); - } + if (insertpoint(permutarray[i], &searchtet, NULL, NULL, &ivf)) { + if (flipstack != NULL) { + // Perform flip to recover Delaunayness. + incrementalflip(permutarray[i], (ivf.iloc == (int) OUTSIDE), &fc); + } + } else { + if (ivf.iloc == (int) ONVERTEX) { + // The point already exists. Mark it and do nothing on it. + swapvertex = org(searchtet); + assert(swapvertex != permutarray[i]); // SELF_CHECK + if (b->object != tetgenbehavior::STL) { + if (!b->quiet) { + printf("Warning: Point #%d is coincident with #%d. Ignored!\n", + pointmark(permutarray[i]), pointmark(swapvertex)); + } + } + setpoint2ppt(permutarray[i], swapvertex); + setpointtype(permutarray[i], DUPLICATEDVERTEX); + dupverts++; + } else if (ivf.iloc == (int) NEARVERTEX) { + swapvertex = point2ppt(permutarray[i]); + if (!b->quiet) { + printf("Warning: Point %d is replaced by point %d.\n", + pointmark(permutarray[i]), pointmark(swapvertex)); + printf(" Avoid creating a very short edge (len = %g) (< %g).\n", + permutarray[i][3], b->minedgelength); + printf(" You may try a smaller tolerance (-T) (current is %g)\n", + b->epsilon); + printf(" or use the option -M0/1 to avoid such replacement.\n"); + } + // Remember it is a duplicated point. + setpointtype(permutarray[i], DUPLICATEDVERTEX); + // Count the number of duplicated points. + dupverts++; } - setpoint2ppt(permutarray[i], swapvertex); - setpointtype(permutarray[i], DUPLICATEDVERTEX); - dupverts++; - continue; - } else if (loc == (int) NREGULARVERTEX) { - // The point is non-regular. Skipped. - continue; - } - if (ivf.lawson) { - // Perform flip to recover Delaunayness. - lawsonflip3d(permutarray[i], ivf.lawson, 0, 0, 0); } } - if (b->brio_hilbert) { - b->brio_hilbert = 0; // Disable it. - } - delete [] permutarray; } diff --git a/external/tetgen/src/flip.cxx b/external/tetgen/src/flip.cxx index 7d228c02c..122088f9a 100644 --- a/external/tetgen/src/flip.cxx +++ b/external/tetgen/src/flip.cxx @@ -3,36 +3,14 @@ //// //// //// //// -/////////////////////////////////////////////////////////////////////////////// -// // -// flippush() Push a face (possibly will be flipped) into flipstack. // -// // -// The face is marked. The flag is used to check the validity of the face on // -// its popup. Some other flips may change it already. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::flippush(badface*& fstack, triface* flipface) -{ - badface *newflipface; - - if (!facemarked(*flipface)) { - newflipface = (badface *) flippool->alloc(); - newflipface->tt = *flipface; - markface(newflipface->tt); - // Push this face into stack. - newflipface->nextitem = fstack; - fstack = newflipface; - } -} - /////////////////////////////////////////////////////////////////////////////// // // // flip23() Perform a 2-to-3 flip (face-to-edge flip). // // // -// 'fliptets' is an array of tetrahedra. On input it contains two tets // -// [a,b,c,d] and [b,a,c,e]. It returns three new tets: [e,d,a,b], [e,d,b,c], // -// [e,d,c,a]. The face [a,b,c] is removed, and the edge [d,e] is created. // +// 'fliptets' is an array of three tets (handles), where the [0] and [1] are // +// [a,b,c,d] and [b,a,c,e]. The three new tets: [e,d,a,b], [e,d,b,c], and // +// [e,d,c,a] are returned in [0], [1], and [2] of 'fliptets'. As a result, // +// The face [a,b,c] is removed, and the edge [d,e] is created. // // // // If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // // the five vertices may be 'dummypoint'. There are two canonical cases: // @@ -43,27 +21,18 @@ void tetgenmesh::flippush(badface*& fstack, triface* flipface) // rotate the three input tets counterclockwisely (right-hand rule) // // until a or b is in c's position. // // // -// If 'flipflag > 0', faces on the convex hull of the five vertices might // -// need to be flipped, e.g., for incremental DT construction or mesh quality // -// improvement. They will be queued in 'flipstack'. // -// // -// If 'flipflag = 1', it is in the process of incrmental flip DT algorithm, // -// and we assume that 'd' must be the newly inserted vertex. In such case, // -// only the link faces at 'd', i.e., three faces [a,b,e], [b,c,e], and [c,a, // -// e] needs to be queued ([Edelsbrunner & Shah'1996] and [M\"ucke'1998]). // +// If 'fc->enqflag' is set, convex hull faces will be queued for flipping. // +// In particular, if 'fc->enqflag' is 1, it is called by incrementalflip() // +// after the insertion of a new point. It is assumed that 'd' is the new // +// point. IN this case, only link faces of 'd' are queued. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, - int chkencflag) +void tetgenmesh::flip23(triface* fliptets, int hullflag, flipconstraints *fc) { triface topcastets[3], botcastets[3]; triface newface, casface; - face checksh; - face checkseg; - badface *bface; // used by chkencflag point pa, pb, pc, pd, pe; - REAL volneg[2], volpos[3], vol_diff; // volumes of involved tet-prisms. REAL attrib, volume; int dummyflag = 0; // range = {-1, 0, 1, 2}. int i; @@ -92,16 +61,12 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, } } - pa = org(fliptets[0]); + pa = org(fliptets[0]); pb = dest(fliptets[0]); pc = apex(fliptets[0]); pd = oppo(fliptets[0]); pe = oppo(fliptets[1]); - if (b->verbose > 3) { - printf(" flip 2-to-3: (%d, %d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pe)); - } flip23count++; // Get the outer boundary faces. @@ -186,7 +151,8 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, setvertices(fliptets[2], pe, pd, pc, pa); // [e,d,c,a] * } - if (calc_tetprism_vol) { + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[2], volpos[3], vol_diff; if (pd != dummypoint) { if (pc != dummypoint) { volpos[0] = tetprismvol(pe, pd, pa, pb); @@ -209,8 +175,8 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, volneg[1] = tetprismvol(pb, pa, pc, pe); } vol_diff = volpos[0] + volpos[1] + volpos[2] - volneg[0] - volneg[1]; - tetprism_vol_sum += vol_diff; // Update the total sum. - } // if (check_tetprism_vol_diff) + fc->tetprism_vol_sum += vol_diff; // Update the total sum. + } // Bond three new tets together. for (i = 0; i < 3; i++) { @@ -219,43 +185,36 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, } // Bond to top outer boundary faces (at [a,b,c,d]). for (i = 0; i < 3; i++) { - enextesym(fliptets[i], newface); - eprevself(newface); // At edges [b,a], [c,b], [a,c]. + eorgoppo(fliptets[i], newface); // At edges [b,a], [c,b], [a,c]. bond(newface, topcastets[i]); } // Bond bottom outer boundary faces (at [b,a,c,e]). for (i = 0; i < 3; i++) { - eprevesym(fliptets[i], newface); - enextself(newface); // At edges [a,b], [b,c], [c,a]. + edestoppo(fliptets[i], newface); // At edges [a,b], [b,c], [c,a]. bond(newface, botcastets[i]); } - // Bond 15 subsegments if there are. if (checksubsegflag) { + // Bond subsegments if there are. + // Each new tet has 5 edges to be checked (except the edge [e,d]). + face checkseg; // The middle three: [a,b], [b,c], [c,a]. - for (i = 0; i < 3; i++) { - tsspivot1(topcastets[i], checkseg); - if (checkseg.sh != NULL) { - enextesym(fliptets[i], newface); - eprevself(newface); // At edges [b,a], [c,b], [a,c]. + for (i = 0; i < 3; i++) { + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); + eorgoppo(fliptets[i], newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } } // The top three: [d,a], [d,b], [d,c]. Two tets per edge. for (i = 0; i < 3; i++) { - eprev(topcastets[i], casface); - tsspivot1(casface, checkseg); - if (checkseg.sh != NULL) { + eprev(topcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); enext(fliptets[i], newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); @@ -263,22 +222,16 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, eprevself(newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } } // The bot three: [a,e], [b,e], [c,e]. Two tets per edge. for (i = 0; i < 3; i++) { enext(botcastets[i], casface); - tsspivot1(casface, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(casface)) { + tsspivot1(casface, checkseg); eprev(fliptets[i], newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); @@ -286,75 +239,53 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, enextself(newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } } - } + } // if (checksubsegflag) - // Bond 6 subfaces if there are. if (checksubfaceflag) { - for (i = 0; i < 3; i++) { - tspivot(topcastets[i], checksh); - if (checksh.sh != NULL) { - enextesym(fliptets[i], newface); - eprevself(newface); // At edge [b,a], [c,b], [a,c]. + // Bond 6 subfaces if there are. + face checksh; + for (i = 0; i < 3; i++) { + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); + eorgoppo(fliptets[i], newface); sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } } for (i = 0; i < 3; i++) { - tspivot(botcastets[i], checksh); - if (checksh.sh != NULL) { - eprevesym(fliptets[i], newface); - enextself(newface); // At edge [a,b], [b,c], [c,a] + if (issubface(botcastets[i])) { + tspivot(botcastets[i], checksh); + edestoppo(fliptets[i], newface); sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } } - } + } // if (checksubfaceflag) - if (chkencflag & 4) { + if (fc->chkencflag & 4) { // Put three new tets into check list. for (i = 0; i < 3; i++) { - if (!marktest2ed(fliptets[i])) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = fliptets[i]; - marktest2(bface->tt); - bface->forg = org(fliptets[i]); - } + enqueuetetrahedron(&(fliptets[i])); } } // Update the point-to-tet map. - setpoint2tet(pa, encode(fliptets[0])); - setpoint2tet(pb, encode(fliptets[0])); - setpoint2tet(pc, encode(fliptets[1])); - setpoint2tet(pd, encode(fliptets[0])); - setpoint2tet(pe, encode(fliptets[0])); + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[1].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + setpoint2tet(pe, (tetrahedron) fliptets[0].tet); if (hullflag > 0) { if (dummyflag != 0) { @@ -387,13 +318,13 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, } } - if (flipflag > 0) { + if (fc->enqflag > 0) { // Queue faces which may be locally non-Delaunay. for (i = 0; i < 3; i++) { eprevesym(fliptets[i], newface); flippush(flipstack, &newface); } - if (flipflag > 1) { + if (fc->enqflag > 1) { for (i = 0; i < 3; i++) { enextesym(fliptets[i], newface); flippush(flipstack, &newface); @@ -408,9 +339,10 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, // // // flip32() Perform a 3-to-2 flip (edge-to-face flip). // // // -// 'fliptets' is an array of three tetrahedra. On input, it contains three // -// tets: [e,d,a,b], [e,d,b,c], and [e,d,c,a]. It returns tw tets: [a,b,c,d], // -// and [b,a,c,e]. The edge [e,d] is replaced by the face [a,b,c]. // +// 'fliptets' is an array of three tets (handles), which are [e,d,a,b], // +// [e,d,b,c], and [e,d,c,a]. The two new tets: [a,b,c,d] and [b,a,c,e] are // +// returned in [0] and [1] of 'fliptets'. As a result, the edge [e,d] is // +// replaced by the face [a,b,c]. // // // // If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // // the five vertices may be 'dummypoint'. There are two canonical cases: // @@ -421,47 +353,33 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, // three old tets counterclockwisely (right-hand rule) until a or b // // is in c's position. // // // -// If 'flipflag > 0', faces on the convex hull of the five vertices might // -// need to be flipped, e.g., for incremental DT construction or mesh quality // -// improvement. They will be queued in 'flipstack'. // -// // -// If 'flipflag = 1', it is in the process of incrmental flip DT algorithm, // -// and we assume that 'a' must be the newly inserted vertex. In such case, // -// only the link faces at 'a', i.e., two faces [c,b,d] and [b,c,e] needs to // -// be queued ( [Edelsbrunner & Shah'1996] and [M\"ucke'1998]). // +// If 'fc->enqflag' is set, convex hull faces will be queued for flipping. // +// In particular, if 'fc->enqflag' is 1, it is called by incrementalflip() // +// after the insertion of a new point. It is assumed that 'a' is the new // +// point. In this case, only link faces of 'a' are queued. // // // // If 'checksubfaceflag' is on (global variable), and assume [e,d] is not a // // segment. There may be two (interior) subfaces sharing at [e,d], which are // // [e,d,p] and [e,d,q], where the pair (p,q) may be either (a,b), or (b,c), // // or (c,a) In such case, a 2-to-2 flip is performed on these two subfaces // // and two new subfaces [p,q,e] and [p,q,d] are created. They are inserted // -// back into the tetrahedralization. However, it is possible that the new // -// subface ([p,q,e] or [p,q,d] already exists. In such case, we just delete // -// the conflict subface. As a result, either 'd' or 'e' is removed from the // -// surface mesh. A better solution would be to detect and perform a 3-to-1 // -// flip to remove 'd' or 'e' (see also 2011-11-15). // +// back into the tetrahedralization. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, - int chkencflag) +void tetgenmesh::flip32(triface* fliptets, int hullflag, flipconstraints *fc) { triface topcastets[3], botcastets[3]; triface newface, casface; - face checksh; + face flipshs[3]; face checkseg; - badface *bface; // used by chkencflag point pa, pb, pc, pd, pe; - REAL volneg[3], volpos[2], vol_diff; // volumes of involved tet-prisms. REAL attrib, volume; int dummyflag = 0; // Rangle = {-1, 0, 1, 2} + int spivot = -1, scount = 0; // for flip22() + int t1ver; int i, j; - // For 2-to-2 flip (subfaces). - face flipshs[3], flipfaces[2]; - point rempt; - int spivot = -1, scount = 0; - if (hullflag > 0) { // Check if e is 'dummypoint'. if (org(fliptets[0]) == dummypoint) { @@ -500,36 +418,24 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, pd = dest(fliptets[0]); pe = org(fliptets[0]); - if (b->verbose > 3) { - printf(" flip 3-to-2: (%d, %d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pe)); - } flip32count++; // Get the outer boundary faces. for (i = 0; i < 3; i++) { - enextesym(fliptets[i], casface); - eprevself(casface); + eorgoppo(fliptets[i], casface); fsym(casface, topcastets[i]); } for (i = 0; i < 3; i++) { - eprevesym(fliptets[i], casface); - enextself(casface); + edestoppo(fliptets[i], casface); fsym(casface, botcastets[i]); } if (checksubfaceflag) { // Check if there are interior subfaces at the edge [e,d]. - spivot = -1; - scount = 0; for (i = 0; i < 3; i++) { tspivot(fliptets[i], flipshs[i]); if (flipshs[i].sh != NULL) { - if (b->verbose > 3) { - printf(" Found an interior subface (%d, %d, %d).\n", - pointmark(sorg(flipshs[i])), pointmark(sdest(flipshs[i])), - pointmark(sapex(flipshs[i]))); - } + // Found an interior subface. stdissolve(flipshs[i]); // Disconnect the sub-tet bond. scount++; } else { @@ -543,28 +449,6 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, fliptets[1].ver = 11; setelemmarker(fliptets[0].tet, 0); // Clear all flags. setelemmarker(fliptets[1].tet, 0); - // NOTE: the element attributes and volume constraint must be set correctly. - if (checksubfaceflag) { - if (scount > 0) { - // There are two subfaces involved in this flip. The three tets are - // separated into two different regions, one may be exterior. The - // first region has two tets, and the second region has only one. - // The two created tets must be in the same region as the first region. - // The element attributes and volume constraint must be set correctly. - //assert(spivot != -1); - // The tet fliptets[spivot] is in the first region. - for (j = 0; j < 2; j++) { - for (i = 0; i < numelemattrib; i++) { - attrib = elemattribute(fliptets[spivot].tet, i); - setelemattribute(fliptets[j].tet, i, attrib); - } - if (b->varvolume) { - volume = volumebound(fliptets[spivot].tet); - setvolumebound(fliptets[j].tet, volume); - } - } - } - } if (checksubsegflag) { // Dealloc the space to subsegments. if (fliptets[0].tet[8] != NULL) { @@ -587,7 +471,28 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, fliptets[1].tet[9] = NULL; } } - + if (checksubfaceflag) { + if (scount > 0) { + // The element attributes and volume constraint must be set correctly. + // There are two subfaces involved in this flip. The three tets are + // separated into two different regions, one may be exterior. The + // first region has two tets, and the second region has only one. + // The two created tets must be in the same region as the first region. + // The element attributes and volume constraint must be set correctly. + //assert(spivot != -1); + // The tet fliptets[spivot] is in the first region. + for (j = 0; j < 2; j++) { + for (i = 0; i < numelemattrib; i++) { + attrib = elemattribute(fliptets[spivot].tet, i); + setelemattribute(fliptets[j].tet, i, attrib); + } + if (b->varvolume) { + volume = volumebound(fliptets[spivot].tet); + setvolumebound(fliptets[j].tet, volume); + } + } + } + } // Delete an old tet. tetrahedrondealloc(fliptets[2].tet); @@ -611,14 +516,15 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, esymself(fliptets[0]); // Adjust abec -> bace. esymself(fliptets[1]); - // The hullsize does not changle. + // The hullsize does not change. } } else { setvertices(fliptets[0], pa, pb, pc, pd); setvertices(fliptets[1], pb, pa, pc, pe); } - if (calc_tetprism_vol) { + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[3], volpos[2], vol_diff; if (pc != dummypoint) { if (pd != dummypoint) { volneg[0] = tetprismvol(pe, pd, pa, pb); @@ -641,7 +547,7 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, volpos[1] = 0.; } vol_diff = volpos[0] + volpos[1] - volneg[0] - volneg[1] - volneg[2]; - tetprism_vol_sum += vol_diff; // Update the total sum. + fc->tetprism_vol_sum += vol_diff; // Update the total sum. } // Bond abcd <==> bace. @@ -660,133 +566,89 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, } if (checksubsegflag) { - // Bond segments to new (flipped) tets. - for (i = 0; i < 3; i++) { - tsspivot1(topcastets[i], checkseg); - if (checkseg.sh != NULL) { + // Bond 9 segments to new (flipped) tets. + for (i = 0; i < 3; i++) { // edges a->b, b->c, c->a. + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); tssbond1(fliptets[0], checkseg); sstbond1(checkseg, fliptets[0]); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } - } - } - enextself(fliptets[0]); - } - // The three top edges. - for (i = 0; i < 3; i++) { - esym(fliptets[0], newface); - eprevself(newface); // edge b->d, c->d, a->d. - enext(topcastets[i], casface); - tsspivot1(casface, checkseg); - if (checkseg.sh != NULL) { - tssbond1(newface, checkseg); - sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } - } - } - enextself(fliptets[0]); - } - // Process the bottom tet bace. - for (i = 0; i < 3; i++) { - tsspivot1(botcastets[i], checkseg); - if (checkseg.sh != NULL) { tssbond1(fliptets[1], checkseg); sstbond1(checkseg, fliptets[1]); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } + enextself(fliptets[0]); eprevself(fliptets[1]); } - // The three bot edges. - for (i = 0; i < 3; i++) { - esym(fliptets[1], newface); - enextself(newface); // edge b<-e, c<-e, a<-e. - eprev(botcastets[i], casface); - tsspivot1(casface, checkseg); - if (checkseg.sh != NULL) { + // The three top edges. + for (i = 0; i < 3; i++) { // edges b->d, c->d, a->d. + esym(fliptets[0], newface); + eprevself(newface); + enext(topcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + } + // The three bot edges. + for (i = 0; i < 3; i++) { // edges b<-e, c<-e, a<-e. + esym(fliptets[1], newface); + enextself(newface); + eprev(botcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } eprevself(fliptets[1]); } - } + } // if (checksubsegflag) if (checksubfaceflag) { + face checksh; // Bond the top three casing subfaces. - for (i = 0; i < 3; i++) { - tspivot(topcastets[i], checksh); - if (checksh.sh != NULL) { - esym(fliptets[0], newface); // At edge [b,a], [c,b], [a,c] + for (i = 0; i < 3; i++) { // At edges [b,a], [c,b], [a,c] + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); + esym(fliptets[0], newface); sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } enextself(fliptets[0]); } // Bond the bottom three casing subfaces. - for (i = 0; i < 3; i++) { - tspivot(botcastets[i], checksh); - if (checksh.sh != NULL) { - esym(fliptets[1], newface); // // At edge [a,b], [b,c], [c,a] + for (i = 0; i < 3; i++) { // At edges [a,b], [b,c], [c,a] + if (issubface(botcastets[i])) { + tspivot(botcastets[i], checksh); + esym(fliptets[1], newface); sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } eprevself(fliptets[1]); } - } - if (checksubfaceflag) { if (scount > 0) { - assert(spivot != -1); // spivot = i, in {0,1,2} + face flipfaces[2]; // Perform a 2-to-2 flip in subfaces. flipfaces[0] = flipshs[(spivot + 1) % 3]; flipfaces[1] = flipshs[(spivot + 2) % 3]; sesymself(flipfaces[1]); - flip22(flipfaces, 0, chkencflag); + flip22(flipfaces, 0, fc->chkencflag); // Connect the flipped subfaces to flipped tets. // First go to the corresponding flipping edge. // Re-use top- and botcastets[0]. @@ -807,49 +669,9 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, sesymself(flipfaces[0]); tsbond(topcastets[0], flipfaces[0]); } else { - // Found two subfaces are duplicated at the same tet face. - // Due to the same reason explained below. - assert(sapex(checksh) == sapex(flipfaces[0])); - sspivot(checksh, checkseg); - assert(checkseg.sh == NULL); - // Delete the two duplicated subfaces. - rempt = sapex(checksh); - if (b->verbose > 2) { - printf(" Remove vertex %d from surface.\n", pointmark(rempt)); - } - // Make sure we do not delete a Steiner points in segment. - assert(pointtype(rempt) == FREEFACETVERTEX); - setpointtype(rempt, FREEVOLVERTEX); - // Re-use flipshs. - //spivot(checksh, flipshs[0]); - flipshs[0] = checksh; - spivotself(flipshs[0]); - if (flipshs[0].sh == flipfaces[0].sh) { - sesym(checksh, flipshs[0]); - spivotself(flipshs[0]); - } - assert(flipshs[0].sh != flipfaces[0].sh); - //spivot(flipfaces[0], flipshs[1]); - flipshs[1] = flipfaces[0]; - spivotself(flipshs[1]); - if (flipshs[1].sh == checksh.sh) { - sesym(flipfaces[0], flipshs[1]); - spivotself(flipshs[1]); - } - assert(flipshs[1].sh != checksh.sh); - // Bond the two subfaces together. - sbond(flipshs[0], flipshs[1]); - // Detach 'checksh' from the adjacent tets. - tsdissolve(topcastets[0]); - fsymself(topcastets[0]); - tsdissolve(topcastets[0]); - // Delete the two duplicated subfaces. - shellfacedealloc(subfaces, checksh.sh); - shellfacedealloc(subfaces, flipfaces[0].sh); + // An invalid 2-to-2 flip. Report a bug. + terminatetetgen(this, 2); } - // // Push topcastets[0] into queue for checking new sliver. - // assert(oppo(topcastets[0]) != dummypoint); - // flippush(&(topcastets[0]), oppo(topcastets[0])); // Connect the bot subface to the bottom tets. esymself(botcastets[0]); sesymself(flipfaces[1]); @@ -861,74 +683,24 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, sesymself(flipfaces[1]); tsbond(botcastets[0], flipfaces[1]); } else { - // Found two subfaces are duplicated at the same tet face. - assert(sapex(checksh) == sapex(flipfaces[1])); - // This happens in case when a Steiner point is not exactly coplanar - // or collinear with the subface or subedge where it was added. - // See figs illustrated in 2011-11-09. - sspivot(checksh, checkseg); - assert(checkseg.sh == NULL); - // Since the edge [p,q] is not a segment, both subfaces must be - // removed. The effect is that the Steiner point is removed from - // the surface triangulation. - // Delete the two duplicated subfaces. - rempt = sapex(checksh); - if (b->verbose > 2) { - printf(" Remove vertex %d from surface.\n", pointmark(rempt)); - } - // Make sure we do not delete a Steiner points in segment. - assert(pointtype(rempt) == FREEFACETVERTEX); - setpointtype(rempt, FREEVOLVERTEX); - // Re-use flipshs. - //spivot(checksh, flipshs[0]); - flipshs[0] = checksh; - spivotself(flipshs[0]); - if (flipshs[0].sh == flipfaces[1].sh) { - sesym(checksh, flipshs[0]); - spivotself(flipshs[0]); - } - assert(flipshs[0].sh != flipfaces[1].sh); - //spivot(flipfaces[1], flipshs[1]); - flipshs[1] = flipfaces[1]; - spivotself(flipshs[1]); - if (flipshs[1].sh == checksh.sh) { - sesym(flipfaces[1], flipshs[1]); - spivotself(flipshs[1]); - } - assert(flipshs[1].sh != checksh.sh); - // Bond the two subfaces together. - sbond(flipshs[0], flipshs[1]); - // Detach 'checksh' from the adjacent tets. - tsdissolve(botcastets[0]); - fsymself(botcastets[0]); - tsdissolve(botcastets[0]); - // Delete the two duplicated subfaces. - shellfacedealloc(subfaces, checksh.sh); - shellfacedealloc(subfaces, flipfaces[1].sh); + // An invalid 2-to-2 flip. Report a bug. + terminatetetgen(this, 2); } - // // Push botcastets[0] into queue for checking new sliver. - // assert(oppo(botcastets[0]) != dummypoint); - // flippush(&(botcastets[0]), oppo(botcastets[0])); - } - } + } // if (scount > 0) + } // if (checksubfaceflag) - if (chkencflag & 4) { + if (fc->chkencflag & 4) { // Put two new tets into check list. for (i = 0; i < 2; i++) { - if (!marktest2ed(fliptets[i])) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = fliptets[i]; - marktest2(bface->tt); - bface->forg = org(fliptets[i]); - } + enqueuetetrahedron(&(fliptets[i])); } } - setpoint2tet(pa, encode(fliptets[0])); - setpoint2tet(pb, encode(fliptets[0])); - setpoint2tet(pc, encode(fliptets[0])); - setpoint2tet(pd, encode(fliptets[0])); - setpoint2tet(pe, encode(fliptets[1])); + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[0].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + setpoint2tet(pe, (tetrahedron) fliptets[1].tet); if (hullflag > 0) { if (dummyflag != 0) { @@ -951,14 +723,14 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, } } - if (flipflag > 0) { + if (fc->enqflag > 0) { // Queue faces which may be locally non-Delaunay. // pa = org(fliptets[0]); // 'a' may be a new vertex. enextesym(fliptets[0], newface); flippush(flipstack, &newface); eprevesym(fliptets[1], newface); flippush(flipstack, &newface); - if (flipflag > 1) { + if (fc->enqflag > 1) { //pb = dest(fliptets[0]); eprevesym(fliptets[0], newface); flippush(flipstack, &newface); @@ -984,28 +756,28 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, // four tets in 'fliptets' are: [p,d,a,b], [p,d,b,c], [p,d,c,a], and [a,b,c, // // p]. On return, 'fliptets[0]' is the new tet [a,b,c,d]. // // // -// If 'hullflag' is set (> 0), one of the four vertices may be 'duumypoint'. // -// The 'hullsize' may be changed. // +// If 'hullflag' is set (> 0), one of the five vertices may be 'dummypoint'. // +// The 'hullsize' may be changed. Note that p may be dummypoint. In this // +// case, four hull tets are replaced by one real tet. // // // // If 'checksubface' flag is set (>0), it is possible that there are three // // interior subfaces connecting at p. If so, a 3-to-1 flip is performed to // // to remove p from the surface triangulation. // // // +// If it is called by the routine incrementalflip(), we assume that d is the // +// newly inserted vertex. // +// // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, - int chkencflag) +void tetgenmesh::flip41(triface* fliptets, int hullflag, flipconstraints *fc) { triface topcastets[3], botcastet; triface newface, neightet; face flipshs[4]; - face checksh; - face checkseg; point pa, pb, pc, pd, pp; - badface *bface; // used by chkencflag - REAL volneg[4], volpos[1], vol_diff; // volumes of involved tet-prisms. int dummyflag = 0; // in {0, 1, 2, 3, 4} int spivot = -1, scount = 0; + int t1ver; int i; pa = org(fliptets[3]); @@ -1014,11 +786,7 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, pd = dest(fliptets[0]); pp = org(fliptets[0]); // The removing vertex. - if (b->verbose > 3) { - printf(" flip 4-to-1: (%d, %d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pp)); - } - // flip41count++; + flip41count++; // Get the outer boundary faces. for (i = 0; i < 3; i++) { @@ -1031,8 +799,6 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, if (checksubfaceflag) { // Check if there are three subfaces at 'p'. // Re-use 'newface'. - spivot = -1; - scount = 0; for (i = 0; i < 3; i++) { fnext(fliptets[3], newface); // [a,b,p,d],[b,c,p,d],[c,a,p,d]. tspivot(newface, flipshs[i]); @@ -1062,6 +828,7 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, } } // if (checksubfaceflag) + // Re-use fliptets[0] for [a,b,c,d]. fliptets[0].ver = 11; setelemmarker(fliptets[0].tet, 0); // Clean all flags. @@ -1080,19 +847,20 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, fliptets[0].tet[9] = NULL; } } - // Delete the other three tets. for (i = 1; i < 4; i++) { tetrahedrondealloc(fliptets[i].tet); } - // Mark the point pp as unused. - setpointtype(pp, UNUSEDVERTEX); - unuverts++; + if (pp != dummypoint) { + // Mark the point pp as unused. + setpointtype(pp, UNUSEDVERTEX); + unuverts++; + } // Create the new tet [a,b,c,d]. if (hullflag > 0) { - // One of the four vertices may be 'dummypoint'. + // One of the five vertices may be 'dummypoint'. if (pa == dummypoint) { // pa is dummypoint. setvertices(fliptets[0], pc, pb, pd, pa); @@ -1113,17 +881,26 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, dummyflag = 4; } else { setvertices(fliptets[0], pa, pb, pc, pd); - dummyflag = 0; + if (pp == dummypoint) { + dummyflag = -1; + } else { + dummyflag = 0; + } } if (dummyflag > 0) { - // We delete 3 hull tets, and create 1 hull tet. + // We deleted 3 hull tets, and create 1 hull tet. hullsize -= 2; + } else if (dummyflag < 0) { + // We deleted 4 hull tets. + hullsize -= 4; + // meshedges does not change. } } else { setvertices(fliptets[0], pa, pb, pc, pd); } - if (calc_tetprism_vol) { + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[4], volpos[1], vol_diff; if (dummyflag > 0) { if (pa == dummypoint) { volneg[0] = 0.; @@ -1147,6 +924,12 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, volneg[3] = tetprismvol(pa, pb, pc, pp); } volpos[0] = 0.; + } else if (dummyflag < 0) { + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = 0.; + volneg[3] = 0.; + volpos[0] = tetprismvol(pa, pb, pc, pd); } else { volneg[0] = tetprismvol(pp, pd, pa, pb); volneg[1] = tetprismvol(pp, pd, pb, pc); @@ -1155,7 +938,7 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, volpos[0] = tetprismvol(pa, pb, pc, pd); } vol_diff = volpos[0] - volneg[0] - volneg[1] - volneg[2] - volneg[3]; - tetprism_vol_sum += vol_diff; // Update the total sum. + fc->tetprism_vol_sum += vol_diff; // Update the total sum. } // Bond the new tet to adjacent tets. @@ -1167,40 +950,29 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, bond(fliptets[0], botcastet); if (checksubsegflag) { + face checkseg; // Bond 6 segments (at edges of [a,b,c,d]) if there there are. for (i = 0; i < 3; i++) { eprev(topcastets[i], newface); // At edges [d,a],[d,b],[d,c]. - tsspivot1(newface, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(newface)) { + tsspivot1(newface, checkseg); esym(fliptets[0], newface); enextself(newface); // At edges [a,d], [b,d], [c,d]. tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } enextself(fliptets[0]); } for (i = 0; i < 3; i++) { - tsspivot1(topcastets[i], checkseg); // At edges [a,b],[b,c],[c,a]. - if (checkseg.sh != NULL) { + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); // At edges [a,b],[b,c],[c,a]. tssbond1(fliptets[0], checkseg); sstbond1(checkseg, fliptets[0]); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } enextself(fliptets[0]); @@ -1208,40 +980,29 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, } if (checksubfaceflag) { + face checksh; // Bond 4 subfaces (at faces of [a,b,c,d]) if there are. for (i = 0; i < 3; i++) { - tspivot(topcastets[i], checksh); // At faces [a,b,d],[b,c,d],[c,a,d] - if (checksh.sh != NULL) { + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); // At faces [a,b,d],[b,c,d],[c,a,d] esym(fliptets[0], newface); // At faces [b,a,d],[c,b,d],[a,c,d] sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } enextself(fliptets[0]); } - tspivot(botcastet, checksh); // At face [b,a,c] - if (checksh.sh != NULL) { + if (issubface(botcastet)) { + tspivot(botcastet, checksh); // At face [b,a,c] sesymself(checksh); tsbond(fliptets[0], checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } - } - if (checksubfaceflag) { if (spivot >= 0) { // Perform a 3-to-1 flip in surface triangulation. // Depending on the value of 'spivot', the three subfaces are: @@ -1275,30 +1036,26 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, } // if (spivot > 0) } // if (checksubfaceflag) - if (chkencflag & 4) { - // Put the new tet into check list. - if (!marktest2ed(fliptets[0])) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = fliptets[0]; - marktest2(bface->tt); - bface->forg = org(fliptets[0]); - } + if (fc->chkencflag & 4) { + enqueuetetrahedron(&(fliptets[0])); } // Update the point-to-tet map. - setpoint2tet(pa, encode(fliptets[0])); - setpoint2tet(pb, encode(fliptets[0])); - setpoint2tet(pc, encode(fliptets[0])); - setpoint2tet(pd, encode(fliptets[0])); + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[0].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); - if (flipflag > 0) { + if (fc->enqflag > 0) { // Queue faces which may be locally non-Delaunay. - for (i = 0; i < 3; i++) { - esym(fliptets[0], newface); - flippush(flipstack, &newface); - enextself(fliptets[0]); + flippush(flipstack, &(fliptets[0])); // [a,b,c] (opposite to new point). + if (fc->enqflag > 1) { + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); + flippush(flipstack, &newface); + enextself(fliptets[0]); + } } - flippush(flipstack, &(fliptets[0])); } recenttet = fliptets[0]; @@ -1306,7 +1063,7 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, /////////////////////////////////////////////////////////////////////////////// // // -// flipnm() Try to flip an edge through a sequence of elementary flips. // +// flipnm() Flip an edge through a sequence of elementary flips. // // // // 'abtets' is an array of 'n' tets in the star of edge [a,b].These tets are // // ordered in a counterclockwise cycle with respect to the vector a->b, i.e.,// @@ -1331,7 +1088,6 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, // - Neither a nor b is 'dummypoint'. // // - [a,b] must not be a segment. // // // -// // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, @@ -1339,27 +1095,19 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, { triface fliptets[3], spintet, flipedge; triface *tmpabtets, *parytet; - face checksh; - face checkseg, *paryseg; point pa, pb, pc, pd, pe, pf; - point tmppts[3]; - REAL abovept[3]; - REAL ori, ori1, ori2; + REAL ori; + int hullflag, hulledgeflag; int reducflag, rejflag; - int hullflag; int reflexlinkedgecount; int edgepivot; int n1, nn; + int t1ver; int i, j; pa = org(abtets[0]); pb = dest(abtets[0]); - if (b->verbose > 2) { - printf(" flipnm(%d): (%d, %d) - n(%d), e(%d).\n", level, pointmark(pa), - pointmark(pb), n, abedgepivot); - } - if (n > 3) { // Try to reduce the size of the Star(ab) by flipping a face in it. reflexlinkedgecount = 0; @@ -1367,9 +1115,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, for (i = 0; i < n; i++) { // Let the face of 'abtets[i]' be [a,b,c]. if (checksubfaceflag) { - // Do not flip this face if it is a constraining face. - tspivot(abtets[i], checksh); - if (checksh.sh != NULL) { + if (issubface(abtets[i])) { continue; // Skip a subface. } } @@ -1378,32 +1124,20 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, (elemcounter(abtets[(i - 1 + n) % n]) > 1)) { continue; } + pc = apex(abtets[i]); pd = apex(abtets[(i + 1) % n]); pe = apex(abtets[(i - 1 + n) % n]); if ((pd == dummypoint) || (pe == dummypoint)) { - // [a,b,c] is a hull face, it is not flipable. - continue; + continue; // [a,b,c] is a hull face. } - if (checkinverttetflag) { - // The mesh contains inverted (or degenerated) elements. - // Only do check if both elements are valid. - if (pc != dummypoint) { - ori = orient3d(pa, pb, pc, pd); - if (ori < 0) { - ori = orient3d(pb, pa, pc, pe); - } - if (ori >= 0) { - continue; // An invalid tet. - } - } else { - continue; - } - } // if (checkinverttetflag) - reducflag = 0; // Not reducible. + + // Decide whether [a,b,c] is flippable or not. + reducflag = 0; hullflag = (pc == dummypoint); // pc may be dummypoint. + hulledgeflag = 0; if (hullflag == 0) { ori = orient3d(pb, pc, pd, pe); // Is [b,c] locally convex? if (ori > 0) { @@ -1417,8 +1151,11 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, } else if (ori == 0) { // [a,b] is flat. if (n == 4) { - // The "flat" tet can be removed immedately by a 3-to-2 flip. + // The "flat" tet can be removed immediately by a 3-to-2 flip. reducflag = 1; + // Check if [e,d] is a hull edge. + pf = apex(abtets[(i + 2) % n]); + hulledgeflag = (pf == dummypoint); } } } @@ -1443,16 +1180,27 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, if (ori < 0) { // Found a 4-to-4 flip: [a,b] => [e,d] reducflag = 1; - ori = 0; // Signal as a 4-to-4 flip (like a co-palanar case). + ori = 0; // Signal as a 4-to-4 flip (like a co-planar case). + hulledgeflag = 1; // [e,d] is a hull edge. } } } } // if (hullflag) + if (reducflag) { + if (nonconvex && hulledgeflag) { + // We will create a hull edge [e,d]. Make sure it does not exist. + if (getedge(pe, pd, &spintet)) { + // The 2-to-3 flip is not a topological valid flip. + reducflag = 0; + } + } + } + if (reducflag) { // [a,b,c] could be removed by a 2-to-3 flip. rejflag = 0; - if (fc != NULL) { + if (fc->checkflipeligibility) { // Check if the flip can be performed. rejflag = checkflipeligibility(1, pa, pb, pc, pd, pe, level, abedgepivot, fc); @@ -1461,7 +1209,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Do flip: [a,b,c] => [e,d]. fliptets[0] = abtets[i]; fsym(fliptets[0], fliptets[1]); // abtets[i-1]. - flip23(fliptets, hullflag, 0, 0); + flip23(fliptets, hullflag, fc); // Shrink the array 'abtets', maintain the original order. // Two tets 'abtets[i-1] ([a,b,e,c])' and 'abtets[i] ([a,b,c,d])' @@ -1479,21 +1227,19 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // [n-2] |___________| [n-2] |___________| // [n-1] |___________| [n-1] |_[i]_2-t-3_| // - eprevself(fliptets[0]); - esymself(fliptets[0]); - enextself(fliptets[0]); // [a,b,e,d] + edestoppoself(fliptets[0]); // [a,b,e,d] // Increase the counter of this new tet (it is in Star(ab)). - increaseelemcounter(fliptets[0]); //marktest(fliptets[0]); + increaseelemcounter(fliptets[0]); abtets[(i - 1 + n) % n] = fliptets[0]; for (j = i; j < n - 1; j++) { abtets[j] = abtets[j + 1]; // Upshift } // The last entry 'abtets[n-1]' is empty. It is used in two ways: - // (i) it remebers the vertex 'c' (in 'abtets[n-1].tet'), and - // (ii) it remebers the position [i] where this flip took place. + // (i) it remembers the vertex 'c' (in 'abtets[n-1].tet'), and + // (ii) it remembers the position [i] where this flip took place. // These informations let us to either undo this flip or recover // the original edge link (for collecting new created tets). - //abtets[n - 1] = fliptets[1]; // [e,d,b,c] is remebered. + //abtets[n - 1] = fliptets[1]; // [e,d,b,c] is remembered. abtets[n - 1].tet = (tetrahedron *) pc; abtets[n - 1].ver = 0; // Clear it. // 'abtets[n - 1].ver' is in range [0,11] -- only uses 4 bits. @@ -1514,27 +1260,28 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Star(ab) is reduced. Try to flip the edge [a,b]. nn = flipnm(abtets, n - 1, level, abedgepivot, fc); - if (nn > 2) { + if (nn == 2) { + // The edge has been flipped. + return nn; + } else { // if (nn > 2) // The edge is not flipped. if (fc->unflip || (ori == 0)) { // Undo the previous 2-to-3 flip, i.e., do a 3-to-2 flip to // transform [e,d] => [a,b,c]. - // 'ori == 0' means that the previous flip created a degenrated + // 'ori == 0' means that the previous flip created a degenerated // tet. It must be removed. - // Remeber that 'abtets[i-1]' is [a,b,e,d]. We can use it to + // Remember that 'abtets[i-1]' is [a,b,e,d]. We can use it to // find another two tets [e,d,b,c] and [e,d,c,a]. fliptets[0] = abtets[(i-1 + (n-1)) % (n-1)]; // [a,b,e,d] - eprevself(fliptets[0]); - esymself(fliptets[0]); - enextself(fliptets[0]); // [e,d,a,b] + edestoppoself(fliptets[0]); // [e,d,a,b] fnext(fliptets[0], fliptets[1]); // [1] is [e,d,b,c] fnext(fliptets[1], fliptets[2]); // [2] is [e,d,c,a] assert(apex(fliptets[0]) == oppo(fliptets[2])); // SELF_CHECK // Restore the two original tets in Star(ab). - flip32(fliptets, hullflag, 0, 0); + flip32(fliptets, hullflag, fc); // Marktest the two restored tets in Star(ab). for (j = 0; j < 2; j++) { - increaseelemcounter(fliptets[j]); //marktest(fliptets[j]); + increaseelemcounter(fliptets[j]); } // Expand the array 'abtets', maintain the original order. for (j = n - 2; j>= i; j--) { @@ -1550,30 +1297,17 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Pop two (flipped) tets from the stack. cavetetlist->objects -= 2; } - } // if (upflip || (ori == 0)) + } // if (unflip || (ori == 0)) } // if (nn > 2) - if (nn == 2) { //if ((nn == 2) || !fullsearch) { - // The edge has been flipped. - return nn; - } if (!fc->unflip) { // The flips are not reversed. The current Star(ab) can not be - // further reduced. Return its size (# of tets). + // further reduced. Return its current size (# of tets). return nn; } // unflip is set. // Continue the search for flips. - } else { - if (b->verbose > 2) { - printf(" -- Reject a 2-to-3 flip at star face (%d, %d, %d)", - pointmark(pa), pointmark(pb), pointmark(pc)); - printf(", link (%d)\n", level); - } - if (fc != NULL) { - fc->rejf23count++; - } - } // if (rejflag) + } } // if (reducflag) } // i @@ -1582,16 +1316,6 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // There are reflex edges in the Link(ab). if (((b->fliplinklevel < 0) && (level < autofliplinklevel)) || ((b->fliplinklevel >= 0) && (level < b->fliplinklevel))) { - // Record the largest level. - if ((level + 1) > maxfliplinklevel) { - maxfliplinklevel = level + 1; - } - if (fc != NULL) { - // Increase the link level counter. - if ((level + 1) > fc->maxflippedlinklevelcount) { - fc->maxflippedlinklevelcount = level + 1; - } - } // Try to reduce the Star(ab) by flipping a reflex edge in Link(ab). for (i = 0; i < n; i++) { // Do not flip this face [a,b,c] if there are two Stars involved. @@ -1601,25 +1325,14 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, } pc = apex(abtets[i]); if (pc == dummypoint) { - continue; // [a,b,dummypoint] is a hull edge. + continue; // [a,b] is a hull edge. } pd = apex(abtets[(i + 1) % n]); pe = apex(abtets[(i - 1 + n) % n]); if ((pd == dummypoint) || (pe == dummypoint)) { continue; // [a,b,c] is a hull face. } - if (checkinverttetflag) { - // The mesh contains inverted (or degenerated) elements. - // Only do check if both elements are valid. - // assert(pc != dummypoint); - ori = orient3d(pa, pb, pc, pd); - if (ori < 0) { - ori = orient3d(pb, pa, pc, pe); - } - if (ori >= 0) { - continue; // An invalid tet. - } - } // if (checkinverttetflag) + edgepivot = 0; // No edge is selected yet. @@ -1645,21 +1358,15 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // An edge is selected. if (checksubsegflag) { // Do not flip it if it is a segment. - tsspivot1(flipedge, checkseg); - if (checkseg.sh != NULL) { - if (b->verbose > 2) { - printf(" -- Can't flip a link(%d) segment (%d, %d).\n", - level, pointmark(org(flipedge)), pointmark(dest(flipedge))); - } - if (fc != NULL) { - fc->encsegcount++; - if (fc->collectencsegflag) { - if (!sinfected(checkseg)) { - // Queue this segment in list. - sinfect(checkseg); - caveencseglist->newindex((void **) &paryseg); - *paryseg = checkseg; - } + if (issubseg(flipedge)) { + if (fc->collectencsegflag) { + face checkseg, *paryseg; + tsspivot1(flipedge, checkseg); + if (!sinfected(checkseg)) { + // Queue this segment in list. + sinfect(checkseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; } } continue; @@ -1674,7 +1381,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, spintet = flipedge; while (1) { n1++; - j += (elemcounter(spintet)); //if (marktested(spintet)) j++; + j += (elemcounter(spintet)); fnextself(spintet); if (spintet.tet == flipedge.tet) break; } @@ -1686,14 +1393,8 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Only two tets can be marktested. assert(j == 2); - flipstarcount++; - // Record the maximum star size. - if (n1 > maxflipstarsize) { - maxflipstarsize = n1; - } if ((b->flipstarsize > 0) && (n1 > b->flipstarsize)) { - // The star size exceeds the given limit (-LL__). - skpflipstarcount++; + // The star size exceeds the given limit. continue; // Do not flip it. } @@ -1710,15 +1411,6 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, fnextself(spintet); if (spintet.tet == flipedge.tet) break; } - // SELF_CHECK BEGIN - // These two tets are inside both of the Stars. - assert(elemcounter(tmpabtets[0]) == 2); - assert(elemcounter(tmpabtets[1]) == 2); - // Marktest the tets in Star(flipedge) but not in Star(ab). - for (j = 2; j < n1; j++) { - assert(elemcounter(tmpabtets[j]) == 1); - //marktest(tmpabtets[j]); - } // Try to flip the selected edge away. nn = flipnm(tmpabtets, n1, level + 1, edgepivot, fc); @@ -1739,10 +1431,8 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, esymself(spintet); eprevself(spintet); // [a,b,e,d] } // edgepivot == 2 - //assert(!marktested(spintet)); // It's a new tet. - assert(elemcounter(spintet) == 0); - //marktest(spintet); // It is in Star(ab). - increaseelemcounter(spintet); + assert(elemcounter(spintet) == 0); // It's a new tet. + increaseelemcounter(spintet); // It is in Star(ab). // Put the new tet at [i-1]-th entry. abtets[(i - 1 + n) % n] = spintet; for (j = i; j < n - 1; j++) { @@ -1769,7 +1459,10 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Continue to flip the edge [a,b]. nn = flipnm(abtets, n - 1, level, abedgepivot, fc); - if (nn > 2) { + if (nn == 2) { + // The edge has been flipped. + return nn; + } else { // if (nn > 2) { // The edge is not flipped. if (fc->unflip) { // Recover the flipped edge ([c,b] or [a,c]). @@ -1823,7 +1516,6 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, enextself(fliptets[1]); // [a,b,c,d] } // edgepivot == 2 for (j = 0; j < 2; j++) { - assert(elemcounter(fliptets[j]) == 0); // SELF_CHECK increaseelemcounter(fliptets[j]); } // Insert the two recovered tets into Star(ab). @@ -1835,10 +1527,6 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, } // if (unflip) } // if (nn > 2) - if (nn == 2) { //if ((nn == 2) || !fullsearch) { - // The edge has been flipped. - return nn; - } if (!fc->unflip) { // The flips are not reversed. The current Star(ab) can not be // further reduced. Return its size (# of tets). @@ -1847,7 +1535,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // unflip is set. // Continue the search for flips. } else { - // The seclected edge is not flipped. + // The selected edge is not flipped. if (fc->unflip) { // The memory should already be freed. assert(nn == n1); @@ -1864,72 +1552,34 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, delete [] tmpabtets; } } // i - } else { - if (b->verbose > 2) { - printf(" -- Maximal link level (%d) reached at edge (%d, %d).\n", - level, pointmark(org(abtets[0])), pointmark(dest(abtets[0]))); - } - if (fc != NULL) { - fc->misfliplinklevelcount++; - } } // if (level...) } // if (reflexlinkedgecount > 0) } else { // Check if a 3-to-2 flip is possible. - pc = apex(abtets[0]); - pd = apex(abtets[1]); - pe = apex(abtets[2]); - - // Check if one of them is dummypoint. If so, we rearrange the vertices - // c, d, and e into p0, p1, and p2, such that p2 is the dummypoint. + // Let the three apexes be c, d,and e. Hull tets may be involved. If so, + // we rearrange them such that the vertex e is dummypoint. hullflag = 0; - if (pc == dummypoint) { + + if (apex(abtets[0]) == dummypoint) { + pc = apex(abtets[1]); + pd = apex(abtets[2]); + pe = apex(abtets[0]); hullflag = 1; - tmppts[0] = pd; - tmppts[1] = pe; - tmppts[2] = pc; - } else if (pd == dummypoint) { - hullflag = 1; - tmppts[0] = pe; - tmppts[1] = pc; - tmppts[2] = pd; - } else if (pe == dummypoint) { - hullflag = 1; - tmppts[0] = pc; - tmppts[1] = pd; - tmppts[2] = pe; + } else if (apex(abtets[1]) == dummypoint) { + pc = apex(abtets[2]); + pd = apex(abtets[0]); + pe = apex(abtets[1]); + hullflag = 2; } else { - tmppts[0] = pc; - tmppts[1] = pd; - tmppts[2] = pe; + pc = apex(abtets[0]); + pd = apex(abtets[1]); + pe = apex(abtets[2]); + hullflag = (pe == dummypoint) ? 3 : 0; } reducflag = 0; rejflag = 0; - if (checkinverttetflag) { - // Only do flip if no tet is inverted (or degenerated). - if (hullflag == 0) { - ori = orient3d(pa, pb, pc, pd); - if (ori < 0) { - ori = orient3d(pa, pb, pd, pe); - if (ori < 0) { - ori = orient3d(pa, pb, pe, pc); - } - } - } else { - ori = orient3d(pa, pb, tmppts[0], tmppts[1]); - } - if (ori >= 0) { - if (b->verbose > 2) { - printf(" -- Hit a non-valid tet (%d, %d) - (%d, %d, %d)", - pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), - pointmark(pe)); - printf(" at link(%d)\n", level); - } - return 3; - } - } // if (checkinverttetflag) if (hullflag == 0) { // Make sure that no inverted tet will be created, i.e. the new tets @@ -1940,98 +1590,63 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, if (ori < 0) { reducflag = 1; } - } else { - if (b->verbose > 2) { - printf(" -- Hit a chrismastree (%d, %d) - (%d, %d, %d)", - pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), - pointmark(pe)); - printf(" at link(%d)\n", level); - } - if (fc != NULL) { - fc->chrismastreecount++; - } } } else { - // [a,b] is a hull edge. Moreover, the tet [a,b,p0,p1] is a hull tet - // ([a,b,p0] and [a,b,p1] are two hull faces). - // This can happen when it is in the middle of a 4-to-4 flip. - // Note that [a,b] may even be a non-convex hull edge. + // [a,b] is a hull edge. + // Note: This can happen when it is in the middle of a 4-to-4 flip. + // Note: [a,b] may even be a non-convex hull edge. if (!nonconvex) { - // [a,b], [a,b,p0] and [a,b,p1] are on the convex hull. - ori = orient3d(pa, pb, tmppts[0], tmppts[1]); + // The mesh is convex, only do flip if it is a coplanar hull edge. + ori = orient3d(pa, pb, pc, pd); if (ori == 0) { - // They four vertices are coplanar. A 2-to-2 flip is possible if - // [a,b] and [p0,p1] are intersecting each other. - // NOTE: The following test is not robust, should be replaced in - // the future. 2011-12-01. - calculateabovepoint4(pa, pb, tmppts[0], tmppts[1]); - for (j = 0; j < 3; j++) { - abovept[j] = dummypoint[j]; - } - // Make sure that no inverted face will be created, i.e., [p1,p0, - // abvpt,pa] and [p0,p1,abvpt,pb] must be valid tets. - ori1 = orient3d(tmppts[0], tmppts[1], abovept, pa); - ori2 = orient3d(tmppts[0], tmppts[1], abovept, pb); - if (ori1 * ori2 < 0) { - reducflag = 1; // Flipable. - } - if (!reducflag) { - if (b->verbose > 2) { - printf(" -- Hit a degenerate chrismastree (%d, %d)", - pointmark(pa), pointmark(pb)); - printf(" - (%d, %d, -1) at link(%d)\n", - pointmark(tmppts[0]), pointmark(tmppts[1]), level); - } - if (fc != NULL) { - fc->chrismastreecount++; - } - } - } else { - if (b->verbose > 2) { - printf(" -- Hit a convex hull edge (%d, %d) at link(%d).\n", - pointmark(pa), pointmark(pb), level); - } - if (fc != NULL) { - fc->convexhulledgecount++; - } - } - } else { // if (nonconvex) - // [a,b,p0] and [a,b,p1] must be two subfaces. - // Since [a,b] is not a segment. A 3-to-2 flip (including a 2-to-2 - // flip) is possible. - // Here we only do flip if there are exactly three tets containing - // the edge [p0,p1]. In this case, the other two tets at [p0,p1] - // (not [a,b,p0,p1]) must be valid. Since they already exist. - for (j = 0; j < 3; j++) { - if (apex(abtets[j]) == dummypoint) { - flipedge = abtets[(j + 1) % 3]; // [a,b,p0,p1]. - break; - } - } - // assert(j < 3); - eprevself(flipedge); - esymself(flipedge); - enextself(flipedge); // [p0,p1,a,b]. - assert(apex(flipedge) == pa); - spintet = flipedge; - j = 0; - while (1) { - j++; - fnextself(spintet); - if (spintet.tet == flipedge.tet) break; - } - if (j == 3) { reducflag = 1; - } else { - if (b->verbose > 2) { - printf(" -- Hit a hull edge (%d, %d) at link(%d).\n", - pointmark(pa), pointmark(pb), level); - } - //if (fc != NULL) { - // fc->convexhulledgecount++; - //} } + } else { // nonconvex + reducflag = 1; } + if (reducflag == 1) { + // [a,b], [a,b,c] and [a,b,d] are on the convex hull. + // Make sure that no inverted tet will be created. + point searchpt = NULL, chkpt; + REAL bigvol = 0.0, ori1, ori2; + // Search an interior vertex which is an apex of edge [c,d]. + // In principle, it can be arbitrary interior vertex. To avoid + // numerical issue, we choose the vertex which belongs to a tet + // 't' at edge [c,d] and 't' has the biggest volume. + fliptets[0] = abtets[hullflag % 3]; // [a,b,c,d]. + eorgoppoself(fliptets[0]); // [d,c,b,a] + spintet = fliptets[0]; + while (1) { + fnextself(spintet); + chkpt = oppo(spintet); + if (chkpt == pb) break; + if ((chkpt != dummypoint) && (apex(spintet) != dummypoint)) { + ori = -orient3d(pd, pc, apex(spintet), chkpt); + assert(ori > 0); + if (ori > bigvol) { + bigvol = ori; + searchpt = chkpt; + } + } + } + if (searchpt != NULL) { + // Now valid the configuration. + ori1 = orient3d(pd, pc, searchpt, pa); + ori2 = orient3d(pd, pc, searchpt, pb); + if (ori1 * ori2 >= 0.0) { + reducflag = 0; // Not valid. + } else { + ori1 = orient3d(pa, pb, searchpt, pc); + ori2 = orient3d(pa, pb, searchpt, pd); + if (ori1 * ori2 >= 0.0) { + reducflag = 0; // Not valid. + } + } + } else { + // No valid searchpt is found. + reducflag = 0; // Do not flip it. + } + } // if (reducflag == 1) } // if (hullflag == 1) if (reducflag) { @@ -2042,10 +1657,12 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // the surface mesh will be automatically performed within the // 3-to-2 flip. nn = 0; + edgepivot = -1; // Re-use it. for (j = 0; j < 3; j++) { - tspivot(abtets[j], checksh); - if (checksh.sh != NULL) { + if (issubface(abtets[j])) { nn++; // Found a subface. + } else { + edgepivot = j; } } assert(nn < 3); @@ -2054,32 +1671,39 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // the boundary recovery phase. The neighbor subface is not yet // recovered. This edge should not be flipped at this moment. rejflag = 1; + } else if (nn == 2) { + // Found two subfaces. A 2-to-2 flip is possible. Validate it. + // Below we check if the two faces [p,q,a] and [p,q,b] are subfaces. + eorgoppo(abtets[(edgepivot + 1) % 3], spintet); // [q,p,b,a] + if (issubface(spintet)) { + rejflag = 1; // Conflict to a 2-to-2 flip. + } else { + esymself(spintet); + if (issubface(spintet)) { + rejflag = 1; // Conflict to a 2-to-2 flip. + } + } } } - if (!rejflag && (fc != NULL)) { + if (!rejflag && fc->checkflipeligibility) { // Here we must exchange 'a' and 'b'. Since in the check... function, // we assume the following point sequence, 'a,b,c,d,e', where // the face [a,b,c] will be flipped and the edge [e,d] will be // created. The two new tets are [a,b,c,d] and [b,a,c,e]. - rejflag = checkflipeligibility(2, tmppts[0], tmppts[1], tmppts[2], - pb, pa, level, abedgepivot, fc); + rejflag = checkflipeligibility(2, pc, pd, pe, pb, pa, level, + abedgepivot, fc); } if (!rejflag) { // Do flip: [a,b] => [c,d,e] - flip32(abtets, hullflag, 0, 0); - sucflipstarcount++; + flip32(abtets, hullflag, fc); if (fc->remove_ndelaunay_edge) { if (level == 0) { - // It is the desired removing edge. - if (tetprism_vol_sum >= fc->bak_tetprism_vol) { - if (b->verbose > 2) { - printf(" -- Reject to flip (%d, %d) at link(%d)\n", - pointmark(pa), pointmark(pb), level); - printf(" due to an increased volume (%.17g).\n", - tetprism_vol_sum - fc->bak_tetprism_vol); - } - // flip back: [c,d,e] => [a,b]. - flip23(abtets, hullflag, 0, 0); + // It is the desired removing edge. Check if we have improved + // the objective function. + if ((fc->tetprism_vol_sum >= 0.0) || + (fabs(fc->tetprism_vol_sum) < fc->bak_tetprism_vol)) { + // No improvement! flip back: [c,d,e] => [a,b]. + flip23(abtets, hullflag, fc); // Increase the element counter -- They are in cavity. for (j = 0; j < 3; j++) { increaseelemcounter(abtets[j]); @@ -2109,15 +1733,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, } } // if (fc->collectnewtets) return 2; - } else { - if (b->verbose > 2) { - printf(" -- Reject a 3-to-2 flip (%d, %d) at link(%d).\n", - pointmark(pa), pointmark(pb), level); - } - if (fc != NULL) { - fc->rejf32count++; - } - } // if (rejflag) + } } // if (reducflag) } // if (n == 3) @@ -2172,7 +1788,7 @@ int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, // 'abtets[1]' is [d,c,e,a] or [#,#,#,a]. if (fc->unflip) { // Do a 2-to-3 flip to recover the edge [a,b]. There may be hull tets. - flip23(abtets, 1, 0, 0); + flip23(abtets, 1, fc); if (fc->collectnewtets) { // Pop up new (flipped) tets from the stack. if (abedgepivot == 0) { @@ -2212,7 +1828,7 @@ int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, fnext(fliptets[1], fliptets[2]); // [e,d,c,a] // Do a 3-to-2 flip: [e,d] => [a,b,c]. // NOTE: hull tets may be invloved. - flip32(fliptets, 1, 0, 0); + flip32(fliptets, 1, fc); // Expand the array 'abtets', maintain the original order. // The new array length is (i+1). for (j = i - 1; j >= t; j--) { @@ -2299,9 +1915,6 @@ int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, printf(" Release %d spaces at f[%d].\n", n1, i); } delete [] tmpabtets; - } else { - assert(fliptype == 0); // Not a saved flip. - assert(0); // Should be not possible. } } // i @@ -2310,668 +1923,7 @@ int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, /////////////////////////////////////////////////////////////////////////////// // // -// lawsonflip3d() A three-dimensional Lawson's flip algorithm. // -// // -// The basic idea of Lawson's algorithm is to flip every face of the triang- // -// ulation which is not locally Delaunay until no such face exists, then the // -// triangulation is a DT. However, in 3D, it is common that a face which is // -// not locally Delaunay and is not flippable. Hence, Laowson's algorithm may // -// get stuck. It is still an open problem, whether there exists a flip algo- // -// rithm which has a guarantee to create a DT in 3D. // -// // -// If only one vertex is added into a DT, then Lawson's flip algorithm is // -// guaranteed to transform it into a new DT [Joe'91]. Moreover, an arbitrary // -// order of flips is sufficient [Edelsbrunner & Shah'96]. // -// // -// In practice, it is desired to remove not locally Delaunay faces by flips // -// as many as possible. For this purpose, a second queue is used to store // -// the not locally Delaunay faces which are not flippable, and try them at a // -// later time. // -// // -// If 'newpt' (p) is not NULL, it is a new vertex just inserted into the // -// tetrahedralization T. // -// // -// 'flipflag' indicates the property of the tetrahedralization 'T' which // -// does not include 'p' yet. // -// // -// If 'peelsliverflag' is set, the purpose of calling Lawson's flip is to // -// remove "hull slivers". This flag only works with a non-convex mesh, i.e., // -// the mesh must contains boundaries (segments and subfaces). // -// // -// 'chkencflag' indicates whether segments, subfaces, and tets should be // -// checked (for encroaching and quality) after flips. // -// // -/////////////////////////////////////////////////////////////////////////////// - -long tetgenmesh::lawsonflip3d(point newpt, int flipflag, int peelsliverflag, - int chkencflag, int flipedgeflag) -{ - badface *popface, *bface; - triface fliptets[5], baktets[2]; - triface fliptet, neightet, *parytet; - face checksh, *parysh; - face checkseg, *paryseg; - point *ppt, pd, pe, pf; - long flipcount; - REAL sign, ori; - int convflag; - int n, i; - - // For removing hull slivers. - face neighsh; - point p1, p2; - point pa, pb, pc, rempt; - REAL ang; - long tetpeelcount; - int remflag; - - flipconstraints fc; - - if (b->verbose > 2) { - printf(" Lawson flip %ld faces.\n", flippool->items); - } - - flipcount = flip23count + flip32count + flip44count; - tetpeelcount = opt_sliver_peels; - - if (flipedgeflag) { - fc.remove_ndelaunay_edge = 1; - fc.unflip = 1; // Unflip if the edge is not flipped. - fc.collectnewtets = 1; - assert(cavetetlist->objects == 0l); - assert(calc_tetprism_vol == 1); // Swith on. - } else { - assert(unflipqueue->objects == 0); // The second queue must be empty. - } - - while (1) { - - while (flipstack != (badface *) NULL) { - - // Pop a face from the stack. - popface = flipstack; - flipstack = flipstack->nextitem; // The next top item in stack. - fliptet = popface->tt; - flippool->dealloc((void *) popface); - - // Skip it if it is a dead tet (destroyed by previous flips). - if (isdeadtet(fliptet)) continue; - // Skip it if it is not the same tet as we saved. - if (!facemarked(fliptet)) continue; - - unmarkface(fliptet); - - if (ishulltet(fliptet)) { - // It is a hull tet. - if (((flipflag == 4) || peelsliverflag) && !b->convex) { - fliptet.ver = epivot[fliptet.ver & 3]; - if (oppo(fliptet) == dummypoint) { - // It's a hull face (oppo(fliptet) == dummypoint). - // Check if there exists a "hull sliver". - fsymself(fliptet); - tspivot(fliptet, checksh); - assert(checksh.sh != NULL); - for (i = 0; i < 3; i++) { - sspivot(checksh, checkseg); - if (checkseg.sh == NULL) { - spivot(checksh, neighsh); - assert(neighsh.sh != NULL); - if (sorg(checksh) != sdest(neighsh)) { - sesymself(neighsh); - } - stpivot(neighsh, neightet); - if (neightet.tet == fliptet.tet) { - // Found a hull sliver 'neightet' [d,e,a,b], where [d,e,a] - // and [e,d,b] are two hull faces. Normally, a 3-to-2 flip - // (including a 2-to-2 flip on hull subfaces) can remove - // this hull sliver. - // A special case is the existence of a hull tet [b,a,d,-1] - // or [a,b,e,-1]. It was creared by a previous hull tet - // removal. Moreover, 'd' or 'e' might be Steiner points - // on segments [a,b]. In this case, eithe [a,d],[b,d] or - // [a,e],[b,e] are subsegments. If so, a 4-to-1 flip - // (including a 3-to-1, and maybe a 2-to-1 flip) should be - // applied to remove an exterior vertex. - // First check if the face [b,a,d] is a hull face. - eprev(neightet, fliptets[0]); - esymself(fliptets[0]); // [d,a,b,e] - enextself(fliptets[0]); // [a,b,d,e] - fsymself(fliptets[0]); // [b,a,d,#] - if (oppo(fliptets[0]) != dummypoint) { - // Second check if the face [a,b,e] is a hull face. - enext(neightet, fliptets[0]); - esymself(fliptets[0]); // [a,e,b,d] - eprevself(fliptets[0]); // [b,a,e,d] - fsymself(fliptets[0]); // [b,a,e,#] - } - - if (oppo(fliptets[0]) != dummypoint) { - // Make sure we do not create an "inverted triangle" in the - // boundary, i.e., in exactly planar case, d and e must - // lie in the different sides of the edge [a,b]. - // If the dihedral angle formed by [a,b,e] and [a,b,d] is - // larger than 90 degree, we can remove [a,b,e,d]. - fliptets[0] = neightet; // [e,d,a,b] - eprevself(fliptets[0]); - esymself(fliptets[0]); - enextself(fliptets[0]); // [a,b,e,d]. - pa = org(fliptets[0]); - pb = dest(fliptets[0]); - p1 = apex(fliptets[0]); // pe - p2 = oppo(fliptets[0]); // pd - ang = facedihedral(pa, pb, p1, p2); - ang *= 2.0; - if (ang > PI) { - if (b->verbose > 2) { - printf(" Remove a hull sliver (%d, %d, %d, %d).\n", - pointmark(org(fliptet)), pointmark(dest(fliptet)), - pointmark(apex(fliptet)), pointmark(oppo(fliptet))); - } - // Remove the ill tet from bounday. - fliptets[0] = neightet; // [e,d,a,b] - fnext(fliptets[0], fliptets[1]); // [e,d,b,c] - fnext(fliptets[1], fliptets[2]); // [e,d,c,a] - // FOR DEBUG - fnext(fliptets[2], fliptets[3]); - assert(fliptets[3].tet == neightet.tet); - assert(oppo(fliptets[1]) == dummypoint); - // Do a 3-to-2 flip to remove the ill tet. Two hull tets - // are removed toether. Two hull subfaces are flipped. - flip32(fliptets, 1, flipflag, 0); - // Update counters. - flip32count--; - flip22count--; - opt_sliver_peels++; - } - } else { - // There exists a thrid hull tet at vertex. - rempt = apex(fliptets[0]); - if (pointmark(rempt) > - (in->numberofpoints - (in->firstnumber ? 0 : 1))) { - if (pointtype(rempt) == FREESEGVERTEX) { - st_segref_count--; - } else if (pointtype(rempt) == FREEFACETVERTEX) { - st_facref_count--; - } else { - assert(0); // Impossible. - } - if (b->verbose > 2) { - printf(" Remove an exterior Steiner vertex %d.\n", - pointmark(rempt)); - } - if (removevertexbyflips(rempt)) { - // exsteinercount++; - } else { - assert(0); // Not possible. - } - } else { - //if (b->verbose > 2) { - // printf(" Remove an exterior input vertex %d.\n", - // pointmark(rempt)); - //} - // Comment: We do not remove an input point. - } - } - break; - } - } // if (checkseg.sh == NULL) - senextself(checksh); - } // i - } else { - // It's a hull edge. - assert(apex(fliptet) == dummypoint); - if (!peelsliverflag) { - // The hull edge may be not locally Delaunay. Put interior - // faces at this edge into 'flipstack' for flipping. - neightet = fliptet; // [a,b,c,d] ('c' is dummypoint). - fnextself(neightet); // [a,b,d,#1] ([a,b,d] is a hull face). - while (1) { - fnextself(neightet); // [a,b,#1,#2] - if (oppo(neightet) != dummypoint) { - // It is an interior face. - flippush(flipstack, &neightet); - } else { - // We assume the interior of the domain is connected. - // Hence we can hit hull faces only twice. - break; - } - } // while (1) - } // if (!peelsliverflag) - } - } // if ((flipflag == 4) || peelsliverflag) - - // Do not flip a hull face/edge UNLESS it is in the process of - // incrementally creating a DT in which the convex hull may be - // enlarged by the flips (when p lies outside of it). - if (flipflag != 1) { - continue; - } - } // if (ishulltet(fliptet)) - - if (peelsliverflag) { - continue; // Only check hull tets. - } - - // Let 'fliptet' be [a,b,c,d], the face [a,b,c] is the flip face. - // Get its opposite tet [b,a,c,e]. - fsym(fliptet, neightet); - - if (ishulltet(neightet)) { - // It is a hull tet. - if (flipflag == 1) { - // Check if the new point is visible by the hull face. - ppt = (point *) neightet.tet; - ori = orient3d(ppt[4], ppt[5], ppt[6], newpt); - if (ori < 0) { - // Visible. Perform a 2-to-3 flip on the flip face. - fliptets[0] = fliptet; // [a,b,c,d], d = newpt. - fliptets[1] = neightet; // [b,a,c,e], c = dummypoint. - flip23(fliptets, 1, flipflag, chkencflag); // flip a hull tet. - //recenttet = fliptets[0]; - } else if (ori == 0) { - // Handle degenerate case ori == 0. - if (oppo(neightet) == newpt) { - // Two hull tets have the same base face. - if (b->verbose > 2) { - printf(" Close an open face (%d, %d, %d)\n", - pointmark(org(fliptet)), pointmark(dest(fliptet)), - pointmark(apex(fliptet))); - } - // The following code connect adjacent tets at corresponding - // sides of the two hull tets. It is hard to understand. - // See an example in 2011-11-11. - // First infect the two hull tets (they will be deleted). - infect(fliptet); - infect(neightet); - // Connect the actual adjacent tets. - for (i = 0; i < 3; i++) { - fnext(fliptet, fliptets[0]); - fnext(neightet, fliptets[1]); - if (!infected(fliptets[0])) { - assert(!infected(fliptets[1])); - bond(fliptets[0], fliptets[1]); - // Update the point-to-tet map. - pa = org(fliptet); - pb = dest(fliptet); - setpoint2tet(pa, encode(fliptets[0])); - setpoint2tet(pb, encode(fliptets[0])); - // Remeber a recent tet for point location. - recenttet = fliptets[0]; - // apex(fliptets[0]) is the new point. The opposite face may - // be not locally Delaunay. Put it in flip stack. - assert(apex(fliptets[0]) == newpt); // SELF_CHECK - esymself(fliptets[0]); - flippush(flipstack, &(fliptets[0])); - assert(apex(fliptets[1]) == newpt); // SELF_CHECK - esymself(fliptets[1]); - flippush(flipstack, &(fliptets[1])); - } - enextself(fliptet); - eprevself(neightet); - } - // Delete the two tets. - tetrahedrondealloc(fliptet.tet); - tetrahedrondealloc(neightet.tet); - // Update the hull size. - hullsize -= 2; - } - } - } // if (flipflag == 1) - - continue; // Do not flip a hull face. - } // if (ishulltet(neightet)) - - if (ishulltet(fliptet)) { - continue; // Do not flip a hull tet. - } - - if ((flipflag == 3) || (flipflag == 4)) { - if (checksubfaceflag) { - // Do not flip a subface. - tspivot(fliptet, checksh); - if (checksh.sh != NULL) { - if (chkencflag & 2) { - // Mesh refinement. - // Put this subface into list. - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(checksh); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface. - } - } - continue; - } - } - } // if ((flipflag == 3) || (flipflag == 4)) - - ppt = (point *) fliptet.tet; - pe = oppo(neightet); - - sign = insphere_s(ppt[4], ppt[5], ppt[6], ppt[7], pe); - - if (sign < 0) { - if (b->verbose > 3) { - printf(" A non-Delaunay face (%d, %d, %d) - %d, %d\n", - pointmark(org(fliptet)), pointmark(dest(fliptet)), - pointmark(apex(fliptet)), pointmark(oppo(fliptet)), - pointmark(pe)); - } - - // Try to flip this face. - pd = oppo(fliptet); - // Check the convexity of its three edges. - convflag = 1; - for (i = 0; i < 3; i++) { - p1 = org(fliptet); - p2 = dest(fliptet); - ori = orient3d(p1, p2, pd, pe); - if (ori < 0) { - // A locally non-convex edge. - convflag = -1; - break; - } else if (ori == 0) { - // A locally flat edge. - convflag = 0; - break; - } - enextself(fliptet); - } - - if (convflag > 0) { - // A 2-to-3 flip is found. - fliptets[0] = fliptet; // abcd, d may be the new vertex. - fliptets[1] = neightet; // bace. - if ((flipflag == 1) || (flipflag == 2)) { // CDT boundary recovery. - if (checksubfaceflag) { - // Check if a subface will be flipped. - tspivot(fliptets[0], checksh); - if (checksh.sh != NULL) { - assert(flipflag < 3); // 1 or 2. - // It is updateing a conforming DT or a CDT. - if (b->verbose > 3) { - printf(" Queue a flipped subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - for (i = 0; i < 2; i++) { - tsdissolve(fliptets[i]); // Disconnect the tet->sub bond. - } - stdissolve(checksh); // Disconnect the sub->tet bond. - // Add the missing subface into list. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } // if (checksh.sh != NULL) - } - } // if ((flipflag == 1) || (flipflag == 2)) - flip23(fliptets, 0, flipflag, chkencflag); - //recenttet = fliptets[0]; // for point location. - } else { - // The edge ('fliptet') is non-convex or flat. - if ((flipflag == 3) || (flipflag == 4)) { - // Do not flip a subsegment. - tsspivot1(fliptet, checkseg); - if (checkseg.sh != NULL) { - if (b->verbose > 3) { - printf(" Found a non-Delaunay segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - // Comment: this should be only possible when a new Steiner - // point is inserted on a segment nearby. - if (chkencflag & 1) { - // Put this segment into list. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(checkseg); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } - } - continue; - } - } - - // A 3-to-2 or 4-to-4 may be possible. - esym(fliptet, fliptets[0]); // [b,a,d,c] - // assert(apex(fliptets[0]) == pd); - n = 0; - do { - fnext(fliptets[n], fliptets[n + 1]); - n++; - } while ((fliptets[n].tet != fliptet.tet) && (n < 5)); - - if (n == 3) { - // Found a 3-to-2 flip. - if ((flipflag == 1) || (flipflag == 2)) { // CDT boundary recovery. - if (checksubsegflag) { - // Check if the flip edge is subsegment. - tsspivot1(fliptets[0], checkseg); - if (checkseg.sh != NULL) { - if (!sinfected(checkseg)) { - // This subsegment will be flipped. Queue it. - if (b->verbose > 3) { - printf(" Queue a flipped segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - sinfect(checkseg); // Only save it once. - subsegstack->newindex((void **) &paryseg); - *paryseg = checkseg; - } - // Clean tet-to-seg pointers. - for (i = 0; i < 3; i++) { - tssdissolve1(fliptets[i]); - } - // Clean the seg-to-tet pointer. - sstdissolve1(checkseg); - } - } - if (checksubfaceflag) { - // Check if there are subfaces to be flipped. - for (i = 0; i < 3; i++) { - tspivot(fliptets[i], checksh); - if (checksh.sh != NULL) {//if (flipshs[i].sh != NULL) { - if (b->verbose > 2) { - printf(" Queue a flipped subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - tsdissolve(fliptets[i]); // Disconnect the tet->sub bond. - stdissolve(checksh); // Disconnect the sub->tet bond. - // Add the missing subface into list. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } - } - } - } // if ((flipflag == 1) || (flipflag == 2)) - - // Now flip the edge. - flip32(fliptets, 0, flipflag, chkencflag); - //recenttet = fliptets[0]; // for point location. - } else { - // There are more than 3 tets shared at this edge. - if ((n == 4) && (convflag < 0)) { - // Check if a 4-to-4 flip is possible. - pf = apex(fliptets[3]); - if (pf == dummypoint) { - // It is a non-convex hull edge shared by four tets (two hull - // tets and two interior tets). - // Let the two interior tets be [a,b,c,d] and [b,a,c,e] where - // [a,b] be the hull edge, [a,b,c] be the interior face. - // [a,b,d] and [a,b,e] are two hull faces. - // A 4-to-4 flip is possible if the two new tets [e,d,b,c] - // and [e,d,c,a] are valid tets. - // Current status: - // 'fliptets[0]' is [a,b,e,c] - // 'fliptets[1]' is [a,b,c,d] - // 'fliptets[2]' is [a,b,d,f] (hull tet) - // 'fliptets[3]' is [a,b,f,e] (hull tet) - pa = org(fliptets[1]); - pb = dest(fliptets[1]); - pc = apex(fliptets[1]); - p1 = oppo(fliptets[1]); // pd - p2 = apex(fliptets[0]); // pe - ori = orient3d(p2, p1, pb, pc); - if (ori < 0) { - ori = orient3d(p2, p1, pc, pa); - if (ori < 0) { - convflag = -2; // A 4-to-4 flip is possible. - } - } - } - } // if ((n == 4) && (convflag < 0)) - if ((n == 4) && ((convflag == 0) || (convflag == -2))) { - // Found a 4-to-4 flip. - if (b->verbose > 3) { - printf(" A 4-to-4 flip (%d, %d) - (%d, %d).\n", - pointmark(org(fliptet)), pointmark(dest(fliptet)), - pointmark(pd), pointmark(pe)); - } - if ((flipflag == 1) || (flipflag == 2)) { // CDT boundary recovery - if (checksubsegflag) { - // Check if the flip edge is subsegment. - tsspivot1(fliptets[0], checkseg); - if (checkseg.sh != NULL) { - if (!sinfected(checkseg)) { - // This subsegment will be flipped. Queue it. - if (b->verbose > 3) { - printf(" Queue a flipped segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - sinfect(checkseg); // Only save it once. - subsegstack->newindex((void **) &paryseg); - *paryseg = checkseg; - } - // Clean the tet-to-seg pointers. - for (i = 0; i < 4; i++) { - tssdissolve1(fliptets[i]); - } - // Clean the seg-to-tet pointer. - sstdissolve1(checkseg); - } - } - if (checksubfaceflag) { - // Check if there are subfaces to be flipped. - for (i = 0; i < 4; i++) { - tspivot(fliptets[i], checksh); - if (checksh.sh != NULL) { - if (b->verbose > 3) { - printf(" Queue a flipped subface (%d,%d,%d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - tsdissolve(fliptets[i]); // Disconnect the tet->sub bond. - stdissolve(checksh); // Disconnect the sub->tet bond. - // Add the missing subface into list. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } - } - } - } // if ((flipflag == 1) || (flipflag == 2)) - - // First do a 2-to-3 flip. - // Comment: This flip temporarily creates either a degenerated - // tet (convflag == 0) or an inverted tet (convflag < 0). - // It is removed by the followed 3-to-2 flip. - fliptets[0] = fliptet; // tet abcd, d is the new vertex. - baktets[0] = fliptets[2]; - baktets[1] = fliptets[3]; - // The flip may involve hull tets. - flip23(fliptets, 1, flipflag, chkencflag); - // Then do a 3-to-2 flip. - enextesymself(fliptets[0]); // fliptets[0] is edab. - eprevself(fliptets[0]); // tet badc, d is the new vertex. - fliptets[1] = baktets[0]; - fliptets[2] = baktets[1]; - flip32(fliptets, 1, flipflag, chkencflag); - flip23count--; - flip32count--; - flip44count++; - //recenttet = fliptets[0]; // for point location. - } else { - // This edge is shared by more than 4 tets. - if (b->verbose > 2) { - printf(" An unflippable non-Delaunay edge (%d,%d).\n", - pointmark(org(fliptet)), pointmark(dest(fliptet))); - } - remflag = 0; - if (flipedgeflag == 2) { - // Try to flip this edge by my edge flip algorithm. - // Remember the the objective value (volume of all tetprisms). - fc.bak_tetprism_vol = tetprism_vol_sum; - if (removeedgebyflips(&fliptet, &fc) == 2) { - if (b->verbose > 2) { - printf(" Decreased quantity: %.17g.\n", - fc.bak_tetprism_vol - tetprism_vol_sum); - } - // Queue new faces in flipstack. - for (i = 0; i < cavetetlist->objects; i++) { - parytet = (triface *) fastlookup(cavetetlist, i); - if (!isdeadtet(*parytet)) { // Skip a dead tet. - for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { - // Avoid queue a face twice. - fsym(*parytet, neightet); - if (!facemarked(neightet)) { - //flippush(flipstack, parytet); - bface = (badface *) flippool->alloc(); - bface->tt = *parytet; - markface(bface->tt); - bface->forg = org(bface->tt); // An alive badface. - bface->fdest = dest(bface->tt); - bface->fapex = apex(bface->tt); - // bface->foppo = oppo(bface->tt); - // Push this face into stack. - bface->nextitem = flipstack; - flipstack = bface; - } - } // parytet->ver - } - } // i - cavetetlist->restart(); - remflag = 1; - } - } - if (!remflag) { - // Found an unflippable non-Delaunay edge. - if (flipedgeflag > 0) { // if (flipflag > 1) { - // Save this face (of the edge) in a second queue. - unflipqueue->newindex((void **) &bface); - bface->tt = fliptet; - bface->forg = org(fliptet); - bface->fdest = dest(fliptet); - bface->fapex = apex(fliptet); // FOR DEBUG. - } - } - } - } // if (n > 3) - } // if (convflag <= 0) - } // if (sign < 0) - - } // while (flipstack != NULL) - - - break; - - } // while (1) - - - if (b->verbose > 2) { - printf(" Total %ld flips", flip23count + flip32count + flip44count - - flipcount); - if ((flipflag == 4) || peelsliverflag) { - printf(", %ld sliver peels", opt_sliver_peels - tetpeelcount); - } - printf("\n"); - } - - - return flip23count + flip32count + flip44count - flipcount; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// insertvertex() Insert a point into current tetrahedralization. // +// insertpoint() Insert a point into current tetrahedralization. // // // // The Bowyer-Watson (B-W) algorithm is used to add a new point p into the // // tetrahedralization T. It first finds a "cavity", denoted as C, in T, C // @@ -2982,74 +1934,47 @@ long tetgenmesh::lawsonflip3d(point newpt, int flipflag, int peelsliverflag, // C and p. If T is not a DT, then C may be not star-shaped. It must be // // modified so that it becomes star-shaped. // // // -// // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, - face *splitseg, insertvertexflags *ivf) +int tetgenmesh::insertpoint(point insertpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf) { - arraypool *swaplist; // for updating cavity. + arraypool *swaplist; triface *cavetet, spintet, neightet, neineitet, *parytet; triface oldtet, newtet, newneitet; - face checksh, *parysh, neighsh, spinsh; + face checksh, neighsh, *parysh; face checkseg, *paryseg; point *pts, pa, pb, pc, *parypt; - badface *bface; - enum locateresult loc; + enum locateresult loc = OUTSIDE; REAL sign, ori; - REAL rd, cent[3]; REAL attrib, volume; - long cutcount, cutshcount, tetcount = 0; - long bakhullsize; bool enqflag; + int t1ver; int i, j, k, s; - int rejptflag, encptflag; // for protecting balls. - int bgmloc; - - if (b->verbose > 2) { printf(" Insert point %d\n", pointmark(insertpt)); } - // Locate the point. - loc = OUTSIDE; // Set a default value. - if (searchtet->tet != NULL) { loc = (enum locateresult) ivf->iloc; } if (loc == OUTSIDE) { - tetcount = ptloc_count; // Count the number of visited tets. if (searchtet->tet == NULL) { if (!b->weighted) { - if (b->brio_hilbert) { // -b - *searchtet = recenttet; - } else { // -b0 - randomsample(insertpt, searchtet); - } + randomsample(insertpt, searchtet); } else { - // There may exist dangling vertex. + // Weighted DT. There may exist dangling vertex. *searchtet = recenttet; } } // Locate the point. - loc = locate(insertpt, searchtet, ivf->chkencflag); - if (b->verbose > 3) { - printf(" Walk distance (# tets): %ld\n", ptloc_count-tetcount); - } - if (ptloc_max_count < (ptloc_count - tetcount)) { - ptloc_max_count = (ptloc_count - tetcount); - } - } - - if (b->verbose > 3) { - printf(" Located tet (%d, %d, %d, %d).\n", - pointmark(org(*searchtet)), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); + loc = locate(insertpt, searchtet); } + ivf->iloc = (int) loc; // The return value. if (b->weighted) { if (loc != OUTSIDE) { @@ -3061,92 +1986,71 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, insertpt[3]); if (sign > 0) { // This new vertex does not lie below the lower hull. Skip it. - if (b->verbose > 1) { - printf(" Point #%d is non-regular, skipped.\n", - pointmark(insertpt)); - } setpointtype(insertpt, NREGULARVERTEX); nonregularcount++; - return NONREGULAR; + ivf->iloc = (int) NONREGULAR; + return 0; } } } - // Create the initial cavity C(p) which contains all tetrahedra directly - // intersect with p. - - // Remember the current hullsize. It is used to restore the hullsize - // if the new point is rejected for insertion. - bakhullsize = hullsize; + // Create the initial cavity C(p) which contains all tetrahedra that + // intersect p. It may include 1, 2, or n tetrahedra. + // If p lies on a segment or subface, also create the initial sub-cavity + // sC(p) which contains all subfaces (and segment) which intersect p. if (loc == OUTSIDE) { - if (b->verbose > 3) { - printf(" Outside hull.\n"); - } + flip14count++; // The current hull will be enlarged. // Add four adjacent boundary tets into list. for (i = 0; i < 4; i++) { decode(searchtet->tet[i], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } - if ((point) searchtet->tet[7] == dummypoint) hullsize--; - // tetrahedrondealloc(searchtet->tet); infect(*searchtet); caveoldtetlist->newindex((void **) &parytet); *parytet = *searchtet; - flip14count++; } else if (loc == INTETRAHEDRON) { - if (b->verbose > 3) { - printf(" Inside tet.\n"); - } + flip14count++; // Add four adjacent boundary tets into list. for (i = 0; i < 4; i++) { decode(searchtet->tet[i], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } - // tetrahedrondealloc(searchtet->tet); infect(*searchtet); caveoldtetlist->newindex((void **) &parytet); *parytet = *searchtet; - flip14count++; } else if (loc == ONFACE) { - if (b->verbose > 3) { - printf(" On face.\n"); - } + flip26count++; // Add six adjacent boundary tets into list. j = (searchtet->ver & 3); // The current face number. for (i = 1; i < 4; i++) { decode(searchtet->tet[(j + i) % 4], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } decode(searchtet->tet[j], spintet); j = (spintet.ver & 3); // The current face number. for (i = 1; i < 4; i++) { decode(spintet.tet[(j + i) % 4], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } - if ((point) spintet.tet[7] == dummypoint) hullsize--; - if ((point) searchtet->tet[7] == dummypoint) hullsize--; - // tetrahedrondealloc(spintet.tet); infect(spintet); caveoldtetlist->newindex((void **) &parytet); *parytet = spintet; - // tetrahedrondealloc(searchtet->tet); infect(*searchtet); caveoldtetlist->newindex((void **) &parytet); *parytet = *searchtet; - flip26count++; - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitsh != NULL) { + if (ivf->splitbdflag) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { // Create the initial sub-cavity sC(p). smarktest(*splitsh); caveshlist->newindex((void **) &parysh); @@ -3154,35 +2058,30 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // if (splitbdflag) } else if (loc == ONEDGE) { - if (b->verbose > 3) { - printf(" On edge.\n"); - } + flipn2ncount++; // Add all adjacent boundary tets into list. spintet = *searchtet; while (1) { - enextesym(spintet, neightet); - fsymself(neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + eorgoppo(spintet, neightet); + decode(neightet.tet[neightet.ver & 3], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; - eprevesym(spintet, neightet); - fsymself(neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + edestoppo(spintet, neightet); + decode(neightet.tet[neightet.ver & 3], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; - if ((point) spintet.tet[7] == dummypoint) hullsize--; - // tetrahedrondealloc(spintet.tet); infect(spintet); caveoldtetlist->newindex((void **) &parytet); *parytet = spintet; fnextself(spintet); if (spintet.tet == searchtet->tet) break; } // while (1) - flipn2ncount++; - if (ivf->splitbdflag) { //if (bowywat > 2) { + if (ivf->splitbdflag) { // Create the initial sub-cavity sC(p). - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { smarktest(*splitseg); splitseg->shver = 0; spivot(*splitseg, *splitsh); @@ -3214,9 +2113,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // if (splitbdflag) } else if (loc == INSTAR) { - if (b->verbose > 3) { - printf(" Inside star.\n"); - } // We assume that all tets in the star are given in 'caveoldtetlist', // and they are all infected. assert(caveoldtetlist->objects > 0); @@ -3228,75 +2124,24 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, decode(cavetet->tet[j], neightet); if (!infected(neightet)) { // It's a boundary face. - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } } } } else if (loc == ONVERTEX) { - pa = org(*searchtet); - if (b->verbose > 3) { - printf(" On vertex %d.\n", pointmark(pa)); - } - if (insertpt != pa) { - // Remember it is a duplicated point. - setpointtype(insertpt, DUPLICATEDVERTEX); - // Set a pointer to the point it duplicates. - setpoint2ppt(insertpt, pa); - } // The point already exist. Do nothing and return. - return (int) loc; - } else if (loc == ENCSUBFACE) { - if (b->verbose > 3) { - printf(" Beyond boundary.\n"); - } - // The vertex lies outside of the region boundary. - if (ivf->rejflag & 2) { - // Check if this vertex lies very close to the boundary face. - // This case needs to be handled due to the rounding off error. - tspivot(*searchtet, checksh); - assert(checksh.sh != NULL); - pa = sorg(checksh); - pb = sdest(checksh); - pc = sapex(checksh); - ori = orient3d(pa, pb, pc, insertpt); - // Re-use cent[3]. - cent[0] = distance(pa, insertpt); - cent[1] = distance(pb, insertpt); - cent[2] = distance(pb, insertpt); - // Choose the largest distance. - if (cent[0] < cent[1]) cent[0] = cent[1]; - if (cent[0] < cent[2]) cent[0] = cent[2]; - if (fabs(ori) / (cent[0] * cent[0] * cent[0]) < b->epsilon) { - // A nearly co-planar subface. We treat this case as coplanar, so - // the insertion point does not lie outside of the domain. - // Queue an encroached subface. - // Calculate the circumcenter of this subface (for refinement). - circumsphere(pa, pb, pc, NULL, cent, &rd); - encshlist->newindex((void **) &bface); - bface->ss = checksh; - bface->forg = pa; // Not a dad one. - for (j = 0; j < 3; j++) bface->cent[j] = cent[j]; - bface->key = rd; - return (int) ENCSUBFACE; - } - } - // Treated it as outside - loc = OUTSIDE; - return (int) loc; - } else { - assert(0); // Unknown type. - } + return 0; + } if (ivf->assignmeshsize) { // Assign mesh size for the new point. if (bgm != NULL) { // Interpolate the mesh size from the background mesh. - pa = org(*searchtet); - bgm->decode(point2bgmtet(pa), neightet); // neightet is in 'bgm'! - bgmloc = bgm->scoutpoint(insertpt, &neightet, 0); // randflag = 0 + bgm->decode(point2bgmtet(org(*searchtet)), neightet); + int bgmloc = (int) bgm->scoutpoint(insertpt, &neightet, 0); if (bgmloc != (int) OUTSIDE) { insertpt[pointmtrindex] = bgm->getpointmeshsize(insertpt, &neightet, bgmloc); @@ -3307,84 +2152,21 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // if (assignmeshsize) - if (ivf->validflag) { //if (bowywat > 2) { - // Validate the initial C(p). Enlarge it at a face which is not visible - // by p. This removes (interior) slivers. Re-use 'cavebdrylist'. - tetcount = 0l; - + if (ivf->bowywat) { + // Update the cavity C(p) using the Bowyer-Watson algorithm. + swaplist = cavetetlist; + cavetetlist = cavebdrylist; + cavebdrylist = swaplist; for (i = 0; i < cavetetlist->objects; i++) { + // 'cavetet' is an adjacent tet at outside of the cavity. cavetet = (triface *) fastlookup(cavetetlist, i); - // Other expansions may make this face inside C(p). + // The tet may be tested and included in the (enlarged) cavity. if (!infected(*cavetet)) { - pc = apex(*cavetet); - // Do valid if it is a face (not a hull edge). - if (pc != dummypoint) { - pa = org(*cavetet); - pb = dest(*cavetet); - ori = orient3d(pa, pb, pc, insertpt); - if (ori <= 0) { - // An invalid face. Enlarge the cavity. - if (b->verbose > 3) { - printf(" Enlarge cavity at (%d, %d, %d)\n", - pointmark(pa), pointmark(pb), pointmark(pc)); - } - // Add the other three faces into list. - j = (cavetet->ver & 3); // The current face number. - for (k = 1; k < 4; k++) { - decode(cavetet->tet[(j + k) % 4], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; - } - if ((point) cavetet->tet[7] == dummypoint) hullsize--; - infect(*cavetet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = *cavetet; - tetcount++; - } else { - // A valid face. - cavebdrylist->newindex((void **) &parytet); - *parytet = *cavetet; - } - } else { - // A hull edge is valid. - cavebdrylist->newindex((void **) &parytet); - *parytet = *cavetet; - } - } // if (!infected(*cavetet)) - } // i - - if (tetcount > 0l) { - // The cavity has been enlarged. Update it. - cavetetlist->restart(); - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - if (!infected(*cavetet)) { - cavetetlist->newindex((void **) &parytet); - *parytet = *cavetet; - } - } // i - } // if (tetcount) - - cavebdrylist->restart(); - tetcount = 0l; - } // if (bowywat > 2) - - // Update the cavity C(p) using the Bowyer-Watson approach (bowywat > 0). - - for (i = 0; i < cavetetlist->objects; i++) { - // 'cavetet' is an adjacent tet at outside of the cavity. - cavetet = (triface *) fastlookup(cavetetlist, i); - // The tet may be tested and included in the (enlarged) cavity. - if (!infected(*cavetet)) { - // Check for two possible cases for this tet: - // (1) It is a cavity tet, or - // (2) it is a cavity boundary face. - // In case (1), this tet is grabbed in the cavity and three adjacent - // tets on other faces of this tet are added into 'cavetetlist'. - enqflag = false; - if (!marktested(*cavetet)) { - if (ivf->bowywat) { + // Check for two possible cases for this tet: + // (1) It is a cavity tet, or + // (2) it is a cavity boundary face. + enqflag = false; + if (!marktested(*cavetet)) { // Do Delaunay (in-sphere) test. pts = (point *) cavetet->tet; if (pts[7] != dummypoint) { @@ -3406,14 +2188,12 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, //if (!nonconvex) { // Include it in the cavity. The convex hull will be enlarged. enqflag = true; // (ori < 0.0); - //} + //} } else if (ori == 0.0) { // A coplanar hull face. We need to test if this hull face is // Delaunay or not. We test if the adjacent tet (not faked) // of this hull face is Delaunay or not. - neightet = *cavetet; - neightet.ver = 3; // The face opposite to dummypoint. - fsym(neightet, neineitet); + decode(cavetet->tet[3], neineitet); if (!infected(neineitet)) { if (!marktested(neineitet)) { // Do Delaunay test on this tet. @@ -3427,12 +2207,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); } enqflag = (sign < 0.0); - } else { - // The adjacent tet has been tested (marktested), and it - // is Delaunay (not get infected). Hence the the hull - // face is Delaunay as well. - // enqflag = false; - } + } } else { // The adjacent tet is non-Delaunay. The hull face is non- // Delaunay as well. Include it in the cavity. @@ -3441,15 +2216,11 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } // if (ori == 0.0) } else { // A hull face (must be a subface). - assert(checksubfaceflag); - assert(ivf->validflag); // We FIRST include it in the initial cavity if the adjacent tet // (not faked) of this hull face is not Delaunay wrt p. // Whether it belongs to the final cavity will be determined // during the validation process. 'validflag'. - neightet = *cavetet; - neightet.ver = 3; // The face opposite to dummypoint. - fsym(neightet, neineitet); + decode(cavetet->tet[3], neineitet); if (!infected(neineitet)) { if (!marktested(neineitet)) { // Do Delaunay test on this tet. @@ -3463,12 +2234,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); } enqflag = (sign < 0.0); - } else { - // The adjacent tet has been tested (marktested), and it - // is Delaunay (not get infected). Hence the the hull - // face is Delaunay as well. - // enqflag = false; - } // if (marktested(neineitet)) + } } else { // The adjacent tet is non-Delaunay. The hull face is non- // Delaunay as well. Include it in the cavity. @@ -3476,200 +2242,131 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } // if (infected(neineitet)) } // if (nonconvex) } // if (pts[7] != dummypoint) - } // if (bowywat) - marktest(*cavetet); // Only test it once. - } // if (!marktested(*cavetet)) + marktest(*cavetet); // Only test it once. + } // if (!marktested(*cavetet)) - if (enqflag) { - // Found a tet in the cavity. Put other three faces in check list. - k = (cavetet->ver & 3); // The current face number - for (j = 1; j < 4; j++) { - decode(cavetet->tet[(j + k) % 4], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; + if (enqflag) { + // Found a tet in the cavity. Put other three faces in check list. + k = (cavetet->ver & 3); // The current face number + for (j = 1; j < 4; j++) { + decode(cavetet->tet[(j + k) % 4], neightet); + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(*cavetet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *cavetet; + } else { + // Found a boundary face of the cavity. + cavetet->ver = epivot[cavetet->ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = *cavetet; } - if ((point) cavetet->tet[7] == dummypoint) hullsize--; - // tetrahedrondealloc(cavetet->tet); - infect(*cavetet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = *cavetet; - } else { - // Found a boundary face of the cavity. It may be a face of a hull - // tet which contains 'dummypoint'. Choose the edge in the face - // such that its endpoints are not 'dummypoint', while its apex - // may be 'dummypoint'. - //j = (cavetet->ver & 3); // j is the face number. - //cavetet->ver = epivot[j]; // [4,5,2,11] - cavebdrylist->newindex((void **) &parytet); - *parytet = *cavetet; - } - } // if (!infected(*cavetet)) - } // i - - if (b->verbose > 3) { - printf(" Initial cavity size: %ld tets, %ld faces.\n", - caveoldtetlist->objects, cavebdrylist->objects); - } + } // if (!infected(*cavetet)) + } // i + cavetetlist->restart(); // Clear the working list. + } // if (ivf->bowywat) if (checksubsegflag) { // Collect all segments of C(p). + shellface *ssptr; for (i = 0; i < caveoldtetlist->objects; i++) { cavetet = (triface *) fastlookup(caveoldtetlist, i); - for (j = 0; j < 6; j++) { - cavetet->ver = edge2ver[j]; - tsspivot1(*cavetet, checkseg); - if (checkseg.sh != NULL) { - if (!sinfected(checkseg)) { - sinfect(checkseg); - cavetetseglist->newindex((void **) &paryseg); - *paryseg = checkseg; + if ((ssptr = (shellface*) cavetet->tet[8]) != NULL) { + for (j = 0; j < 6; j++) { + if (ssptr[j]) { + sdecode(ssptr[j], checkseg); + if (!sinfected(checkseg)) { + sinfect(checkseg); + cavetetseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } } - } + } // j } - } + } // i // Uninfect collected segments. for (i = 0; i < cavetetseglist->objects; i++) { - checkseg = * (face *) fastlookup(cavetetseglist, i); - suninfect(checkseg); + paryseg = (face *) fastlookup(cavetetseglist, i); + suninfect(*paryseg); + } + + if (ivf->rejflag & 1) { + // Reject this point if it encroaches upon any segment. + face *paryseg1; + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg1 = (face *) fastlookup(cavetetseglist, i); + if (checkseg4encroach((point) paryseg1->sh[3], (point) paryseg1->sh[4], + insertpt)) { + encseglist->newindex((void **) &paryseg); + *paryseg = *paryseg1; + } + } // i + if (encseglist->objects > 0) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSEGMENT; + return 0; + } } } // if (checksubsegflag) if (checksubfaceflag) { // Collect all subfaces of C(p). + shellface *sptr; for (i = 0; i < caveoldtetlist->objects; i++) { cavetet = (triface *) fastlookup(caveoldtetlist, i); - oldtet = *cavetet; - for (oldtet.ver = 0; oldtet.ver < 4; oldtet.ver++) { - tspivot(oldtet, checksh); - if (checksh.sh != NULL) { - if (!sinfected(checksh)) { - sinfect(checksh); - cavetetshlist->newindex((void **) &parysh); - *parysh = checksh; + if ((sptr = (shellface*) cavetet->tet[9]) != NULL) { + for (j = 0; j < 4; j++) { + if (sptr[j]) { + sdecode(sptr[j], checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + cavetetshlist->newindex((void **) &parysh); + *parysh = checksh; + } } - } + } // j } - } + } // i // Uninfect collected subfaces. for (i = 0; i < cavetetshlist->objects; i++) { - checksh = * (face *) fastlookup(cavetetshlist, i); - suninfect(checksh); + parysh = (face *) fastlookup(cavetetshlist, i); + suninfect(*parysh); + } + + if (ivf->rejflag & 2) { + REAL rd, cent[3]; + badface *bface; + // Reject this point if it encroaches upon any subface. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + if (checkfac4encroach((point) parysh->sh[3], (point) parysh->sh[4], + (point) parysh->sh[5], insertpt, cent, &rd)) { + encshlist->newindex((void **) &bface); + bface->ss = *parysh; + bface->forg = (point) parysh->sh[3]; // Not a dad one. + for (j = 0; j < 3; j++) bface->cent[j] = cent[j]; + bface->key = rd; + } + } + if (encshlist->objects > 0) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSUBFACE; + return 0; + } } } // if (checksubfaceflag) - if (ivf->rejflag & 1) { - // Reject insertion of this point if it encroaches upon any segment. - for (i = 0; i < cavetetseglist->objects; i++) { - checkseg = * (face *) fastlookup(cavetetseglist, i); - pa = sorg(checkseg); - pb = sdest(checkseg); - if (checkseg4encroach(pa, pb, insertpt)) { - if (b->verbose > 3) { - printf(" Found an encroached seg (%d, %d).\n", - pointmark(pa), pointmark(pb)); - } - encseglist->newindex((void **) &paryseg); - *paryseg = checkseg; - } - } // i - if (encseglist->objects > 0) { - if (b->verbose > 3) { - printf(" Found %ld encroached segments. Reject it.\n", - encseglist->objects); - } - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - // Clear working lists. - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetseglist->restart(); - cavetetshlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - return (int) ENCSEGMENT; - } - } // if (reject & 1) + if ((ivf->iloc == (int) OUTSIDE) && ivf->refineflag) { + // The vertex lies outside of the domain. And it does not encroach + // upon any boundary segment or subface. Do not insert it. + insertpoint_abort(splitseg, ivf); + return 0; + } - if (ivf->rejflag & 2) { - // Reject insertion of this point if it encroaches upon any subface. - for (i = 0; i < cavetetshlist->objects; i++) { - checksh = * (face *) fastlookup(cavetetshlist, i); - pa = sorg(checksh); - pb = sdest(checksh); - pc = sapex(checksh); - if (checkfac4encroach(pa, pb, pc, insertpt, cent, &rd)) { - if (b->verbose > 3) { - printf(" Found an encroached subface (%d, %d, %d).\n", - pointmark(pa), pointmark(pb), pointmark(pc)); - } - encshlist->newindex((void **) &bface); - bface->ss = checksh; - bface->forg = pa; // Not a dad one. - for (j = 0; j < 3; j++) bface->cent[j] = cent[j]; - bface->key = rd; - } - } // i - if (encshlist->objects > 0) { - if (b->verbose > 3) { - printf(" Found %ld encroached subfaces. Reject it.\n", - encshlist->objects); - } - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetseglist->restart(); - cavetetshlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - return (int) ENCSUBFACE; - } - } // if (reject & 2) - - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { - // Update the sC(p). + if (ivf->splitbdflag) { + // The new point locates in surface mesh. Update the sC(p). // We have already 'smarktested' the subfaces which directly intersect // with p in 'caveshlist'. From them, we 'smarktest' their neighboring // subfaces which are included in C(p). Do not across a segment. @@ -3678,8 +2375,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, assert(smarktested(*parysh)); checksh = *parysh; for (j = 0; j < 3; j++) { - sspivot(checksh, checkseg); - if (checkseg.sh == NULL) { + if (!isshsubseg(checksh)) { spivot(checksh, neighsh); assert(neighsh.sh != NULL); if (!smarktested(neighsh)) { @@ -3689,10 +2385,10 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (infected(neightet)) { // This subface is inside C(p). // Check if its diametrical circumsphere encloses 'p'. - pa = sorg(neighsh); - pb = sdest(neighsh); - pc = sapex(neighsh); - sign = incircle3d(pa, pb, pc, insertpt); + // The purpose of this check is to avoid forming invalid + // subcavity in surface mesh. + sign = incircle3d(sorg(neighsh), sdest(neighsh), + sapex(neighsh), insertpt); if (sign < 0) { smarktest(neighsh); caveshlist->newindex((void **) &parysh); @@ -3705,48 +2401,34 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, senextself(checksh); } // j } // i - if (b->verbose > 3) { - printf(" Initial subcavity size: %ld subfacess.\n", - caveshlist->objects); - } - } + } // if (ivf->splitbdflag) - cutcount = 0l; + if (ivf->validflag) { + // Validate C(p) and update it if it is not star-shaped. + int cutcount = 0; - if (ivf->validflag) { - //if (bowywat > 1) { // if (bowywat == 2 || bowywat == 3) { - // T is a CT. Validation is needed (fig/dump-cavity-case8). - cavetetlist->restart(); // Re-use it. - - //if (splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { if (ivf->respectbdflag) { // The initial cavity may include subfaces which are not on the facets - // being splitting. Find them and make them as boundary of C(p). - // Comment: We have already 'smarktested' the subfaces in sC(p). - // It is needed by 'splitbdflag'. + // being splitting. Find them and make them as boundary of C(p). + // Comment: We have already 'smarktested' the subfaces in sC(p). They + // are completely inside C(p). for (i = 0; i < cavetetshlist->objects; i++) { parysh = (face *) fastlookup(cavetetshlist, i); stpivot(*parysh, neightet); if (infected(neightet)) { fsymself(neightet); if (infected(neightet)) { + // Found a subface inside C(p). if (!smarktested(*parysh)) { - if (b->verbose > 3) { - printf(" Found a subface (%d, %d, %d) inside cavity.\n", - pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), - pointmark(sapex(*parysh))); - } // It is possible that this face is a boundary subface. // Check if it is a hull face. - assert(apex(neightet) != dummypoint); + //assert(apex(neightet) != dummypoint); if (oppo(neightet) != dummypoint) { fsymself(neightet); } if (oppo(neightet) != dummypoint) { - pa = org(neightet); - pb = dest(neightet); - pc = apex(neightet); - ori = orient3d(pa, pb, pc, insertpt); + ori = orient3d(org(neightet), dest(neightet), apex(neightet), + insertpt); if (ori < 0) { // A visible face, get its neighbor face. fsymself(neightet); @@ -3758,27 +2440,20 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } // Cut this tet if it is either invisible by or coplanar with p. if (ori >= 0) { - if (b->verbose > 3) { - printf(" Cut tet (%d, %d, %d, %d)\n", - pointmark(org(neightet)), pointmark(dest(neightet)), - pointmark(apex(neightet)), pointmark(oppo(neightet))); - } uninfect(neightet); unmarktest(neightet); cutcount++; - neightet.ver = epivot[neightet.ver & 3]; + neightet.ver = epivot[neightet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neightet; // Add three new faces to find new boundaries. for (j = 0; j < 3; j++) { esym(neightet, neineitet); - neineitet.ver = epivot[neineitet.ver & 3]; + neineitet.ver = epivot[neineitet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neineitet; enextself(neightet); } - // Update hullsize. - if (oppo(neightet) == dummypoint) hullsize++; } // if (ori >= 0) } } @@ -3800,10 +2475,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (spintet.tet == neightet.tet) break; } if (infected(spintet)) { - if (b->verbose > 3) { - printf(" Found an interior segment (%d, %d).\n", - pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg))); - } // Find an adjacent tet at this segment such that both faces // at this segment are not visible by p. pa = org(neightet); @@ -3828,7 +2499,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } } - } else { } fnextself(spintet); if (spintet.tet == neightet.tet) break; @@ -3846,42 +2516,34 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, uninfect(neightet); unmarktest(neightet); cutcount++; - neightet.ver = epivot[neightet.ver & 3]; + neightet.ver = epivot[neightet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neightet; // Add three new faces to find new boundaries. for (j = 0; j < 3; j++) { esym(neightet, neineitet); - neineitet.ver = epivot[neineitet.ver & 3]; + neineitet.ver = epivot[neineitet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neineitet; enextself(neightet); } - // Update hullsize. - //if (oppo(neightet) == dummypoint) hullsize++; - if ((point) (neightet.tet[7]) == dummypoint) hullsize++; } } } // i - } // if (bowywat > 2) + } // if (ivf->respectbdflag) // Update the cavity by removing invisible faces until it is star-shaped. for (i = 0; i < cavebdrylist->objects; i++) { cavetet = (triface *) fastlookup(cavebdrylist, i); - // 'cavetet' is an exterior tet adjacent to the cavity. - assert(cavetet->ver == epivot[cavetet->ver & 3]); // SELF_CHECK - // It must be not inside the cavity (since we only cut tets). - assert(!infected(*cavetet)); + // 'cavetet' is an exterior tet adjacent to the cavity. // Check if its neighbor is inside C(p). fsym(*cavetet, neightet); if (infected(neightet)) { if (apex(*cavetet) != dummypoint) { // It is a cavity boundary face. Check its visibility. if (oppo(neightet) != dummypoint) { - pa = org(*cavetet); - pb = dest(*cavetet); - pc = apex(*cavetet); - ori = orient3d(pa, pb, pc, insertpt); + ori = orient3d(org(*cavetet), dest(*cavetet), apex(*cavetet), + insertpt); enqflag = (ori > 0); // Comment: if ori == 0 (coplanar case), we also cut the tet. } else { @@ -3898,24 +2560,17 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, cavetetlist->newindex((void **) &parytet); *parytet = *cavetet; } else { - if (b->verbose > 3) { - printf(" Cut tet (%d, %d, %d, %d)\n", - pointmark(org(neightet)), pointmark(dest(neightet)), - pointmark(apex(neightet)), pointmark(oppo(neightet))); - } uninfect(neightet); unmarktest(neightet); cutcount++; // Add three new faces to find new boundaries. for (j = 0; j < 3; j++) { esym(neightet, neineitet); - neineitet.ver = epivot[neineitet.ver & 3]; + neineitet.ver = epivot[neineitet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neineitet; enextself(neightet); } - // Update the hullsize. - if (oppo(neightet) == dummypoint) hullsize++; // 'cavetet' is not on the cavity boundary anymore. unmarktest(*cavetet); } @@ -3927,14 +2582,11 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (cutcount > 0) { // The cavity has been updated. - // Update the cavity boundary faces. cavebdrylist->restart(); for (i = 0; i < cavetetlist->objects; i++) { cavetet = (triface *) fastlookup(cavetetlist, i); // 'cavetet' was an exterior tet adjacent to the cavity. - assert(cavetet->ver == epivot[cavetet->ver & 3]); // SELF_CHECK - assert(!infected(*cavetet)); fsym(*cavetet, neightet); if (infected(neightet)) { // It is a cavity boundary face. @@ -3962,31 +2614,13 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // The cavity should contain at least one tet. if (caveoldtetlist->objects == 0l) { - assert(cavebdrylist->objects == 0l); - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetseglist->restart(); - cavetetshlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - return (int) BADELEMENT; + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; } - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { - cutshcount = 0; + if (ivf->splitbdflag) { + int cutshcount = 0; // Update the sub-cavity sC(p). for (i = 0; i < caveshlist->objects; i++) { parysh = (face *) fastlookup(caveshlist, i); @@ -4000,11 +2634,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } if (!enqflag) { - if (b->verbose > 3) { - printf(" Cut subface (%d, %d, %d).\n", - pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), - pointmark(sapex(*parysh))); - } sunmarktest(*parysh); // Use the last entry of this array to fill this entry. j = caveshlist->objects - 1; @@ -4021,16 +2650,16 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, i = 0; // Count the number of invalid subfaces/segments. // Valid the updated sub-cavity sC(p). if (loc == ONFACE) { - if (splitsh != NULL) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { // The to-be split subface should be in sC(p). if (!smarktested(*splitsh)) i++; } } else if (loc == ONEDGE) { - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { // The to-be split segment should be in sC(p). if (!smarktested(*splitseg)) i++; } - if (splitsh != NULL) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { // All subfaces at this edge should be in sC(p). pa = sorg(*splitsh); neighsh = *splitsh; @@ -4052,127 +2681,140 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (i > 0) { // The updated sC(p) is invalid. Do not insert this vertex. - if (b->verbose > 3) { - printf(" Found %d invalid items. Reject it.\n", i); - } - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetseglist->restart(); - cavetetshlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - return (int) BADELEMENT; + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; } } // if (cutshcount > 0) - } // if (bowywat > 2) - + } // if (ivf->splitbdflag) } // if (cutcount > 0) - } // if (validflag) // if (bowywat > 1) - - if (b->verbose > 3) { - printf(" Final cavity: %ld tets, %ld faces.", - caveoldtetlist->objects, cavebdrylist->objects); - if (cutcount > 0l) { - printf(" Updated %ld times.", cutcount); - } - printf("\n"); - } - + } // if (ivf->validflag) if (ivf->refineflag) { // The new point is inserted by Delaunay refinement, i.e., it is the // circumcenter of a tetrahedron, or a subface, or a segment. // Do not insert this point if the tetrahedron, or subface, or segment // is not inside the final cavity. - rejptflag = 0; - if (ivf->refineflag == 1) { - // The new point is the circumcenter of a tetrahedron. - assert(!isdeadtet(ivf->refinetet)); - if (!infected(ivf->refinetet)) { - rejrefinetetcount++; - rejptflag = 1; - } - } else if (ivf->refineflag == 2) { - // The new point is the circumcenter of a subface. - assert(ivf->refinesh.sh != NULL); - if (!smarktested(ivf->refinesh)) { - rejrefineshcount++; - rejptflag = 1; - } + if (((ivf->refineflag == 1) && !infected(ivf->refinetet)) || + ((ivf->refineflag == 2) && !smarktested(ivf->refinesh))) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; } - if (rejptflag) { - if (b->verbose > 2) { - printf(" Point %d does not refine its element. Rejected.\n", - pointmark(insertpt)); - } - // Restore the original status. - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - // Clear working lists. - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetshlist->restart(); - cavetetseglist->restart(); - cavetetvertlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - - // Restore the hullsize. - hullsize = bakhullsize; - loc = BADELEMENT; - return (int) loc; - } // if (rejptflag) } // if (ivf->refineflag) - rejptflag = (ivf->rejflag & 4); - encptflag = 0; + if (b->plc && (loc != INSTAR)) { + // Reject the new point if it lies too close to an existing point (b->plc), + // or it lies inside a protecting ball of near vertex (ivf->rejflag & 4). + // Collect the list of vertices of the initial cavity. + if (loc == OUTSIDE) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 3; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } else if (loc == INTETRAHEDRON) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 4; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } else if (loc == ONFACE) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 3; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + if (pts[3] != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[3]; + } + fsym(*searchtet, spintet); + if (oppo(spintet) != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = oppo(spintet); + } + } else if (loc == ONEDGE) { + spintet = *searchtet; + cavetetvertlist->newindex((void **) &parypt); + *parypt = org(spintet); + cavetetvertlist->newindex((void **) &parypt); + *parypt = dest(spintet); + while (1) { + if (apex(spintet) != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = apex(spintet); + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } + } - if (b->weighted || b->plc || rejptflag) { + int rejptflag = (ivf->rejflag & 4); + REAL rd; + pts = NULL; + + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + rd = distance(*parypt, insertpt); + // Is the point very close to an existing point? + if (rd < b->minedgelength) { + pts = parypt; + loc = NEARVERTEX; + break; + } + if (rejptflag) { + // Is the point encroaches upon an existing point? + if (rd < (0.5 * (*parypt)[pointmtrindex])) { + pts = parypt; + loc = ENCVERTEX; + break; + } + } + } + cavetetvertlist->restart(); // Clear the work list. + + if (pts != NULL) { + // The point is either too close to an existing vertex (NEARVERTEX) + // or encroaches upon (inside the protecting ball) of that vertex. + if (loc == NEARVERTEX) { + if (b->nomergevertex) { // -M0/1 option. + // In this case, we still insert this vertex. Although it is very + // close to an existing vertex. Give a warning, anyway. + if (!b->quiet) { + printf("Warning: Two points, %d and %d, are very close.\n", + pointmark(insertpt), pointmark(*pts)); + printf(" Creating a very short edge (len = %g) (< %g).\n", + rd, b->minedgelength); + printf(" You may try a smaller tolerance (-T) (current is %g)\n", + b->epsilon); + printf(" to avoid this warning.\n"); + } + } else { + insertpt[3] = rd; // Only for reporting. + setpoint2ppt(insertpt, *pts); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; + } + } else { // loc == ENCVERTEX + // The point lies inside the protection ball. + setpoint2ppt(insertpt, *pts); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; + } + } + } // if (b->plc && (loc != INSTAR)) + + if (b->weighted || ivf->cdtflag || ivf->smlenflag + ) { + // There may be other vertices inside C(p). We need to find them. // Collect all vertices of C(p). for (i = 0; i < caveoldtetlist->objects; i++) { cavetet = (triface *) fastlookup(caveoldtetlist, i); - assert(infected(*cavetet)); + //assert(infected(*cavetet)); pts = (point *) &(cavetet->tet[4]); for (j = 0; j < 4; j++) { if (pts[j] != dummypoint) { @@ -4184,118 +2826,48 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // j } // i - if (b->verbose > 3) { - printf(" %ld cavity vertices.\n", cavetetvertlist->objects); - } // Uninfect all collected (cavity) vertices. for (i = 0; i < cavetetvertlist->objects; i++) { parypt = (point *) fastlookup(cavetetvertlist, i); puninfect(*parypt); } - if (b->plc || rejptflag) { - // Check if p is too close to an existing vertex. - pts = NULL; - for (i = 0; i < cavetetvertlist->objects; i++) { + if (ivf->smlenflag) { + REAL len; + // Get the length of the shortest edge connecting to 'newpt'. + parypt = (point *) fastlookup(cavetetvertlist, 0); + ivf->smlen = distance(*parypt, insertpt); + ivf->parentpt = *parypt; + for (i = 1; i < cavetetvertlist->objects; i++) { parypt = (point *) fastlookup(cavetetvertlist, i); - rd = distance(*parypt, insertpt); - // Is the point very close to an existing point? - if (rd < b->minedgelength) { - pts = parypt; - break; + len = distance(*parypt, insertpt); + if (len < ivf->smlen) { + ivf->smlen = len; + ivf->parentpt = *parypt; } - if (rejptflag) { - // Is the point encroaches upon an existing point? - if (rd < (*parypt)[pointmtrindex]) { - // The point lies inside the protection ball. - if (b->verbose > 2) { - printf(" Point %d lies in protball of %d. Rejected.\n", - pointmark(insertpt), pointmark(*parypt)); - } - pts = parypt; - encptflag = 1; - break; - } - } - } // i - if (pts != NULL) { - // p is too close to *pts. - if (ivf->iloc != (int) INSTAR) { - if (pointmark(insertpt) <= in->numberofpoints) { - // It's an input point. - if (!b->quiet) { - printf("Warning: Point %d is replaced by point %d.\n", - pointmark(insertpt), pointmark(*pts)); - } - // Count the number of duplicated points. - dupverts++; - } else { // It's a Steiner point. - if (b->verbose) { - if (!rejptflag) { - printf("Warning: Reject a Steiner point %d (close to %d).\n", - pointmark(insertpt), pointmark(*pts)); - } - } - } - // Remember it is a duplicated point. - setpointtype(insertpt, DUPLICATEDVERTEX); - // Set a pointer to the point it duplicates. - setpoint2ppt(insertpt, *pts); + } + } + } - // Restore the original status. - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - // Clear working lists. - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetshlist->restart(); - cavetetseglist->restart(); - cavetetvertlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - if (!encptflag) { - loc = NEARVERTEX; - } else { - loc = ENCVERTEX; - } - return (int) loc; - } else { // (iloc == (int) INSTAR) - // The cavity is guaranteed to be valid by the caller of this - // function. We still insert this vertex. - if (b->verbose) { - printf("Warning: The Steiner point %d is very close to %d.\n", - pointmark(insertpt), pointmark(*pts)); - } - } - } // if (pts != NULL) - } - } - - // The new point will be inserted. - totaldeadtets += caveoldtetlist->objects; - totalbowatcavsize += cavebdrylist->objects; - if (maxbowatcavsize < cavebdrylist->objects) { - maxbowatcavsize = cavebdrylist->objects; + if (ivf->cdtflag) { + // Unmark tets. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + unmarktest(*cavetet); + } + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); + } + // Clean up arrays which are not needed. + cavetetlist->restart(); + if (checksubsegflag) { + cavetetseglist->restart(); + } + if (checksubfaceflag) { + cavetetshlist->restart(); + } + return 1; } // Before re-mesh C(p). Process the segments and subfaces which are on the @@ -4317,7 +2889,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, while (1) { j++; if (!infected(spintet)) { - neineitet = spintet; // An outer tet. Remember it. + neineitet = spintet; // An outer tet. Remember it. } else { k++; // An in tet. } @@ -4338,12 +2910,8 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sstbond1(*paryseg, neineitet); } else { // k == j // The segment is inside C(p). - if (!ivf->splitbdflag) {//if (bowywat < 3) { // if (bowywat == 2) { + if (!ivf->splitbdflag) { checkseg = *paryseg; - if (b->verbose > 3) { - printf(" Queueing a missing seg (%d, %d)\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } sinfect(checkseg); // Flag it as an interior segment. caveencseglist->newindex((void **) &paryseg); *paryseg = checkseg; @@ -4358,10 +2926,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sinfect(*paryseg); } } // i - if (b->verbose > 3) { - printf(" %ld (%ld) cavity (interior) segments.\n", - cavetetseglist->objects, caveencseglist->objects); - } } // if (checksubsegflag) if (checksubfaceflag) { @@ -4374,7 +2938,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, for (j = 0; j < 2; j++) { stpivot(*parysh, neightet); if (!infected(neightet)) { - checksh = *parysh; // Remeber this side. + checksh = *parysh; // Remember this side. } else { k++; } @@ -4391,13 +2955,8 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // This side is the outer boundary of C(p). *parysh = checksh; } else { // k == 2 - if (!ivf->splitbdflag) { //if (bowywat < 3) { // if (bowywat == 2) { + if (!ivf->splitbdflag) { checksh = *parysh; - if (b->verbose > 3) { - printf(" Queueing a missing subface (%d, %d, %d)\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } sinfect(checksh); // Flag it. caveencshlist->newindex((void **) &parysh); *parysh = checksh; @@ -4412,31 +2971,26 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sinfect(*parysh); } } // i - if (b->verbose > 3) { - printf(" %ld (%ld) cavity (interior) subfaces.\n", - cavetetshlist->objects, caveencshlist->objects); - } - } // if (checksubfaceflag) { + } // if (checksubfaceflag) // Create new tetrahedra to fill the cavity. for (i = 0; i < cavebdrylist->objects; i++) { cavetet = (triface *) fastlookup(cavebdrylist, i); neightet = *cavetet; - assert(!infected(neightet)); unmarktest(neightet); // Unmark it. // Get the oldtet (inside the cavity). fsym(neightet, oldtet); if (apex(neightet) != dummypoint) { - // Create a new tet in the cavity (see Fig. bowyerwatson 1 or 3). + // Create a new tet in the cavity. maketetrahedron(&newtet); setorg(newtet, dest(neightet)); setdest(newtet, org(neightet)); setapex(newtet, apex(neightet)); setoppo(newtet, insertpt); } else { - // Create a new hull tet (see Fig. bowyerwatson 2). - hullsize++; + // Create a new hull tet. + hullsize++; maketetrahedron(&newtet); setorg(newtet, org(neightet)); setdest(newtet, dest(neightet)); @@ -4465,10 +3019,8 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, //setpoint2tet(insertpt, encode(newtet)); setpoint2tet(insertpt, (tetrahedron) (newtet.tet)); - if (ivf->lawson > 1) { // if (lawson == 2 || lawson == 3) { - // Re-use this list to save new interior cavity faces. - cavetetlist->restart(); - } + // Re-use this list to save new interior cavity faces. + cavetetlist->restart(); // Connect adjacent new tetrahedra together. for (i = 0; i < cavebdrylist->objects; i++) { @@ -4490,11 +3042,9 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } fsym(spintet, newneitet); esymself(newneitet); - assert(newneitet.tet[newneitet.ver & 3] == NULL); // FOR DEBUG + assert(newneitet.tet[newneitet.ver & 3] == NULL); bond(neightet, newneitet); - if (ivf->lawson > 1) { - // We are updateing a CDT. Queue the internal face. - // See also fig/dump-cavity-case13, -case21. + if (ivf->lawson > 1) { cavetetlist->newindex((void **) &parytet); *parytet = neightet; } @@ -4538,13 +3088,14 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } - if (splitsh != NULL) { + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { // Split a subface or a segment. - sinsertvertex(insertpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat); + sinsertvertex(insertpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); } if (checksubfaceflag) { - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { + if (ivf->splitbdflag) { // Recover new subfaces in C(p). for (i = 0; i < caveshbdlist->objects; i++) { // Get an old subface at edge [a, b]. @@ -4589,8 +3140,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // There should be no missing interior subfaces in C(p). assert(caveencshlist->objects == 0l); } else { - // bowywat = 1 or bowywat = 2. - // The Boundary reocvery phase. + // The Boundary recovery phase. // Put all new subfaces into stack for recovery. for (i = 0; i < caveshbdlist->objects; i++) { // Get an old subface at edge [a, b]. @@ -4598,12 +3148,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, spivot(*parysh, checksh); // The new subface [a, b, p]. // Do not recover a deleted new face (degenerated). if (checksh.sh[3] != NULL) { - if (b->verbose > 3) { - printf(" Queue new subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - //sdissolve(checksh); // It has not been connected yet. subfacstack->newindex((void **) &parysh); *parysh = checksh; } @@ -4615,11 +3159,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // Some subfaces inside C(p) might be split in sinsertvertex(). // Only queue those faces which are not split. if (!smarktested(*parysh)) { - if (b->verbose > 3) { - printf(" Queue a missing subface (%d, %d, %d) x%lx.\n", - pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), - pointmark(sapex(*parysh)), (uintptr_t) parysh->sh); - } checksh = *parysh; suninfect(checksh); stdissolve(checksh); // Detach connections to old tets. @@ -4631,7 +3170,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } // if (checksubfaceflag) if (checksubsegflag) { - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { + if (ivf->splitbdflag) { if (splitseg != NULL) { // Recover the two new subsegments in C(p). for (i = 0; i < cavesegshlist->objects; i++) { @@ -4646,11 +3185,9 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, stpivot(checksh, neightet); } else { // It's a dangling segment. - pa = sorg(checkseg); - pb = sdest(checkseg); - point2tetorg(pa, neightet); - finddirection(&neightet, pb); - assert(dest(neightet) == pb); + point2tetorg(sorg(checkseg), neightet); + finddirection(&neightet, sdest(checkseg)); + assert(dest(neightet) == sdest(checkseg)); } assert(!infected(neightet)); sstbond1(checkseg, neightet); @@ -4665,17 +3202,12 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // There should be no interior segment in C(p). assert(caveencseglist->objects == 0l); } else { - // bowywat == 1 or bowywat == 2; // The Boundary Recovery Phase. // Queue missing segments in C(p) for recovery. if (splitseg != NULL) { // Queue two new subsegments in C(p) for recovery. for (i = 0; i < cavesegshlist->objects; i++) { paryseg = (face *) fastlookup(cavesegshlist, i); - if (b->verbose > 3) { - printf(" Queue new subseg (%d, %d)\n", - pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg))); - } checkseg = *paryseg; //sstdissolve1(checkseg); // It has not been connected yet. s = randomnation(subsegstack->objects + 1); @@ -4689,10 +3221,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, paryseg = (face *) fastlookup(caveencseglist, i); assert(sinfected(*paryseg)); if (!smarktested(*paryseg)) { // It may be split. - if (b->verbose > 3) { - printf(" Queue a missing segment (%d, %d).\n", - pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg))); - } checkseg = *paryseg; suninfect(checkseg); sstdissolve1(checkseg); // Detach connections to old tets. @@ -4706,12 +3234,10 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // if (checksubsegflag) - if (b->plc || b->weighted) { + if (b->weighted + ) { // Some vertices may be completed inside the cavity. They must be // detected and added to recovering list. - if (b->plc) { - tetcount = subvertstack->objects; // Re-use tetcount; - } // Since every "live" vertex must contain a pointer to a non-dead // tetrahedron, we can check for each vertex this pointer. for (i = 0; i < cavetetvertlist->objects; i++) { @@ -4723,26 +3249,12 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (b->verbose > 1) { printf(" Point #%d is non-regular after the insertion of #%d.\n", pointmark(*pts), pointmark(insertpt)); - } + } setpointtype(*pts, NREGULARVERTEX); nonregularcount++; - } else { - if (b->verbose > 3) { - printf(" Queue a dangling vertex %d.\n", pointmark(*pts)); - } - subvertstack->newindex((void **) &parypt); - *parypt = *pts; } } } - if (b->plc) { - if (subvertstack->objects > tetcount) { - // There are missing vertices after inserting the new point. - printf("DBG: Insert %d. Found %ld interior vertices.\n", - pointmark(insertpt), subvertstack->objects); - assert(0); // NEED TO DEBUG. - } - } } if (ivf->chkencflag & 1) { @@ -4751,23 +3263,14 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, paryseg = (face *) fastlookup(cavetetseglist, i); // Skip if it is the split segment. if (!sinfected(*paryseg)) { - // Skip it if it has already queued. - if (!smarktest2ed(*paryseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = *paryseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(*paryseg); // An alive badface. - } + enqueuesubface(badsubsegs, paryseg); } } if (splitseg != NULL) { // Queue the two new subsegments inside C(p). for (i = 0; i < cavesegshlist->objects; i++) { paryseg = (face *) fastlookup(cavesegshlist, i); - bface = (badface *) badsubsegs->alloc(); - bface->ss = *paryseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(*paryseg); // An alive badface. + enqueuesubface(badsubsegs, paryseg); } } } // if (chkencflag & 1) @@ -4778,15 +3281,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, parysh = (face *) fastlookup(cavetetshlist, i); // Skip if it is a split subface. if (!sinfected(*parysh)) { - // Skip it if it has already queued. - if (!smarktest2ed(*parysh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = *parysh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(*parysh); // An alive badface. - //bface->fdest = sdest(*parysh); - //bface->fapex = sapex(*parysh); - } + enqueuesubface(badsubfacs, parysh); } } // Queue all new subfaces inside C(p). @@ -4796,11 +3291,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, spivot(*parysh, checksh); // checksh is a new subface [a, b, p]. // Do not recover a deleted new face (degenerated). if (checksh.sh[3] != NULL) { - //assert(!smarktest2ed(checksh)); - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface. + enqueuesubface(badsubfacs, &checksh); } } } // if (chkencflag & 2) @@ -4809,23 +3300,23 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // Queue all new tetrahedra in C(p). for (i = 0; i < cavebdrylist->objects; i++) { cavetet = (triface *) fastlookup(cavebdrylist, i); - //assert(!marktest2ed(*cavetet)); - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = *cavetet; - marktest2(bface->tt); - bface->forg = org(*cavetet); + enqueuetetrahedron(cavetet); } } - // C(p) is re-meshed successfully. + // C(p) is re-meshed successfully. - // Deleted the old tets in C(p). + // Delete the old tets in C(p). for (i = 0; i < caveoldtetlist->objects; i++) { searchtet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*searchtet)) { + hullsize--; + } tetrahedrondealloc(searchtet->tet); } - if (splitsh != NULL) { + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { // Delete the old subfaces in sC(p). for (i = 0; i < caveshlist->objects; i++) { parysh = (face *) fastlookup(caveshlist, i); @@ -4847,7 +3338,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } shellfacedealloc(subfaces, parysh->sh); } - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { // Delete the old segment in sC(p). shellfacedealloc(subsegs, splitseg->sh); } @@ -4856,22 +3347,16 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (ivf->lawson) { for (i = 0; i < cavebdrylist->objects; i++) { searchtet = (triface *) fastlookup(cavebdrylist, i); - //flippush(flipstack, searchtet, insertpt); flippush(flipstack, searchtet); } if (ivf->lawson > 1) { for (i = 0; i < cavetetlist->objects; i++) { searchtet = (triface *) fastlookup(cavetetlist, i); - //flippush(flipstack, searchtet, oppo(*searchtet)); flippush(flipstack, searchtet); } } } - // The vertex should already have a type. - assert(pointtype(insertpt) != UNUSEDVERTEX); - - // Clean the working lists. @@ -4889,17 +3374,60 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, caveencshlist->restart(); } - if (b->plc || b->weighted) { + if (b->weighted || ivf->validflag) { cavetetvertlist->restart(); } - if (splitsh != NULL) { + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { caveshlist->restart(); caveshbdlist->restart(); cavesegshlist->restart(); } - return (int) loc; + return 1; // Point is inserted. +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// insertpoint_abort() Abort the insertion of a new vertex. // +// // +// The cavity will be restored. All working lists are cleared. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::insertpoint_abort(face *splitseg, insertvertexflags *ivf) +{ + triface *cavetet; + face *parysh; + int i; + + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + uninfect(*cavetet); + unmarktest(*cavetet); + } + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); + } + cavetetlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + cavetetseglist->restart(); + cavetetshlist->restart(); + if (ivf->splitbdflag) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + sunmarktest(*splitseg); + } + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + assert(smarktested(*parysh)); + sunmarktest(*parysh); + } + caveshlist->restart(); + cavesegshlist->restart(); + } } //// //// diff --git a/external/tetgen/src/geom.cxx b/external/tetgen/src/geom.cxx index 9ffeed7a0..bd80213b4 100644 --- a/external/tetgen/src/geom.cxx +++ b/external/tetgen/src/geom.cxx @@ -4,9 +4,148 @@ //// //// // PI is the ratio of a circle's circumference to its diameter. - REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; +/////////////////////////////////////////////////////////////////////////////// +// // +// insphere_s() Insphere test with symbolic perturbation. // +// // +// Given four points pa, pb, pc, and pd, test if the point pe lies inside or // +// outside the circumscribed sphere of the four points. // +// // +// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // +// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // +// points pa, pb, and pc. Otherwise, the returned sign is flipped. // +// // +// Return a positive value (> 0) if pe lies inside, a negative value (< 0) // +// if pe lies outside the sphere, the returned value will not be zero. // +// // +/////////////////////////////////////////////////////////////////////////////// + +REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) +{ + REAL sign; + + sign = insphere(pa, pb, pc, pd, pe); + if (sign != 0.0) { + return sign; + } + + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; + + pt[0] = pa; + pt[1] = pb; + pt[2] = pc; + pt[3] = pd; + pt[4] = pe; + + // Sort the five points such that their indices are in the increasing + // order. An optimized bubble sort algorithm is used, i.e., it has + // the worst case O(n^2) runtime, but it is usually much faster. + swaps = 0; // Record the total number of swaps. + n = 5; + do { + count = 0; + n = n - 1; + for (i = 0; i < n; i++) { + if (pointmark(pt[i]) > pointmark(pt[i+1])) { + swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; + count++; + } + } + swaps += count; + } while (count > 0); // Continue if some points are swapped. + + oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); + if (oriA != 0.0) { + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriA = -oriA; + return oriA; + } + + oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); + assert(oriB != 0.0); // SELF_CHECK + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// orient4d_s() 4d orientation test with symbolic perturbation. // +// // +// Given four lifted points pa', pb', pc', and pd' in R^4,test if the lifted // +// point pe' in R^4 lies below or above the hyperplane passing through the // +// four points pa', pb', pc', and pd'. // +// // +// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // +// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // +// the points pa, pb, and pc. Otherwise, the returned sign is flipped. // +// // +// Return a positive value (> 0) if pe' lies below, a negative value (< 0) // +// if pe' lies above the hyperplane, the returned value should not be zero. // +// // +/////////////////////////////////////////////////////////////////////////////// + +REAL tetgenmesh::orient4d_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, + REAL dheight, REAL eheight) +{ + REAL sign; + + sign = orient4d(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight); + if (sign != 0.0) { + return sign; + } + + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; + + pt[0] = pa; + pt[1] = pb; + pt[2] = pc; + pt[3] = pd; + pt[4] = pe; + + // Sort the five points such that their indices are in the increasing + // order. An optimized bubble sort algorithm is used, i.e., it has + // the worst case O(n^2) runtime, but it is usually much faster. + swaps = 0; // Record the total number of swaps. + n = 5; + do { + count = 0; + n = n - 1; + for (i = 0; i < n; i++) { + if (pointmark(pt[i]) > pointmark(pt[i+1])) { + swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; + count++; + } + } + swaps += count; + } while (count > 0); // Continue if some points are swapped. + + oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); + if (oriA != 0.0) { + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriA = -oriA; + return oriA; + } + + oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); + assert(oriB != 0.0); // SELF_CHECK + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; +} + /////////////////////////////////////////////////////////////////////////////// // // // tri_edge_test() Triangle-edge intersection test. // @@ -20,7 +159,7 @@ REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; // If T and E intersect each other, they may intersect in different ways. If // // 'level' > 0, their intersection type will be reported 'types' and 'pos'. // // // -// The retrun value indicates one of the following cases: // +// The return value indicates one of the following cases: // // - 0, T and E are disjoint. // // - 1, T and E intersect each other. // // - 2, T and E are not coplanar. They intersect at a single point. // @@ -29,6 +168,10 @@ REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; // // /////////////////////////////////////////////////////////////////////////////// +#define SETVECTOR3(V, a0, a1, a2) (V)[0] = (a0); (V)[1] = (a1); (V)[2] = (a2) + +#define SWAP2(a0, a1, tmp) (tmp) = (a0); (a0) = (a1); (a1) = (tmp) + int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, point R, int level, int *types, int *pos) { @@ -45,14 +188,14 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, REAL n[3], len; // Calculate a lift point, saved in dummypoint. facenormal(A, B, C, n, 1, NULL); - len = sqrt(DOT(n, n)); + len = sqrt(dot(n, n)); if (len != 0) { n[0] /= len; n[1] /= len; n[2] /= len; - len = DIST(A, B); - len += DIST(B, C); - len += DIST(C, A); + len = distance(A, B); + len += distance(B, C); + len += distance(C, A); len /= 3.0; R = abovept; //dummypoint; R[0] = A[0] + len * n[0]; @@ -73,7 +216,6 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, sB = orient3d(P, Q, R, B); sC = orient3d(P, Q, R, C); - triedgcopcount++; if (sA < 0) { if (sB < 0) { @@ -657,7 +799,6 @@ int tetgenmesh::tri_edge_tail(point A,point B,point C,point P,point Q,point R, REAL s1, s2, s3; int z1; - triedgcount++; if (sP < 0) { if (sQ < 0) { // (--) disjoint @@ -1152,6 +1293,9 @@ void tetgenmesh::lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N) // Return a negative value if pd is inside the circumcircle of the triangle // // pa, pb, and pc. // // // +// IMPORTANT: It assumes that [a,b] is the common edge, i.e., the two input // +// triangles are [a,b,c] and [b,a,d]. // +// // /////////////////////////////////////////////////////////////////////////////// REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) @@ -1161,19 +1305,19 @@ REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) // Calculate the areas of the two triangles [a, b, c] and [b, a, d]. facenormal(pa, pb, pc, n1, 1, NULL); - area2[0] = DOT(n1, n1); + area2[0] = dot(n1, n1); facenormal(pb, pa, pd, n2, 1, NULL); - area2[1] = DOT(n2, n2); + area2[1] = dot(n2, n2); if (area2[0] > area2[1]) { // Choose [a, b, c] as the base triangle. circumsphere(pa, pb, pc, NULL, c, &r); - d = DIST(c, pd); + d = distance(c, pd); } else { // Choose [b, a, d] as the base triangle. if (area2[1] > 0) { circumsphere(pb, pa, pd, NULL, c, &r); - d = DIST(c, pc); + d = distance(c, pc); } else { // The four points are collinear. This case only happens on the boundary. return 0; // Return "not inside". @@ -1188,150 +1332,6 @@ REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) return sign; } -/////////////////////////////////////////////////////////////////////////////// -// // -// insphere_s() Insphere test with symbolic perturbation. // -// // -// Given four points pa, pb, pc, and pd, test if the point pe lies inside or // -// outside the circumscirbed sphere of the four points. // -// // -// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // -// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // -// points pa, pb, and pc. Otherwise, the returned sign is flipped. // -// // -// Return a positive value (> 0) if pe lies inside, a negative value (< 0) // -// if pe lies outside the sphere, the returned value will not be zero. // -// // -/////////////////////////////////////////////////////////////////////////////// - -REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) -{ - REAL sign; - - sign = insphere(pa, pb, pc, pd, pe); - if (sign != 0.0) { - return sign; - } - - insphere_sos_count++; - - // Symbolic perturbation. - point pt[5], swappt; - REAL oriA, oriB; - int swaps, count; - int n, i; - - pt[0] = pa; - pt[1] = pb; - pt[2] = pc; - pt[3] = pd; - pt[4] = pe; - - // Sort the five points such that their indices are in the increasing - // order. An optimized bubble sort algorithm is used, i.e., it has - // the worst case O(n^2) runtime, but it is usually much faster. - swaps = 0; // Record the total number of swaps. - n = 5; - do { - count = 0; - n = n - 1; - for (i = 0; i < n; i++) { - if (pointmark(pt[i]) > pointmark(pt[i+1])) { - swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; - count++; - } - } - swaps += count; - } while (count > 0); // Continue if some points are swapped. - - oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); - if (oriA != 0.0) { - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriA = -oriA; - return oriA; - } - - oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); - assert(oriB != 0.0); // SELF_CHECK - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriB = -oriB; - return oriB; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// orient4d_s() 4d orientation test with symbolic perturbation. // -// // -// Given four lifted points pa', pb', pc', and pd' in R^4,test if the lifted // -// point pe' in R^4 lies below or above the hyperplance passing through the // -// four points pa', pb', pc', and pd'. // -// // -// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // -// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // -// the points pa, pb, and pc. Otherwise, the returned sign is flipped. // -// // -// Return a positive value (> 0) if pe' lies below, a negative value (< 0) // -// if pe' lies above the hyperplane, the returned value should not be zero. // -// // -/////////////////////////////////////////////////////////////////////////////// - -REAL tetgenmesh::orient4d_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, - REAL aheight, REAL bheight, REAL cheight, - REAL dheight, REAL eheight) -{ - REAL sign; - - sign = orient4d(pa, pb, pc, pd, pe, - aheight, bheight, cheight, dheight, eheight); - if (sign != 0.0) { - return sign; - } - - orient4d_sos_count++; - - // Symbolic perturbation. - point pt[5], swappt; - REAL oriA, oriB; - int swaps, count; - int n, i; - - pt[0] = pa; - pt[1] = pb; - pt[2] = pc; - pt[3] = pd; - pt[4] = pe; - - // Sort the five points such that their indices are in the increasing - // order. An optimized bubble sort algorithm is used, i.e., it has - // the worst case O(n^2) runtime, but it is usually much faster. - swaps = 0; // Record the total number of swaps. - n = 5; - do { - count = 0; - n = n - 1; - for (i = 0; i < n; i++) { - if (pointmark(pt[i]) > pointmark(pt[i+1])) { - swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; - count++; - } - } - swaps += count; - } while (count > 0); // Continue if some points are swapped. - - oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); - if (oriA != 0.0) { - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriA = -oriA; - return oriA; - } - - oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); - assert(oriB != 0.0); // SELF_CHECK - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriB = -oriB; - return oriB; -} - /////////////////////////////////////////////////////////////////////////////// // // // facenormal() Calculate the normal of the face. // @@ -1368,9 +1368,9 @@ void tetgenmesh::facenormal(point pa, point pb, point pc, REAL *n, int pivot, v3[0] = pc[0] - pb[0]; // edge vector v3: b->c v3[1] = pc[1] - pb[1]; v3[2] = pc[2] - pb[2]; - L1 = DOT(v1, v1); - L2 = DOT(v2, v2); - L3 = DOT(v3, v3); + L1 = dot(v1, v1); + L2 = dot(v2, v2); + L3 = dot(v3, v3); // Sort the three edge lengths. if (L1 < L2) { if (L2 < L3) { @@ -1394,7 +1394,7 @@ void tetgenmesh::facenormal(point pa, point pb, point pc, REAL *n, int pivot, } // Calculate the face normal. - CROSS(pv1, pv2, n); + cross(pv1, pv2, n); // Inverse the direction; n[0] = -n[0]; n[1] = -n[1]; @@ -1428,9 +1428,8 @@ REAL tetgenmesh::shortdistance(REAL* p, REAL* e1, REAL* e2) v2[2] = p[2] - e1[2]; len = sqrt(dot(v1, v1)); -#ifdef SELF_CHECK assert(len != 0.0); -#endif + v1[0] /= len; v1[1] /= len; v1[2] /= len; @@ -1462,6 +1461,27 @@ REAL tetgenmesh::triarea(REAL* pa, REAL* pb, REAL* pc) return 0.5 * sqrt(dot(A[2], A[2])); // The area of [a,b,c]. } +REAL tetgenmesh::orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx; + REAL ady, bdy, cdy; + REAL adz, bdz, cdz; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + return adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); +} + /////////////////////////////////////////////////////////////////////////////// // // // interiorangle() Return the interior angle (0 - 2 * PI) between vectors // @@ -1491,9 +1511,8 @@ REAL tetgenmesh::interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n) len1 = sqrt(dot(v1, v1)); len2 = sqrt(dot(v2, v2)); lenlen = len1 * len2; -#ifdef SELF_CHECK assert(lenlen != 0.0); -#endif + costheta = dot(v1, v2) / lenlen; if (costheta > 1.0) { costheta = 1.0; // Roundoff. @@ -1630,7 +1649,7 @@ bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, REAL* cosdd, REAL* cosmaxd, REAL* cosmind) { REAL N[4][3], vol, cosd, len; - int f1, f2, i, j; + int f1 = 0, f2 = 0, i, j; vol = 0; // Check if the tet is valid or not. @@ -1686,7 +1705,7 @@ bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, } } - // Calculate the consine of the dihedral angles of the edges. + // Calculate the cosine of the dihedral angles of the edges. for (i = 0; i < 6; i++) { switch (i) { case 0: f1 = 0; f2 = 1; break; // [c,d]. @@ -1698,6 +1717,7 @@ bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, } cosd = -dot(N[f1], N[f2]); if (cosd < -1.0) cosd = -1.0; // Rounding. + if (cosd > 1.0) cosd = 1.0; // Rounding. if (cosdd) cosdd[i] = cosd; if (cosmaxd || cosmind) { if (i == 0) { @@ -1715,7 +1735,7 @@ bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, /////////////////////////////////////////////////////////////////////////////// // // -// tetallnormal() Get the in-noramls of the four faces of a given tet. // +// tetallnormal() Get the in-normals of the four faces of a given tet. // // // // Let tet be abcd. N[4][3] returns the four normals, which are: N[0] cbd, // // N[1] acd, N[2] bad, N[3] abc (exactly corresponding to the face indices // @@ -1834,7 +1854,7 @@ REAL tetgenmesh::tetaspectratio(point pa, point pb, point pc, point pd) // // // Return TRUE if the input points are not degenerate and the circumcenter // // and circumradius are returned in 'cent' and 'radius' respectively if they // -// are not NULLs. Otherwise, return FALSE indicated the points are degenrate.// +// are not NULLs. Otherwise, return FALSE, the four points are co-planar. // // // /////////////////////////////////////////////////////////////////////////////// @@ -1986,6 +2006,56 @@ void tetgenmesh::planelineint(REAL* pa, REAL* pb, REAL* pc, REAL* e1, REAL* e2, } } +/////////////////////////////////////////////////////////////////////////////// +// // +// linelineint() Calculate the intersection(s) of two line segments. // +// // +// Calculate the line segment [P, Q] that is the shortest route between two // +// lines from A to B and C to D. Calculate also the values of tp and tq // +// where: P = A + tp (B - A), and Q = C + tq (D - C). // +// // +// Return 1 if the line segment exists. Otherwise, return 0. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::linelineint(REAL* A, REAL* B, REAL* C, REAL* D, REAL* P, + REAL* Q, REAL* tp, REAL* tq) +{ + REAL vab[3], vcd[3], vca[3]; + REAL vab_vab, vcd_vcd, vab_vcd; + REAL vca_vab, vca_vcd; + REAL det, eps; + int i; + + for (i = 0; i < 3; i++) { + vab[i] = B[i] - A[i]; + vcd[i] = D[i] - C[i]; + vca[i] = A[i] - C[i]; + } + + vab_vab = dot(vab, vab); + vcd_vcd = dot(vcd, vcd); + vab_vcd = dot(vab, vcd); + + det = vab_vab * vcd_vcd - vab_vcd * vab_vcd; + // Round the result. + eps = det / (fabs(vab_vab * vcd_vcd) + fabs(vab_vcd * vab_vcd)); + if (eps < b->epsilon) { + return 0; + } + + vca_vab = dot(vca, vab); + vca_vcd = dot(vca, vcd); + + *tp = (vcd_vcd * (- vca_vab) + vab_vcd * vca_vcd) / det; + *tq = (vab_vcd * (- vca_vab) + vab_vab * vca_vcd) / det; + + for (i = 0; i < 3; i++) P[i] = A[i] + (*tp) * vab[i]; + for (i = 0; i < 3; i++) Q[i] = C[i] + (*tq) * vcd[i]; + + return 1; +} + /////////////////////////////////////////////////////////////////////////////// // // // tetprismvol() Calculate the volume of a tetrahedral prism in 4D. // @@ -2033,6 +2103,129 @@ REAL tetgenmesh::tetprismvol(REAL* p0, REAL* p1, REAL* p2, REAL* p3) return fabs(vol[0]) + fabs(vol[1]) + fabs(vol[2]) + fabs(vol[3]); } +/////////////////////////////////////////////////////////////////////////////// +// // +// calculateabovepoint() Calculate a point above a facet in 'dummypoint'. // +// // +/////////////////////////////////////////////////////////////////////////////// + +bool tetgenmesh::calculateabovepoint(arraypool *facpoints, point *ppa, + point *ppb, point *ppc) +{ + point *ppt, pa, pb, pc; + REAL v1[3], v2[3], n[3]; + REAL lab, len, A, area; + REAL x, y, z; + int i; + + ppt = (point *) fastlookup(facpoints, 0); + pa = *ppt; // a is the first point. + pb = pc = NULL; // Avoid compiler warnings. + + // Get a point b s.t. the length of [a, b] is maximal. + lab = 0; + for (i = 1; i < facpoints->objects; i++) { + ppt = (point *) fastlookup(facpoints, i); + x = (*ppt)[0] - pa[0]; + y = (*ppt)[1] - pa[1]; + z = (*ppt)[2] - pa[2]; + len = x * x + y * y + z * z; + if (len > lab) { + lab = len; + pb = *ppt; + } + } + lab = sqrt(lab); + if (lab == 0) { + if (!b->quiet) { + printf("Warning: All points of a facet are coincident with %d.\n", + pointmark(pa)); + } + return false; + } + + // Get a point c s.t. the area of [a, b, c] is maximal. + v1[0] = pb[0] - pa[0]; + v1[1] = pb[1] - pa[1]; + v1[2] = pb[2] - pa[2]; + A = 0; + for (i = 1; i < facpoints->objects; i++) { + ppt = (point *) fastlookup(facpoints, i); + v2[0] = (*ppt)[0] - pa[0]; + v2[1] = (*ppt)[1] - pa[1]; + v2[2] = (*ppt)[2] - pa[2]; + cross(v1, v2, n); + area = dot(n, n); + if (area > A) { + A = area; + pc = *ppt; + } + } + if (A == 0) { + // All points are collinear. No above point. + if (!b->quiet) { + printf("Warning: All points of a facet are collinaer with [%d, %d].\n", + pointmark(pa), pointmark(pb)); + } + return false; + } + + // Calculate an above point of this facet. + facenormal(pa, pb, pc, n, 1, NULL); + len = sqrt(dot(n, n)); + n[0] /= len; + n[1] /= len; + n[2] /= len; + lab /= 2.0; // Half the maximal length. + dummypoint[0] = pa[0] + lab * n[0]; + dummypoint[1] = pa[1] + lab * n[1]; + dummypoint[2] = pa[2] + lab * n[2]; + + if (ppa != NULL) { + // Return the three points. + *ppa = pa; + *ppb = pb; + *ppc = pc; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Calculate an above point. It lies above the plane containing the subface // +// [a,b,c], and save it in dummypoint. Moreover, the vector pa->dummypoint // +// is the normal of the plane. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::calculateabovepoint4(point pa, point pb, point pc, point pd) +{ + REAL n1[3], n2[3], *norm; + REAL len, len1, len2; + + // Select a base. + facenormal(pa, pb, pc, n1, 1, NULL); + len1 = sqrt(dot(n1, n1)); + facenormal(pa, pb, pd, n2, 1, NULL); + len2 = sqrt(dot(n2, n2)); + if (len1 > len2) { + norm = n1; + len = len1; + } else { + norm = n2; + len = len2; + } + assert(len > 0); + norm[0] /= len; + norm[1] /= len; + norm[2] /= len; + len = distance(pa, pb); + dummypoint[0] = pa[0] + len * norm[0]; + dummypoint[1] = pa[1] + len * norm[1]; + dummypoint[2] = pa[2] + len * norm[2]; +} + //// //// //// //// //// geom_cxx ///////////////////////////////////////////////////////////////// diff --git a/external/tetgen/src/io.cxx b/external/tetgen/src/io.cxx index 6bf8df131..d23a6e04f 100644 --- a/external/tetgen/src/io.cxx +++ b/external/tetgen/src/io.cxx @@ -31,24 +31,24 @@ bool tetgenio::load_node_call(FILE* infile, int markers, int uvflag, // Initialize 'pointlist', 'pointattributelist', and 'pointmarkerlist'. pointlist = new REAL[numberofpoints * 3]; if (pointlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } if (numberofpointattributes > 0) { pointattributelist = new REAL[numberofpoints * numberofpointattributes]; if (pointattributelist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } if (markers) { pointmarkerlist = new int[numberofpoints]; if (pointmarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } if (uvflag) { pointparamlist = new pointparam[numberofpoints]; if (pointparamlist == NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } @@ -196,11 +196,11 @@ bool tetgenio::load_node(char* filebasename) mesh_dim = 3; numberofpointattributes = 0; // no point attribute. markers = 0; // no boundary marker. - uvflag = 0; // no uv parameters (reuqired by a PSC). + uvflag = 0; // no uv parameters (required by a PSC). // Read the first line of the file. stringptr = readnumberline(inputline, infile, innodefilename); - // Does this file contain an index colume? + // Does this file contain an index column? stringptr = strstr(inputline, "rbox"); if (stringptr == NULL) { // Read number of points, number of dimensions, number of point @@ -275,7 +275,7 @@ bool tetgenio::load_edge(char* filebasename) if (numberofedges > 0) { edgelist = new int[numberofedges * 2]; if (edgelist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } stringptr = findnextnumber(stringptr); if (*stringptr == '\0') { @@ -298,13 +298,13 @@ bool tetgenio::load_edge(char* filebasename) if (*stringptr == '\0') { printf("Error: Edge %d is missing vertex %d in %s.\n", i + firstnumber, j + 1, inedgefilename); - terminatetetgen(1); + terminatetetgen(NULL, 1); } corner = (int) strtol(stringptr, &stringptr, 0); if (corner < firstnumber || corner >= numberofpoints + firstnumber) { printf("Error: Edge %d has an invalid vertex index.\n", i + firstnumber); - terminatetetgen(1); + terminatetetgen(NULL, 1); } edgelist[index++] = corner; } @@ -366,12 +366,12 @@ bool tetgenio::load_face(char* filebasename) if (numberoftrifaces > 0) { trifacelist = new int[numberoftrifaces * 3]; if (trifacelist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } if (markers) { trifacemarkerlist = new int[numberoftrifaces]; if (trifacemarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } } @@ -386,13 +386,13 @@ bool tetgenio::load_face(char* filebasename) if (*stringptr == '\0') { printf("Error: Face %d is missing vertex %d in %s.\n", i + firstnumber, j + 1, infilename); - terminatetetgen(1); + terminatetetgen(NULL, 1); } corner = (int) strtol(stringptr, &stringptr, 0); if (corner < firstnumber || corner >= numberofpoints + firstnumber) { printf("Error: Face %d has an invalid vertex index.\n", i + firstnumber); - terminatetetgen(1); + terminatetetgen(NULL, 1); } trifacelist[index++] = corner; } @@ -477,14 +477,14 @@ bool tetgenio::load_tet(char* filebasename) // Allocate memory for tetrahedra. tetrahedronlist = new int[numberoftetrahedra * numberofcorners]; if (tetrahedronlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } // Allocate memory for output tetrahedron attributes if necessary. if (numberoftetrahedronattributes > 0) { tetrahedronattributelist = new REAL[numberoftetrahedra * numberoftetrahedronattributes]; if (tetrahedronattributelist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } @@ -499,13 +499,13 @@ bool tetgenio::load_tet(char* filebasename) if (*stringptr == '\0') { printf("Error: Tetrahedron %d is missing vertex %d in %s.\n", i + firstnumber, j + 1, infilename); - terminatetetgen(1); + terminatetetgen(NULL, 1); } corner = (int) strtol(stringptr, &stringptr, 0); if (corner < firstnumber || corner >= numberofpoints + firstnumber) { printf("Error: Tetrahedron %d has an invalid vertex index.\n", i + firstnumber); - terminatetetgen(1); + terminatetetgen(NULL, 1); } tetrahedronlist[index++] = corner; } @@ -567,7 +567,7 @@ bool tetgenio::load_vol(char* filebasename) tetrahedronvolumelist = new REAL[volelements]; if (tetrahedronvolumelist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } // Read the list of volume constraints. @@ -746,7 +746,7 @@ bool tetgenio::load_mtr(char* filebasename) // Allocate space for pointmtrlist. pointmtrlist = new REAL[numberofpoints * numberofpointmtrs]; if (pointmtrlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } mtrindex = 0; for (i = 0; i < numberofpoints; i++) { @@ -756,7 +756,7 @@ bool tetgenio::load_mtr(char* filebasename) if (*stringptr == '\0') { printf("Error: Metric %d is missing value #%d in %s.\n", i + firstnumber, j + 1, mtrfilename); - terminatetetgen(1); + terminatetetgen(NULL, 1); } mtr = (REAL) strtod(stringptr, &stringptr); pointmtrlist[mtrindex++] = mtr; @@ -813,10 +813,10 @@ bool tetgenio::load_poly(char* filebasename) } // Initialize the default values. - mesh_dim = 3; // Three-dimemsional accoordinates. + mesh_dim = 3; // Three-dimensional coordinates. numberofpointattributes = 0; // no point attribute. markers = 0; // no boundary marker. - uvflag = 0; // no uv parameters (reuqired by a PSC). + uvflag = 0; // no uv parameters (required by a PSC). // Read number of points, number of dimensions, number of point // attributes, and number of boundary markers. @@ -2067,7 +2067,7 @@ bool tetgenio::load_medit(char* filebasename, int istetmesh) // // // load_vtk() Load VTK surface mesh from file (.vtk ascii or binary). // // // -// This function is contributed by: Bryn Lloyd, Computer Vision Laborator, // +// This function is contributed by: Bryn Lloyd, Computer Vision Laboratory, // // ETH, Zuerich. May 7, 2007. // // // /////////////////////////////////////////////////////////////////////////////// diff --git a/external/tetgen/src/main.cxx b/external/tetgen/src/main.cxx index bea019406..63ba64dc7 100644 --- a/external/tetgen/src/main.cxx +++ b/external/tetgen/src/main.cxx @@ -31,7 +31,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, tetgenio *addin, tetgenio *bgmin) { tetgenmesh m; - clock_t tv[10], ts[5]; // Timing informations (defined in time.h) + clock_t tv[12], ts[5]; // Timing informations (defined in time.h) REAL cps = (REAL) CLOCKS_PER_SEC; tv[0] = clock(); @@ -40,7 +40,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.in = in; m.addin = addin; - if ((b->metric) && (bgmin->numberofpoints > 0)) { + if (b->metric && bgmin && (bgmin->numberofpoints > 0)) { m.bgm = new tetgenmesh(); // Create an empty background mesh. m.bgm->b = b; m.bgm->in = bgmin; @@ -49,14 +49,14 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.initializepools(); m.transfernodes(); - exactinit(b->noexact, b->nostaticfilter, m.xmax - m.xmin, - m.ymax - m.ymin, m.zmax - m.zmin); + exactinit(b->verbose, b->noexact, b->nostaticfilter, + m.xmax - m.xmin, m.ymax - m.ymin, m.zmax - m.zmin); tv[1] = clock(); - if (b->refine) { + if (b->refine) { // -r m.reconstructmesh(); - } else { // b->plc + } else { // -p m.incrementaldelaunay(ts[0]); } @@ -73,7 +73,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, } } - if (b->plc) { // -p + if (b->plc && !b->refine) { // -p m.meshsurface(); ts[0] = clock(); @@ -128,7 +128,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, tv[4] = clock(); - if (b->plc) { // -p + if (b->plc && !b->refine) { // -p if (b->nobisect) { // -Y m.recoverboundary(ts[0]); } else { @@ -138,7 +138,12 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, ts[1] = clock(); if (!b->quiet) { - printf("Boundary recovery seconds: %g\n", ((REAL)(ts[1]-tv[4])) / cps); + if (b->nobisect) { + printf("Boundary recovery "); + } else { + printf("Constrained Delaunay "); + } + printf("seconds: %g\n", ((REAL)(ts[1] - tv[4])) / cps); if (b->verbose) { printf(" Segment recovery seconds: %g\n",((REAL)(ts[0]-tv[4]))/ cps); printf(" Facet recovery seconds: %g\n", ((REAL)(ts[1]-ts[0])) / cps); @@ -150,69 +155,86 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, ts[2] = clock(); if (!b->quiet) { - printf("Exterior tets removal seconds: %g\n", - ((REAL)(ts[2]-ts[1])) / cps); + printf("Exterior tets removal seconds: %g\n",((REAL)(ts[2]-ts[1]))/cps); } if (b->nobisect) { // -Y - m.suppresssteinerpoints(); + if (m.subvertstack->objects > 0l) { + m.suppresssteinerpoints(); - ts[3] = clock(); + ts[3] = clock(); - if (!b->quiet) { - printf("Steiner suppression seconds: %g\n",((REAL)(ts[3]-ts[2]))/cps); - } - - m.recoverdelaunay(); - - ts[4] = clock(); - - if (!b->quiet) { - printf("Delaunay recovery seconds: %g\n", ((REAL)(ts[4]-ts[3])) / cps); + if (!b->quiet) { + printf("Steiner suppression seconds: %g\n", + ((REAL)(ts[3]-ts[2]))/cps); + } } } } tv[5] = clock(); + if (b->coarsen) { // -R + m.meshcoarsening(); + } + + tv[6] = clock(); + + if (!b->quiet) { + if (b->coarsen) { + printf("Mesh coarsening seconds: %g\n", ((REAL)(tv[6] - tv[5])) / cps); + } + } + + if ((b->plc && b->nobisect) || b->coarsen) { + m.recoverdelaunay(); + } + + tv[7] = clock(); + + if (!b->quiet) { + if ((b->plc && b->nobisect) || b->coarsen) { + printf("Delaunay recovery seconds: %g\n", ((REAL)(tv[7] - tv[6]))/cps); + } + } + if ((b->plc || b->refine) && b->insertaddpoints) { // -i if ((addin != NULL) && (addin->numberofpoints > 0)) { m.insertconstrainedpoints(addin); } } - tv[6] = clock(); + tv[8] = clock(); if (!b->quiet) { - if ((b->plc || b->refine) && b->insertaddpoints) { + if ((b->plc || b->refine) && b->insertaddpoints) { // -i if ((addin != NULL) && (addin->numberofpoints > 0)) { - printf("Constrained points seconds: %g\n", ((REAL)(tv[6]-tv[5]))/cps); + printf("Constrained points seconds: %g\n", ((REAL)(tv[8]-tv[7]))/cps); } } } - if (b->quality) { - m.delaunayrefinement(); + m.delaunayrefinement(); } - tv[7] = clock(); + tv[9] = clock(); if (!b->quiet) { if (b->quality) { - printf("Refinement seconds: %g\n", ((REAL)(tv[7] - tv[6])) / cps); + printf("Refinement seconds: %g\n", ((REAL)(tv[9] - tv[8])) / cps); } } - if ((b->plc || b->refine) && (b->optlevel > 0) && !b->conforming) { + if ((b->plc || b->refine) && (b->optlevel > 0)) { m.optimizemesh(); } - tv[8] = clock(); + tv[10] = clock(); if (!b->quiet) { - if ((b->plc || b->refine) && (b->optlevel > 0) && !b->conforming) { - printf("Optimization seconds: %g\n", ((REAL)(tv[8] - tv[7])) / cps); + if ((b->plc || b->refine) && (b->optlevel > 0)) { + printf("Optimization seconds: %g\n", ((REAL)(tv[10] - tv[9])) / cps); } } @@ -221,7 +243,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.jettisonnodes(); } - if (b->order == 2) { + if ((b->order == 2) && !b->convex) { m.highorder(); } @@ -246,7 +268,6 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, if (!b->quiet) { printf("NOT writing an .ele file.\n"); } - m.numberedges(); } else { if (m.tetrahedrons->items > 0l) { m.outelements(out); @@ -319,11 +340,11 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, } - tv[9] = clock(); + tv[11] = clock(); if (!b->quiet) { - printf("\nOutput seconds: %g\n", ((REAL)(tv[9] - tv[8])) / cps); - printf("Total running seconds: %g\n", ((REAL)(tv[9] - tv[0])) / cps); + printf("\nOutput seconds: %g\n", ((REAL)(tv[11] - tv[10])) / cps); + printf("Total running seconds: %g\n", ((REAL)(tv[11] - tv[0])) / cps); } if (b->docheck) { @@ -346,7 +367,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, /////////////////////////////////////////////////////////////////////////////// // // -// main() The entrance for running TetGen from command line. // +// main() The command line interface of TetGen. // // // /////////////////////////////////////////////////////////////////////////////// @@ -356,7 +377,7 @@ int main(int argc, char *argv[]) /////////////////////////////////////////////////////////////////////////////// // // -// tetrahedralize() The entrance for calling TetGen from another program. // +// tetrahedralize() The library interface of TetGen. // // // /////////////////////////////////////////////////////////////////////////////// @@ -373,17 +394,17 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, tetgenio in, addin, bgmin; if (!b.parse_commandline(argc, argv)) { - terminatetetgen(10); + terminatetetgen(NULL, 10); } // Read input files. if (b.refine) { // -r if (!in.load_tetmesh(b.infilename, (int) b.object)) { - terminatetetgen(10); + terminatetetgen(NULL, 10); } } else { // -p if (!in.load_plc(b.infilename, (int) b.object)) { - terminatetetgen(10); + terminatetetgen(NULL, 10); } } if (b.insertaddpoints) { // -i @@ -402,7 +423,7 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, #else // with TETLIBRARY if (!b.parse_commandline(switches)) { - terminatetetgen(10); + terminatetetgen(NULL, 10); } tetrahedralize(&b, in, out, addin, bgmin); diff --git a/external/tetgen/src/mempool.cxx b/external/tetgen/src/mempool.cxx index c5cb6b785..c7f7a899c 100644 --- a/external/tetgen/src/mempool.cxx +++ b/external/tetgen/src/mempool.cxx @@ -5,16 +5,25 @@ // Initialize fast lookup tables for mesh maniplulation primitives. -int tetgenmesh::mod12[36] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; -int tetgenmesh::mod6[18] = {0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, - 0, 1, 2, 3, 4, 5}; +int tetgenmesh::bondtbl[12][12] = {{0,},}; +int tetgenmesh::enexttbl[12] = {0,}; +int tetgenmesh::eprevtbl[12] = {0,}; +int tetgenmesh::enextesymtbl[12] = {0,}; +int tetgenmesh::eprevesymtbl[12] = {0,}; +int tetgenmesh::eorgoppotbl[12] = {0,}; +int tetgenmesh::edestoppotbl[12] = {0,}; +int tetgenmesh::fsymtbl[12][12] = {{0,},}; +int tetgenmesh::facepivot1[12] = {0,}; +int tetgenmesh::facepivot2[12][12] = {{0,},}; +int tetgenmesh::tsbondtbl[12][6] = {{0,},}; +int tetgenmesh::stbondtbl[12][6] = {{0,},}; +int tetgenmesh::tspivottbl[12][6] = {{0,},}; +int tetgenmesh::stpivottbl[12][6] = {{0,},}; -// Table 'edgepivot' takes an directed edge (version) as input, returns the +// Table 'esymtbl' takes an directed edge (version) as input, returns the // inversed edge (version) of it. -int tetgenmesh::edgepivot[12] = {9, 6, 11, 4, 3, 7, 1, 5, 10, 0, 8, 2}; +int tetgenmesh::esymtbl[12] = {9, 6, 11, 4, 3, 7, 1, 5, 10, 0, 8, 2}; // The following four tables give the 12 permutations of the set {0,1,2,3}. // An offset 4 is added to each element for a direct access of the points @@ -31,6 +40,11 @@ int tetgenmesh::oppopivot[12] = {4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7}; int tetgenmesh::ver2edge[12] = {0, 1, 2, 3, 3, 5, 1, 5, 4, 0, 4, 2}; int tetgenmesh::edge2ver[ 6] = {0, 1, 2, 3, 8, 5}; +// Edge versions whose apex or opposite may be dummypoint. + +int tetgenmesh::epivot[12] = {4, 5, 2, 11, 4, 5, 2, 11, 4, 5, 2, 11}; + + // Table 'snextpivot' takes an edge version as input, returns the next edge // version in the same edge ring. @@ -44,9 +58,91 @@ int tetgenmesh::sorgpivot [6] = {3, 4, 4, 5, 5, 3}; int tetgenmesh::sdestpivot[6] = {4, 3, 5, 4, 3, 5}; int tetgenmesh::sapexpivot[6] = {5, 5, 3, 3, 4, 4}; -// Edge versions whose apex or opposite may be dummypoint. +/////////////////////////////////////////////////////////////////////////////// +// // +// inittable() Initialize the look-up tables. // +// // +/////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::epivot[4] = {4, 5, 2, 11}; +void tetgenmesh::inittables() +{ + int i, j; + + + // i = t1.ver; j = t2.ver; + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); + } + } + + + // i = t1.ver; j = t2.ver + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + fsymtbl[i][j] = (j + 12 - (i & 12)) % 12; + } + } + + + for (i = 0; i < 12; i++) { + facepivot1[i] = (esymtbl[i] & 3); + } + + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + facepivot2[i][j] = fsymtbl[esymtbl[i]][j]; + } + } + + for (i = 0; i < 12; i++) { + enexttbl[i] = (i + 4) % 12; + eprevtbl[i] = (i + 8) % 12; + } + + for (i = 0; i < 12; i++) { + enextesymtbl[i] = esymtbl[enexttbl[i]]; + eprevesymtbl[i] = esymtbl[eprevtbl[i]]; + } + + for (i = 0; i < 12; i++) { + eorgoppotbl [i] = eprevtbl[esymtbl[enexttbl[i]]]; + edestoppotbl[i] = enexttbl[esymtbl[eprevtbl[i]]]; + } + + int soffset, toffset; + + // i = t.ver, j = s.shver + for (i = 0; i < 12; i++) { + for (j = 0; j < 6; j++) { + if ((j & 1) == 0) { + soffset = (6 - ((i & 12) >> 1)) % 6; + toffset = (12 - ((j & 6) << 1)) % 12; + } else { + soffset = (i & 12) >> 1; + toffset = (j & 6) << 1; + } + tsbondtbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); + stbondtbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); + } + } + + + // i = t.ver, j = s.shver + for (i = 0; i < 12; i++) { + for (j = 0; j < 6; j++) { + if ((j & 1) == 0) { + soffset = (i & 12) >> 1; + toffset = (j & 6) << 1; + } else { + soffset = (6 - ((i & 12) >> 1)) % 6; + toffset = (12 - ((j & 6) << 1)) % 12; + } + tspivottbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); + stpivottbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); + } + } +} /////////////////////////////////////////////////////////////////////////////// // // @@ -80,6 +176,7 @@ void tetgenmesh::arraypool::poolinit(int sizeofobject, int log2objperblk) log2objectsperblock = log2objperblk; // Compute the number of objects in each block. objectsperblock = ((int) 1) << log2objectsperblock; + objectsperblockmark = objectsperblock - 1; // No memory has been allocated. totalmemory = 0l; @@ -237,26 +334,22 @@ void* tetgenmesh::arraypool::lookup(int objectindex) // // // newindex() Allocate space for a fresh object from the pool. // // // +// 'newptr' returns a pointer to the new object (it must not be a NULL). // +// // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::arraypool::newindex(void **newptr) { - void *newobject; - int newindex; - // Allocate an object at index 'firstvirgin'. - newindex = objects; - newobject = (void *) (getblock(objects) + + int newindex = objects; + *newptr = (void *) (getblock(objects) + (objects & (objectsperblock - 1)) * objectbytes); objects++; - // If 'newptr' is not NULL, use it to return a pointer to the object. - if (newptr != (void **) NULL) { - *newptr = newobject; - } return newindex; } + /////////////////////////////////////////////////////////////////////////////// // // // memorypool() The constructors of memorypool. // @@ -270,7 +363,6 @@ tetgenmesh::memorypool::memorypool() deaditemstack = (void *) NULL; pathblock = (void **) NULL; pathitem = (void *) NULL; - itemwordtype = POINTER; alignbytes = 0; itembytes = itemwords = 0; itemsperblock = 0; @@ -279,10 +371,10 @@ tetgenmesh::memorypool::memorypool() pathitemsleft = 0; } -tetgenmesh::memorypool:: -memorypool(int bytecount, int itemcount, enum wordtype wtype, int alignment) +tetgenmesh::memorypool::memorypool(int bytecount, int itemcount, int wsize, + int alignment) { - poolinit(bytecount, itemcount, wtype, alignment); + poolinit(bytecount, itemcount, wsize, alignment); } /////////////////////////////////////////////////////////////////////////////// @@ -316,14 +408,9 @@ tetgenmesh::memorypool::~memorypool() // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::memorypool:: -poolinit(int bytecount, int itemcount, enum wordtype wtype, int alignment) +void tetgenmesh::memorypool::poolinit(int bytecount,int itemcount,int wordsize, + int alignment) { - int wordsize; - - // Initialize values in the pool. - itemwordtype = wtype; - wordsize = (itemwordtype == POINTER) ? sizeof(void *) : sizeof(REAL); // Find the proper alignment, which must be at least as large as: // - The parameter `alignment'. // - The primary word type, to avoid unaligned accesses. @@ -348,7 +435,7 @@ poolinit(int bytecount, int itemcount, enum wordtype wtype, int alignment) firstblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + alignbytes); if (firstblock == (void **) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } // Set the next block pointer to NULL. *(firstblock) = (void *) NULL; @@ -412,7 +499,7 @@ void* tetgenmesh::memorypool::alloc() newblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + alignbytes); if (newblock == (void **) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } *nowblock = (void *) newblock; // The next block pointer is NULL. @@ -433,11 +520,7 @@ void* tetgenmesh::memorypool::alloc() // Allocate a new item. newitem = nextitem; // Advance `nextitem' pointer to next free item in block. - if (itemwordtype == POINTER) { - nextitem = (void *) ((void **) nextitem + itemwords); - } else { - nextitem = (void *) ((REAL *) nextitem + itemwords); - } + nextitem = (void *) ((uintptr_t) nextitem + itembytes); unallocateditems--; maxitems++; } @@ -521,11 +604,7 @@ void* tetgenmesh::memorypool::traverse() } newitem = pathitem; // Find the next item in the block. - if (itemwordtype == POINTER) { - pathitem = (void *) ((void **) pathitem + itemwords); - } else { - pathitem = (void *) ((REAL *) pathitem + itemwords); - } + pathitem = (void *) ((uintptr_t) pathitem + itembytes); pathitemsleft--; return newitem; } @@ -536,8 +615,7 @@ void* tetgenmesh::memorypool::traverse() // // // 'idx2verlist' returns the created map. Traverse all vertices, a pointer // // to each vertex is set into the array. The pointer to the first vertex is // -// saved in 'idx2verlist[0]'. Don't forget to minus 'in->firstnumber' when // -// to get the vertex form its index. // +// saved in 'idx2verlist[in->firstnumber]'. // // // /////////////////////////////////////////////////////////////////////////////// @@ -554,7 +632,7 @@ void tetgenmesh::makeindex2pointmap(point*& idx2verlist) points->traversalinit(); pointloop = pointtraverse(); - idx = in->firstnumber;; + idx = in->firstnumber; while (pointloop != (point) NULL) { idx2verlist[idx++] = pointloop; pointloop = pointtraverse(); @@ -743,38 +821,6 @@ tetgenmesh::shellface* tetgenmesh::shellfacetraverse(memorypool *pool) return newshellface; } -/////////////////////////////////////////////////////////////////////////////// -// // -// badfacedealloc() Deallocate space for a badface, marking it dead. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::badfacedealloc(memorypool *pool, badface *dying) -{ - // Set badface's forg to NULL. This makes it possible to detect dead - // ones when traversing the list of all items. - dying->forg = (point) NULL; - pool->dealloc((void *) dying); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// badfacetraverse() Traverse the pools, skipping dead ones. // -// // -/////////////////////////////////////////////////////////////////////////////// - -tetgenmesh::badface* tetgenmesh::badfacetraverse(memorypool *pool) -{ - badface *newsh; - - do { - newsh = (badface *) pool->traverse(); - if (newsh == (badface *) NULL) { - return (badface *) NULL; - } - } while (newsh->forg == (point) NULL); // Skip dead ones. - return newsh; -} /////////////////////////////////////////////////////////////////////////////// // // @@ -829,7 +875,7 @@ void tetgenmesh::maketetrahedron(triface *newtet) newtet->tet[5] = NULL; newtet->tet[6] = NULL; newtet->tet[7] = NULL; - // No attached segments and sbfaces yet. + // No attached segments and subfaces yet. newtet->tet[8] = NULL; newtet->tet[9] = NULL; // Initialize the marker (clear all flags). @@ -848,7 +894,7 @@ void tetgenmesh::maketetrahedron(triface *newtet) /////////////////////////////////////////////////////////////////////////////// // // // makeshellface() Create a new shellface with version zero. Used for // -// both subfaces and seusegments. // +// both subfaces and subsegments. // // // /////////////////////////////////////////////////////////////////////////////// @@ -871,18 +917,17 @@ void tetgenmesh::makeshellface(memorypool *pool, face *newface) // No adjoining tetrahedra. newface->sh[9] = NULL; newface->sh[10] = NULL; - if (b->quality && checkconstraints) { + if (checkconstraints) { // Initialize the maximum area bound. setareabound(*newface, 0.0); } - // Clear the infection and marktest bits. ((int *) (newface->sh))[shmarkindex + 1] = 0; - + if (useinsertradius) { + setfacetindex(*newface, 0); + } // Set the boundary marker to zero. setshellmark(*newface, 0); - // Set the default face type. - setshelltype(*newface, NSHARP); newface->shver = 0; } @@ -909,7 +954,7 @@ void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) } setpoint2tet(*pnewpoint, NULL); setpoint2ppt(*pnewpoint, NULL); - if (b->plc || b->psc || b->refine) { + if (b->plc || b->refine) { // Initialize the point-to-simplex field. setpoint2sh(*pnewpoint, NULL); if (b->metric && (bgm != NULL)) { @@ -917,20 +962,11 @@ void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) } } // Initialize the point marker (starting from in->firstnumber). - i = (int) points->items - (in->firstnumber == 1 ? 0 : 1); - setpointmark(*pnewpoint, i); - // Initialize the point type. + setpointmark(*pnewpoint, (int) (points->items) - (!in->firstnumber)); + // Clear all flags. + ((int *) (*pnewpoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. setpointtype(*pnewpoint, vtype); - // Clear the point flags. - puninfect(*pnewpoint); - punmarktest(*pnewpoint); - if (b->psc) { - // Initialize the u,v coordinates. - setpointgeomuv(*pnewpoint, 0, 0); - setpointgeomuv(*pnewpoint, 1, 0); - // Initialize the geometry tag. - setpointgeomtag(*pnewpoint, 0); - } } /////////////////////////////////////////////////////////////////////////////// @@ -946,13 +982,16 @@ void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) void tetgenmesh::initializepools() { - enum memorypool::wordtype wtype; - int pointsize, elesize, shsize; + int pointsize = 0, elesize = 0, shsize = 0; + int i; if (b->verbose) { printf(" Initializing memorypools.\n"); + printf(" tetrahedron per block: %d.\n", b->tetrahedraperblock); } + inittables(); + // There are three input point lists available, which are in, addin, // and bgm->in. These point lists may have different number of // attributes. Decide the maximum number. @@ -979,17 +1018,25 @@ void tetgenmesh::initializepools() if (in->segmentconstraintlist || in->facetconstraintlist) { checkconstraints = 1; } + if (b->plc || b->refine) { + // Save the insertion radius for Steiner points if boundaries + // are allowed be split. + if (!b->nobisect || checkconstraints) { + useinsertradius = 1; + } + } // The index within each point at which its metric tensor is found. // Each vertex has three coordinates. if (b->psc) { // '-s' option (PSC), the u,v coordinates are provided. pointmtrindex = 5 + numpointattrib; + // The index within each point at which its u, v coordinates are found. + // Comment: They are saved after the list of point attributes. + pointparamindex = pointmtrindex - 2; } else { pointmtrindex = 3 + numpointattrib; } - // The index within each point at which its u, v coordinates are found. - pointparamindex = 3 + (numpointattrib > 0); // For '-m' option. A tensor field is provided (*.mtr or *.b.mtr file). if (b->metric) { // Decide the size (1, 3, or 6) of the metric tensor. @@ -1007,6 +1054,11 @@ void tetgenmesh::initializepools() // For '-q' option. Make sure to have space for saving a scalar value. sizeoftensor = b->quality ? 1 : 0; } + if (useinsertradius) { + // Increase a space (REAL) for saving point insertion radius, it is + // saved directly after the metric. + sizeoftensor++; + } // The index within each point at which an element pointer is found, where // the index is measured in pointers. Ensure the index is aligned to a // sizeof(tetrahedron)-byte address. @@ -1032,17 +1084,14 @@ void tetgenmesh::initializepools() // The index within each point at which the boundary marker is found, // Ensure the point marker is aligned to a sizeof(int)-byte address. pointmarkindex = (pointsize + sizeof(int) - 1) / sizeof(int); - // Now point size is the ints (inidcated by pointmarkindex) plus: + // Now point size is the ints (indicated by pointmarkindex) plus: // - an integer for boundary marker; // - an integer for vertex type; // - an integer for geometry tag (optional, -s option). pointsize = (pointmarkindex + 2 + (b->psc ? 1 : 0)) * sizeof(tetrahedron); - // Decide the wordtype used in vertex pool. - wtype = (sizeof(REAL) >= sizeof(tetrahedron)) ? - memorypool::FLOATINGPOINT : memorypool::POINTER; // Initialize the pool of vertices. - points = new memorypool(pointsize, b->vertexperblock, wtype, 0); + points = new memorypool(pointsize, b->vertexperblock, sizeof(REAL), 0); if (b->verbose) { printf(" Size of a point: %d bytes.\n", points->itembytes); @@ -1050,7 +1099,32 @@ void tetgenmesh::initializepools() // Initialize the infinite vertex. dummypoint = (point) new char[pointsize]; - setpointmark(dummypoint, -1); + // Initialize all fields of this point. + dummypoint[0] = 0.0; + dummypoint[1] = 0.0; + dummypoint[2] = 0.0; + for (i = 0; i < numpointattrib; i++) { + dummypoint[3 + i] = 0.0; + } + // Initialize the metric tensor. + for (i = 0; i < sizeoftensor; i++) { + dummypoint[pointmtrindex + i] = 0.0; + } + setpoint2tet(dummypoint, NULL); + setpoint2ppt(dummypoint, NULL); + if (b->plc || b->psc || b->refine) { + // Initialize the point-to-simplex field. + setpoint2sh(dummypoint, NULL); + if (b->metric && (bgm != NULL)) { + setpoint2bgmtet(dummypoint, NULL); + } + } + // Initialize the point marker (starting from in->firstnumber). + setpointmark(dummypoint, -1); // The unique marker for dummypoint. + // Clear all flags. + ((int *) (dummypoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. + setpointtype(dummypoint, UNUSEDVERTEX); // Does not matter. // The number of bytes occupied by a tetrahedron is varying by the user- // specified options. The contents of the first 12 pointers are listed @@ -1083,7 +1157,7 @@ void tetgenmesh::initializepools() // The index within each element at which its attributes are found, where // the index is measured in REALs. elemattribindex = (elesize + sizeof(REAL) - 1) / sizeof(REAL); - // The index within each element at which the maximum voulme bound is + // The index within each element at which the maximum volume bound is // found, where the index is measured in REALs. volumeboundindex = elemattribindex + numelemattrib; // If element attributes or an constraint are needed, increase the number @@ -1096,8 +1170,8 @@ void tetgenmesh::initializepools() // Having determined the memory size of an element, initialize the pool. - tetrahedrons = new memorypool(elesize, b->tetrahedraperblock, - memorypool::POINTER, 16); + tetrahedrons = new memorypool(elesize, b->tetrahedraperblock, sizeof(void *), + 16); if (b->verbose) { printf(" Size of a tetrahedron: %d (%d) bytes.\n", elesize, @@ -1114,7 +1188,7 @@ void tetgenmesh::initializepools() areaboundindex = (shsize + sizeof(REAL) - 1) / sizeof(REAL); // If -q switch is in use, increase the number of bytes occupied by // a subface for saving maximum area bound. - if (b->quality && checkconstraints) { + if (checkconstraints) { shsize = (areaboundindex + 1) * sizeof(REAL); } else { shsize = areaboundindex * sizeof(REAL); @@ -1125,12 +1199,16 @@ void tetgenmesh::initializepools() // Increase the number of bytes by two or three integers, one for facet // marker, one for shellface type, and optionally one for pbc group. shsize = (shmarkindex + 2) * sizeof(shellface); + if (useinsertradius) { + // Increase the number of byte by one integer for storing facet index. + // set/read by setfacetindex() and getfacetindex. + shsize = (shmarkindex + 3) * sizeof(shellface); + } // Initialize the pool of subfaces. Each subface record is eight-byte // aligned so it has room to store an edge version (from 0 to 5) in // the least three bits. - subfaces = new memorypool(shsize, b->shellfaceperblock, - memorypool::POINTER, 8); + subfaces = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); if (b->verbose) { printf(" Size of a shellface: %d (%d) bytes.\n", shsize, @@ -1139,40 +1217,36 @@ void tetgenmesh::initializepools() // Initialize the pool of subsegments. The subsegment's record is same // with subface. - subsegs = new memorypool(shsize, b->shellfaceperblock, - memorypool::POINTER, 8); + subsegs = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); // Initialize the pool for tet-subseg connections. tet2segpool = new memorypool(6 * sizeof(shellface), b->shellfaceperblock, - memorypool::POINTER, 0); + sizeof(void *), 0); // Initialize the pool for tet-subface connections. tet2subpool = new memorypool(4 * sizeof(shellface), b->shellfaceperblock, - memorypool::POINTER, 0); + sizeof(void *), 0); // Initialize arraypools for segment & facet recovery. subsegstack = new arraypool(sizeof(face), 10); subfacstack = new arraypool(sizeof(face), 10); subvertstack = new arraypool(sizeof(point), 8); - suppsteinerptlist = new arraypool(sizeof(point), 8); - - // Initialize arraypools for surface Bowyer-Watson algorithm. + // Initialize arraypools for surface point insertion/deletion. caveshlist = new arraypool(sizeof(face), 8); caveshbdlist = new arraypool(sizeof(face), 8); cavesegshlist = new arraypool(sizeof(face), 4); cavetetshlist = new arraypool(sizeof(face), 8); cavetetseglist = new arraypool(sizeof(face), 8); - caveencshlist = new arraypool(sizeof(face), 8); caveencseglist = new arraypool(sizeof(face), 8); } // Initialize the pools for flips. - flippool = new memorypool(sizeof(badface), 1024, memorypool::POINTER, 0); + flippool = new memorypool(sizeof(badface), 1024, sizeof(void *), 0); unflipqueue = new arraypool(sizeof(badface), 10); - // Initialize the arraypools for Bowyer-Watson algorithm. + // Initialize the arraypools for point insertion. cavetetlist = new arraypool(sizeof(triface), 10); cavebdrylist = new arraypool(sizeof(triface), 10); caveoldtetlist = new arraypool(sizeof(triface), 10); diff --git a/external/tetgen/src/meshstat.cxx b/external/tetgen/src/meshstat.cxx index 541e79216..57a08c274 100644 --- a/external/tetgen/src/meshstat.cxx +++ b/external/tetgen/src/meshstat.cxx @@ -3,6 +3,32 @@ //// //// //// //// +/////////////////////////////////////////////////////////////////////////////// +// // +// printfcomma() Print a (large) number with the 'thousands separator'. // +// // +// The following code was simply copied from "stackoverflow". // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::printfcomma(unsigned long n) +{ + unsigned long n2 = 0; + int scale = 1; + while (n >= 1000) { + n2 = n2 + scale * (n % 1000); + n /= 1000; + scale *= 1000; + } + printf ("%ld", n); + while (scale != 1) { + scale /= 1000; + n = n2 / scale; + n2 = n2 % scale; + printf (",%03ld", n); + } +} + /////////////////////////////////////////////////////////////////////////////// // // // checkmesh() Test the mesh for topological consistency. // @@ -148,12 +174,12 @@ int tetgenmesh::checkmesh(int topoflag) // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::checkshells(/*int sub2tet*/) +int tetgenmesh::checkshells() { triface neightet, symtet; face shloop, spinsh, nextsh; face checkseg; - point pa, pb; //, *ppt; + point pa, pb; int bakcount; int horrors, i; @@ -181,10 +207,10 @@ int tetgenmesh::checkshells(/*int sub2tet*/) while ((nextsh.sh != NULL) && (nextsh.sh != shloop.sh)) { if (nextsh.sh[3] == NULL) { printf(" !! !! Wrong subface-subface connection (Dead subface).\n"); - printf(" First: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" First: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); - printf(" Second: x%lx (DEAD)\n", (unsigned long) nextsh.sh); + printf(" Second: x%lx (DEAD)\n", (uintptr_t) nextsh.sh); horrors++; break; } @@ -192,10 +218,10 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (!(((sorg(nextsh) == pa) && (sdest(nextsh) == pb)) || ((sorg(nextsh) == pb) && (sdest(nextsh) == pa)))) { printf(" !! !! Wrong subface-subface connection.\n"); - printf(" First: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" First: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); - printf(" Scond: x%lx (%d, %d, %d).\n", (unsigned long) nextsh.sh, + printf(" Scond: x%lx (%d, %d, %d).\n", (uintptr_t) nextsh.sh, pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), pointmark(sapex(nextsh))); horrors++; @@ -204,10 +230,10 @@ int tetgenmesh::checkshells(/*int sub2tet*/) // Check they should not have the same apex. if (sapex(nextsh) == sapex(spinsh)) { printf(" !! !! Existing two duplicated subfaces.\n"); - printf(" First: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" First: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); - printf(" Scond: x%lx (%d, %d, %d).\n", (unsigned long) nextsh.sh, + printf(" Scond: x%lx (%d, %d, %d).\n", (uintptr_t) nextsh.sh, pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), pointmark(sapex(nextsh))); horrors++; @@ -221,19 +247,19 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (checkseg.sh != NULL) { if (checkseg.sh[3] == NULL) { printf(" !! !! Wrong subface-subseg connection (Dead subseg).\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) shloop.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, pointmark(sorg(shloop)), pointmark(sdest(shloop)), pointmark(sapex(shloop))); - printf(" Sub: x%lx (Dead)\n", (unsigned long) checkseg.sh); + printf(" Sub: x%lx (Dead)\n", (uintptr_t) checkseg.sh); horrors++; } else { if (!(((sorg(checkseg) == pa) && (sdest(checkseg) == pb)) || ((sorg(checkseg) == pb) && (sdest(checkseg) == pa)))) { printf(" !! !! Wrong subface-subseg connection.\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) shloop.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, pointmark(sorg(shloop)), pointmark(sdest(shloop)), pointmark(sapex(shloop))); - printf(" Seg: x%lx (%d, %d).\n", (unsigned long) checkseg.sh, + printf(" Seg: x%lx (%d, %d).\n", (uintptr_t) checkseg.sh, pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); horrors++; } @@ -247,20 +273,20 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (neightet.tet != NULL) { if (neightet.tet[4] == NULL) { printf(" !! !! Wrong sub-to-tet connection (Dead tet)\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) shloop.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, pointmark(sorg(shloop)), pointmark(sdest(shloop)), pointmark(sapex(shloop))); - printf(" Tet: x%lx (DEAD)\n", (unsigned long) neightet.tet); + printf(" Tet: x%lx (DEAD)\n", (uintptr_t) neightet.tet); horrors++; } else { if (!((sorg(shloop) == org(neightet)) && (sdest(shloop) == dest(neightet)))) { printf(" !! !! Wrong sub-to-tet connection\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) shloop.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, pointmark(sorg(shloop)), pointmark(sdest(shloop)), pointmark(sapex(shloop))); printf(" Tet: x%lx (%d, %d, %d, %d).\n", - (unsigned long) neightet.tet, pointmark(org(neightet)), + (uintptr_t) neightet.tet, pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), pointmark(oppo(neightet))); horrors++; @@ -269,11 +295,11 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (!((sorg(spinsh) == org(neightet)) && (sdest(spinsh) == dest(neightet)))) { printf(" !! !! Wrong tet-sub connection.\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); printf(" Tet: x%lx (%d, %d, %d, %d).\n", - (unsigned long) neightet.tet, pointmark(org(neightet)), + (uintptr_t) neightet.tet, pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), pointmark(oppo(neightet))); horrors++; @@ -284,11 +310,11 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (!((sorg(spinsh) == org(symtet)) && (sdest(spinsh) == dest(symtet)))) { printf(" !! !! Wrong tet-sub connection.\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); printf(" Tet: x%lx (%d, %d, %d, %d).\n", - (unsigned long) symtet.tet, pointmark(org(symtet)), + (uintptr_t) symtet.tet, pointmark(org(symtet)), pointmark(dest(symtet)), pointmark(apex(symtet)), pointmark(oppo(symtet))); horrors++; @@ -343,8 +369,10 @@ int tetgenmesh::checksegments() face sseg, checkseg; point pa, pb; int miscount; + int t1ver; int horrors, i; + if (!b->quiet) { printf(" Checking tet->seg connections...\n"); } @@ -368,9 +396,9 @@ int tetgenmesh::checksegments() ((org(tetloop) == pb) && (dest(tetloop) == pa)))) { printf(" !! Wrong tet-seg connection.\n"); printf(" Tet: x%lx (%d, %d, %d, %d) - Seg: x%lx (%d, %d).\n", - (unsigned long) tetloop.tet, pointmark(org(tetloop)), + (uintptr_t) tetloop.tet, pointmark(org(tetloop)), pointmark(dest(tetloop)), pointmark(apex(tetloop)), - pointmark(oppo(tetloop)), (unsigned long) sseg.sh, + pointmark(oppo(tetloop)), (uintptr_t) sseg.sh, pointmark(pa), pointmark(pb)); horrors++; } else { @@ -381,11 +409,11 @@ int tetgenmesh::checksegments() if (checkseg.sh != sseg.sh) { printf(" !! Wrong tet->seg connection.\n"); printf(" Tet: x%lx (%d, %d, %d, %d) - ", - (unsigned long) neightet.tet, pointmark(org(neightet)), + (uintptr_t) neightet.tet, pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), pointmark(oppo(neightet))); if (checkseg.sh != NULL) { - printf("Seg x%lx (%d, %d).\n", (unsigned long) checkseg.sh, + printf("Seg x%lx (%d, %d).\n", (uintptr_t) checkseg.sh, pointmark(sorg(checkseg)),pointmark(sdest(checkseg))); } else { printf("Seg: NULL.\n"); @@ -405,9 +433,9 @@ int tetgenmesh::checksegments() ((org(neightet) == pb) && (dest(neightet) == pa)))) { printf(" !! Wrong seg->tet connection (Wrong edge).\n"); printf(" Tet: x%lx (%d, %d, %d, %d) - Seg: x%lx (%d, %d).\n", - (unsigned long) neightet.tet, pointmark(org(neightet)), + (uintptr_t) neightet.tet, pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), - pointmark(oppo(neightet)), (unsigned long) sseg.sh, + pointmark(oppo(neightet)), (uintptr_t) sseg.sh, pointmark(pa), pointmark(pb)); horrors++; } @@ -424,7 +452,7 @@ int tetgenmesh::checksegments() printf(" !! A marked edge: (%d, %d, %d, %d) -- x%lx %d.\n", pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), pointmark(oppo(neightet)), - (unsigned long) neightet.tet, neightet.ver); + (uintptr_t) neightet.tet, neightet.ver); // Check if all tets at the edge are marked. spintet = neightet; while (1) { @@ -433,7 +461,7 @@ int tetgenmesh::checksegments() printf(" !! !! An unmarked edge (%d, %d, %d, %d) -- x%lx %d.\n", pointmark(org(spintet)), pointmark(dest(spintet)), pointmark(apex(spintet)), pointmark(oppo(spintet)), - (unsigned long) spintet.tet, spintet.ver); + (uintptr_t) spintet.tet, spintet.ver); horrors++; } if (spintet.tet == neightet.tet) break; @@ -466,7 +494,7 @@ int tetgenmesh::checksegments() // sesymself(spinsh); // printf(" !! Wrong ori at subface (%d, %d, %d) -- x%lx %d\n", // pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), - // pointmark(sapex(spinsh)), (unsigned long) spinsh.sh, + // pointmark(sapex(spinsh)), (uintptr_t) spinsh.sh, // spinsh.shver); // horrors++; //} @@ -479,7 +507,7 @@ int tetgenmesh::checksegments() printf(" !! !! No seg at tet (%d, %d, %d, %d) -- x%lx %d\n", pointmark(org(spintet)), pointmark(dest(spintet)), pointmark(apex(spintet)), pointmark(oppo(spintet)), - (unsigned long) spintet.tet, spintet.ver); + (uintptr_t) spintet.tet, spintet.ver); horrors++; } if (checkseg.sh != sseg.sh) { @@ -498,7 +526,7 @@ int tetgenmesh::checksegments() } else { printf(" !! Wrong seg-subface (%d, %d, %d) -- x%lx %d connect\n", pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), - pointmark(sapex(spinsh)), (unsigned long) spinsh.sh, + pointmark(sapex(spinsh)), (uintptr_t) spinsh.sh, spinsh.shver); horrors++; break; @@ -779,6 +807,7 @@ int tetgenmesh::checkconforming(int flag) REAL cent[3], radius, dist, diff, rd, len; bool enq; int encsubsegs, encsubfaces; + int t1ver; int i; REAL A[4][4], rhs[4], D; @@ -934,10 +963,10 @@ void tetgenmesh::qualitystatistics() REAL tetaspect, tetradius; REAL smalldiangle, bigdiangle; REAL smallfaangle, bigfaangle; - int radiustable[12]; - int aspecttable[16]; - int dihedangletable[18]; - int faceangletable[18]; + unsigned long radiustable[12]; + unsigned long aspecttable[16]; + unsigned long dihedangletable[18]; + unsigned long faceangletable[18]; int indx[4]; int radiusindex; int aspectindex; @@ -965,10 +994,10 @@ void tetgenmesh::qualitystatistics() aspectratiotable[8] = 25.0; aspectratiotable[9] = 50.0; aspectratiotable[10] = 100.0; aspectratiotable[11] = 0.0; - for (i = 0; i < 12; i++) radiustable[i] = 0; - for (i = 0; i < 12; i++) aspecttable[i] = 0; - for (i = 0; i < 18; i++) dihedangletable[i] = 0; - for (i = 0; i < 18; i++) faceangletable[i] = 0; + for (i = 0; i < 12; i++) radiustable[i] = 0l; + for (i = 0; i < 12; i++) aspecttable[i] = 0l; + for (i = 0; i < 18; i++) dihedangletable[i] = 0l; + for (i = 0; i < 18; i++) faceangletable[i] = 0l; minaltitude = xmax - xmin + ymax - ymin + zmax - zmin; minaltitude = minaltitude * minaltitude; @@ -982,16 +1011,26 @@ void tetgenmesh::qualitystatistics() biggestdiangle = biggestfaangle = 0.0; + int attrnum = numelemattrib - 1; + // Loop all elements, calculate quality parameters for each element. tetrahedrons->traversalinit(); tetloop.tet = tetrahedrontraverse(); while (tetloop.tet != (tetrahedron *) NULL) { + if (b->convex) { + // Skip tets in the exterior. + if (elemattribute(tetloop.tet, attrnum) == -1.0) { + tetloop.tet = tetrahedrontraverse(); + continue; + } + } + // Get four vertices: p0, p1, p2, p3. for (i = 0; i < 4; i++) p[i] = (point) tetloop.tet[4 + i]; // Get the tet volume. - tetvol = orient3d(p[1], p[0], p[2], p[3]) / 6.0; + tetvol = orient3dfast(p[1], p[0], p[2], p[3]) / 6.0; total_tet_vol += tetvol; total_tetprism_vol += tetprismvol(p[0], p[1], p[2], p[3]); @@ -1011,7 +1050,7 @@ void tetgenmesh::qualitystatistics() for (i = 0; i < 3; i++) V[4][i] = p[2][i] - p[1][i]; // V[4]: p1->p2. for (i = 0; i < 3; i++) V[5][i] = p[0][i] - p[2][i]; // V[5]: p2->p0. - // Get the squares of the edge lengthes. + // Get the squares of the edge lengths. for (i = 0; i < 6; i++) edgelength[i] = dot(V[i], V[i]); // Calculate the longest and shortest edge length. @@ -1153,7 +1192,7 @@ void tetgenmesh::qualitystatistics() - // Calulate the largest and smallest face angles. + // Calculate the largest and smallest face angles. for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { fsym(tetloop, neightet); // Only do the calulation once for a face. @@ -1240,16 +1279,16 @@ void tetgenmesh::qualitystatistics() smallestdiangle, sbuf); printf(" Aspect ratio histogram:\n"); - printf(" < %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", + printf(" < %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", aspectratiotable[0], aspecttable[0], aspectratiotable[5], aspectratiotable[6], aspecttable[6]); for (i = 1; i < 5; i++) { - printf(" %6.6g - %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", + printf(" %6.6g - %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", aspectratiotable[i - 1], aspectratiotable[i], aspecttable[i], aspectratiotable[i + 5], aspectratiotable[i + 6], aspecttable[i + 6]); } - printf(" %6.6g - %-6.6g : %8d | %6.6g - : %8d\n", + printf(" %6.6g - %-6.6g : %8ld | %6.6g - : %8ld\n", aspectratiotable[4], aspectratiotable[5], aspecttable[5], aspectratiotable[10], aspecttable[11]); printf(" (A tetrahedron's aspect ratio is its longest edge length"); @@ -1258,7 +1297,7 @@ void tetgenmesh::qualitystatistics() printf(" Face angle histogram:\n"); for (i = 0; i < 9; i++) { - printf(" %3d - %3d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %3d degrees: %8ld | %3d - %3d degrees: %8ld\n", i * 10, i * 10 + 10, faceangletable[i], i * 10 + 90, i * 10 + 100, faceangletable[i + 9]); } @@ -1270,20 +1309,20 @@ void tetgenmesh::qualitystatistics() printf(" Dihedral angle histogram:\n"); // Print the three two rows: - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", 0, 5, dihedangletable[0], 80, 110, dihedangletable[9]); - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", 5, 10, dihedangletable[1], 110, 120, dihedangletable[10]); // Print the third to seventh rows. for (i = 2; i < 7; i++) { - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", (i - 1) * 10, (i - 1) * 10 + 10, dihedangletable[i], (i - 1) * 10 + 110, (i - 1) * 10 + 120, dihedangletable[i + 9]); } // Print the last two rows. - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", 60, 70, dihedangletable[7], 170, 175, dihedangletable[16]); - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", 70, 80, dihedangletable[8], 175, 180, dihedangletable[17]); if (minfacetdihed != PI) { printf(" Minimum input dihedral angle is %g (degree).\n", @@ -1295,6 +1334,96 @@ void tetgenmesh::qualitystatistics() } +/////////////////////////////////////////////////////////////////////////////// +// // +// memorystatistics() Report the memory usage. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::memorystatistics() +{ + printf("Memory usage statistics:\n\n"); + + // Count the number of blocks of tetrahedra. + int tetblocks = 0; + tetrahedrons->pathblock = tetrahedrons->firstblock; + while (tetrahedrons->pathblock != NULL) { + tetblocks++; + tetrahedrons->pathblock = (void **) *(tetrahedrons->pathblock); + } + + // Calculate the total memory (in bytes) used by storing meshes. + unsigned long totalmeshmemory = 0l, totalt2shmemory = 0l; + totalmeshmemory = points->maxitems * points->itembytes + + tetrahedrons->maxitems * tetrahedrons->itembytes; + if (b->plc || b->refine) { + totalmeshmemory += (subfaces->maxitems * subfaces->itembytes + + subsegs->maxitems * subsegs->itembytes); + totalt2shmemory = (tet2subpool->maxitems * tet2subpool->itembytes + + tet2segpool->maxitems * tet2segpool->itembytes); + } + + unsigned long totalalgomemory = 0l; + totalalgomemory = cavetetlist->totalmemory + cavebdrylist->totalmemory + + caveoldtetlist->totalmemory + + flippool->maxitems * flippool->itembytes; + if (b->plc || b->refine) { + totalalgomemory += (subsegstack->totalmemory + subfacstack->totalmemory + + subvertstack->totalmemory + + caveshlist->totalmemory + caveshbdlist->totalmemory + + cavesegshlist->totalmemory + + cavetetshlist->totalmemory + + cavetetseglist->totalmemory + + caveencshlist->totalmemory + + caveencseglist->totalmemory + + cavetetvertlist->totalmemory + + unflipqueue->totalmemory); + } + + printf(" Maximum number of tetrahedra: %ld\n", tetrahedrons->maxitems); + printf(" Maximum number of tet blocks (blocksize = %d): %d\n", + b->tetrahedraperblock, tetblocks); + /* + if (b->plc || b->refine) { + printf(" Approximate memory for tetrahedral mesh (bytes): %ld\n", + totalmeshmemory); + + printf(" Approximate memory for extra pointers (bytes): %ld\n", + totalt2shmemory); + } else { + printf(" Approximate memory for tetrahedralization (bytes): %ld\n", + totalmeshmemory); + } + printf(" Approximate memory for algorithms (bytes): %ld\n", + totalalgomemory); + printf(" Approximate memory for working arrays (bytes): %ld\n", + totalworkmemory); + printf(" Approximate total used memory (bytes): %ld\n", + totalmeshmemory + totalt2shmemory + totalalgomemory + + totalworkmemory); + */ + if (b->plc || b->refine) { + printf(" Approximate memory for tetrahedral mesh (bytes): "); + printfcomma(totalmeshmemory); printf("\n"); + + printf(" Approximate memory for extra pointers (bytes): "); + printfcomma(totalt2shmemory); printf("\n"); + } else { + printf(" Approximate memory for tetrahedralization (bytes): "); + printfcomma(totalmeshmemory); printf("\n"); + } + printf(" Approximate memory for algorithms (bytes): "); + printfcomma(totalalgomemory); printf("\n"); + printf(" Approximate memory for working arrays (bytes): "); + printfcomma(totalworkmemory); printf("\n"); + printf(" Approximate total used memory (bytes): "); + printfcomma(totalmeshmemory + totalt2shmemory + totalalgomemory + + totalworkmemory); + printf("\n"); + + printf("\n"); +} + /////////////////////////////////////////////////////////////////////////////// // // // statistics() Print all sorts of cool facts. // @@ -1327,23 +1456,34 @@ void tetgenmesh::statistics() } printf(" Mesh tetrahedra: %ld\n", tetnumber); printf(" Mesh faces: %ld\n", facenumber); - printf(" Mesh edges: %ld\n", meshedges); + if (meshedges > 0l) { + printf(" Mesh edges: %ld\n", meshedges); + } else { + if (!nonconvex) { + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + meshedges = vsize + facenumber - tetnumber - 1; + printf(" Mesh edges: %ld\n", meshedges); + } + } if (b->plc || b->refine) { - printf(" Mesh boundary faces: %ld\n", subfaces->items); - printf(" Mesh boundary edges: %ld\n", subsegs->items); - if (st_segref_count > 0l) { - printf(" Steiner points on boundary edges: %ld\n", st_segref_count); + printf(" Mesh faces on facets: %ld\n", subfaces->items); + printf(" Mesh edges on segments: %ld\n", subsegs->items); + if (st_volref_count > 0l) { + printf(" Steiner points inside domain: %ld\n", st_volref_count); } if (st_facref_count > 0l) { - printf(" Steiner points on boundary faces: %ld\n", st_facref_count); + printf(" Steiner points on facets: %ld\n", st_facref_count); } - if (st_volref_count > 0l) { - printf(" Steiner points in mesh domain: %ld\n", st_volref_count); + if (st_segref_count > 0l) { + printf(" Steiner points on segments: %ld\n", st_segref_count); } } else { printf(" Convex hull faces: %ld\n", hullsize); - printf(" Convex hull edges: %ld\n", meshhulledges); + if (meshhulledges > 0l) { + printf(" Convex hull edges: %ld\n", meshhulledges); + } } if (b->weighted) { // -w option printf(" Skipped non-regular points: %ld\n", nonregularcount); @@ -1357,6 +1497,9 @@ void tetgenmesh::statistics() qualitystatistics(); } } + if (tetrahedrons->items > 0l) { + memorystatistics(); + } } } diff --git a/external/tetgen/src/optimize.cxx b/external/tetgen/src/optimize.cxx index 8236394f9..f66502615 100644 --- a/external/tetgen/src/optimize.cxx +++ b/external/tetgen/src/optimize.cxx @@ -3,6 +3,267 @@ //// //// //// //// +/////////////////////////////////////////////////////////////////////////////// +// // +// lawsonflip3d() A three-dimensional Lawson's algorithm. // +// // +/////////////////////////////////////////////////////////////////////////////// + +long tetgenmesh::lawsonflip3d(flipconstraints *fc) +{ + triface fliptets[5], neightet, hulltet; + face checksh, casingout; + badface *popface, *bface; + point pd, pe, *pts; + REAL sign, ori; + long flipcount, totalcount = 0l; + long sliver_peels = 0l; + int t1ver; + int i; + + + while (1) { + + if (b->verbose > 2) { + printf(" Lawson flip %ld faces.\n", flippool->items); + } + flipcount = 0l; + + while (flipstack != (badface *) NULL) { + // Pop a face from the stack. + popface = flipstack; + fliptets[0] = popface->tt; + flipstack = flipstack->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // Skip it if it is a dead tet (destroyed by previous flips). + if (isdeadtet(fliptets[0])) continue; + // Skip it if it is not the same tet as we saved. + if (!facemarked(fliptets[0])) continue; + + unmarkface(fliptets[0]); + + if (ishulltet(fliptets[0])) continue; + + fsym(fliptets[0], fliptets[1]); + if (ishulltet(fliptets[1])) { + if (nonconvex) { + // Check if 'fliptets[0]' it is a hull sliver. + tspivot(fliptets[0], checksh); + for (i = 0; i < 3; i++) { + if (!isshsubseg(checksh)) { + spivot(checksh, casingout); + //assert(casingout.sh != NULL); + if (sorg(checksh) != sdest(casingout)) sesymself(casingout); + stpivot(casingout, neightet); + if (neightet.tet == fliptets[0].tet) { + // Found a hull sliver 'neightet'. Let it be [e,d,a,b], where + // [e,d,a] and [d,e,b] are hull faces. + edestoppo(neightet, hulltet); // [a,b,e,d] + fsymself(hulltet); // [b,a,e,#] + if (oppo(hulltet) == dummypoint) { + pe = org(neightet); + if ((pointtype(pe) == FREEFACETVERTEX) || + (pointtype(pe) == FREESEGVERTEX)) { + removevertexbyflips(pe); + } + } else { + eorgoppo(neightet, hulltet); // [b,a,d,e] + fsymself(hulltet); // [a,b,d,#] + if (oppo(hulltet) == dummypoint) { + pd = dest(neightet); + if ((pointtype(pd) == FREEFACETVERTEX) || + (pointtype(pd) == FREESEGVERTEX)) { + removevertexbyflips(pd); + } + } else { + // Perform a 3-to-2 flip to remove the sliver. + fliptets[0] = neightet; // [e,d,a,b] + fnext(fliptets[0], fliptets[1]); // [e,d,b,c] + fnext(fliptets[1], fliptets[2]); // [e,d,c,a] + flip32(fliptets, 1, fc); + // Update counters. + flip32count--; + flip22count--; + sliver_peels++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + } + } + break; + } // if (neightet.tet == fliptets[0].tet) + } // if (!isshsubseg(checksh)) + senextself(checksh); + } // i + } // if (nonconvex) + continue; + } + + if (checksubfaceflag) { + // Do not flip if it is a subface. + if (issubface(fliptets[0])) continue; + } + + // Test whether the face is locally Delaunay or not. + pts = (point *) fliptets[1].tet; + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], oppo(fliptets[0])); + + if (sign < 0) { + // A non-Delaunay face. Try to flip it. + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); + + // Check the convexity of its three edges. Stop checking either a + // locally non-convex edge (ori < 0) or a flat edge (ori = 0) is + // encountered, and 'fliptet' represents that edge. + for (i = 0; i < 3; i++) { + ori = orient3d(org(fliptets[0]), dest(fliptets[0]), pd, pe); + if (ori <= 0) break; + enextself(fliptets[0]); + } + + if (ori > 0) { + // A 2-to-3 flip is found. + // [0] [a,b,c,d], + // [1] [b,a,c,e]. no dummypoint. + flip23(fliptets, 0, fc); + flipcount++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } else { // ori <= 0 + // The edge ('fliptets[0]' = [a',b',c',d]) is non-convex or flat, + // where the edge [a',b'] is one of [a,b], [b,c], and [c,a]. + if (checksubsegflag) { + // Do not flip if it is a segment. + if (issubseg(fliptets[0])) continue; + } + // Check if there are three or four tets sharing at this edge. + esymself(fliptets[0]); // [b,a,d,c] + for (i = 0; i < 3; i++) { + fnext(fliptets[i], fliptets[i+1]); + } + if (fliptets[3].tet == fliptets[0].tet) { + // A 3-to-2 flip is found. (No hull tet.) + flip32(fliptets, 0, fc); + flipcount++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } else { + // There are more than 3 tets at this edge. + fnext(fliptets[3], fliptets[4]); + if (fliptets[4].tet == fliptets[0].tet) { + // There are exactly 4 tets at this edge. + if (nonconvex) { + if (apex(fliptets[3]) == dummypoint) { + // This edge is locally non-convex on the hull. + // It can be removed by a 4-to-4 flip. + ori = 0; + } + } // if (nonconvex) + if (ori == 0) { + // A 4-to-4 flip is found. (Two hull tets may be involved.) + // Current tets in 'fliptets': + // [0] [b,a,d,c] (d may be newpt) + // [1] [b,a,c,e] + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + esymself(fliptets[0]); // [a,b,c,d] + // A 2-to-3 flip replaces face [a,b,c] by edge [e,d]. + // This creates a degenerate tet [e,d,a,b] (tmpfliptets[0]). + // It will be removed by the followed 3-to-2 flip. + flip23(fliptets, 0, fc); // No hull tet. + fnext(fliptets[3], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Current tets in 'fliptets': + // [0] [...] + // [1] [b,a,d,e] (degenerated, d may be new point). + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + // A 3-to-2 flip replaces edge [b,a] by face [d,e,f]. + // Hull tets may be involved (f may be dummypoint). + flip32(&(fliptets[1]), (apex(fliptets[3]) == dummypoint), fc); + flipcount++; + flip23count--; + flip32count--; + flip44count++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } // if (ori == 0) + } + } + } // if (ori <= 0) + + // This non-Delaunay face is unflippable. Save it. + unflipqueue->newindex((void **) &bface); + bface->tt = fliptets[0]; + bface->forg = org(fliptets[0]); + bface->fdest = dest(fliptets[0]); + bface->fapex = apex(fliptets[0]); + } // if (sign < 0) + } // while (flipstack) + + if (b->verbose > 2) { + if (flipcount > 0) { + printf(" Performed %ld flips.\n", flipcount); + } + } + // Accumulate the counter of flips. + totalcount += flipcount; + + assert(flippool->items == 0l); + // Return if no unflippable faces left. + if (unflipqueue->objects == 0l) break; + // Return if no flip has been performed. + if (flipcount == 0l) break; + + // Try to flip the unflippable faces. + for (i = 0; i < unflipqueue->objects; i++) { + bface = (badface *) fastlookup(unflipqueue, i); + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + flippush(flipstack, &(bface->tt)); + } + } + unflipqueue->restart(); + + } // while (1) + + if (b->verbose > 2) { + if (totalcount > 0) { + printf(" Performed %ld flips.\n", totalcount); + } + if (sliver_peels > 0) { + printf(" Removed %ld hull slivers.\n", sliver_peels); + } + if (unflipqueue->objects > 0l) { + printf(" %ld unflippable edges remained.\n", unflipqueue->objects); + } + } + + return totalcount + sliver_peels; +} + /////////////////////////////////////////////////////////////////////////////// // // // recoverdelaunay() Recovery the locally Delaunay property. // @@ -12,8 +273,8 @@ void tetgenmesh::recoverdelaunay() { arraypool *flipqueue, *nextflipqueue, *swapqueue; - badface *bface, *parybface; triface tetloop, neightet, *parytet; + badface *bface, *parybface; point *ppt; flipconstraints fc; int i, j; @@ -22,28 +283,16 @@ void tetgenmesh::recoverdelaunay() printf("Recovering Delaunayness...\n"); } - //if (b->verbose) { - // printf(" max_flipstarsize = %d.\n", b->optmaxflipstarsize); - // printf(" max_fliplinklevel = %d.\n", b->delmaxfliplevel); - //} - - calc_tetprism_vol = 1; tetprism_vol_sum = 0.0; // Initialize it. - assert(flipstack == NULL); - assert(unflipqueue->objects == 0l); - // Put all interior faces of the mesh into 'flipstack'. tetrahedrons->traversalinit(); tetloop.tet = tetrahedrontraverse(); while (tetloop.tet != NULL) { for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { - // Avoid queue a face twice. - fsym(tetloop, neightet); - if (!ishulltet(neightet)) { - if (!facemarked(neightet)) { - flippush(flipstack, &tetloop); - } + decode(tetloop.tet[tetloop.ver], neightet); + if (!facemarked(neightet)) { + flippush(flipstack, &tetloop); } } ppt = (point *) &(tetloop.tet[4]); @@ -51,6 +300,10 @@ void tetgenmesh::recoverdelaunay() tetloop.tet = tetrahedrontraverse(); } + // Calulate a relatively lower bound for small improvement. + // Used to avoid rounding error in volume calculation. + fc.bak_tetprism_vol = tetprism_vol_sum * b->epsilon * 1e-3; + if (b->verbose) { printf(" Initial obj = %.17g\n", tetprism_vol_sum); } @@ -58,37 +311,35 @@ void tetgenmesh::recoverdelaunay() if (b->verbose > 1) { printf(" Recover Delaunay [Lawson] : %ld\n", flippool->items); } - assert(unflipqueue->objects == 0l); // First only use the basic Lawson's flip. - lawsonflip3d(NULL, 4, 0, 0, 1); + fc.remove_ndelaunay_edge = 1; + fc.enqflag = 2; + + lawsonflip3d(&fc); if (b->verbose > 1) { - printf(" New obj = %.17g\n", tetprism_vol_sum); + printf(" obj (after Lawson) = %.17g\n", tetprism_vol_sum); } if (unflipqueue->objects == 0l) { - // The mesh is Delaunay. - return; + return; // The mesh is Delaunay. } - // Set the common options. - fc.remove_ndelaunay_edge = 1; fc.unflip = 1; // Unflip if the edge is not flipped. - fc.collectnewtets = 1; + fc.collectnewtets = 1; // new tets are returned in 'cavetetlist'. + fc.enqflag = 0; - autofliplinklevel = 1; // Init value. - b->fliplinklevel = -1; + autofliplinklevel = 1; // Init level. + b->fliplinklevel = -1; // No fixed level. // For efficiency reason, we limit the maximium size of the edge star. - // 'b->optmaxflipstarsize' is set by -OOOOO (5 Os), default is 10. int bakmaxflipstarsize = b->flipstarsize; - b->flipstarsize = 10; //b->optmaxflipstarsize; + b->flipstarsize = 10; // default flipqueue = new arraypool(sizeof(badface), 10); nextflipqueue = new arraypool(sizeof(badface), 10); - - + // Swap the two flip queues. swapqueue = flipqueue; flipqueue = unflipqueue; @@ -96,60 +347,61 @@ void tetgenmesh::recoverdelaunay() while (flipqueue->objects > 0l) { - while (flipqueue->objects > 0l) { + if (b->verbose > 1) { + printf(" Recover Delaunay [level = %2d] #: %ld.\n", + autofliplinklevel, flipqueue->objects); + } - if (b->verbose > 1) { - printf(" Recover Delaunay [level = %2d] #: %ld.\n", - autofliplinklevel, flipqueue->objects); - } - - for (i = 0; i < flipqueue->objects; i++) { - bface = (badface *) fastlookup(flipqueue, i); - if (getedge(bface->forg, bface->fdest, &bface->tt)) { - // Remember the the objective value (volume of all tetprisms). - fc.bak_tetprism_vol = tetprism_vol_sum; - if (removeedgebyflips(&(bface->tt), &fc) == 2) { - if (b->verbose > 2) { - printf(" Decreased quantity: %.17g.\n", - fc.bak_tetprism_vol - tetprism_vol_sum); + for (i = 0; i < flipqueue->objects; i++) { + bface = (badface *) fastlookup(flipqueue, i); + if (getedge(bface->forg, bface->fdest, &bface->tt)) { + if (removeedgebyflips(&(bface->tt), &fc) == 2) { + tetprism_vol_sum += fc.tetprism_vol_sum; + fc.tetprism_vol_sum = 0.0; // Clear it. + // Queue new faces for flips. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + // A queued new tet may be dead. + if (!isdeadtet(*parytet)) { + for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { + // Avoid queue a face twice. + decode(parytet->tet[parytet->ver], neightet); + if (!facemarked(neightet)) { + flippush(flipstack, parytet); + } + } // parytet->ver } - // Queue new faces for flips. - for (j = 0; j < cavetetlist->objects; j++) { - parytet = (triface *) fastlookup(cavetetlist, j); - // A queued new tet may be dead. - if (!isdeadtet(*parytet)) { - for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { - // Avoid queue a face twice. - fsym(*parytet, neightet); - if (!facemarked(neightet)) { - flippush(flipstack, parytet); - } - } // parytet->ver - } - } // j - cavetetlist->restart(); - // Remove locally non-Delaunay faces. New non-Delaunay edges - // may be found. They are saved in 'unflipqueue'. - lawsonflip3d(NULL, 4, 0, 0, 1); - } else { - // Unable to remove this edge. Save it. - nextflipqueue->newindex((void **) &parybface); + } // j + cavetetlist->restart(); + // Remove locally non-Delaunay faces. New non-Delaunay edges + // may be found. They are saved in 'unflipqueue'. + fc.enqflag = 2; + lawsonflip3d(&fc); + fc.enqflag = 0; + // There may be unflipable faces. Add them in flipqueue. + for (j = 0; j < unflipqueue->objects; j++) { + bface = (badface *) fastlookup(unflipqueue, j); + flipqueue->newindex((void **) &parybface); *parybface = *bface; } + unflipqueue->restart(); + } else { + // Unable to remove this edge. Save it. + nextflipqueue->newindex((void **) &parybface); + *parybface = *bface; + // Normally, it should be zero. + //assert(fc.tetprism_vol_sum == 0.0); + // However, due to rounding errors, a tiny value may appear. + fc.tetprism_vol_sum = 0.0; } - } // i - - flipqueue->restart(); - - // Swap the two flip queues. - swapqueue = flipqueue; - flipqueue = unflipqueue; - unflipqueue = swapqueue; - } // while (flipqueue->objects > 0l) + } + } // i if (b->verbose > 1) { - printf(" New obj = %.17g.\n", tetprism_vol_sum); + printf(" obj (after level %d) = %.17g.\n", autofliplinklevel, + tetprism_vol_sum); } + flipqueue->restart(); // Swap the two flip queues. swapqueue = flipqueue; @@ -157,14 +409,13 @@ void tetgenmesh::recoverdelaunay() nextflipqueue = swapqueue; if (flipqueue->objects > 0l) { - // 'b->delmaxfliplevel' is set by -OOOO, default is 1. + // default 'b->delmaxfliplevel' is 1. if (autofliplinklevel >= b->delmaxfliplevel) { // For efficiency reason, we do not search too far. break; } autofliplinklevel+=b->fliplinklevelinc; } - } // while (flipqueue->objects > 0l) if (flipqueue->objects > 0l) { @@ -173,16 +424,13 @@ void tetgenmesh::recoverdelaunay() } } - b->flipstarsize = bakmaxflipstarsize; - - delete nextflipqueue; - delete flipqueue; - - calc_tetprism_vol = 0; - if (b->verbose) { - printf(" Final obj = %.17g\n", tetprism_vol_sum); + printf(" Final obj = %.17g\n", tetprism_vol_sum); } + + b->flipstarsize = bakmaxflipstarsize; + delete flipqueue; + delete nextflipqueue; } /////////////////////////////////////////////////////////////////////////////// @@ -195,11 +443,7 @@ int tetgenmesh::gettetrahedron(point pa, point pb, point pc, point pd, triface *searchtet) { triface spintet; - - if (b->verbose > 2) { - printf(" Get tet [%d,%d,%d,%d].\n", pointmark(pa), pointmark(pb), - pointmark(pc), pointmark(pd)); - } + int t1ver; if (getedge(pa, pb, searchtet)) { spintet = *searchtet; @@ -261,6 +505,7 @@ long tetgenmesh::improvequalitybyflips() fc.remove_large_angle = 1; fc.unflip = 1; fc.collectnewtets = 1; + fc.checkflipeligibility = 1; totalremcount = 0l; @@ -290,34 +535,30 @@ long tetgenmesh::improvequalitybyflips() // Here we simply re-compute them. Slow!!. ppt = (point *) & (bface->tt.tet[4]); tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, - &maxdd, NULL); + &bface->key, NULL); bface->forg = ppt[0]; bface->fdest = ppt[1]; bface->fapex = ppt[2]; bface->foppo = ppt[3]; bface->tt.ver = 11; } + if (bface->key == 0) { + // Re-comput the quality values. Due to smoothing operations. + ppt = (point *) & (bface->tt.tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, + &bface->key, NULL); + } cosdd = bface->cent; remflag = 0; for (i = 0; (i < 6) && !remflag; i++) { if (cosdd[i] < cosmaxdihed) { // Found a large dihedral angle. bface->tt.ver = edge2ver[i]; // Go to the edge. - if (b->verbose > 2) { - printf(" Found a large angle [%d,%d,%d,%d] (%g).\n", - pointmark(org(bface->tt)), pointmark(dest(bface->tt)), - pointmark(apex(bface->tt)), pointmark(oppo(bface->tt)), - acos(cosdd[i]) / PI * 180.0); - } fc.cosdihed_in = cosdd[i]; fc.cosdihed_out = 0.0; // 90 degree. n = removeedgebyflips(&(bface->tt), &fc); if (n == 2) { // Edge is flipped. - if (b->verbose > 2) { - printf(" Reduced a large angle to %g degree.\n", - acos(fc.cosdihed_out) / PI * 180.0); - } remflag = 1; if (fc.cosdihed_out < cosmaxdihed) { // Queue new bad tets for further improvements. @@ -325,8 +566,6 @@ long tetgenmesh::improvequalitybyflips() parytet = (triface *) fastlookup(cavetetlist, j); if (!isdeadtet(*parytet)) { ppt = (point *) & (parytet->tet[4]); - //if (!marktest2ed(*parytet)) { - assert(!marktest2ed(*parytet)); // SELF_CHECK // Do not test a hull tet. if (ppt[3] != dummypoint) { tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], ncosdd, @@ -345,8 +584,7 @@ long tetgenmesh::improvequalitybyflips() parybface->cent[n] = ncosdd[n]; } } - } // if (ppt[3] != dummypoint) { - //} + } // if (ppt[3] != dummypoint) } } // j } // if (fc.cosdihed_out < cosmaxdihed) @@ -418,8 +656,8 @@ long tetgenmesh::improvequalitybyflips() // has two orientations, ccw or cw, with respect to 'p'. 'ccw' indicates // // the orientation is ccw (1) or not (0). // // // -// 'of' is a structure contains the parameters of the objective function. It // -// is needed by the evaluation of the function value. // +// 'opm' is a structure contains the parameters of the objective function. // +// It is needed by the evaluation of the function value. // // // // The return value indicates weather the point is smoothed or not. // // // @@ -440,16 +678,6 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, int numdirs, iter; int i, j, k; - if (b->verbose > 2) { - printf(" Smooth a point: %ld faces.\n", linkfacelist->objects); - if (opm->min_max_dihedangle) { - printf(" Init value = %g (degree).\n", - acos(opm->initval - 1.0) / PI * 180.0); - } else { - printf(" Init value = %g.\n", opm->initval); - } - } - // Decide the number of moving directions. numdirs = (int) linkfacelist->objects; if (numdirs > opm->numofsearchdirs) { @@ -502,7 +730,8 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, if (ori < 0.0) { // Calcuate the objective function value. if (opm->max_min_volume) { - val = -ori; + //val = -ori; + val = - orient3dfast(pa, pb, pc, nextpt); } else if (opm->max_min_aspectratio) { val = tetaspectratio(pa, pb, pc, nextpt); } else if (opm->min_max_dihedangle) { @@ -515,8 +744,10 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, } } else { // ori >= 0.0; // An invalid new tet. + // This may happen if the mesh contains inverted elements. if (opm->max_min_volume) { - val = -ori; + //val = -ori; + val = - orient3dfast(pa, pb, pc, nextpt); } else { // Discard this point. break; // j @@ -536,7 +767,6 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, } // j if (j == linkfacelist->objects) { // The function value has been improved. - assert(minval > opm->imprval); opm->imprval = minval; // Save the new location of the point. for (j = 0; j < 3; j++) bestpt[j] = nextpt[j]; @@ -582,60 +812,16 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, } // while (1) if (iter > 0) { - // The point has been smooothed. - opm->smthiter = iter; // Remember the number of iterations. - if (b->verbose > 2) { - printf(" Smoothed: %d iterations.\n", iter); - if (opm->min_max_dihedangle) { - printf(" Fina value = %g (degree).\n", - acos(opm->imprval - 1.0) / PI * 180.0); - } else { - printf(" Fina value = %g.\n", opm->imprval); - } - } + // The point has been smoothed. + opm->smthiter = iter; // Remember the number of iterations. // The point has been smoothed. Update it to its new position. for (i = 0; i < 3; i++) smtpt[i] = startpt[i]; - - if (opm->flipflag) { - // Push all affected faces into 'flipstack'. - triface starttet, neightet; - for (i = 0; i < linkfacelist->objects; i++) { - parytet = (triface *) fastlookup(linkfacelist, i); - starttet = *parytet; - for (starttet.ver = 0; starttet.ver < 4; starttet.ver++) { - fsym(starttet, neightet); - if (!infected(neightet)) { - flippush(flipstack, &starttet); - } - } - infect(*parytet); - } - for (i = 0; i < linkfacelist->objects; i++) { - parytet = (triface *) fastlookup(linkfacelist, i); - uninfect(*parytet); - } - } else if (opm->checkencflag) { - // Push all affected tets into pool. - badface *bface; - for (i = 0; i < linkfacelist->objects; i++) { - parytet = (triface *) fastlookup(linkfacelist, i); - if (!marktest2ed(*parytet)) { - marktest2(*parytet); // Only queue it once. - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = *parytet; - bface->forg = org(bface->tt); - } - } - } - } else { - if (b->verbose > 2) { - printf(" Not smoothed.\n"); - } } return iter; } + /////////////////////////////////////////////////////////////////////////////// // // // improvequalitysmoothing() Improve mesh quality by smoothing. // @@ -645,31 +831,30 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, long tetgenmesh::improvequalitybysmoothing(optparameters *opm) { arraypool *flipqueue, *swapqueue; + triface *parytet; badface *bface, *parybface; point *ppt; long totalsmtcount, smtcount; int smtflag; - int iter, i, k; + int iter, i, j, k; //assert(unflipqueue->objects > 0l); flipqueue = new arraypool(sizeof(badface), 10); - totalsmtcount = 0l; - // Swap the two flip queues. swapqueue = flipqueue; flipqueue = unflipqueue; unflipqueue = swapqueue; + totalsmtcount = 0l; iter = 0; while (flipqueue->objects > 0l) { smtcount = 0l; - //while (flipqueue->objects > 0l) { if (b->verbose > 1) { - printf(" Improving mesh qualiy by smoothing [%d]#: %ld.\n", + printf(" Improving mesh quality by smoothing [%d]#: %ld.\n", iter, flipqueue->objects); } @@ -677,6 +862,7 @@ long tetgenmesh::improvequalitybysmoothing(optparameters *opm) bface = (badface *) fastlookup(flipqueue, k); if (gettetrahedron(bface->forg, bface->fdest, bface->fapex, bface->foppo, &bface->tt)) { + // Operate on it if it is not in 'unflipqueue'. if (!marktested(bface->tt)) { // Here we simply re-compute the quality. Since other smoothing // operation may have moved the vertices of this tet. @@ -686,10 +872,7 @@ long tetgenmesh::improvequalitybysmoothing(optparameters *opm) if (bface->key < cossmtdihed) { // if (maxdd < cosslidihed) { // It is a sliver. Try to smooth its vertices. smtflag = 0; - //if (opm->min_max_dihedangle) { - opm->initval = bface->key + 1.0; - //opm->checkencflag = 4; // Queue affected tets. - //} + opm->initval = bface->key + 1.0; for (i = 0; (i < 4) && !smtflag; i++) { if (pointtype(ppt[i]) == FREEVOLVERTEX) { getvertexstar(1, ppt[i], cavetetlist, NULL, NULL); @@ -702,78 +885,61 @@ long tetgenmesh::improvequalitybysmoothing(optparameters *opm) opm->smthiter = 0; // reset smoothpoint(ppt[i], cavetetlist, 1, opm); } + // This tet is modifed. smtcount++; - } + if ((opm->imprval - 1.0) < cossmtdihed) { + // There are slivers in new tets. Queue them. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + assert(!isdeadtet(*parytet)); + // Operate it if it is not in 'unflipqueue'. + if (!marktested(*parytet)) { + // Evaluate its quality. + // Re-use ppt, bface->key, bface->cent. + ppt = (point *) & (parytet->tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], + bface->cent, &bface->key, NULL); + if (bface->key < cossmtdihed) { + // A new sliver. Queue it. + marktest(*parytet); // It is in unflipqueue. + unflipqueue->newindex((void **) &parybface); + parybface->tt = *parytet; + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.ver = 11; + parybface->key = 0.0; + } + } + } // j + } // if ((opm->imprval - 1.0) < cossmtdihed) + } // if (smtflag) cavetetlist->restart(); - } + } // if (pointtype(ppt[i]) == FREEVOLVERTEX) } // i - if (smtflag) { - // This tet is modifed. - smtcount++; - if ((opm->imprval - 1.0) < cossmtdihed) { - // Queue new slivers. - badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); - while (bface != NULL) { - assert(!isdeadtet(bface->tt)); - assert(marktest2ed(bface->tt)); - unmarktest2(bface->tt); - if (!marktested(bface->tt)) { - ppt = (point *) & (bface->tt.tet[4]); - tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, - &(bface->key), NULL); - if (bface->key < cossmtdihed) { - // A new sliver. Queue it. - marktest(bface->tt); // It is in unflipqueue. - bface->forg = ppt[0]; - bface->fdest = ppt[1]; - bface->fapex = ppt[2]; - bface->foppo = ppt[3]; - bface->tt.ver = 11; - unflipqueue->newindex((void **) &parybface); - *parybface = *bface; - } - } - bface = badfacetraverse(badtetrahedrons); - } - } else { - // No new slivers. Only unmark the queued tets. - badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); - while (bface != NULL) { - assert(!isdeadtet(bface->tt)); - assert(marktest2ed(bface->tt)); - unmarktest2(bface->tt); - bface = badfacetraverse(badtetrahedrons); - } - } - badtetrahedrons->restart(); - } else { + if (!smtflag) { // Didn't smooth. Queue it again. - // Adjust the vertices for flipping. marktest(bface->tt); // It is in unflipqueue. - bface->forg = ppt[0]; - bface->fdest = ppt[1]; - bface->fapex = ppt[2]; - bface->foppo = ppt[3]; - bface->tt.ver = 11; unflipqueue->newindex((void **) &parybface); - *parybface = *bface; + parybface->tt = bface->tt; + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.ver = 11; + parybface->key = 0.0; } - } // if (maxdd < cosslidihed) + } // if (maxdd < cosslidihed) } // if (!marktested(...)) - } // gettetrahedron(...) + } // if (gettetrahedron(...)) } // k flipqueue->restart(); - // } // while - // Unmark the tets in unflipqueue. for (i = 0; i < unflipqueue->objects; i++) { bface = (badface *) fastlookup(unflipqueue, i); - assert(!isdeadtet(bface->tt)); - assert(marktested(bface->tt)); unmarktest(bface->tt); } @@ -813,24 +979,20 @@ int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) { triface *abtets; triface searchtet, spintet, *parytet; - face checkseg; point pa, pb, steinerpt; optparameters opm; insertvertexflags ivf; REAL smtpt[3], midpt[3]; int success; - int loc; + int t1ver; int n, i; // 'slitet' is [c,d,a,b], where [c,d] has a big dihedral angle. // Go to the opposite edge [a,b]. - eprev(*slitet, searchtet); - esymself(searchtet); - enextself(searchtet); // [a,b,c,d]. + edestoppo(*slitet, searchtet); // [a,b,c,d]. // Do not split a segment. - tsspivot1(searchtet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(searchtet)) { return 0; } @@ -900,19 +1062,11 @@ int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) cavetetlist->restart(); if (!success) { - if (b->verbose > 2) { - printf(" Unable to relocate the initial point.\n"); - } delete [] abtets; return 0; } - if (steinerleft == 0) { - // The desired number of Steiner points is reached. - return 0; - } - // Insert the Steiner point. makepoint(&steinerpt, FREEVOLVERTEX); for (i = 0; i < 3; i++) steinerpt[i] = smtpt[i]; @@ -923,24 +1077,22 @@ int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) caveoldtetlist->newindex((void **) &parytet); *parytet = abtets[i]; } + searchtet = abtets[0]; // No need point location. + if (b->metric) { + locate(steinerpt, &searchtet); // For size interpolation. + } + + delete [] abtets; + ivf.iloc = (int) INSTAR; - ivf.bowywat = 0; // Do not use Bowyer-Watson algorithm. - ivf.lawson = 0; // Do not flip. - ivf.rejflag = 0; ivf.chkencflag = chkencflag; - ivf.sloc = 0; - ivf.sbowywat = 0; - ivf.splitbdflag = 0; - ivf.validflag = 0; - ivf.respectbdflag = 0; - ivf.assignmeshsize = 0; + ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, NULL, NULL, &ivf); - if (loc == (int) INSTAR) { + if (insertpoint(steinerpt, &searchtet, NULL, NULL, &ivf)) { // The vertex has been inserted. - st_volref_count++; //st_inpoly_count++; + st_volref_count++; if (steinerleft > 0) steinerleft--; return 1; } else { @@ -948,8 +1100,6 @@ int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) pointdealloc(steinerpt); return 0; } - - delete [] abtets; } /////////////////////////////////////////////////////////////////////////////// @@ -962,24 +1112,24 @@ long tetgenmesh::removeslivers(int chkencflag) { arraypool *flipqueue, *swapqueue; badface *bface, *parybface; + triface slitet, *parytet; point *ppt; - REAL *cosdd; + REAL cosdd[6], maxcosd; long totalsptcount, sptcount; - int iter, j, k; + int iter, i, j, k; //assert(unflipqueue->objects > 0l); flipqueue = new arraypool(sizeof(badface), 10); - totalsptcount = 0l; - // Swap the two flip queues. swapqueue = flipqueue; flipqueue = unflipqueue; unflipqueue = swapqueue; + totalsptcount = 0l; iter = 0; - while (flipqueue->objects > 0l) { + while ((flipqueue->objects > 0l) && (steinerleft != 0)) { sptcount = 0l; @@ -988,74 +1138,63 @@ long tetgenmesh::removeslivers(int chkencflag) iter, flipqueue->objects); } - for (k = 0; k < flipqueue->objects; k++) { + for (k = 0; (k < flipqueue->objects) && (steinerleft != 0); k++) { bface = (badface *) fastlookup(flipqueue, k); if (gettetrahedron(bface->forg, bface->fdest, bface->fapex, bface->foppo, &bface->tt)) { - //if (!marktested(bface->tt)) { - // Here we simply re-compute the quality. Since other smoothing + if ((bface->key == 0) || (bface->tt.ver != 11)) { + // Here we need to re-compute the quality. Since other smoothing // operation may have moved the vertices of this tet. ppt = (point *) & (bface->tt.tet[4]); tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, &bface->key, NULL); - if (bface->key < cosslidihed) { - // It is a sliver. Try to split it. - cosdd = bface->cent; - for (j = 0; j < 6; j++) { - if (cosdd[j] < cosslidihed) { - // Found a large dihedral angle. - bface->tt.ver = edge2ver[j]; // Go to the edge. - if (b->verbose > 2) { - printf(" Found a bad tet [%d,%d,%d,%d] (%g).\n", - pointmark(org(bface->tt)), pointmark(dest(bface->tt)), - pointmark(apex(bface->tt)), pointmark(oppo(bface->tt)), - acos(cosdd[j]) / PI * 180.0); - } - if (splitsliver(&(bface->tt), cosdd[j], chkencflag)) { - sptcount++; - break; + } + if (bface->key < cosslidihed) { + // It is a sliver. Try to split it. + slitet.tet = bface->tt.tet; + //cosdd = bface->cent; + for (j = 0; j < 6; j++) { + if (bface->cent[j] < cosslidihed) { + // Found a large dihedral angle. + slitet.ver = edge2ver[j]; // Go to the edge. + if (splitsliver(&slitet, bface->cent[j], chkencflag)) { + sptcount++; + break; + } + } + } // j + if (j < 6) { + // A sliver is split. Queue new slivers. + badtetrahedrons->traversalinit(); + parytet = (triface *) badtetrahedrons->traverse(); + while (parytet != NULL) { + unmarktest2(*parytet); + ppt = (point *) & (parytet->tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], cosdd, + &maxcosd, NULL); + if (maxcosd < cosslidihed) { + // A new sliver. Queue it. + unflipqueue->newindex((void **) &parybface); + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.tet = parytet->tet; + parybface->tt.ver = 11; + parybface->key = maxcosd; + for (i = 0; i < 6; i++) { + parybface->cent[i] = cosdd[i]; } } - } // j - if (j < 6) { - // A sliver is split. Queue new slivers. - badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); - while (bface != NULL) { - assert(!isdeadtet(bface->tt)); - assert(marktest2ed(bface->tt)); - unmarktest2(bface->tt); - ppt = (point *) & (bface->tt.tet[4]); - tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, - &(bface->key), NULL); - if (bface->key < cosslidihed) { - // A new sliver. Queue it. - //marktest(bface->tt); // It is in unflipqueue. - bface->forg = ppt[0]; - bface->fdest = ppt[1]; - bface->fapex = ppt[2]; - bface->foppo = ppt[3]; - bface->tt.ver = 11; - unflipqueue->newindex((void **) &parybface); - *parybface = *bface; - } - bface = badfacetraverse(badtetrahedrons); - } - badtetrahedrons->restart(); - } else { - // Didn't split. Queue it again. - // Adjust the vertices for flipping. - //marktest(bface->tt); // It is in unflipqueue. - bface->forg = ppt[0]; - bface->fdest = ppt[1]; - bface->fapex = ppt[2]; - bface->foppo = ppt[3]; - bface->tt.ver = 11; - unflipqueue->newindex((void **) &parybface); - *parybface = *bface; - } // if (j == 6) - } // if (bface->key < cosslidihed) - // } // if (!marktested(bface->tt)) + parytet = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } else { + // Didn't split. Queue it again. + unflipqueue->newindex((void **) &parybface); + *parybface = *bface; + } // if (j == 6) + } // if (bface->key < cosslidihed) } // if (gettetrahedron(...)) } // k @@ -1112,24 +1251,34 @@ void tetgenmesh::optimizemesh() printf("Optimizing mesh...\n"); } + optpasses = ((1 << b->optlevel) - 1); + if (b->verbose) { printf(" Optimization level = %d.\n", b->optlevel); printf(" Optimization scheme = %d.\n", b->optscheme); + printf(" Number of iteration = %d.\n", optpasses); printf(" Min_Max dihed angle = %g.\n", b->optmaxdihedral); } - optpasses = ((1 << b->optlevel) - 1); - totalsmtcount = totalsptcount = totalremcount = 0l; cosmaxdihed = cos(b->optmaxdihedral / 180.0 * PI); cossmtdihed = cos(b->optminsmtdihed / 180.0 * PI); cosslidihed = cos(b->optminslidihed / 180.0 * PI); + int attrnum = numelemattrib - 1; + // Put all bad tetrahedra into array. tetrahedrons->traversalinit(); checktet.tet = tetrahedrontraverse(); while (checktet.tet != NULL) { + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(checktet.tet, attrnum) == -1.0) { + checktet.tet = tetrahedrontraverse(); + continue; + } + } ppt = (point *) & (checktet.tet[4]); tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], ncosdd, &maxdd, NULL); if (maxdd < cosmaxdihed) { @@ -1153,16 +1302,16 @@ void tetgenmesh::optimizemesh() if ((unflipqueue->objects > 0l) && ((b->optscheme & 2) || (b->optscheme & 4))) { - - badtetrahedrons = new memorypool(sizeof(badface), b->tetrahedraperblock, - memorypool::POINTER, 0); + // The pool is only used by removeslivers(). + badtetrahedrons = new memorypool(sizeof(triface), b->tetrahedraperblock, + sizeof(void *), 0); // Smoothing options. opm.min_max_dihedangle = 1; opm.numofsearchdirs = 10; // opm.searchstep = 0.001; opm.maxiter = 30; // Limit the maximum iterations. - opm.checkencflag = 4; // Queue affected tets after smoothing. + //opm.checkencflag = 4; // Queue affected tets after smoothing. chkencflag = 4; // Queue affected tets after splitting a sliver. iter = 0; @@ -1210,13 +1359,13 @@ void tetgenmesh::optimizemesh() if (b->verbose) { if (totalremcount > 0l) { - printf(" Removed %ld bad tets.\n", totalremcount); + printf(" Removed %ld edges.\n", totalremcount); } if (totalsmtcount > 0l) { printf(" Smoothed %ld points.\n", totalsmtcount); } if (totalsptcount > 0l) { - printf(" Split %ld bad tets.\n", totalsptcount); + printf(" Split %ld slivers.\n", totalsptcount); } } } diff --git a/external/tetgen/src/output.cxx b/external/tetgen/src/output.cxx index e0a72580c..b89d3527e 100644 --- a/external/tetgen/src/output.cxx +++ b/external/tetgen/src/output.cxx @@ -23,7 +23,7 @@ void tetgenmesh::jettisonnodes() int remcount; if (!b->quiet) { - printf("Jettisoning redundants points.\n"); + printf("Jettisoning redundant points.\n"); } points->traversalinit(); @@ -52,11 +52,11 @@ void tetgenmesh::jettisonnodes() pointloop = pointtraverse(); } if (b->verbose) { - printf(" %d duplicated vertices are removed.\n", dupverts); - printf(" %d unused vertices are removed.\n", unuverts); + printf(" %ld duplicated vertices are removed.\n", dupverts); + printf(" %ld unused vertices are removed.\n", unuverts); } - dupverts = 0; - unuverts = 0; + dupverts = 0l; + unuverts = 0l; // The following line ensures that dead items in the pool of nodes cannot // be allocated for the new created nodes. This ensures that the input @@ -80,6 +80,7 @@ void tetgenmesh::highorder() point *extralist, *adjextralist; point torg, tdest, newpoint; int highorderindex; + int t1ver; int i, j; if (!b->quiet) { @@ -89,7 +90,7 @@ void tetgenmesh::highorder() // Initialize the 'highordertable'. highordertable = new point[tetrahedrons->items * 6]; if (highordertable == (point *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } // This will overwrite the slot for element markers. @@ -166,28 +167,18 @@ void tetgenmesh::highorder() // This routine is called when '-p' or '-r', and '-E' options are used. The // // total number of edges depends on the genus of the input surface mesh. // // // +// NOTE: This routine must be called after outelements(). So all elements // +// have been indexed. // +// // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::numberedges() { triface worktet, spintet; - int firstindex, eindex; int ishulledge; + int t1ver; int i; - // Determine the first index (0 or 1). - firstindex = b->zeroindex ? 0 : in->firstnumber; - - // First indexing all tetrahedra. - tetrahedrons->traversalinit(); - eindex = firstindex; - worktet.tet = tetrahedrontraverse(); - while (worktet.tet != NULL) { - setelemindex(worktet.tet, eindex); - eindex++; - worktet.tet = tetrahedrontraverse(); - } - meshedges = meshhulledges = 0l; tetrahedrons->traversalinit(); @@ -262,7 +253,7 @@ void tetgenmesh::outnodes(tetgenio* out) outfile = fopen(outnodefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outnodefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } // Number of points, number of dimensions, number of point attributes, // and number of boundary markers (zero or one). @@ -272,14 +263,14 @@ void tetgenmesh::outnodes(tetgenio* out) out->pointlist = new REAL[points->items * 3]; if (out->pointlist == (REAL *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } // Allocate space for 'pointattributelist' if necessary; if (nextras > 0) { out->pointattributelist = new REAL[points->items * nextras]; if (out->pointattributelist == (REAL *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } // Allocate space for 'pointmarkerlist' if necessary; @@ -287,14 +278,14 @@ void tetgenmesh::outnodes(tetgenio* out) out->pointmarkerlist = new int[points->items]; if (out->pointmarkerlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } if (b->psc) { out->pointparamlist = new tetgenio::pointparam[points->items]; if (out->pointparamlist == NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } out->numberofpoints = points->items; @@ -449,7 +440,7 @@ void tetgenmesh::outmetrics(tetgenio* out) outfile = fopen(outmtrfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of points, number of point metrices, // fprintf(outfile, "%ld %d\n", points->items, sizeoftensor + 3); @@ -459,7 +450,7 @@ void tetgenmesh::outmetrics(tetgenio* out) // out->pointmtrlist = new REAL[points->items * (sizeoftensor + 3)]; out->pointmtrlist = new REAL[points->items]; if (out->pointmtrlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } out->numberofpointmtrs = 1; // (sizeoftensor + 3); mtrindex = 0; @@ -503,7 +494,6 @@ void tetgenmesh::outelements(tetgenio* out) FILE *outfile = NULL; char outelefilename[FILENAMESIZE]; tetrahedron* tptr; - triface worktet, spintet; point p1, p2, p3, p4; point *extralist; REAL *talist = NULL; @@ -514,7 +504,6 @@ void tetgenmesh::outelements(tetgenio* out) int highorderindex = 11; int elementnumber; int eextras; - int ishulledge; int i; if (out == (tetgenio *) NULL) { @@ -538,7 +527,7 @@ void tetgenmesh::outelements(tetgenio* out) outfile = fopen(outelefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outelefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } // Number of tetras, points per tetra, attributes per tetra. fprintf(outfile, "%ld %d %d\n", ntets, b->order == 1 ? 4 : 10, eextras); @@ -547,14 +536,14 @@ void tetgenmesh::outelements(tetgenio* out) out->tetrahedronlist = new int[ntets * (b->order == 1 ? 4 : 10)]; if (out->tetrahedronlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } // Allocate memory for output tetrahedron attributes if necessary. if (eextras > 0) { out->tetrahedronattributelist = new REAL[ntets * eextras]; if (out->tetrahedronattributelist == (REAL *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } out->numberoftetrahedra = ntets; @@ -568,7 +557,7 @@ void tetgenmesh::outelements(tetgenio* out) // Determine the first index (0 or 1). firstindex = b->zeroindex ? 0 : in->firstnumber; - shift = 0; // Default no shiftment. + shift = 0; // Default no shift. if ((in->firstnumber == 1) && (firstindex == 0)) { shift = 1; // Shift the output indices by 1. } @@ -627,36 +616,6 @@ void tetgenmesh::outelements(tetgenio* out) elementnumber++; } - // Count the number of edges (# Voronoi faces). - meshedges = meshhulledges = 0l; - - tetrahedrons->traversalinit(); - tptr = tetrahedrontraverse(); - while (tptr != (tetrahedron *) NULL) { - // Count the number of Voronoi faces. Look at the six edges of this - // tet. Count an edge only if this tet's pointer is smaller than - // those of other non-hull tets which share this edge. - worktet.tet = tptr; - for (i = 0; i < 6; i++) { - worktet.ver = edge2ver[i]; - ishulledge = 0; - fnext(worktet, spintet); - do { - if (!ishulltet(spintet)) { - if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; - } else { - ishulledge = 1; - } - fnextself(spintet); - } while (spintet.tet != worktet.tet); - // Count this edge if no adjacent tets are smaller than this tet. - if (spintet.tet == worktet.tet) { - meshedges++; - if (ishulledge) meshhulledges++; - } - } - tptr = tetrahedrontraverse(); - } if (out == (tetgenio *) NULL) { fprintf(outfile, "# Generated by %s\n", b->commandline); @@ -683,13 +642,13 @@ void tetgenmesh::outfaces(tetgenio* out) int faceid, marker = 0; int firstindex, shift; int facenumber; - int index; + int index = 0; // For -o2 option. triface workface; point *extralist, pp[3] = {0,0,0}; int highorderindex = 11; - int i; + int o2index = 0, i; if (out == (tetgenio *) NULL) { strcpy(facefilename, b->outfilename); @@ -711,23 +670,25 @@ void tetgenmesh::outfaces(tetgenio* out) outfile = fopen(facefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } fprintf(outfile, "%ld %d\n", faces, !b->nobound); } else { // Allocate memory for 'trifacelist'. - //out->trifacelist = new int[faces * 3]; - out->trifacelist = new int[faces * (b->order == 1 ? 3 : 6)]; + out->trifacelist = new int[faces * 3]; if (out->trifacelist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2facelist = new int[faces * 3]; } // Allocate memory for 'trifacemarkerlist' if necessary. if (!b->nobound) { out->trifacemarkerlist = new int[faces]; if (out->trifacemarkerlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } if (b->neighout > 1) { @@ -735,13 +696,12 @@ void tetgenmesh::outfaces(tetgenio* out) out->adjtetlist = new int[faces * 2]; if (out->adjtetlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } out->numberoftrifaces = faces; elist = out->trifacelist; emlist = out->trifacemarkerlist; - index = 0; } // Determine the first index (0 or 1). @@ -769,7 +729,8 @@ void tetgenmesh::outfaces(tetgenio* out) if (b->order == 2) { // -o2 // Get the three extra vertices on edges. extralist = (point *) (tface.tet[highorderindex]); - workface = tface; + // The extra vertices are on edges opposite the corners. + enext(tface, workface); for (i = 0; i < 3; i++) { pp[i] = extralist[ver2edge[workface.ver]]; enextself(workface); @@ -828,9 +789,9 @@ void tetgenmesh::outfaces(tetgenio* out) elist[index++] = pointmark(tdest) - shift; elist[index++] = pointmark(tapex) - shift; if (b->order == 2) { // -o2 - elist[index++] = pointmark(pp[0]) - shift; - elist[index++] = pointmark(pp[1]) - shift; - elist[index++] = pointmark(pp[2]) - shift; + out->o2facelist[o2index++] = pointmark(pp[0]) - shift; + out->o2facelist[o2index++] = pointmark(pp[1]) - shift; + out->o2facelist[o2index++] = pointmark(pp[2]) - shift; } if (!b->nobound) { emlist[facenumber - in->firstnumber] = marker; @@ -888,7 +849,7 @@ void tetgenmesh::outhullfaces(tetgenio* out) outfile = fopen(facefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } fprintf(outfile, "%ld 0\n", hullsize); } else { @@ -896,7 +857,7 @@ void tetgenmesh::outhullfaces(tetgenio* out) out->trifacelist = new int[hullsize * 3]; if (out->trifacelist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } out->numberoftrifaces = hullsize; elist = out->trifacelist; @@ -969,9 +930,11 @@ void tetgenmesh::outsubfaces(tetgenio* out) // For -o2 option. triface workface; - point *extralist, pp[3]; + point *extralist, pp[3] = {0,0,0}; int highorderindex = 11; - int i; + int o2index = 0, i; + + int t1ver; // used by fsymself() if (out == (tetgenio *) NULL) { strcpy(facefilename, b->outfilename); @@ -990,29 +953,31 @@ void tetgenmesh::outsubfaces(tetgenio* out) outfile = fopen(facefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of subfaces. fprintf(outfile, "%ld %d\n", subfaces->items, !b->nobound); } else { // Allocate memory for 'trifacelist'. - //out->trifacelist = new int[subfaces->items * 3]; - out->trifacelist = new int[subfaces->items * (b->order == 1 ? 3 : 6)]; + out->trifacelist = new int[subfaces->items * 3]; if (out->trifacelist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2facelist = new int[subfaces->items * 3]; } if (!b->nobound) { // Allocate memory for 'trifacemarkerlist'. out->trifacemarkerlist = new int[subfaces->items]; if (out->trifacemarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } if (b->neighout > 1) { // '-nn' switch. out->adjtetlist = new int[subfaces->items * 2]; if (out->adjtetlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } out->numberoftrifaces = subfaces->items; @@ -1067,11 +1032,19 @@ void tetgenmesh::outsubfaces(tetgenio* out) } } if (!b->nobound) { - if (in->facetmarkerlist) { - faceid = shellmark(faceloop) - 1; - marker = in->facetmarkerlist[faceid]; + if (b->refine) { // -r option. + if (in->trifacemarkerlist) { + marker = shellmark(faceloop); + } else { + marker = 1; // Default marker for a subface is 1. + } } else { - marker = 1; // Default marker for a subface is 1. + if (in->facetmarkerlist) { + faceid = shellmark(faceloop) - 1; + marker = in->facetmarkerlist[faceid]; + } else { + marker = 1; // Default marker for a subface is 1. + } } } if (b->neighout > 1) { @@ -1108,9 +1081,9 @@ void tetgenmesh::outsubfaces(tetgenio* out) elist[index++] = pointmark(tdest) - shift; elist[index++] = pointmark(tapex) - shift; if (b->order == 2) { // -o2 - elist[index++] = pointmark(pp[0]) - shift; - elist[index++] = pointmark(pp[1]) - shift; - elist[index++] = pointmark(pp[2]) - shift; + out->o2facelist[o2index++] = pointmark(pp[0]) - shift; + out->o2facelist[o2index++] = pointmark(pp[1]) - shift; + out->o2facelist[o2index++] = pointmark(pp[2]) - shift; } if (!b->nobound) { emlist[index1++] = marker; @@ -1150,12 +1123,14 @@ void tetgenmesh::outedges(tetgenio* out) int ishulledge; int firstindex, shift; int edgenumber, marker; - int index, index1; + int index = 0, index1 = 0, index2 = 0; + int t1ver; int i; // For -o2 option. point *extralist, pp = NULL; int highorderindex = 11; + int o2index = 0; if (out == (tetgenio *) NULL) { strcpy(edgefilename, b->outfilename); @@ -1170,30 +1145,47 @@ void tetgenmesh::outedges(tetgenio* out) } } + if (meshedges == 0l) { + if (nonconvex) { + numberedges(); // Count the edges. + } else { + // Use Euler's characteristic to get the numbe of edges. + // It states V - E + F - C = 1, hence E = V + F - C - 1. + long tsize = tetrahedrons->items - hullsize; + long fsize = (tsize * 4l + hullsize) / 2l; + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + meshedges = vsize + fsize - tsize - 1; + } + } + if (out == (tetgenio *) NULL) { outfile = fopen(edgefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", edgefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } // Write the number of edges, boundary markers (0 or 1). fprintf(outfile, "%ld %d\n", meshedges, !b->nobound); } else { // Allocate memory for 'edgelist'. - //out->edgelist = new int[meshedges * 2]; - out->edgelist = new int[meshedges * (b->order == 1 ? 2 : 3)]; + out->edgelist = new int[meshedges * 2]; if (out->edgelist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->order == 2) { // -o2 switch + out->o2edgelist = new int[meshedges]; } if (!b->nobound) { out->edgemarkerlist = new int[meshedges]; } + if (b->neighout > 1) { // '-nn' switch. + out->edgeadjtetlist = new int[meshedges]; + } out->numberofedges = meshedges; elist = out->edgelist; emlist = out->edgemarkerlist; - index = 0; - index1 = 0; // if (!b->nobound) } // Determine the first index (0 or 1). @@ -1207,9 +1199,7 @@ void tetgenmesh::outedges(tetgenio* out) tetloop.tet = tetrahedrontraverse(); edgenumber = firstindex; // in->firstnumber; while (tetloop.tet != (tetrahedron *) NULL) { - // Count the number of Voronoi faces. Look at the six edges of this - // tet. Count an edge only if this tet's pointer is smaller than - // those of other non-hull tets which share this edge. + // Count the number of Voronoi faces. worktet.tet = tetloop.tet; for (i = 0; i < 6; i++) { worktet.ver = edge2ver[i]; @@ -1243,7 +1233,7 @@ void tetgenmesh::outedges(tetgenio* out) elist[index++] = pointmark(torg) - shift; elist[index++] = pointmark(tdest) - shift; if (b->order == 2) { // -o2 - elist[index++] = pointmark(pp) - shift; + out->o2edgelist[o2index++] = pointmark(pp) - shift; } } if (!b->nobound) { @@ -1268,6 +1258,13 @@ void tetgenmesh::outedges(tetgenio* out) emlist[index1++] = marker; } } + if (b->neighout > 1) { // '-nn' switch. + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", elemindex(tetloop.tet)); + } else { + out->edgeadjtetlist[index2++] = elemindex(tetloop.tet); + } + } if (out == (tetgenio *) NULL) { fprintf(outfile, "\n"); } @@ -1305,6 +1302,13 @@ void tetgenmesh::outsubsegments(tetgenio* out) triface workface, spintet; point *extralist, pp = NULL; int highorderindex = 11; + int o2index = 0; + + // For -nn option. + int neigh = -1; + int index2 = 0; + + int t1ver; // used by fsymself() if (out == (tetgenio *) NULL) { strcpy(edgefilename, b->outfilename); @@ -1323,20 +1327,25 @@ void tetgenmesh::outsubsegments(tetgenio* out) outfile = fopen(edgefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", edgefilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of subsegments. fprintf(outfile, "%ld 1\n", subsegs->items); } else { // Allocate memory for 'edgelist'. - //out->edgelist = new int[subsegs->items * 2]; out->edgelist = new int[subsegs->items * (b->order == 1 ? 2 : 3)]; if (out->edgelist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2edgelist = new int[subsegs->items]; } out->edgemarkerlist = new int[subsegs->items]; if (out->edgemarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->neighout > 1) { + out->edgeadjtetlist = new int[subsegs->items]; } out->numberofedges = subsegs->items; elist = out->edgelist; @@ -1357,8 +1366,7 @@ void tetgenmesh::outsubsegments(tetgenio* out) while (edgeloop.sh != (shellface *) NULL) { torg = sorg(edgeloop); tdest = sdest(edgeloop); - if (b->order == 2) { // -o2 - // Get the extra vertex on this edge. + if ((b->order == 2) || (b->neighout > 1)) { sstpivot1(edgeloop, workface); if (workface.tet != NULL) { // We must find a non-hull tet. @@ -1372,12 +1380,24 @@ void tetgenmesh::outsubsegments(tetgenio* out) assert(!ishulltet(spintet)); workface = spintet; } + } + } + if (b->order == 2) { // -o2 + // Get the extra vertex on this edge. + if (workface.tet != NULL) { extralist = (point *) workface.tet[highorderindex]; pp = extralist[ver2edge[workface.ver]]; } else { pp = torg; // There is no extra node available. } } + if (b->neighout > 1) { // -nn + if (workface.tet != NULL) { + neigh = elemindex(workface.tet); + } else { + neigh = -1; + } + } marker = shellmark(edgeloop); if (marker == 0) { marker = 1; // Default marker of a boundary edge is 1. @@ -1388,15 +1408,22 @@ void tetgenmesh::outsubsegments(tetgenio* out) if (b->order == 2) { // -o2 fprintf(outfile, " %4d", pointmark(pp) - shift); } - fprintf(outfile, " %d\n", marker); + fprintf(outfile, " %d", marker); + if (b->neighout > 1) { // -nn + fprintf(outfile, " %4d", neigh); + } + fprintf(outfile, "\n"); } else { // Output three vertices of this face; elist[index++] = pointmark(torg) - shift; elist[index++] = pointmark(tdest) - shift; if (b->order == 2) { // -o2 - elist[index++] = pointmark(pp) - shift; + out->o2edgelist[o2index++] = pointmark(pp) - shift; } out->edgemarkerlist[i++] = marker; + if (b->neighout > 1) { // -nn + out->edgeadjtetlist[index2++] = neigh; + } } edgenumber++; edgeloop.sh = shellfacetraverse(subsegs); @@ -1445,7 +1472,7 @@ void tetgenmesh::outneighbors(tetgenio* out) outfile = fopen(neighborfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", neighborfilename); - terminatetetgen(1); + terminatetetgen(this, 1); } // Number of tetrahedra, four faces per tetrahedron. fprintf(outfile, "%ld %d\n", ntets, 4); @@ -1454,7 +1481,7 @@ void tetgenmesh::outneighbors(tetgenio* out) out->neighborlist = new int[ntets * 4]; if (out->neighborlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } nlist = out->neighborlist; } @@ -1504,18 +1531,13 @@ void tetgenmesh::outneighbors(tetgenio* out) // Voronoi edge connects two Voronoi vertices at two sides of a common Dela- // // unay face. At a face of convex hull, it becomes a ray (goto the infinity).// // A Voronoi face is the convex hull of all Voronoi vertices around a common // -// Delaunay edge. It is a closed polygon for any interal Delaunay edge. At a // +// Delaunay edge. It is a closed polygon for any internal Delaunay edge. At a// // ridge, it is unbounded. Each Voronoi cell is the convex hull of all Vor- // // onoi vertices around a common Delaunay vertex. It is a polytope for any // // internal Delaunay vertex. It is an unbounded polyhedron for a Delaunay // // vertex belonging to the convex hull. // // // -// aunay tetrahedralization - the power diagram - of the weighted point set. // -// Note that the vertices of the power disgram are the centers of the ortho- // -// spheres of the tetrahedra. // -// // // NOTE: This routine is only used when the input is only a set of point. // -// // // Comment: Special thanks to Victor Liu for finding and fixing few bugs. // // // /////////////////////////////////////////////////////////////////////////////// @@ -1525,7 +1547,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) FILE *outfile = NULL; char outfilename[FILENAMESIZE]; tetgenio::voroedge *vedge = NULL; - tetgenio::vorofacet *vfacet; + tetgenio::vorofacet *vfacet = NULL; arraypool *tetlist, *ptlist; triface tetloop, worktet, spintet, firsttet; point pt[4], ploop, neipt; @@ -1538,6 +1560,8 @@ void tetgenmesh::outvoronoi(tetgenio* out) int index, shift, end1, end2; int i, j; + int t1ver; // used by fsymself() + // Output Voronoi vertices to .v.node file. if (out == (tetgenio *) NULL) { strcpy(outfilename, b->outfilename); @@ -1578,14 +1602,15 @@ void tetgenmesh::outvoronoi(tetgenio* out) // The number of Delaunay faces (Voronoi edges). faces = (4l * ntets + hullsize) / 2l; // The number of Delaunay edges (Voronoi faces). - // edges = points->items + faces - ntets - 1; - edges = meshedges; // Counted in outelements() or numberedges(); + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + edges = vsize + faces - ntets - 1; if (out == (tetgenio *) NULL) { outfile = fopen(outfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of voronoi points, 3 dim, no attributes, no marker. fprintf(outfile, "%ld 3 0 0\n", ntets); @@ -1594,7 +1619,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) out->numberofvpoints = (int) ntets; out->vpointlist = new REAL[out->numberofvpoints * 3]; if (out->vpointlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } @@ -1650,7 +1675,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) outfile = fopen(outfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of Voronoi edges, no marker. fprintf(outfile, "%ld 0\n", faces); @@ -1752,7 +1777,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) outfile = fopen(outfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of Voronoi faces. fprintf(outfile, "%ld 0\n", edges); @@ -1760,7 +1785,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) out->numberofvfacets = edges; out->vfacetlist = new tetgenio::vorofacet[out->numberofvfacets]; if (out->vfacetlist == (tetgenio::vorofacet *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } @@ -1866,7 +1891,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) outfile = fopen(outfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of Voronoi cells. fprintf(outfile, "%ld\n", points->items - unuverts - dupverts); @@ -1874,7 +1899,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) out->numberofvcells = points->items - unuverts - dupverts; out->vcelllist = new int*[out->numberofvcells]; if (out->vcelllist == (int **) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } @@ -2253,6 +2278,11 @@ void tetgenmesh::outmesh2vtk(char* ofilename) int nnodes = 4; int celltype = 10; + if (b->order == 2) { + printf(" Write VTK not implemented for order 2 elements \n"); + return; + } + int NEL = tetrahedrons->items - hullsize; int NN = points->items; @@ -2300,10 +2330,6 @@ void tetgenmesh::outmesh2vtk(char* ofilename) tetrahedrons->traversalinit(); tptr = tetrahedrontraverse(); //elementnumber = firstindex; // in->firstnumber; - if (b->order == 2) { - printf(" Write VTK not implemented for order 2 elements \n"); - return; - } while (tptr != (tetrahedron *) NULL) { if (!b->reversetetori) { p1 = (point) tptr[4]; @@ -2329,6 +2355,20 @@ void tetgenmesh::outmesh2vtk(char* ofilename) } fprintf(outfile, "\n"); + if (numelemattrib > 0) { + // Output tetrahedra region attributes. + fprintf(outfile, "CELL_DATA %d\n", NEL); + fprintf(outfile, "SCALARS cell_scalars int 1\n"); + fprintf(outfile, "LOOKUP_TABLE default\n"); + tetrahedrons->traversalinit(); + tptr = tetrahedrontraverse(); + while (tptr != (tetrahedron *) NULL) { + fprintf(outfile, "%d\n", (int) elemattribute(tptr, numelemattrib - 1)); + tptr = tetrahedrontraverse(); + } + fprintf(outfile, "\n"); + } + fclose(outfile); } diff --git a/external/tetgen/src/reconstruct.cxx b/external/tetgen/src/reconstruct.cxx index ce0be2d7a..70d8fee78 100644 --- a/external/tetgen/src/reconstruct.cxx +++ b/external/tetgen/src/reconstruct.cxx @@ -13,54 +13,38 @@ void tetgenmesh::carveholes() { arraypool *tetarray, *hullarray; - triface tetloop, neightet, hulltet, *parytet, *parytet1; - triface openface, casface; - triface *regiontets; - face checksh, casingout, casingin, *parysh; + triface tetloop, neightet, *parytet, *parytet1; + triface *regiontets = NULL; + face checksh, *parysh; face checkseg; - point *ppt, pa, pb, pc, *parypt; - enum locateresult loc; - REAL volume; - long delsegcount, delvertcount, delsteinercount; - int regioncount; - int attrnum, attr, maxattr; - int remflag; + point ptloop, *parypt; + int t1ver; int i, j, k; - tetrahedron ptr; - shellface sptr; - if (!b->quiet) { - printf("Removing exterior tetrahedra ...\n"); + if (b->convex) { + printf("Marking exterior tetrahedra ...\n"); + } else { + printf("Removing exterior tetrahedra ...\n"); + } } - // Initialize the pool of exterior tets. tetarray = new arraypool(sizeof(triface), 10); hullarray = new arraypool(sizeof(triface), 10); - regiontets = NULL; - regioncount = 0; - maxattr = 0; // Choose a small number here. - //attrnum = in->numberoftetrahedronattributes; - attrnum = numelemattrib - (b->regionattrib > 0); - // Comment: The element region marker is at the end of the list of - // the element attributes. - - // Mark as infected any unprotected hull tets. + // Collect unprotected tets and hull tets. tetrahedrons->traversalinit(); tetloop.ver = 11; // The face opposite to dummypoint. tetloop.tet = alltetrahedrontraverse(); while (tetloop.tet != (tetrahedron *) NULL) { - if ((point) tetloop.tet[7] == dummypoint) { + if (ishulltet(tetloop)) { // Is this side protected by a subface? - tspivot(tetloop, checksh); - if (checksh.sh == NULL) { + if (!issubface(tetloop)) { + // Collect an unprotected hull tet and tet. infect(tetloop); - tetarray->newindex((void **) &parytet); + hullarray->newindex((void **) &parytet); *parytet = tetloop; - hullsize--; - // Add the adjacent tet (not a hull tet) as well. // tetloop's face number is 11 & 3 = 3. decode(tetloop.tet[3], neightet); if (!infected(neightet)) { @@ -79,50 +63,43 @@ void tetgenmesh::carveholes() // Search a tet containing the i-th hole point. neightet.tet = NULL; randomsample(&(in->holelist[i]), &neightet); - loc = locate(&(in->holelist[i]), &neightet, 0); - if (loc != OUTSIDE) { + if (locate(&(in->holelist[i]), &neightet) != OUTSIDE) { // The tet 'neightet' contain this point. if (!infected(neightet)) { infect(neightet); tetarray->newindex((void **) &parytet); *parytet = neightet; // Add its adjacent tet if it is not protected. - tspivot(neightet, checksh); - if (checksh.sh == NULL) { + if (!issubface(neightet)) { decode(neightet.tet[neightet.ver & 3], tetloop); if (!infected(tetloop)) { infect(tetloop); - tetarray->newindex((void **) &parytet); + if (ishulltet(tetloop)) { + hullarray->newindex((void **) &parytet); + } else { + tetarray->newindex((void **) &parytet); + } *parytet = tetloop; } - } else { + } + else { // It is protected. Check if its adjacent tet is a hull tet. decode(neightet.tet[neightet.ver & 3], tetloop); - if (!infected(tetloop)) { - if (ishulltet(tetloop)) { - // It is hull tet, add it into the list. Moreover, the subface - // is dead, i.e., both sides are in exterior. + if (ishulltet(tetloop)) { + // It is hull tet, add it into the list. Moreover, the subface + // is dead, i.e., both sides are in exterior. + if (!infected(tetloop)) { infect(tetloop); - tetarray->newindex((void **) &parytet); + hullarray->newindex((void **) &parytet); *parytet = tetloop; - stdissolve(checksh); - assert(!sinfected(checksh)); - //if (!sinfected(checksh)) { - sinfect(checksh); // Only queue it once. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - //} - hullsize--; } - } else { + } + if (infected(tetloop)) { // Both sides of this subface are in exterior. - stdissolve(checksh); - assert(!sinfected(checksh)); - //if (!sinfected(checksh)) { - sinfect(checksh); // Only queue it once. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - //} + tspivot(neightet, checksh); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; } } } // if (!infected(neightet)) @@ -134,9 +111,9 @@ void tetgenmesh::carveholes() } } } // i - } + } // if (in->numberofholes > 0) - if (b->regionattrib && (in->numberofregions > 0)) { // If has -A option. + if (b->regionattrib && (in->numberofregions > 0)) { // -A option. // Record the tetrahedra that contains the region points for assigning // region attributes after the holes have been carved. regiontets = new triface[in->numberofregions]; @@ -145,12 +122,8 @@ void tetgenmesh::carveholes() // Search a tet containing the i-th region point. neightet.tet = NULL; randomsample(&(in->regionlist[i]), &neightet); - loc = locate(&(in->regionlist[i]), &neightet, 0); - if (loc != OUTSIDE) { + if (locate(&(in->regionlist[i]), &neightet) != OUTSIDE) { regiontets[i/5] = neightet; - if ((int) in->regionlist[i + 3] > maxattr) { - maxattr = (int) in->regionlist[i + 3]; - } } else { if (!b->quiet) { printf("Warning: The %d-th region point ", i/5+1); @@ -161,61 +134,50 @@ void tetgenmesh::carveholes() } } - - // Find and infect all exterior tets (in concave place and in holes). + // Collect all exterior tets (in concave place and in holes). for (i = 0; i < tetarray->objects; i++) { parytet = (triface *) fastlookup(tetarray, i); - // Check its three neighbors if it is not a hull tet. - if ((point) parytet->tet[7] != dummypoint) { - j = (parytet->ver & 3); // j is the current face number. - // Check the neighbors of the other three faces. - for (k = 0, j++; k < 3; k++, j++) { - decode(parytet->tet[j % 4], neightet); // neightet may be a hull tet. - if (!infected(neightet)) { - // Is neightet protected by a subface. - tspivot(neightet, checksh); - if (checksh.sh == NULL) { - // Not proected. Add it into the list. - // It should not be a hull tet. Since all unproected hull tets - // should have already been added into the list. - assert(!ishulltet(neightet)); // SELF_CHECK - infect(neightet); - tetarray->newindex((void **) &parytet1); - *parytet1 = neightet; - } else { - // It is protected. However, if neightet is a hull tet, it is - // also an exterior tet. Moverover, the subface is dead, i.e., - // both sides of it are exterior. - if ((point) neightet.tet[7] == dummypoint) { - infect(neightet); - tetarray->newindex((void **) &parytet1); - *parytet1 = neightet; - // Both sides of this subface are exterior. - stdissolve(checksh); - // Queue this subface (to be deleted later). - assert(!sinfected(checksh)); - //if (!sinfected(checksh)) { - sinfect(checksh); // Only queue it once. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - //} - hullsize--; - } - } + j = (parytet->ver & 3); // j is the current face number. + // Check the other three adjacent tets. + for (k = 1; k < 4; k++) { + decode(parytet->tet[(j + k) % 4], neightet); + // neightet may be a hull tet. + if (!infected(neightet)) { + // Is neightet protected by a subface. + if (!issubface(neightet)) { + // Not proected. Collect it. (It must not be a hull tet). + infect(neightet); + tetarray->newindex((void **) &parytet1); + *parytet1 = neightet; } else { - // Both sides of this face are in exterior. - // Check if there is a subface. - tspivot(neightet, checksh); - if (checksh.sh != NULL) { - if (!sinfected(checksh)) { - sinfect(checksh); // Only queue it once. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } + // Protected. Check if it is a hull tet. + if (ishulltet(neightet)) { + // A hull tet. Collect it. + infect(neightet); + hullarray->newindex((void **) &parytet1); + *parytet1 = neightet; + // Both sides of this subface are exterior. + tspivot(neightet, checksh); + // Queue this subface (to be deleted later). + assert(!sinfected(checksh)); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; } } - } // j, k - } + } else { + // Both sides of this face are in exterior. + // If there is a subface. It should be collected. + if (issubface(neightet)) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } + } // j, k } // i if (b->regionattrib && (in->numberofregions > 0)) { @@ -231,21 +193,48 @@ void tetgenmesh::carveholes() } } + // Collect vertices which point to infected tets. These vertices + // may get deleted after the removal of exterior tets. + // If -Y1 option is used, collect all Steiner points for removal. + // The lists 'cavetetvertlist' and 'subvertstack' are re-used. + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + if ((pointtype(ptloop) != UNUSEDVERTEX) && + (pointtype(ptloop) != DUPLICATEDVERTEX)) { + decode(point2tet(ptloop), neightet); + if (infected(neightet)) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = ptloop; + } + if (b->nobisect && (b->nobisect_param > 0)) { // -Y1 + // Queue it if it is a Steiner point. + if (pointmark(ptloop) > + (in->numberofpoints - (in->firstnumber ? 0 : 1))) { + subvertstack->newindex((void **) &parypt); + *parypt = ptloop; + } + } + } + ptloop = pointtraverse(); + } -if (!b->convex) { + if (!b->convex && (tetarray->objects > 0l)) { // No -c option. + // Remove exterior tets. Hull tets are updated. + arraypool *newhullfacearray; + triface hulltet, casface; + point pa, pb, pc; - // Create new hull tets. - // Update point-to-tet map, segment-to-tet map, and subface-to-tet map. - for (i = 0; i < tetarray->objects; i++) { - parytet = (triface *) fastlookup(tetarray, i); - if ((point) parytet->tet[7] != dummypoint) { - // We must check all four adjacent tets. + newhullfacearray = new arraypool(sizeof(triface), 10); + + // Create and save new hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); for (j = 0; j < 4; j++) { decode(parytet->tet[j], tetloop); if (!infected(tetloop)) { - // This face becomes a hull face. + // Found a new hull face (must be a subface). tspivot(tetloop, checksh); - assert(checksh.sh != NULL); // SELF_CHECK maketetrahedron(&hulltet); pa = org(tetloop); pb = dest(tetloop); @@ -257,8 +246,8 @@ if (!b->convex) { tsbond(hulltet, checksh); // Update the segment-to-tet map. for (k = 0; k < 3; k++) { - tsspivot1(tetloop, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(tetloop)) { + tsspivot1(tetloop, checkseg); tssbond1(hulltet, checkseg); sstbond1(checkseg, hulltet); } @@ -266,313 +255,221 @@ if (!b->convex) { eprevself(hulltet); } // Update the point-to-tet map. - ptr = encode(tetloop); - setpoint2tet(pa, ptr); - setpoint2tet(pb, ptr); - setpoint2tet(pc, ptr); - // Save this hull tet in list. - hullarray->newindex((void **) &parytet1); - *parytet1 = hulltet; - } + setpoint2tet(pa, (tetrahedron) tetloop.tet); + setpoint2tet(pb, (tetrahedron) tetloop.tet); + setpoint2tet(pc, (tetrahedron) tetloop.tet); + // Save the exterior tet at this hull face. It still holds pointer + // to the adjacent interior tet. Use it to connect new hull tets. + newhullfacearray->newindex((void **) &parytet1); + parytet1->tet = parytet->tet; + parytet1->ver = j; + } // if (!infected(tetloop)) } // j - } else { - // It is a hull tet. Clear the adjacent hull tets' connections to it. - // Our data structure ensures that the 3rd face opposites dummypoint. - for (j = 0; j < 3; j++) { - decode(parytet->tet[j], neightet); - if (neightet.tet != NULL) { - assert(ishulltet(neightet)); - if (!infected(neightet)) { - neightet.tet[neightet.ver & 3] = NULL; - } - } - } // j - } - } // i - - // Update the hull size. - hullsize += hullarray->objects; - - // Remove all exterior tetrahedra (including infected hull tets). - for (i = 0; i < tetarray->objects; i++) { - parytet = (triface *) fastlookup(tetarray, i); - tetrahedrondealloc(parytet->tet); - } // i - - tetarray->restart(); - - - if (subfacstack->objects > 0) { - // Remove all subfaces which do not attach to any tetrahedron. - // Segments which are not attached to any subfaces and tets - // are deleted too. - delsegcount = 0; - for (i = 0; i < subfacstack->objects; i++) { - parysh = (face *) fastlookup(subfacstack, i); - if (i == 0) { - if (b->verbose) { - printf("Warning: Removing an open face (%d, %d, %d)\n", - pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), - pointmark(sapex(*parysh))); - } - } - // Dissolve this subface from face links. - for (j = 0; j < 3; j++) { - spivot(*parysh, casingout); - sspivot(*parysh, checkseg); - if (casingout.sh != NULL) { - casingin = casingout; - while (1) { - spivot(casingin, checksh); - if (checksh.sh == parysh->sh) break; - casingin = checksh; - } - if (casingin.sh != casingout.sh) { - // Update the link: ... -> casingin -> casingout ->... - sbond1(casingin, casingout); - } else { - // Only one subface at this edge is left. - sdissolve(casingout); - } - if (checkseg.sh != NULL) { - // Make sure the segment does not connect to a dead one. - ssbond(casingout, checkseg); - } - } else { - if (checkseg.sh != NULL) { - // The segment is also dead. - if (delsegcount == 0) { - if (b->verbose) { - printf("Warning: Removing a dangling segment (%d, %d)\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - } - shellfacedealloc(subsegs, checkseg.sh); - delsegcount++; - } - } - senextself(*parysh); - } // j - // Delete this subface. - shellfacedealloc(subfaces, parysh->sh); } // i - if (b->verbose) { - printf(" Deleted %ld subfaces.\n", subfacstack->objects); - if (delsegcount > 0) { - printf(" Deleted %ld segments.\n", delsegcount); - } - } - subfacstack->restart(); - } - - // Some vertices may be not belong to any tet. Mark them. - delvertcount = unuverts; - delsteinercount = 0l; - points->traversalinit(); - pa = pointtraverse(); - while (pa != NULL) { - if (pointtype(pa) != UNUSEDVERTEX) { - remflag = 0; - decode(point2tet(pa), neightet); - if ((neightet.tet == NULL) || (neightet.tet[4] == NULL)) { - remflag = 1; // It's a dead tet. - } else { - // Check if this tet contains pa. - ppt = (point *) &(neightet.tet[4]); - if (!((ppt[0] == pa) || (ppt[1] == pa) || - (ppt[2] == pa) || (ppt[3] == pa))) { - remflag = 1; // It's a wrong pointer. - } - } - if (remflag) { - // Found an exterior vertex. - if (pointmark(pa) > - (in->numberofpoints - (in->firstnumber ? 0 : 1))) { - if (pointtype(pa) == FREESEGVERTEX) { - st_segref_count--; - } else if (pointtype(pa) == FREEFACETVERTEX) { - st_facref_count--; - } else { - assert(pointtype(pa) == FREEVOLVERTEX); - st_volref_count--; //st_inpoly_count--; - } - delsteinercount++; // A Steiner point. - if (steinerleft > 0) steinerleft++; - } - setpointtype(pa, UNUSEDVERTEX); - unuverts++; - } else { - // This vertex survived. - if (b->nobisect && (b->nobisect_param > 1)) { // -Y2 - // Queue it if it is a Steiner point. - if ((pointtype(pa) == FREESEGVERTEX) || - (pointtype(pa) == FREEFACETVERTEX) || - (pointtype(pa) == FREEVOLVERTEX)) { - subvertstack->newindex((void **) &parypt); - *parypt = pa; - } - } - } - } - pa = pointtraverse(); - } - - if (b->verbose) { - if (unuverts > delvertcount) { - if (delsteinercount > 0l) { - if (unuverts > (delvertcount + delsteinercount)) { - printf(" Removed %ld exterior input vertices.\n", - unuverts - delvertcount - delsteinercount); - } - printf(" Removed %ld exterior Steiner vertices.\n", delsteinercount); - } else { - printf(" Removed %ld exterior input vertices.\n", - unuverts - delvertcount); - } - } - } - - - // Connect new hull tets. - for (i = 0; i < hullarray->objects; i++) { - parytet = (triface *) fastlookup(hullarray, i); - hulltet = *parytet; - for (j = 0; j < 3; j++) { - esym(hulltet, neightet); - if (neightet.tet[neightet.ver & 3] == NULL) { - tspivot(hulltet, checksh); - assert(checksh.sh != NULL); - // Get the next subface in the same face ring of checksh. It must - // exist, otherwise, checksh is either a dangling subface (which - // should be removed already), or it is not a hull face. - sfnext(checksh, casingout); - assert(casingout.sh != NULL); - // Go to the hull side. - sesymself(casingout); - stpivot(casingout, casface); - assert(ishulltet(casface)); - esymself(casface); - assert(casface.tet[casface.ver & 3] == NULL); - // Bond the two hull tets together. - bond(neightet, casface); - } - enextself(hulltet); - } - } - -} else { // '-c' option is set. - - - long bak_subface_count = subfaces->items; - long bak_segment_count = subsegs->items; - - // In this case, we regard every hull face/edge is a subface/segment. - for (i = 0; i < tetarray->objects; i++) { - parytet = (triface *) fastlookup(tetarray, i); - // Only need the hull tet to find convex hull faces. - if ((point) parytet->tet[7] == dummypoint) { - hulltet.tet = parytet->tet; - hulltet.ver = 3; // The hull face. - tspivot(hulltet, checksh); // SELF_CHECK - if (checksh.sh == NULL) { - // Create a subface. - makeshellface(subfaces, &checksh); - pa = org(hulltet); - pb = dest(hulltet); - pc = apex(hulltet); - setsorg(checksh, pa); - setsdest(checksh, pb); - setsapex(checksh, pc); - // Create the point-to-subface map. - sptr = sencode(checksh); - setpoint2sh(pa, sptr); - setpoint2sh(pb, sptr); - setpoint2sh(pc, sptr); - } - // Insert this subface. - // Note: Even the subface is already exist, it may have been - // disconnected from its adjacent tets. - tsbond(hulltet, checksh); - fsym(hulltet, neightet); - assert(infected(neightet)); - sesymself(checksh); - tsbond(neightet, checksh); - sesymself(checksh); - // Create three segments. + // Connect new hull tets. + for (i = 0; i < newhullfacearray->objects; i++) { + parytet = (triface *) fastlookup(newhullfacearray, i); + fsym(*parytet, neightet); + // Get the new hull tet. + fsym(neightet, hulltet); for (j = 0; j < 3; j++) { - tsspivot1(hulltet, checkseg); - if (checkseg.sh == NULL) { - // Create a segment. - makeshellface(subsegs, &checkseg); - pa = org(hulltet); - pb = dest(hulltet); - setshvertices(checkseg, pa, pb, NULL); - // Insert the segment into the mesh. - tetloop = hulltet; - pc = apex(hulltet); - checksh.sh = NULL; + esym(hulltet, casface); + if (casface.tet[casface.ver & 3] == NULL) { + // Since the boundary of the domain may not be a manifold, we + // find the adjacent hull face by traversing the tets in the + // exterior (which are all infected tets). + neightet = *parytet; while (1) { - tssbond1(tetloop, checkseg); - tspivot(tetloop, checksh); - if (checksh.sh != NULL) { - ssbond1(checksh, checkseg); - sbond1(checkseg, checksh); - } - fnextself(tetloop); - if (apex(tetloop) == pc) break; + fnextself(neightet); + if (!infected(neightet)) break; } - sstbond1(checkseg, tetloop); + if (!ishulltet(neightet)) { + // An interior tet. Get the new hull tet. + fsymself(neightet); + esymself(neightet); + } + // Bond them together. + bond(casface, neightet); } enextself(hulltet); + enextself(*parytet); + } // j + } // i + + if (subfacstack->objects > 0l) { + // Remove all subfaces which do not attach to any tetrahedron. + // Segments which are not attached to any subfaces and tets + // are deleted too. + face casingout, casingin; + long delsegcount = 0l; + + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + if (i == 0) { + if (b->verbose) { + printf("Warning: Removing an open face (%d, %d, %d)\n", + pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), + pointmark(sapex(*parysh))); + } + } + // Dissolve this subface from face links. + for (j = 0; j < 3; j++) { + spivot(*parysh, casingout); + sspivot(*parysh, checkseg); + if (casingout.sh != NULL) { + casingin = casingout; + while (1) { + spivot(casingin, checksh); + if (checksh.sh == parysh->sh) break; + casingin = checksh; + } + if (casingin.sh != casingout.sh) { + // Update the link: ... -> casingin -> casingout ->... + sbond1(casingin, casingout); + } else { + // Only one subface at this edge is left. + sdissolve(casingout); + } + if (checkseg.sh != NULL) { + // Make sure the segment does not connect to a dead one. + ssbond(casingout, checkseg); + } + } else { + if (checkseg.sh != NULL) { + // The segment is also dead. + if (delsegcount == 0) { + if (b->verbose) { + printf("Warning: Removing a dangling segment (%d, %d)\n", + pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); + } + } + shellfacedealloc(subsegs, checkseg.sh); + delsegcount++; + } + } + senextself(*parysh); + } // j + // Delete this subface. + shellfacedealloc(subfaces, parysh->sh); + } // i + if (b->verbose) { + printf(" Deleted %ld subfaces.\n", subfacstack->objects); + if (delsegcount > 0) { + printf(" Deleted %ld segments.\n", delsegcount); + } } - // Save this hull tet in list. - hullarray->newindex((void **) &parytet1); - *parytet1 = hulltet; + subfacstack->restart(); + } // if (subfacstack->objects > 0l) + + if (cavetetvertlist->objects > 0l) { + // Some vertices may lie in exterior. Marke them as UNUSEDVERTEX. + long delvertcount = unuverts; + long delsteinercount = 0l; + + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + decode(point2tet(*parypt), neightet); + if (infected(neightet)) { + // Found an exterior vertex. + if (pointmark(*parypt) > + (in->numberofpoints - (in->firstnumber ? 0 : 1))) { + // A Steiner point. + if (pointtype(*parypt) == FREESEGVERTEX) { + st_segref_count--; + } else if (pointtype(*parypt) == FREEFACETVERTEX) { + st_facref_count--; + } else { + assert(pointtype(*parypt) == FREEVOLVERTEX); + st_volref_count--; + } + delsteinercount++; + if (steinerleft > 0) steinerleft++; + } + setpointtype(*parypt, UNUSEDVERTEX); + unuverts++; + } + } + + if (b->verbose) { + if (unuverts > delvertcount) { + if (delsteinercount > 0l) { + if (unuverts > (delvertcount + delsteinercount)) { + printf(" Removed %ld exterior input vertices.\n", + unuverts - delvertcount - delsteinercount); + } + printf(" Removed %ld exterior Steiner vertices.\n", + delsteinercount); + } else { + printf(" Removed %ld exterior input vertices.\n", + unuverts - delvertcount); + } + } + } + cavetetvertlist->restart(); + // Comment: 'subvertstack' will be cleaned in routine + // suppresssteinerpoints(). + } // if (cavetetvertlist->objects > 0l) + + // Update the hull size. + hullsize += (newhullfacearray->objects - hullarray->objects); + + // Delete all exterior tets and old hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + tetrahedrondealloc(parytet->tet); } - } // i + tetarray->restart(); - hullsize += hullarray->objects; - - if (subfacstack->objects > 0) { - // Uninfect the collected exterior subfaces. - for (i = 0; i < subfacstack->objects; i++) { - parysh = (face *) fastlookup(subfacstack, i); - suninfect(*parysh); + for (i = 0; i < hullarray->objects; i++) { + parytet = (triface *) fastlookup(hullarray, i); + tetrahedrondealloc(parytet->tet); } - } + hullarray->restart(); + + delete newhullfacearray; + } // if (!b->convex && (tetarray->objects > 0l)) + + if (b->convex && (tetarray->objects > 0l)) { // With -c option + // In this case, all exterior tets get a region marker '-1'. + assert(b->regionattrib > 0); // -A option must be enabled. + int attrnum = numelemattrib - 1; + + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + setelemattribute(parytet->tet, attrnum, -1); + } + tetarray->restart(); - if (b->regionattrib) { - // Only the hull tets need to be uninfected. for (i = 0; i < hullarray->objects; i++) { parytet = (triface *) fastlookup(hullarray, i); uninfect(*parytet); } - } else { - // Uninfect all collected tets. - for (i = 0; i < tetarray->objects; i++) { - parytet = (triface *) fastlookup(tetarray, i); - uninfect(*parytet); + hullarray->restart(); + + if (subfacstack->objects > 0l) { + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + suninfect(*parysh); + } + subfacstack->restart(); } - } - tetarray->restart(); + if (cavetetvertlist->objects > 0l) { + cavetetvertlist->restart(); + } + } // if (b->convex && (tetarray->objects > 0l)) - if (b->verbose) { - printf(" Created %ld convex hull boundary faces.\n", - subfaces->items - bak_subface_count); - printf(" Created %ld convex hull boundary edges.\n", - subsegs->items - bak_segment_count); - } - -} // if (b->convex) - - - // Set region attributes (the -A option). - if (b->regionattrib) { + if (b->regionattrib) { // With -A option. if (!b->quiet) { printf("Spreading region attributes.\n"); } + REAL volume; + int attr, maxattr = 0; // Choose a small number here. + int attrnum = numelemattrib - 1; + // Comment: The element region marker is at the end of the list of + // the element attributes. + int regioncount = 0; // If has user-defined region attributes. if (in->numberofregions > 0) { @@ -580,6 +477,9 @@ if (!b->convex) { for (i = 0; i < 5 * in->numberofregions; i += 5) { if (regiontets[i/5].tet != NULL) { attr = (int) in->regionlist[i + 3]; + if (attr > maxattr) { + maxattr = attr; + } volume = in->regionlist[i + 4]; tetarray->restart(); // Re-use this array. infect(regiontets[i/5]); @@ -598,8 +498,7 @@ if (!b->convex) { // Is the adjacent already checked? if (!infected(neightet)) { // Is this side protected by a subface? - tspivot(neightet, checksh); - if (checksh.sh == NULL) { + if (!issubface(neightet)) { infect(neightet); tetarray->newindex((void **) &parytet); *parytet = neightet; @@ -633,8 +532,7 @@ if (!b->convex) { // Is the adjacent tet already checked? if (!infected(neightet)) { // Is this side protected by a subface? - tspivot(neightet, checksh); - if (checksh.sh == NULL) { + if (!issubface(neightet)) { infect(neightet); tetarray->newindex((void **) &parytet); *parytet = neightet; @@ -657,49 +555,49 @@ if (!b->convex) { tetloop.tet = tetrahedrontraverse(); } - if (b->verbose) { - assert(regioncount > 0); + //assert(regioncount > 0); if (regioncount > 1) { printf(" Found %d subdomains.\n", regioncount); } else { - printf(" Found 1 domain.\n"); + printf(" Found %d domain.\n", regioncount); } } } // if (b->regionattrib) - if (b->regionattrib && (in->numberofregions > 0)) { // If has -A option. + if (regiontets != NULL) { delete [] regiontets; } delete tetarray; delete hullarray; -if (!b->convex) { + if (!b->convex) { // No -c option + // The mesh is non-convex now. + nonconvex = 1; - // The mesh is non-convex now. - nonconvex = 1; - - - // Push all hull tets into 'flipstack'. - tetrahedrons->traversalinit(); - tetloop.ver = 11; // The face opposite to dummypoint. - tetloop.tet = alltetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - if ((point) tetloop.tet[7] == dummypoint) { - flippush(flipstack, &tetloop); - } + // Push all hull tets into 'flipstack'. + tetrahedrons->traversalinit(); + tetloop.ver = 11; // The face opposite to dummypoint. tetloop.tet = alltetrahedrontraverse(); - } + while (tetloop.tet != (tetrahedron *) NULL) { + if ((point) tetloop.tet[7] == dummypoint) { + fsym(tetloop, neightet); + flippush(flipstack, &neightet); + } + tetloop.tet = alltetrahedrontraverse(); + } - // Peel "slivers" off the hull. - lawsonflip3d(NULL, 4, 1, 0, 0); - - if (b->verbose && (opt_sliver_peels > 0l)) { - printf(" Peeled %ld hull slivers.\n", opt_sliver_peels); - } - -} + flipconstraints fc; + fc.enqflag = 2; + long sliver_peel_count = lawsonflip3d(&fc); + if (sliver_peel_count > 0l) { + if (b->verbose) { + printf(" Removed %ld hull slivers.\n", sliver_peel_count); + } + } + unflipqueue->restart(); + } // if (!b->convex) } /////////////////////////////////////////////////////////////////////////////// @@ -723,20 +621,33 @@ void tetgenmesh::reconstructmesh() REAL angtol, ang; int eextras, marker = 0; int bondflag; + int t1ver; int idx, i, j, k; if (!b->quiet) { printf("Reconstructing mesh ...\n"); } - // Default assume the mesh is non-convex. - nonconvex = 1; - // Create a map from indices to vertices. + if (b->convex) { // -c option. + // Assume the mesh is convex. Exterior tets have region attribute -1. + assert(in->numberoftetrahedronattributes > 0); + } else { + // Assume the mesh is non-convex. + nonconvex = 1; + } + + // Create a map from indices to vertices. makeindex2pointmap(idx2verlist); + // 'idx2verlist' has length 'in->numberofpoints + 1'. + if (in->firstnumber == 1) { + idx2verlist[0] = dummypoint; // Let 0th-entry be dummypoint. + } // Allocate an array that maps each vertex to its adjacent tets. ver2tetarray = new tetrahedron[in->numberofpoints + 1]; - for (i = 0; i < in->numberofpoints; i++) { + //for (i = 0; i < in->numberofpoints + 1; i++) { + for (i = in->firstnumber; i < in->numberofpoints + in->firstnumber; i++) { + setpointtype(idx2verlist[i], VOLVERTEX); // initial type. ver2tetarray[i] = NULL; } @@ -746,7 +657,6 @@ void tetgenmesh::reconstructmesh() idx = i * in->numberofcorners; for (j = 0; j < 4; j++) { p[j] = idx2verlist[in->tetrahedronlist[idx++]]; - setpointtype(p[j], VOLVERTEX); // initial type. } // Check the orientation. ori = orient3d(p[0], p[1], p[2], p[3]); @@ -794,7 +704,6 @@ void tetgenmesh::reconstructmesh() p[2] = apex(tetloop); // c prevchktet = tetloop; do { - assert(checktet.ver < 4); // SELF_CHECK q[0] = org(checktet); // a' q[1] = dest(checktet); // b' q[2] = apex(checktet); // c' @@ -980,51 +889,54 @@ void tetgenmesh::reconstructmesh() } // i } // if (in->trifacelist) - // Indentify subfaces from the mesh. - // Create subfaces for hull faces (if they're not subface yet) and - // interior faces which separate two different materials. - eextras = in->numberoftetrahedronattributes; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { - tspivot(tetloop, neighsh); - if (neighsh.sh == NULL) { - bondflag = 0; - fsym(tetloop, checktet); - if (ishulltet(checktet)) { - bondflag = 1; // A hull face. - } else { - if (eextras > 0) { - if (elemattribute(tetloop.tet, eextras - 1) != - elemattribute(checktet.tet, eextras - 1)) { - bondflag = 1; // An interior interface. - } + // Indentify subfaces from the mesh. + // Create subfaces for hull faces (if they're not subface yet) and + // interior faces which separate two different materials. + eextras = in->numberoftetrahedronattributes; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + tspivot(tetloop, neighsh); + if (neighsh.sh == NULL) { + bondflag = 0; + fsym(tetloop, checktet); + if (ishulltet(checktet)) { + // A hull face. + if (!b->convex) { + bondflag = 1; // Insert a hull subface. + } + } else { + if (eextras > 0) { + if (elemattribute(tetloop.tet, eextras - 1) != + elemattribute(checktet.tet, eextras - 1)) { + bondflag = 1; // Insert an interior interface. } } - if (bondflag) { - // Create a new subface. - makeshellface(subfaces, &subloop); - p[0] = org(tetloop); - p[1] = dest(tetloop); - p[2] = apex(tetloop); - setshvertices(subloop, p[0], p[1], p[2]); - // Create the point-to-subface map. - sptr = sencode(subloop); - for (j = 0; j < 3; j++) { - setpointtype(p[j], FACETVERTEX); // initial type. - setpoint2sh(p[j], sptr); - } - setshellmark(subloop, 0); // Default marker. - // Insert the subface into the mesh. - tsbond(tetloop, subloop); - sesymself(subloop); - tsbond(checktet, subloop); - } // if (bondflag) - } // if (neighsh.sh == NULL) - } - tetloop.tet = tetrahedrontraverse(); + } + if (bondflag) { + // Create a new subface. + makeshellface(subfaces, &subloop); + p[0] = org(tetloop); + p[1] = dest(tetloop); + p[2] = apex(tetloop); + setshvertices(subloop, p[0], p[1], p[2]); + // Create the point-to-subface map. + sptr = sencode(subloop); + for (j = 0; j < 3; j++) { + setpointtype(p[j], FACETVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(subloop, 0); // Default marker. + // Insert the subface into the mesh. + tsbond(tetloop, subloop); + sesymself(subloop); + tsbond(checktet, subloop); + } // if (bondflag) + } // if (neighsh.sh == NULL) } + tetloop.tet = tetrahedrontraverse(); + } // Connect subfaces together. subfaces->traversalinit(); @@ -1058,11 +970,8 @@ void tetgenmesh::reconstructmesh() subloop.sh = shellfacetraverse(subfaces); } - //if (b->verbose) { - // printf(" Created %ld subfaces.\n", subfaces->items); - //} - // Segments will be introudced. + // Segments will be introduced. if (in->edgelist != NULL) { // A .edge file is given. It may contain boundary edges. Insert them. for (i = 0; i < in->numberofedges; i++) { @@ -1124,90 +1033,187 @@ void tetgenmesh::reconstructmesh() } // i } // if (in->edgelist) - // Identify segments from the mesh. - // Create segments for non-manifold edges (which are shared by more - // than two subfaces), and for non-coplanar edges, i.e., two subfaces - // form an dihedral angle > 'b->facet_ang_tol' (degree). - angtol = b->facet_ang_tol / 180.0 * PI; - subfaces->traversalinit(); - subloop.shver = 0; - subloop.sh = shellfacetraverse(subfaces); - while (subloop.sh != (shellface *) NULL) { - for (i = 0; i < 3; i++) { - sspivot(subloop, segloop); - if (segloop.sh == NULL) { - // Check if this edge is a segment. - bondflag = 0; - // Counter the number of subfaces at this edge. - idx = 0; - nextsh = subloop; - while (1) { - idx++; - spivotself(nextsh); - if (nextsh.sh == subloop.sh) break; - } - if (idx != 2) { - // It's a non-manifold edge. Insert a segment. + // Identify segments from the mesh. + // Create segments for non-manifold edges (which are shared by more + // than two subfaces), and for non-coplanar edges, i.e., two subfaces + // form an dihedral angle > 'b->facet_ang_tol' (degree). + angtol = b->facet_ang_tol / 180.0 * PI; + subfaces->traversalinit(); + subloop.shver = 0; + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != (shellface *) NULL) { + for (i = 0; i < 3; i++) { + sspivot(subloop, segloop); + if (segloop.sh == NULL) { + // Check if this edge is a segment. + bondflag = 0; + // Counter the number of subfaces at this edge. + idx = 0; + nextsh = subloop; + while (1) { + idx++; + spivotself(nextsh); + if (nextsh.sh == subloop.sh) break; + } + if (idx != 2) { + // It's a non-manifold edge. Insert a segment. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + bondflag = 1; + } else { + spivot(subloop, neighsh); + if (shellmark(subloop) != shellmark(neighsh)) { + // It's an interior interface. Insert a segment. p[0] = sorg(subloop); p[1] = sdest(subloop); bondflag = 1; } else { - // Check the dihedral angle formed by the two subfaces. - spivot(subloop, neighsh); - p[0] = sorg(subloop); - p[1] = sdest(subloop); - p[2] = sapex(subloop); - p[3] = sapex(neighsh); - ang = facedihedral(p[0], p[1], p[2], p[3]); - if (ang > PI) ang = 2 * PI - ang; - if (ang < angtol) { - bondflag = 1; + if (!b->convex) { + // Check the dihedral angle formed by the two subfaces. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + p[2] = sapex(subloop); + p[3] = sapex(neighsh); + ang = facedihedral(p[0], p[1], p[2], p[3]); + if (ang > PI) ang = 2 * PI - ang; + if (ang < angtol) { + bondflag = 1; + } } } - if (bondflag) { - // Create a new subface. - makeshellface(subsegs, &segloop); - setshvertices(segloop, p[0], p[1], NULL); - // Create the point-to-segment map. - sptr = sencode(segloop); - for (j = 0; j < 2; j++) { - setpointtype(p[j], RIDGEVERTEX); // initial type. - setpoint2sh(p[j], sptr); + } + if (bondflag) { + // Create a new segment. + makeshellface(subsegs, &segloop); + setshvertices(segloop, p[0], p[1], NULL); + // Create the point-to-segment map. + sptr = sencode(segloop); + for (j = 0; j < 2; j++) { + setpointtype(p[j], RIDGEVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(segloop, 0); // Initially has no marker. + // Insert the subface into the mesh. + stpivot(subloop, tetloop); + q[2] = apex(tetloop); + while (1) { + tssbond1(tetloop, segloop); + tspivot(tetloop, neighsh); + if (neighsh.sh != NULL) { + ssbond1(neighsh, segloop); } - setshellmark(segloop, marker); - // Insert the subface into the mesh. - stpivot(subloop, tetloop); - q[2] = apex(tetloop); - while (1) { - tssbond1(tetloop, segloop); - tspivot(tetloop, neighsh); - if (neighsh.sh != NULL) { - ssbond1(neighsh, segloop); - } - fnextself(tetloop); - if (apex(tetloop) == q[2]) break; - } // while (1) - // Remember an adjacent tet for this segment. - sstbond1(segloop, tetloop); - sbond1(segloop, subloop); - } // if (bondflag) - } // if (neighsh.sh == NULL) - senextself(subloop); - } - subloop.sh = shellfacetraverse(subfaces); - } + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; + } // while (1) + // Remember an adjacent tet for this segment. + sstbond1(segloop, tetloop); + sbond1(segloop, subloop); + } // if (bondflag) + } // if (neighsh.sh == NULL) + senextself(subloop); + } // i + subloop.sh = shellfacetraverse(subfaces); + } // Remember the number of input segments. insegments = subsegs->items; - //if (b->verbose) { - // printf(" Created %ld segments.\n", subsegs->items); - //} + if (!b->nobisect || checkconstraints) { + // Mark Steiner points on segments and facets. + // - all vertices which remaining type FEACTVERTEX become + // Steiner points in facets (= FREEFACERVERTEX). + // - vertices on segment need to be checked. + face* segperverlist; + int* idx2seglist; + face parentseg, nextseg; + verttype vt; + REAL area, len, l1, l2; + int fmarker; + + makepoint2submap(subsegs, idx2seglist, segperverlist); + + points->traversalinit(); + point ptloop = pointtraverse(); + while (ptloop != NULL) { + vt = pointtype(ptloop); + if (vt == VOLVERTEX) { + setpointtype(ptloop, FREEVOLVERTEX); + st_volref_count++; + } else if (vt == FACETVERTEX) { + setpointtype(ptloop, FREEFACETVERTEX); + st_facref_count++; + } else if (vt == RIDGEVERTEX) { + idx = pointmark(ptloop) - in->firstnumber; + if ((idx2seglist[idx + 1] - idx2seglist[idx]) == 2) { + i = idx2seglist[idx]; + parentseg = segperverlist[i]; + nextseg = segperverlist[i + 1]; + sesymself(nextseg); + p[0] = sorg(nextseg); + p[1] = sdest(parentseg); + // Check if three points p[0], ptloop, p[2] are (nearly) collinear. + len = distance(p[0], p[1]); + l1 = distance(p[0], ptloop); + l2 = distance(ptloop, p[1]); + if (((l1 + l2 - len) / len) < b->epsilon) { + // They are (nearly) collinear. + setpointtype(ptloop, FREESEGVERTEX); + // Connect nextseg and parentseg together at ptloop. + senextself(nextseg); + senext2self(parentseg); + sbond(nextseg, parentseg); + st_segref_count++; + } + } + } + ptloop = pointtraverse(); + } + + // Are there area constraints? + if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { + // Set maximum area constraints on facets. + for (i = 0; i < in->numberoffacetconstraints; i++) { + fmarker = (int) in->facetconstraintlist[i * 2]; + area = in->facetconstraintlist[i * 2 + 1]; + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + if (shellmark(subloop) == fmarker) { + setareabound(subloop, area); + } + subloop.sh = shellfacetraverse(subfaces); + } + } + } + + // Are there length constraints? + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + // Set maximum length constraints on segments. + int e1, e2; + for (i = 0; i < in->numberofsegmentconstraints; i++) { + e1 = (int) in->segmentconstraintlist[i * 3]; + e2 = (int) in->segmentconstraintlist[i * 3 + 1]; + len = in->segmentconstraintlist[i * 3 + 2]; + // Search for edge [e1, e2]. + idx = e1 - in->firstnumber; + for (j = idx2seglist[idx]; j < idx2seglist[idx + 1]; j++) { + parentseg = segperverlist[j]; + if (pointmark(sdest(parentseg)) == e2) { + setareabound(parentseg, len); + break; + } + } + } + } + + delete [] idx2seglist; + delete [] segperverlist; + } + // Set global flags. checksubsegflag = 1; checksubfaceflag = 1; - //nonconvex = 1; delete [] idx2verlist; delete [] ver2tetarray; @@ -1229,33 +1235,37 @@ int tetgenmesh::scoutpoint(point searchpt, triface *searchtet, int randflag) { point pa, pb, pc, pd; enum locateresult loc = OUTSIDE; - REAL vol, ori1, ori2, ori3, ori4; - int iter; + REAL vol, ori1, ori2 = 0, ori3 = 0, ori4 = 0; + int t1ver; - if (searchtet->tet == NULL) { - *searchtet = recenttet; + + // Randomly select a good starting tet. + if (randflag) { + randomsample(searchpt, searchtet); + } else { + if (searchtet->tet == NULL) { + *searchtet = recenttet; + } } + loc = locate(searchpt, searchtet); - iter = 0; - while (1) { - // Randonmly select a good starting tet. - if (randflag) { - randomsample(searchpt, searchtet); + if (loc == OUTSIDE) { + if (b->convex) { // -c option + // The point lies outside of the convex hull. + return (int) loc; } - loc = locate(searchpt, searchtet, 0); - if (loc == OUTSIDE) { - // Not found. This happens when the mesh is not convex. - if (!randflag) break; - iter++; - if (iter > 3) { - searchtet->tet = NULL; - break; - } - } else { - // Found the point. - break; + // Test if it lies nearly on the hull face. + // Reuse vol, ori1. + pa = org(*searchtet); + pb = dest(*searchtet); + pc = apex(*searchtet); + vol = triarea(pa, pb, pc); + ori1 = orient3dfast(pa, pb, pc, searchpt); + if (fabs(ori1 / vol) < b->epsilon) { + loc = ONFACE; // On face (or on edge, or on vertex). + fsymself(*searchtet); } - } // while (1) + } if (loc != OUTSIDE) { // Round the result of location. @@ -1263,11 +1273,11 @@ int tetgenmesh::scoutpoint(point searchpt, triface *searchtet, int randflag) pb = dest(*searchtet); pc = apex(*searchtet); pd = oppo(*searchtet); - vol = orient3d(pa, pb, pc, pd); - ori1 = orient3d(pa, pb, pc, searchpt); - ori2 = orient3d(pb, pa, pd, searchpt); - ori3 = orient3d(pc, pb, pd, searchpt); - ori4 = orient3d(pa, pc, pd, searchpt); + vol = orient3dfast(pa, pb, pc, pd); + ori1 = orient3dfast(pa, pb, pc, searchpt); + ori2 = orient3dfast(pb, pa, pd, searchpt); + ori3 = orient3dfast(pc, pb, pd, searchpt); + ori4 = orient3dfast(pa, pc, pd, searchpt); if (fabs(ori1 / vol) < b->epsilon) ori1 = 0; if (fabs(ori2 / vol) < b->epsilon) ori2 = 0; if (fabs(ori3 / vol) < b->epsilon) ori3 = 0; @@ -1282,30 +1292,31 @@ int tetgenmesh::scoutpoint(point searchpt, triface *searchtet, int randflag) pc = apex(*searchtet); pd = oppo(*searchtet); - vol = orient3d(pa, pb, pc, pd); - assert(vol < 0); // vol != 0 + vol = orient3dfast(pa, pb, pc, pd); + if (vol < 0) { + ori1 = orient3dfast(pa, pb, pc, searchpt); + if (fabs(ori1 / vol) < b->epsilon) ori1 = 0; // Rounding. + if (ori1 <= 0) { + ori2 = orient3dfast(pb, pa, pd, searchpt); + if (fabs(ori2 / vol) < b->epsilon) ori2 = 0; + if (ori2 <= 0) { + ori3 = orient3dfast(pc, pb, pd, searchpt); + if (fabs(ori3 / vol) < b->epsilon) ori3 = 0; + if (ori3 <= 0) { + ori4 = orient3dfast(pa, pc, pd, searchpt); + if (fabs(ori4 / vol) < b->epsilon) ori4 = 0; + if (ori4 <= 0) { + // Found the tet. Return its location. + break; + } // ori4 + } // ori3 + } // ori2 + } // ori1 + } - ori1 = orient3d(pa, pb, pc, searchpt); - if (fabs(ori1 / vol) < b->epsilon) ori1 = 0; // Rounding. - if (ori1 <= 0) { - ori2 = orient3d(pb, pa, pd, searchpt); - if (fabs(ori2 / vol) < b->epsilon) ori2 = 0; - if (ori2 <= 0) { - ori3 = orient3d(pc, pb, pd, searchpt); - if (fabs(ori3 / vol) < b->epsilon) ori3 = 0; - if (ori3 <= 0) { - ori4 = orient3d(pa, pc, pd, searchpt); - if (fabs(ori4 / vol) < b->epsilon) ori4 = 0; - if (ori4 <= 0) { - // Found the tet. Return its location. - break; - } // ori4 - } // ori3 - } // ori2 - } // ori1 - - searchtet->tet = bgm->tetrahedrontraverse(); + searchtet->tet = tetrahedrontraverse(); } // while (searchtet->tet != NULL) + nonregularcount++; // Re-use this counter. } if (searchtet->tet != NULL) { @@ -1412,11 +1423,11 @@ REAL tetgenmesh::getpointmeshsize(point searchpt, triface *searchtet, int iloc) if ((pts[0][pointmtrindex] > 0) && (pts[1][pointmtrindex] > 0) && (pts[2][pointmtrindex] > 0) && (pts[3][pointmtrindex] > 0)) { // P1 interpolation. - volume = orient3d(pts[0], pts[1], pts[2], pts[3]); - vol[0] = orient3d(searchpt, pts[1], pts[2], pts[3]); - vol[1] = orient3d(pts[0], searchpt, pts[2], pts[3]); - vol[2] = orient3d(pts[0], pts[1], searchpt, pts[3]); - vol[3] = orient3d(pts[0], pts[1], pts[2], searchpt); + volume = orient3dfast(pts[0], pts[1], pts[2], pts[3]); + vol[0] = orient3dfast(searchpt, pts[1], pts[2], pts[3]); + vol[1] = orient3dfast(pts[0], searchpt, pts[2], pts[3]); + vol[2] = orient3dfast(pts[0], pts[1], searchpt, pts[3]); + vol[3] = orient3dfast(pts[0], pts[1], pts[2], searchpt); for (i = 0; i < 4; i++) { wei[i] = fabs(vol[i] / volume); size += (wei[i] * pts[i][pointmtrindex]); @@ -1474,6 +1485,11 @@ void tetgenmesh::interpolatemeshsize() if (!b->quiet) { printf("Interpolating mesh size ...\n"); } + + long bak_nonregularcount = nonregularcount; + nonregularcount = 0l; // Count the number of (slow) global searches. + long baksmaples = bgm->samples; + bgm->samples = 3l; count = 0; // Count the number of interpolated points. // Interpolate sizes for all points in the current mesh. @@ -1510,45 +1526,220 @@ void tetgenmesh::interpolatemeshsize() if (b->verbose) { printf(" Interoplated %d points.\n", count); + if (nonregularcount > 0l) { + printf(" Performed %ld brute-force searches.\n", nonregularcount); + } printf(" Size rangle [%.17g, %.17g].\n", minval, maxval); } + + bgm->samples = baksmaples; + nonregularcount = bak_nonregularcount; } /////////////////////////////////////////////////////////////////////////////// // // // insertconstrainedpoints() Insert a list of points into the mesh. // // // +// Assumption: The bounding box of the insert point set should be no larger // +// than the bounding box of the mesh. (Required by point sorting). // +// // /////////////////////////////////////////////////////////////////////////////// +void tetgenmesh::insertconstrainedpoints(point *insertarray, int arylen, + int rejflag) +{ + triface searchtet, spintet; + face splitsh; + face splitseg; + insertvertexflags ivf; + flipconstraints fc; + int randflag = 0; + int t1ver; + int i; + + if (b->verbose) { + printf(" Inserting %d constrained points\n", arylen); + } + + if (b->no_sort) { // -b/1 option. + if (b->verbose) { + printf(" Using the input order.\n"); + } + } else { + if (b->verbose) { + printf(" Permuting vertices.\n"); + } + point swappoint; + int randindex; + srand(arylen); + for (i = 0; i < arylen; i++) { + randindex = rand() % (i + 1); + swappoint = insertarray[i]; + insertarray[i] = insertarray[randindex]; + insertarray[randindex] = swappoint; + } + if (b->brio_hilbert) { // -b1 option + if (b->verbose) { + printf(" Sorting vertices.\n"); + } + hilbert_init(in->mesh_dim); + int ngroup = 0; + brio_multiscale_sort(insertarray, arylen, b->brio_threshold, + b->brio_ratio, &ngroup); + } else { // -b0 option. + randflag = 1; + } // if (!b->brio_hilbert) + } // if (!b->no_sort) + + long bak_nonregularcount = nonregularcount; + nonregularcount = 0l; + long baksmaples = samples; + samples = 3l; // Use at least 3 samples. Updated in randomsample(). + + long bak_seg_count = st_segref_count; + long bak_fac_count = st_facref_count; + long bak_vol_count = st_volref_count; + + // Initialize the insertion parameters. + if (b->incrflip) { // -l option + // Use incremental flip algorithm. + ivf.bowywat = 0; + ivf.lawson = 1; + ivf.validflag = 0; // No need to validate the cavity. + fc.enqflag = 2; + } else { + // Use Bowyer-Watson algorithm. + ivf.bowywat = 1; + ivf.lawson = 0; + ivf.validflag = 1; // Validate the B-W cavity. + } + ivf.rejflag = rejflag; + ivf.chkencflag = 0; + ivf.sloc = (int) INSTAR; + ivf.sbowywat = 3; + ivf.splitbdflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + encseglist = new arraypool(sizeof(face), 8); + encshlist = new arraypool(sizeof(badface), 8); + + // Insert the points. + for (i = 0; i < arylen; i++) { + // Find the location of the inserted point. + // Do not use 'recenttet', since the mesh may be non-convex. + searchtet.tet = NULL; + ivf.iloc = scoutpoint(insertarray[i], &searchtet, randflag); + + // Decide the right type for this point. + setpointtype(insertarray[i], FREEVOLVERTEX); // Default. + splitsh.sh = NULL; + splitseg.sh = NULL; + if (ivf.iloc == (int) ONEDGE) { + if (issubseg(searchtet)) { + tsspivot1(searchtet, splitseg); + setpointtype(insertarray[i], FREESEGVERTEX); + //ivf.rejflag = 0; + } else { + // Check if it is a subface edge. + spintet = searchtet; + while (1) { + if (issubface(spintet)) { + tspivot(spintet, splitsh); + setpointtype(insertarray[i], FREEFACETVERTEX); + //ivf.rejflag |= 1; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + } + } else if (ivf.iloc == (int) ONFACE) { + if (issubface(searchtet)) { + tspivot(searchtet, splitsh); + setpointtype(insertarray[i], FREEFACETVERTEX); + //ivf.rejflag |= 1; + } + } + + // Now insert the point. + if (insertpoint(insertarray[i], &searchtet, &splitsh, &splitseg, &ivf)) { + if (flipstack != NULL) { + // There are queued faces. Use flips to recover Delaunayness. + lawsonflip3d(&fc); + // There may be unflippable edges. Ignore them. + unflipqueue->restart(); + } + // Update the Steiner counters. + if (pointtype(insertarray[i]) == FREESEGVERTEX) { + st_segref_count++; + } else if (pointtype(insertarray[i]) == FREEFACETVERTEX) { + st_facref_count++; + } else { + st_volref_count++; + } + } else { + // Point is not inserted. + //pointdealloc(insertarray[i]); + setpointtype(insertarray[i], UNUSEDVERTEX); + unuverts++; + encseglist->restart(); + encshlist->restart(); + } + } // i + + delete encseglist; + delete encshlist; + + if (b->verbose) { + printf(" Inserted %ld (%ld, %ld, %ld) vertices.\n", + st_segref_count + st_facref_count + st_volref_count - + (bak_seg_count + bak_fac_count + bak_vol_count), + st_segref_count - bak_seg_count, st_facref_count - bak_fac_count, + st_volref_count - bak_vol_count); + if (nonregularcount > 0l) { + printf(" Performed %ld brute-force searches.\n", nonregularcount); + } + } + + nonregularcount = bak_nonregularcount; + samples = baksmaples; +} + void tetgenmesh::insertconstrainedpoints(tetgenio *addio) { - triface searchtet, spintet; - face checksh, *splitsh; - face checkseg, *splitseg; - point newpt; - insertvertexflags ivf; + point *insertarray, newpt; REAL x, y, z, w; - int attribindex, mtrindex; - int randflag; - int count, index; - int loc; - int i, j; + int index, attribindex, mtrindex; + int arylen, i, j; if (!b->quiet) { printf("Inserting constrained points ...\n"); } - randflag = 1; // Randomly select start tet for point location. - count = 0; + insertarray = new point[addio->numberofpoints]; + arylen = 0; index = 0; attribindex = 0; mtrindex = 0; for (i = 0; i < addio->numberofpoints; i++) { + x = addio->pointlist[index++]; + y = addio->pointlist[index++]; + z = addio->pointlist[index++]; + // Test if this point lies inside the bounding box. + if ((x < xmin) || (x > xmax) || (y < ymin) || (y > ymax) || + (z < zmin) || (z > zmax)) { + if (b->verbose) { + printf("Warning: Point #%d lies outside the bounding box. Ignored\n", + i + in->firstnumber); + } + continue; + } makepoint(&newpt, UNUSEDVERTEX); - x = newpt[0] = addio->pointlist[index++]; - y = newpt[1] = addio->pointlist[index++]; - z = newpt[2] = addio->pointlist[index++]; + newpt[0] = x; + newpt[1] = y; + newpt[2] = z; // Read the point attributes. (Including point weights.) for (j = 0; j < addio->numberofpointattributes; j++) { newpt[3 + j] = addio->pointattributelist[attribindex++]; @@ -1574,88 +1765,231 @@ void tetgenmesh::insertconstrainedpoints(tetgenio *addio) newpt[3] = w; // Regular tetrahedralization. } } - - // Find the location of the inserted point. - searchtet.tet = NULL; - ivf.iloc = scoutpoint(newpt, &searchtet, randflag); - if (ivf.iloc != (int) OUTSIDE) { - // Found the point. - // Initialize the insertion parameters. - if (b->psc) { - ivf.bowywat = 0; // Do not enlarge the initial cavity. - ivf.validflag = 0; // Do not validate the initial cavity. - } else { - ivf.bowywat = 3; // Use the "Bowyer-Watson" algorithm to form cavity. - ivf.validflag = 1; // Validate the B-W cavity. - } - ivf.lawson = 3; // ??? - ivf.rejflag = 0; // ??? - ivf.chkencflag = 0; - ivf.sloc = ivf.iloc; - ivf.sbowywat = ivf.bowywat; // Surface mesh options. - ivf.splitbdflag = 1; - ivf.respectbdflag = 1; - ivf.assignmeshsize = b->metric; - - splitsh = NULL; - splitseg = NULL; - - // Set the right point type. - if (ivf.iloc == (int) ONEDGE) { - tsspivot1(searchtet, checkseg); - if (checkseg.sh != NULL) { - setpointtype(newpt, RIDGEVERTEX); - spivot(checkseg, checksh); - splitsh = &checksh; - splitseg = &checkseg; - } else { - // Check if it is a subface edge. - spintet = searchtet; - while (1) { - tspivot(spintet, checksh); - if (checksh.sh != NULL) { - setpointtype(newpt, FACETVERTEX); - splitsh = &checksh; - break; - } - fnextself(spintet); - if (spintet.tet == searchtet.tet) break; - } - } - } else if (ivf.iloc == (int) ONFACE) { - tspivot(searchtet, checksh); - if (checksh.sh != NULL) { - setpointtype(newpt, FACETVERTEX); - splitsh = &checksh; - } - } else { - setpointtype(newpt, VOLVERTEX); - } - - // Insert the vertex. - loc = insertvertex(newpt, &searchtet, splitsh, splitseg, &ivf); - - if (loc == ivf.iloc) { - // The point has been inserted. - lawsonflip3d(newpt, 4, 0, ivf.chkencflag, 0); - count++; - } else { - if (!b->quiet) { - printf("Warning: Failed to insert point #%d. Ignored.\n", i); - } - pointdealloc(newpt); - } - } else { - if (!b->quiet) { - printf("Warning: Can't locate add point #%d. Ignored.\n", i); - } - pointdealloc(newpt); - } + insertarray[arylen] = newpt; + arylen++; } // i - if (b->verbose) { - printf(" Inserted %d of %d vertices.\n", count, addio->numberofpoints); + // Insert the points. + int rejflag = 0; // Do not check encroachment. + if (b->metric) { // -m option. + rejflag |= 4; // Reject it if it lies in some protecting balls. } + + insertconstrainedpoints(insertarray, arylen, rejflag); + + delete [] insertarray; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// meshcoarsening() Deleting (selected) vertices. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::collectremovepoints(arraypool *remptlist) +{ + point ptloop, *parypt; + verttype vt; + + // If a mesh sizing function is given. Collect vertices whose mesh size + // is greater than its smallest edge length. + if (b->metric) { // -m option + REAL len, smlen; + int i; + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + if (ptloop[pointmtrindex] > 0) { + // Get the smallest edge length at this vertex. + getvertexstar(1, ptloop, cavetetlist, cavetetvertlist, NULL); + parypt = (point *) fastlookup(cavetetvertlist, 0); + smlen = distance(ptloop, *parypt); + for (i = 1; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + len = distance(ptloop, *parypt); + if (len < smlen) { + smlen = len; + } + } + cavetetvertlist->restart(); + cavetetlist->restart(); + if (smlen < ptloop[pointmtrindex]) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + } + ptloop = pointtraverse(); + } + if (b->verbose > 1) { + printf(" Coarsen %ld oversized points.\n", remptlist->objects); + } + } + + // If 'in->pointmarkerlist' exists, Collect vertices with markers '-1'. + if (in->pointmarkerlist != NULL) { + long bak_count = remptlist->objects; + points->traversalinit(); + ptloop = pointtraverse(); + int index = 0; + while (ptloop != NULL) { + if (index < in->numberofpoints) { + if (in->pointmarkerlist[index] == -1) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + } else { + // Remaining are not input points. Stop here. + break; + } + index++; + ptloop = pointtraverse(); + } + if (b->verbose > 1) { + printf(" Coarsen %ld marked points.\n", remptlist->objects - bak_count); + } + } // if (in->pointmarkerlist != NULL) + + if (b->coarsen_param > 0) { // -R1/# + // Remove a coarsen_percent number of interior points. + assert((b->coarsen_percent > 0) && (b->coarsen_percent <= 1.0)); + if (b->verbose > 1) { + printf(" Coarsen %g percent of interior points.\n", + b->coarsen_percent * 100.0); + } + arraypool *intptlist = new arraypool(sizeof(point *), 10); + // Count the total number of interior points. + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + vt = pointtype(ptloop); + if ((vt == VOLVERTEX) || (vt == FREEVOLVERTEX) || + (vt == FREEFACETVERTEX) || (vt == FREESEGVERTEX)) { + intptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + ptloop = pointtraverse(); + } + if (intptlist->objects > 0l) { + // Sort the list of points randomly. + point *parypt_i, swappt; + int randindex, i; + srand(intptlist->objects); + for (i = 0; i < intptlist->objects; i++) { + randindex = rand() % (i + 1); // randomnation(i + 1); + parypt_i = (point *) fastlookup(intptlist, i); + parypt = (point *) fastlookup(intptlist, randindex); + // Swap this two points. + swappt = *parypt_i; + *parypt_i = *parypt; + *parypt = swappt; + } + int remcount = (int) ((REAL) intptlist->objects * b->coarsen_percent); + // Return the first remcount points. + for (i = 0; i < remcount; i++) { + parypt_i = (point *) fastlookup(intptlist, i); + if (!pinfected(*parypt_i)) { + pinfected(*parypt_i); + remptlist->newindex((void **) &parypt); + *parypt = *parypt_i; + } + } + } + delete intptlist; + } + + // Unmark all collected vertices. + for (int i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + puninfect(*parypt); + } +} + +void tetgenmesh::meshcoarsening() +{ + arraypool *remptlist; + + if (!b->quiet) { + printf("Mesh coarsening ...\n"); + } + + // Collect the set of points to be removed + remptlist = new arraypool(sizeof(point *), 10); + collectremovepoints(remptlist); + + if (remptlist->objects == 0l) { + delete remptlist; + return; + } + + if (b->verbose) { + if (remptlist->objects > 0l) { + printf(" Removing %ld points...\n", remptlist->objects); + } + } + + point *parypt, *plastpt; + long ms = remptlist->objects; + int nit = 0; + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = -1; + autofliplinklevel = 1; // Init value. + int i; + + while (1) { + + if (b->verbose > 1) { + printf(" Removing points [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + remptlist->objects); + } + + // Remove the list of points. + for (i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + assert(pointtype(*parypt) != UNUSEDVERTEX); + if (removevertexbyflips(*parypt)) { + // Move the last entry to the current place. + plastpt = (point *) fastlookup(remptlist, remptlist->objects - 1); + *parypt = *plastpt; + remptlist->objects--; + i--; + } + } + + if (remptlist->objects > 0l) { + if (b->fliplinklevel >= 0) { + break; // We have tried all levels. + } + if (remptlist->objects == ms) { + nit++; + if (nit >= 3) { + // Do the last round with unbounded flip link level. + b->fliplinklevel = 100000; + } + } else { + ms = remptlist->objects; + if (nit > 0) { + nit--; + } + } + autofliplinklevel+=b->fliplinklevelinc; + } else { + // All points are removed. + break; + } + } // while (1) + + if (remptlist->objects > 0l) { + if (b->verbose) { + printf(" %ld points are not removed !\n", remptlist->objects); + } + } + + b->fliplinklevel = bak_fliplinklevel; + delete remptlist; } //// //// diff --git a/external/tetgen/src/refine.cxx b/external/tetgen/src/refine.cxx index 72064f008..e47878138 100644 --- a/external/tetgen/src/refine.cxx +++ b/external/tetgen/src/refine.cxx @@ -5,497 +5,199 @@ /////////////////////////////////////////////////////////////////////////////// // // -// marksharpsegments() Mark sharp segments. // +// makefacetverticesmap() Create a map from facet to its vertices. // // // -// A segment is SHARP if there are two facets intersecting at it with an // -// internal dihedral angle (*) less than an angle \theta. // -// // -// A theoretical value of \theta is arccos(1/3) \approx 70.54 degree. It is // -// possible to relax it in practice. Here we choose \theta = 65 degree. // -// // -// The minimum dihedral angle between facets (minfacetdihed) is calulcated. // +// All facets will be indexed (starting from 0). The map is saved in two // +// global arrays: 'idx2facetlist' and 'facetverticeslist'. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::marksharpsegments() +void tetgenmesh::makefacetverticesmap() { - triface adjtet; - face startsh, spinsh, neighsh; - face segloop, nextseg, prevseg; - point eorg, edest; - REAL ang, smallang; - bool issharp; - int sharpcount; - - // For storing extremely small dihedral angle. - face *parysh, *parysh1; - REAL exsmallang; - int exsharpcount; + arraypool *facetvertexlist, *vertlist, **paryvertlist; + face subloop, neighsh, *parysh, *parysh1; + point pa, *ppt, *parypt; + verttype vt; + int facetindex, totalvertices; int i, j, k; - if (b->verbose > 0) { - printf(" Marking sharp segments.\n"); + if (b->verbose) { + printf(" Creating the facet vertices map.\n"); } - minfacetdihed = PI; - smallang = 65.0 * PI / 180.0; // 65 degree. - exsmallang = 5.0 * PI / 180.0; // 5 degree. - sharpcount = exsharpcount = 0; + facetvertexlist = new arraypool(sizeof(arraypool *), 10); + facetindex = totalvertices = 0; - // A segment s may have been split into many subsegments. Operate the one - // which contains the origin of s. Then mark the rest of subsegments. - subsegs->traversalinit(); - segloop.sh = shellfacetraverse(subsegs); - while (segloop.sh != (shellface *) NULL) { - segloop.shver = 0; - senext2(segloop, prevseg); - spivotself(prevseg); - if (prevseg.sh == NULL) { - // Operate on this seg s. - issharp = false; - spivot(segloop, startsh); - if (startsh.sh != NULL) { - // First check if two facets form an acute dihedral angle at s. - eorg = sorg(segloop); - edest = sdest(segloop); - spinsh = startsh; - while (1) { - if (sorg(spinsh) != eorg) sesymself(spinsh); - // Only do test when the spinsh is faceing inward. - stpivot(spinsh, adjtet); - if (adjtet.tet != NULL) { - if (!ishulltet(adjtet)) { - // Get the subface on the adjacent facet. - spivot(spinsh, neighsh); - // Do not calculate if it is self-bonded. - if ((neighsh.sh != NULL) && (neighsh.sh != spinsh.sh)) { - // Calculate the dihedral angle between the two subfaces. - ang = facedihedral(eorg, edest, sapex(spinsh), sapex(neighsh)); - // Only do check if a sharp angle has not been found. - if (!issharp) issharp = (ang < smallang); - // Remember the smallest facet dihedral angle. - minfacetdihed = minfacetdihed < ang ? minfacetdihed : ang; - if (ang < exsmallang) { - // It's an extremely small dihedral angle. - // Mark the two facets. - // To avoid too many Steiner points, do not refine them. - if (shelltype(spinsh) != SHARP) { - setshelltype(spinsh, SHARP); - cavesegshlist->newindex((void **) &parysh); - *parysh = spinsh; - } - if (shelltype(neighsh) != SHARP) { - setshelltype(neighsh, SHARP); - cavesegshlist->newindex((void **) &parysh); - *parysh = neighsh; - } - exsharpcount++; + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + if (!sinfected(subloop)) { + // A new facet. Create its vertices list. + vertlist = new arraypool(sizeof(point *), 8); + ppt = (point *) &(subloop.sh[3]); + for (k = 0; k < 3; k++) { + vt = pointtype(ppt[k]); + if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + pinfect(ppt[k]); + vertlist->newindex((void **) &parypt); + *parypt = ppt[k]; + } + } + sinfect(subloop); + caveshlist->newindex((void **) &parysh); + *parysh = subloop; + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + setfacetindex(*parysh, facetindex); + for (j = 0; j < 3; j++) { + if (!isshsubseg(*parysh)) { + spivot(*parysh, neighsh); + assert(neighsh.sh != NULL); + if (!sinfected(neighsh)) { + pa = sapex(neighsh); + if (!pinfected(pa)) { + vt = pointtype(pa); + if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + pinfect(pa); + vertlist->newindex((void **) &parypt); + *parypt = pa; } } - } - } - // Go to the next facet. - spivotself(spinsh); - if (spinsh.sh == NULL) break; // A single subface case. - if (spinsh.sh == startsh.sh) break; - } - } // if (startsh.sh != NULL) - if (issharp) { - if (b->verbose > 2) { - printf(" Mark a sharp segment (%d, %d).\n", - pointmark(eorg), pointmark(edest)); - } - setshelltype(segloop, SHARP); - // The endpoint of this segment is acute. - if (pointtype(eorg) == RIDGEVERTEX) { - setpointtype(eorg, ACUTEVERTEX); - } else { - assert(pointtype(eorg) == ACUTEVERTEX); // SELF_CHECK - } - // Set the type for all subsegments at forwards. - edest = sdest(segloop); - senext(segloop, nextseg); - spivotself(nextseg); - while (nextseg.sh != NULL) { - setshelltype(nextseg, SHARP); - // Adjust the direction of nextseg. - nextseg.shver = 0; - if (sorg(nextseg) != edest) { - sesymself(nextseg); - } - assert(sorg(nextseg) == edest); - edest = sdest(nextseg); - // Go the next connected subsegment at edest. - senextself(nextseg); - spivotself(nextseg); - } - // The endpoint of this segment is acute. - if (pointtype(edest) == RIDGEVERTEX) { - setpointtype(edest, ACUTEVERTEX); - } else { - assert(pointtype(edest) == ACUTEVERTEX); // SELF_CHECK - } - sharpcount++; - } // if (issharp) - } // if (prevseg.sh == NULL) - segloop.sh = shellfacetraverse(subsegs); - } - - // Mark all facets at extremely small dihedral angles. - if (cavesegshlist->objects > 0) { - for (i = 0; i < cavesegshlist->objects; i++) { - parysh = (face *) fastlookup(cavesegshlist, i); - caveshlist->newindex((void **) &parysh1); - *parysh1 = *parysh; - for (j = 0; j < caveshlist->objects; j++) { - parysh1 = (face *) fastlookup(caveshlist, j); - spinsh = *parysh1; - for (k = 0; k < 3; k++) { - sspivot(spinsh, nextseg); - if (nextseg.sh == NULL) { - spivot(spinsh, neighsh); - if (shelltype(neighsh) != SHARP) { - setshelltype(neighsh, SHARP); + sinfect(neighsh); caveshlist->newindex((void **) &parysh1); *parysh1 = neighsh; } } - senextself(spinsh); - } // k - } // j + senextself(*parysh); + } + } // i + totalvertices += (int) vertlist->objects; + // Uninfect facet vertices. + for (k = 0; k < vertlist->objects; k++) { + parypt = (point *) fastlookup(vertlist, k); + puninfect(*parypt); + } caveshlist->restart(); - } // i - cavesegshlist->restart(); - } // if (cavesegshlist->objects > 0) + // Save this vertex list. + facetvertexlist->newindex((void **) &paryvertlist); + *paryvertlist = vertlist; + facetindex++; + } + subloop.sh = shellfacetraverse(subfaces); + } + + // All subfaces are infected. Uninfect them. + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + assert(sinfected(subloop)); + suninfect(subloop); + subloop.sh = shellfacetraverse(subfaces); + } if (b->verbose) { - if (sharpcount > 0) { - printf(" Found %d (%d) sharp segments.\n", sharpcount, exsharpcount); - } - printf(" Minimum fac-fac angle = %g.\n", minfacetdihed / PI * 180.0); + printf(" Found %ld facets.\n", facetvertexlist->objects); } + + idx2facetlist = new int[facetindex + 1]; + facetverticeslist = new point[totalvertices]; + + totalworkmemory += ((facetindex + 1) * sizeof(int) + + totalvertices * sizeof(point *)); + + idx2facetlist[0] = 0; + for (i = 0, k = 0; i < facetindex; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + idx2facetlist[i + 1] = (idx2facetlist[i] + (int) vertlist->objects); + for (j = 0; j < vertlist->objects; j++) { + parypt = (point *) fastlookup(vertlist, j); + facetverticeslist[k] = *parypt; + k++; + } + } + assert(k == totalvertices); + + // Free the lists. + for (i = 0; i < facetvertexlist->objects; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + delete vertlist; + } + delete facetvertexlist; } /////////////////////////////////////////////////////////////////////////////// // // -// decidefeaturepointsizes() Calculate sizes for all feature points. // -// // -// A feature point is either an acute vertex or a Steiner point on a sharp // -// segment. Each feature point p will be protected by a ball whose radius // -// is called its "feature size". // -// // -// NOTE: we should have already marked all features points in the two func- // -// tions: markacutevertices() and marksharpsegments(). Each feature point // -// has the type ACUTEVERTEX or FREESEGVERTEX. // -// // -// The feature size of a vertex is the minimum of the following sizes: // -// (0) the (approximated) local feature size (the distance to the second // -// nearest boundary) of the vertex; -// (1) the value specified in .mtr file (-m option); // -// (2) the cubic root of a fixed maximal volume constraint ('-a__'); // -// (3) the cubic root of a maximal volume constraint in a region ('-a'); // -// (4) the square root of a maximal area constraint in a .var file; // -// (5) a maximal length constraint in a .var file; // -// // -// If 'b->nobisect' ('-Y' option) is set, every input vertex has a size. It // -// is used to prevent creating too close Steiner points. // +// Check whether two segments, or a segment and a facet, or two facets are // +// adjacent to each other. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::decidefeaturepointsizes() +int tetgenmesh::segsegadjacent(face *seg1, face *seg2) { - arraypool *tetlist, *verlist; - triface starttet, *parytet; - face checksh, parentsh, shloop; - face checkseg, prevseg, nextseg, testseg; - point ploop, adjpt, e1, e2, *parypt; - REAL lfs_0, lfs_1, lfs_2; - REAL len, vol, maxlen = 0.0, varlen; - REAL ang, a, a1, a2, a3, prjpt[3], n[3]; - int featureflag, featurecount; - int i, j; + int segidx1 = getfacetindex(*seg1); + int segidx2 = getfacetindex(*seg2); - if (b->verbose > 0) { - printf(" Deciding feature-point sizes.\n"); + if (segidx1 == segidx2) return 0; + + point pa1 = segmentendpointslist[segidx1 * 2]; + point pb1 = segmentendpointslist[segidx1 * 2 + 1]; + point pa2 = segmentendpointslist[segidx2 * 2]; + point pb2 = segmentendpointslist[segidx2 * 2 + 1]; + + if ((pa1 == pa2) || (pa1 == pb2) || (pb1 == pa2) || (pb1 == pb2)) { + return 1; } - - // Initialize working lists. - tetlist = cavetetlist; - verlist = cavetetvertlist; - - if (b->fixedvolume) { - // A fixed volume constraint is imposed. This gives an upper bound of - // the maximal radius of the protect ball of a vertex. - maxlen = pow(6.0 * b->maxvolume, 1.0 / 3.0); - } - - // First, assign a size of p if p is a feature point or an input point and - // the -Y option is used. - featurecount = 0; - points->traversalinit(); - ploop = pointtraverse(); - while (ploop != (point) NULL) { - // Check if it is a feature point. - featureflag = 0; - // Only calculate the size if it has a size zero. - // The point may already has a positive size (-m option). - if (ploop[pointmtrindex] == 0) { - if (pointtype(ploop) == ACUTEVERTEX) { - featureflag = 1; - } else { - if (b->nobisect) { // '-Y' option - if ((pointtype(ploop) == RIDGEVERTEX) || - (pointtype(ploop) == FACETVERTEX) || - (pointtype(ploop) == VOLVERTEX)) { - featureflag = 1; // It is an input vertex. - } - } - } - } - if (featureflag) { - // Form star(p). - getvertexstar(1, ploop, tetlist, verlist, NULL); - // Calculate lfs_0(p), i.e., the smallest distance from p to a vertex. - // We approximate it by taking the distance of p to its nearest - // vertex in Link(p). - lfs_0 = longest; - for (i = 0; i < verlist->objects; i++) { - parypt = (point *) fastlookup(verlist, i); - adjpt = * parypt; - if (adjpt == dummypoint) { - continue; // Skip a dummypoint. - } - if (pointtype(adjpt) == FREESEGVERTEX) { - // A Steiner point. Get the subsegment. - sdecode(point2sh(adjpt), checkseg); - assert(checkseg.sh != NULL); - checkseg.shver = 0; - if (sdest(checkseg) != adjpt) { - sesymself(checkseg); - } - assert(sdest(checkseg) == adjpt); - // It is possible that the original segment of 'adjpt' does not - // have 'ploop' as an endpoint. - if (sorg(checkseg) == ploop) { - // Find the other end point of the original segment. - nextseg = checkseg; - while (1) { - senext(nextseg, testseg); - spivotself(testseg); - if (testseg.sh == NULL) break; - // Go to the next subseg. - nextseg = testseg; - // Adjust the direction of the nextseg. - nextseg.shver = 0; - if (sorg(nextseg) != adjpt) { - sesymself(nextseg); - } - assert(sorg(nextseg) == adjpt); - adjpt = sdest(nextseg); - } - } - } else if (pointtype(adjpt) == FREEFACETVERTEX) { - // Ignore a Steiner point on facet. - continue; - } else if (pointtype(adjpt) == FREEVOLVERTEX) { - // Ignore a Steiner point in volume. - continue; - } - len = distance(ploop, adjpt); - if (lfs_0 > len) lfs_0 = len; - } // i - assert(lfs_0 < longest); // SELF_CHECK - ploop[pointmtrindex] = lfs_0; - // Calculate lfs_1(p), i.e., the smallest distance from p to a segment. - // We approximate it by restricting the segments in Link(p). - lfs_1 = lfs_0; - for (i = 0; i < tetlist->objects; i++) { - parytet = (triface *) fastlookup(tetlist, i); - for (j = 0; j < 3; j++) { - tsspivot1(*parytet, checkseg); - if (checkseg.sh != NULL) { - e1 = sorg(checkseg); - e2 = sdest(checkseg); - // Only do calculation if the projeciton of 'p' lies inside the - // segment [e1, e2]. - ang = interiorangle(ploop, e1, e2, NULL); - ang *= 2.0; - if (ang > PI) { - len = shortdistance(ploop, e1, e2); - if (lfs_1 > len) { - lfs_1 = len; - } - } - } - enextself(*parytet); - } // j - } // i - if (ploop[pointmtrindex] > lfs_1) { - ploop[pointmtrindex] = lfs_1; - } - // Calculate lfs_2(p), i.e., the smallest distance from p to a facet. - // We approximate it by restricting the facets in Link(p). - lfs_2 = lfs_0; - for (i = 0; i < tetlist->objects; i++) { - parytet = (triface *) fastlookup(tetlist, i); - tspivot(*parytet, checksh); - if (checksh.sh != NULL) { - adjpt = sorg(checksh); - e1 = sdest(checksh); - e2 = sapex(checksh); - // Only do calculation if the projeciton of 'p' lies inside the - // subface [adjpt, e1, e2]. - projpt2face(ploop, adjpt, e1, e2, prjpt); - facenormal(adjpt, e1, e2, n, 1, NULL); - a = sqrt(dot(n, n)); // area of [adjpt, e1, e2]. - if (a > 0) { - facenormal(adjpt, e1, prjpt, n, 1, NULL); - a1 = sqrt(dot(n, n)); - facenormal(e1, e2, prjpt, n, 1, NULL); - a2 = sqrt(dot(n, n)); - facenormal(e2, adjpt, prjpt, n, 1, NULL); - a3 = sqrt(dot(n, n)); - if ((fabs(a1 + a2 + a3 - a) / a) < b->epsilon) { - len = distance(ploop, prjpt); - if (lfs_2 > len) { - lfs_2 = len; - } - } - } else { - assert(0); // a degenerate triangle. - } // if (a > 0) - } - } - if (ploop[pointmtrindex] > lfs_2) { - ploop[pointmtrindex] = lfs_2; - } - if (b->fixedvolume) { - // A fixed volume constraint is imposed. Adjust H(p) <= maxlen. - if (ploop[pointmtrindex] > maxlen) { - ploop[pointmtrindex] = maxlen; - } - } - if (b->varvolume) { - // Variant volume constraints are imposed. Adjust H(p) <= varlen. - for (i = 0; i < tetlist->objects; i++) { - parytet = (triface *) fastlookup(tetlist, i); - starttet = *parytet; - vol = volumebound(starttet.tet); - if (vol > 0.0) { - varlen = pow(6 * vol, 1.0 / 3.0); - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; - } - } - } - } - // The size is calculated. - assert(ploop[pointmtrindex] > 0); // SELF_CHECK - // Clear working lists. - tetlist->restart(); - verlist->restart(); - featurecount++; - } // if (featureflag) - ploop = pointtraverse(); - } - - if (b->verbose) { - printf(" %d feature points.\n", featurecount); - } - - // Second only assign sizes for all Steiner points which were inserted on - // sharp segments. The sizes are interpolated from the endpoints of - // the segments. - featurecount = 0; - points->traversalinit(); - ploop = pointtraverse(); - while (ploop != (point) NULL) { - if (ploop[pointmtrindex] == 0.0) { - if (pointtype(ploop) == FREESEGVERTEX) { - // A Steiner point on segment. - featureflag = 0; - sdecode(point2sh(ploop), checkseg); - assert(checkseg.sh != NULL); - checkseg.shver = 0; - e1 = farsorg(checkseg); // The origin of this seg. - e2 = farsdest(checkseg); // The dest of this seg. - if (b->nobisect) { // '-Y' option. - assert(e1[pointmtrindex] > 0); // SELF_CHECK - assert(e2[pointmtrindex] > 0); // SELF_CHECK - featureflag = 1; - } else { - if ((e1[pointmtrindex] > 0) && (e2[pointmtrindex] > 0)) { - featureflag = 1; - } - } - if (featureflag) { - len = distance(e1, e2); - lfs_0 = distance(e1, ploop); // Re-use lfs_0. - ploop[pointmtrindex] = e1[pointmtrindex] - + (lfs_0 / len) * (e2[pointmtrindex] - e1[pointmtrindex]); - featurecount++; - } // if (featureflag) - } - } // if (ploop[pointmtrindex] == 0.0) - ploop = pointtraverse(); - } - - if (b->verbose && (featurecount > 0)) { - printf(" %d Steiner feature points.\n", featurecount); - } - - if (checkconstraints) { - // A .var file exists. Adjust feature sizes. And make sure that every - // corner of a constraining facet get a size. - if (in->facetconstraintlist) { - // Have facet area constrains. - subfaces->traversalinit(); - shloop.sh = shellfacetraverse(subfaces); - while (shloop.sh != (shellface *) NULL) { - varlen = areabound(shloop); - if (varlen > 0.0) { - // Check if the three corners are feature points. - varlen = sqrt(varlen); - for (j = 0; j < 3; j++) { - ploop = (point) shloop.sh[3 + j]; - if (ploop[pointmtrindex] > 0) { - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; - } - } else { - // This corner has no size yet. Set it. - ploop[pointmtrindex] = varlen; - } - } // j - } - shloop.sh = shellfacetraverse(subfaces); - } - } - if (in->segmentconstraintlist) { - // Have facet area constrains. - subsegs->traversalinit(); - shloop.sh = shellfacetraverse(subsegs); - while (shloop.sh != (shellface *) NULL) { - varlen = areabound(shloop); - if (varlen > 0.0) { - // Check if the two endpoints are feature points. - for (j = 0; j < 2; j++) { - ploop = (point) shloop.sh[3 + j]; - if (ploop[pointmtrindex] > 0.0) { - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; - } - } else { - ploop[pointmtrindex] = varlen; - } - } // j - } - shloop.sh = shellfacetraverse(subsegs); - } - } - } // if (checkconstraints) + return 0; } +int tetgenmesh::segfacetadjacent(face *subseg, face *subsh) +{ + int segidx = getfacetindex(*subseg); + point pa = segmentendpointslist[segidx * 2]; + point pb = segmentendpointslist[segidx * 2 + 1]; + + pinfect(pa); + pinfect(pb); + + int fidx = getfacetindex(*subsh); + int count = 0, i; + + for (i = idx2facetlist[fidx]; i < idx2facetlist[fidx+1]; i++) { + if (pinfected(facetverticeslist[i])) count++; + } + + puninfect(pa); + puninfect(pb); + + return count == 1; +} + +int tetgenmesh::facetfacetadjacent(face *subsh1, face *subsh2) +{ + int count = 0, i; + + int fidx1 = getfacetindex(*subsh1); + int fidx2 = getfacetindex(*subsh2); + + if (fidx1 == fidx2) return 0; + + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + pinfect(facetverticeslist[i]); + } + + for (i = idx2facetlist[fidx2]; i < idx2facetlist[fidx2+1]; i++) { + if (pinfected(facetverticeslist[i])) count++; + } + + // Uninfect the vertices. + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + puninfect(facetverticeslist[i]); + } + + return count > 0; +} /////////////////////////////////////////////////////////////////////////////// // // @@ -505,22 +207,24 @@ void tetgenmesh::decidefeaturepointsizes() int tetgenmesh::checkseg4encroach(point pa, point pb, point checkpt) { - REAL ang; - REAL prjpt[3], u, v, t; - // Check if the point lies inside the diametrical sphere of this seg. - ang = interiorangle(checkpt, pa, pb, NULL); - ang *= 2.0; // Compare it to PI/2 (90 degree). + REAL v1[3], v2[3]; - if (ang > PI) { + v1[0] = pa[0] - checkpt[0]; + v1[1] = pa[1] - checkpt[1]; + v1[2] = pa[2] - checkpt[2]; + v2[0] = pb[0] - checkpt[0]; + v2[1] = pb[1] - checkpt[1]; + v2[2] = pb[2] - checkpt[2]; + + if (dot(v1, v2) < 0) { // Inside. - if (b->metric || b->nobisect) { // -m or -Y option. + if (b->metric) { // -m option. if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0)) { - // In this case, we're sure that the projection of 'checkpt' lies - // inside the segment [a,b]. Check if 'checkpt' lies inside the - // protecting region of this seg. + // The projection of 'checkpt' lies inside the segment [a,b]. + REAL prjpt[3], u, v, t; projpt2edge(checkpt, pa, pb, prjpt); - // Get the mesh size at the location 'prjpt'. + // Interoplate the mesh size at the location 'prjpt'. u = distance(pa, pb); v = distance(pa, prjpt); t = v / u; @@ -558,21 +262,11 @@ int tetgenmesh::checkseg4encroach(point pa, point pb, point checkpt) int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) { - triface searchtet, spintet; - point forg, fdest, eapex; - REAL ccent[3], len, r, d, diff; + REAL ccent[3], len, r; int i; - REAL ti, tj, t, midpt[3]; - REAL ang; - int eid; - - forg = sorg(*chkseg); - fdest = sdest(*chkseg); - - if (b->verbose > 2) { - printf(" Check segment (%d, %d)\n", pointmark(forg), pointmark(fdest)); - } + point forg = sorg(*chkseg); + point fdest = sdest(*chkseg); // Initialize the return values. encpt = NULL; @@ -587,21 +281,13 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) // First check its quality. if (checkconstraints && (areabound(*chkseg) > 0.0)) { if (len > areabound(*chkseg)) { - if (b->verbose > 2) { - printf(" has too large size, len = %g (> %g)\n", len, - areabound(*chkseg)); - } qflag = 1; return 1; } } - if (b->fixedvolume) { // if (b->varvolume || b->fixedvolume) { + if (b->fixedvolume) { if ((len * len * len) > b->maxvolume) { - if (b->verbose > 2) { - printf(" has too large size, len^3 = %g (> %g)\n", len*len*len, - b->maxvolume); - } qflag = 1; return 1; } @@ -616,33 +302,15 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) } } - if (b->psc) { - // Check if it satisfies the approximation requirement. - eid = shellmark(*chkseg); - if ((pointtype(forg) == ACUTEVERTEX)||(pointtype(forg) == RIDGEVERTEX)) { - ti = in->getvertexparamonedge(in->geomhandle, pointmark(forg), eid); - } else { - ti = pointgeomuv(forg, 0); - } - if ((pointtype(fdest) == ACUTEVERTEX)||(pointtype(fdest) == RIDGEVERTEX)) { - tj = in->getvertexparamonedge(in->geomhandle, pointmark(fdest), eid); - } else { - tj = pointgeomuv(fdest, 0); - } - t = 0.5 * (ti + tj); - in->getsteineronedge(in->geomhandle, eid, t, midpt); - ang = interiorangle(midpt, forg, fdest, NULL) / PI * 180.0; - if (ang < b->facet_ang_tol) { - // Refine this segment. - if (b->verbose > 2) { - printf(" has bad approx, ang = %g\n", ang); - } - qflag = 1; - return 1; - } - } // if (b->psc) // Second check if it is encroached. + // Comment: There may exist more than one encroaching points of this segment. + // The 'encpt' returns the one which is closet to it. + triface searchtet, spintet; + point eapex; + REAL d, diff, smdist = 0; + int t1ver; + sstpivot1(*chkseg, searchtet); spintet = searchtet; while (1) { @@ -653,8 +321,21 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) if (fabs(diff) / r < b->epsilon) diff = 0.0; // Rounding. if (diff < 0) { // This segment is encroached by eapex. - encpt = eapex; - break; + if (useinsertradius) { + if (encpt == NULL) { + encpt = eapex; + smdist = d; + } else { + // Choose the closet encroaching point. + if (d < smdist) { + encpt = eapex; + smdist = d; + } + } + } else { + encpt = eapex; + break; + } } } fnextself(spintet); @@ -662,9 +343,6 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) } // while (1) if (encpt != NULL) { - if (b->verbose > 2) { - printf(" is encroached by %d\n", pointmark(encpt)); - } return 1; } @@ -683,79 +361,136 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::splitsegment(face *splitseg, point encpt, int qflag, +int tetgenmesh::splitsegment(face *splitseg, point encpt, REAL rrp, + point encpt1, point encpt2, int qflag, int chkencflag) { - triface searchtet; - face searchsh; - point newpt, pa, pb; - insertvertexflags ivf; - REAL len; //, len1; - int loc; - //int i; + point pa = sorg(*splitseg); + point pb = sdest(*splitseg); - pa = sorg(*splitseg); - pb = sdest(*splitseg); - len = distance(pa, pb); - if (b->verbose > 2) { - printf(" Split segment (%d, %d).\n", pointmark(pa), pointmark(pb)); - } - if (qflag == 0) { - if (shelltype(*splitseg) == SHARP) { - // Do not split it (due to a very small angle) even it is encroached. - // Avoid creating too many Steiner points. - return 0; - } - // Quickly check if we CAN split this segment. - if (encpt == NULL) { - // Do not split this segment if the length is smaller than the mesh - // size at one of its endpoints. - if ((len < pa[pointmtrindex]) || (len < pb[pointmtrindex])) { - return 0; + if ((encpt == NULL) && (qflag == 0)) { + if (useinsertradius) { + // Do not split this segment if the length is smaller than the smaller + // insertion radius at its endpoints. + REAL len = distance(pa, pb); + REAL smrrv = getpointinsradius(pa); + REAL rrv = getpointinsradius(pb); + if (rrv > 0) { + if (smrrv > 0) { + if (rrv < smrrv) { + smrrv = rrv; + } + } else { + smrrv = rrv; + } + } + if (smrrv > 0) { + if ((fabs(smrrv - len) / len) < b->epsilon) smrrv = len; + if (len < smrrv) { + return 0; + } } } } + if (b->nobisect) { // With -Y option. + // Only split this segment if it is allowed to be split. + if (checkconstraints) { + // Check if it has a non-zero length bound. + if (areabound(*splitseg) == 0) { + // It is not allowed. However, if all of facets containing this seg + // is allowed to be split, we still split it. + face parentsh, spinsh; + //splitseg.shver = 0; + spivot(*splitseg, parentsh); + if (parentsh.sh == NULL) { + return 0; // A dangling segment. Do not split it. + } + spinsh = parentsh; + while (1) { + if (areabound(spinsh) == 0) break; + spivotself(spinsh); + if (spinsh.sh == parentsh.sh) break; + } + if (areabound(spinsh) == 0) { + // All facets at this seg are not allowed to be split. + return 0; // Do not split it. + } + } + } else { + return 0; // Do not split this segment. + } + } // if (b->nobisect) + + triface searchtet; + face searchsh; + point newpt; + insertvertexflags ivf; + makepoint(&newpt, FREESEGVERTEX); getsteinerptonsegment(splitseg, encpt, newpt); - // Split the segment by the Bowyer-Watson algorithm. sstpivot1(*splitseg, searchtet); ivf.iloc = (int) ONEDGE; - if (b->psc) { - ivf.bowywat = 0; // Do not enlarge the initial cavity. - ivf.validflag = 0; // Do not validate the initial cavity. - } else { - ivf.bowywat = 3; // Preserve subsegments and subfaces; - ivf.validflag = 1; // Validate the B-W cavity. - } - ivf.lawson = b->conforming ? 3 : 1; // Check flip for internal new faces?. + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + ivf.validflag = 1; // Validate the B-W cavity. + ivf.lawson = 2; // Do flips to recover Delaunayness. ivf.rejflag = 0; // Do not check encroachment of new segments/facets. - if ((encpt == NULL) && (qflag == 0)) { + if (b->metric) { ivf.rejflag |= 4; // Do check encroachment of protecting balls. } ivf.chkencflag = chkencflag; - ivf.sloc = ivf.iloc; - ivf.sbowywat = ivf.bowywat; // Surface mesh options. + ivf.sloc = (int) INSTAR; // ivf.iloc; + ivf.sbowywat = 3; // ivf.bowywat; // Surface mesh options. ivf.splitbdflag = 1; ivf.respectbdflag = 1; - ivf.assignmeshsize = 1; + ivf.assignmeshsize = b->metric; + ivf.smlenflag = useinsertradius; - loc = insertvertex(newpt, &searchtet, &searchsh, splitseg, &ivf); - if (loc == (int) ONEDGE) { - if (b->verbose > 2) { - printf(" Point inserted successfully on segment.\n"); - } - // Flip non-locally Delaunay faces at the link of its star. - lawsonflip3d(newpt, 4, 0, chkencflag, 0); + if (insertpoint(newpt, &searchtet, &searchsh, splitseg, &ivf)) { st_segref_count++; if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + // Update 'rv' (to be the shortest distance). + REAL rv = ivf.smlen, rp; + if (pointtype(ivf.parentpt) == FREESEGVERTEX) { + face parentseg1, parentseg2; + sdecode(point2sh(newpt), parentseg1); + sdecode(point2sh(ivf.parentpt), parentseg2); + if (segsegadjacent(&parentseg1, &parentseg2)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } else if (pointtype(ivf.parentpt) == FREEFACETVERTEX) { + face parentseg, parentsh; + sdecode(point2sh(newpt), parentseg); + sdecode(point2sh(ivf.parentpt), parentsh); + if (segfacetadjacent(&parentseg, &parentsh)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } + setpointinsradius(newpt, rv); + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); + } return 1; } else { + // Point is not inserted. pointdealloc(newpt); return 0; } @@ -769,7 +504,7 @@ int tetgenmesh::splitsegment(face *splitseg, point encpt, int qflag, void tetgenmesh::repairencsegs(int chkencflag) { - badface *bface; + face *bface; point encpt = NULL; int qflag = 0; @@ -777,20 +512,25 @@ void tetgenmesh::repairencsegs(int chkencflag) // if an unlimited number of Steiner points is allowed. while ((badsubsegs->items > 0) && (steinerleft != 0)) { badsubsegs->traversalinit(); - bface = badfacetraverse(badsubsegs); + bface = (face *) badsubsegs->traverse(); while ((bface != NULL) && (steinerleft != 0)) { - // A queued segment may have been deleted (split). - if (bface->ss.sh[3] != NULL) { - // A queued segment may have been processed. - if (smarktest2ed(bface->ss)) { - sunmarktest2(bface->ss); - if (checkseg4split(&(bface->ss), encpt, qflag)) { - splitsegment(&(bface->ss), encpt, qflag, chkencflag); + // Skip a deleleted element. + if (bface->shver >= 0) { + // A queued segment may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued segment may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + if (checkseg4split(bface, encpt, qflag)) { + splitsegment(bface, encpt, 0, NULL, NULL, qflag, chkencflag); + } } } + // Remove this entry from list. + bface->shver = -1; // Signal it as a deleted element. + badsubsegs->dealloc((void *) bface); } - badfacedealloc(badsubsegs, bface); // Remove this entry from list. - bface = badfacetraverse(badsubsegs); + bface = (face *) badsubsegs->traverse(); } } @@ -803,19 +543,37 @@ void tetgenmesh::repairencsegs(int chkencflag) assert(0); // Unknown case. } badsubsegs->traversalinit(); - bface = badfacetraverse(badsubsegs); + bface = (face *) badsubsegs->traverse(); while (bface != NULL) { - if (bface->ss.sh[3] != NULL) { - if (smarktest2ed(bface->ss)) { - sunmarktest2(bface->ss); + // Skip a deleleted element. + if (bface->shver >= 0) { + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } } } - bface = badfacetraverse(badsubsegs); + bface = (face *) badsubsegs->traverse(); } badsubsegs->restart(); } } +/////////////////////////////////////////////////////////////////////////////// +// // +// enqueuesubface() Queue a subface or a subsegment for encroachment chk. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::enqueuesubface(memorypool *pool, face *chkface) +{ + if (!smarktest2ed(*chkface)) { + smarktest2(*chkface); // Only queue it once. + face *queface = (face *) pool->alloc(); + *queface = *chkface; + } +} + /////////////////////////////////////////////////////////////////////////////// // // // checkfac4encroach() Check if a subface is encroached by a point. // @@ -826,8 +584,6 @@ int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, REAL* cent, REAL* r) { REAL rd, len; - REAL prjpt[3], n[3]; - REAL a, a1, a2, a3; circumsphere(pa, pb, pc, NULL, cent, &rd); assert(rd != 0); @@ -836,10 +592,12 @@ int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, if (len < rd) { // The point lies inside the circumsphere of this face. - if (b->metric || b->nobisect) { // -m or -Y option. + if (b->metric) { // -m option. if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0) && (pc[pointmtrindex] > 0)) { // Get the projection of 'checkpt' in the plane of pa, pb, and pc. + REAL prjpt[3], n[3]; + REAL a, a1, a2, a3; projpt2face(checkpt, pa, pb, pc, prjpt); // Get the face area of [a,b,c]. facenormal(pa, pb, pc, n, 1, NULL); @@ -861,10 +619,6 @@ int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, if (len < rd) { return 1; // Encroached. } - } else { - // The projection lies outside the face. - // In this case, 'p' must close to another face or a segment than - // to this one. We ignore this boundary face. } } else { return 1; // No protecting ball. Encroached. @@ -895,14 +649,10 @@ int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, REAL *cent) { - triface searchtet; - face checksh; // *parysh; - face checkseg; point pa, pb, pc; - REAL area, rd, len, sintheta; + REAL area, rd, len; REAL A[4][4], rhs[4], D; int indx[4]; - REAL elen[3]; int i; encpt = NULL; @@ -912,11 +662,6 @@ int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, pb = sdest(*chkfac); pc = sapex(*chkfac); - if (b->verbose > 2) { - printf(" Check subface (%d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc)); - } - // Compute the coefficient matrix A (3x3). A[0][0] = pb[0] - pa[0]; A[0][1] = pb[1] - pa[1]; @@ -929,100 +674,97 @@ int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, area = 0.5 * sqrt(dot(A[2], A[2])); // The area of [a,b,c]. // Compute the right hand side vector b (3x1). - elen[0] = dot(A[0], A[0]); // edge [a,b] - elen[1] = dot(A[1], A[1]); // edge [a,c] - rhs[0] = 0.5 * elen[0]; - rhs[1] = 0.5 * elen[1]; + rhs[0] = 0.5 * dot(A[0], A[0]); // edge [a,b] + rhs[1] = 0.5 * dot(A[1], A[1]); // edge [a,c] rhs[2] = 0.0; // Solve the 3 by 3 equations use LU decomposition with partial - // pivoting and backward and forward substitute.. - if (lu_decmp(A, 3, indx, &D, 0)) { - lu_solve(A, 3, indx, rhs, 0); - cent[0] = pa[0] + rhs[0]; - cent[1] = pa[1] + rhs[1]; - cent[2] = pa[2] + rhs[2]; - rd = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); + // pivoting and backward and forward substitute. + if (!lu_decmp(A, 3, indx, &D, 0)) { + // A degenerate triangle. + assert(0); + } - if (b->verbose > 3) { - printf(" circent: (%g, %g, %g)\n", cent[0], cent[1], cent[2]); - printf(" cirradi: %g\n", rd); + lu_solve(A, 3, indx, rhs, 0); + cent[0] = pa[0] + rhs[0]; + cent[1] = pa[1] + rhs[1]; + cent[2] = pa[2] + rhs[2]; + rd = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); + + if (checkconstraints && (areabound(*chkfac) > 0.0)) { + // Check if the subface has too big area. + if (area > areabound(*chkfac)) { + qflag = 1; + return 1; } + } - // Check the quality (radius-edge ratio) of this subface. - // Re-use variables 'A', 'rhs', and 'D'. - A[2][0] = pb[0] - pc[0]; - A[2][1] = pb[1] - pc[1]; - A[2][2] = pb[2] - pc[2]; - elen[2] = dot(A[2], A[2]); // edge [b,c] - // Get the shortest edge length in 'D'. - D = elen[0]; // edge [a,b] - for (i = 1; i < 3; i++) { - if (D > elen[i]) D = elen[i]; + if (b->fixedvolume) { + if ((area * sqrt(area)) > b->maxvolume) { + qflag = 1; + return 1; } + } + if (b->varvolume) { + triface adjtet; + REAL volbnd; + int t1ver; - D = sqrt(D); - if (b->verbose > 3) { - printf(" shortest edge length = %g\n", D); - } - - rhs[3] = rd / D; // The radius-edge ratio. - - // Check if this subface is nearly degenerate. - sintheta = 1.0 / (2.0 * rhs[3]); - if (sintheta < sintheta_tol) { - // Do not split this subface. Save it in list. - if (b->verbose > 1) { - printf(" !! A degenerated subface, theta = %g (deg)\n", - asin(sintheta) / PI * 180.0); - } - return 0; // Do not split a degenerated subface. - } - - if (checkconstraints && (areabound(*chkfac) > 0.0)) { - // Check if the subface has too big area. - if (area > areabound(*chkfac)) { - if (b->verbose > 2) { - printf(" has too big area: %g (> %g)\n", area, - areabound(*chkfac)); - } + stpivot(*chkfac, adjtet); + if (!ishulltet(adjtet)) { + volbnd = volumebound(adjtet.tet); + if ((volbnd > 0) && (area * sqrt(area)) > volbnd) { qflag = 1; return 1; } } - - if (b->metric) { // -m option. Check mesh size. - // Check if the ccent lies outside one of the prot.balls at vertices. - if (((pa[pointmtrindex] > 0) && (rd > pa[pointmtrindex])) || - ((pb[pointmtrindex] > 0) && (rd > pb[pointmtrindex])) || - ((pc[pointmtrindex] > 0) && (rd > pc[pointmtrindex]))) { - qflag = 1; // Enforce mesh size. + fsymself(adjtet); + if (!ishulltet(adjtet)) { + volbnd = volumebound(adjtet.tet); + if ((volbnd > 0) && (area * sqrt(area)) > volbnd) { + qflag = 1; return 1; } } + } - - // Check if this subface is locally encroached. - for (i = 0; i < 2; i++) { - stpivot(*chkfac, searchtet); - if (!ishulltet(searchtet)) { - len = distance(oppo(searchtet), cent); - if ((fabs(len - rd) / rd) < b->epsilon) len = rd;// Rounding. - if (len < rd) { - if (b->verbose > 2) { - printf(" is encroached by point %d\n", - pointmark(oppo(searchtet))); - } - encpt = oppo(searchtet); - return 1; - } - } - sesymself(*chkfac); + if (b->metric) { // -m option. Check mesh size. + // Check if the ccent lies outside one of the prot.balls at vertices. + if (((pa[pointmtrindex] > 0) && (rd > pa[pointmtrindex])) || + ((pb[pointmtrindex] > 0) && (rd > pb[pointmtrindex])) || + ((pc[pointmtrindex] > 0) && (rd > pc[pointmtrindex]))) { + qflag = 1; // Enforce mesh size. + return 1; } - } + } - return 0; + triface searchtet; + REAL smlen = 0; + + // Check if this subface is locally encroached. + for (i = 0; i < 2; i++) { + stpivot(*chkfac, searchtet); + if (!ishulltet(searchtet)) { + len = distance(oppo(searchtet), cent); + if ((fabs(len - rd) / rd) < b->epsilon) len = rd;// Rounding. + if (len < rd) { + if (smlen == 0) { + smlen = len; + encpt = oppo(searchtet); + } else { + if (len < smlen) { + smlen = len; + encpt = oppo(searchtet); + } + } + //return 1; + } + } + sesymself(*chkfac); + } + + return encpt != NULL; //return 0; } /////////////////////////////////////////////////////////////////////////////// @@ -1031,125 +773,171 @@ int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, // // // The subface may be encroached, or in bad-quality. It is split at its cir- // // cumcenter ('ccent'). Do not split it if 'ccent' encroaches upon any seg- // -// ments. Instead, one of the encroached segments is split. It is possible // -// that none of the encorached segments can be split. // +// ment. Instead, one of the encroached segments is split. It is possible // +// that none of the encroached segments can be split. // // // // The return value indicates whether a new point is inserted (> 0) or not // -// (= 0). Furthermore, it is inserted on an encorached segment (= 1) or in- // -// side the facet (= 2). // +// (= 0). Furthermore, it is inserted on an encroached segment (= 1) or // +// in-side the facet (= 2). // +// // +// 'encpt' is a vertex encroaching upon this subface, i.e., it causes the // +// split of this subface. If 'encpt' is NULL, then the cause of the split // +// this subface is a rejected tet circumcenter 'p', and 'encpt1' is the // +// parent of 'p'. // // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::splitsubface(face *splitfac, point encpt, int qflag, - REAL *ccent, int chkencflag) +int tetgenmesh::splitsubface(face *splitfac, point encpt, point encpt1, + int qflag, REAL *ccent, int chkencflag) { - badface *bface; - triface searchtet; + point pa = sorg(*splitfac); + point pb = sdest(*splitfac); + point pc = sapex(*splitfac); + + + + if (b->nobisect) { // With -Y option. + if (checkconstraints) { + // Only split if it is allowed to be split. + // Check if this facet has a non-zero constraint. + if (areabound(*splitfac) == 0) { + return 0; // Do not split it. + } + } else { + return 0; + } + } // if (b->nobisect) + face searchsh; - face checkseg, *paryseg; - point newpt, pa, pb, pc; insertvertexflags ivf; - REAL rd; - int splitflag; - int loc; + point newpt; + REAL rv = 0., rp; // Insertion radius of newpt. int i; - - pa = sorg(*splitfac); - pb = sdest(*splitfac); - pc = sapex(*splitfac); - - if (b->verbose > 2) { - printf(" Split subface (%d, %d, %d).\n", pointmark(pa), pointmark(pb), - pointmark(pc)); - } - - - // Quickly check if we CAN split this subface. - if (qflag == 0) { - // Do not split this subface if it forms a very small dihedral with - // another facet. Avoid creating too many Steiner points. - if (shelltype(*splitfac) == SHARP) { - return 0; - } - // Do not split this subface if the 'ccent' lies inside the protect balls - // of one of its vertices. - rd = distance(ccent, pa); - if ((rd <= pa[pointmtrindex]) || (rd <= pb[pointmtrindex]) || - (rd <= pc[pointmtrindex])) { - return 0; - } - } - // Initialize the inserting point. makepoint(&newpt, FREEFACETVERTEX); + // Split the subface at its circumcenter. + for (i = 0; i < 3; i++) newpt[i] = ccent[i]; - // Split the subface at its circumcenter. - for (i = 0; i < 3; i++) newpt[i] = ccent[i]; - // Search a subface which contains 'newpt'. - searchsh = *splitfac; - // Calculate an above point. It lies above the plane containing - // the subface [a,b,c], and save it in dummypoint. Moreover, - // the vector cent->dummypoint is the normal of the plane. - calculateabovepoint4(newpt, pa, pb, pc); - // Parameters: 'aflag' = 1, - above point exists. - // 'cflag' = 0, - non-convex, check co-planarity of the result. - // 'rflag' = 0, - no need to round the locating result. - ivf.iloc = (int) slocate(newpt, &searchsh, 1, 0, 0); - if ((ivf.iloc == (int) ONFACE) || (ivf.iloc == (int) ONEDGE)) { - // Insert this point. - } else { - pointdealloc(newpt); - return 0; + if (useinsertradius) { + if (encpt != NULL) { + rv = distance(newpt, encpt); + if (pointtype(encpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(encpt), parentseg); + if (segfacetadjacent(&parentseg, splitfac)) { + rp = getpointinsradius(encpt); + if (rv < (sqrt(2.0) * rp)) { + // This insertion may cause no termination. + pointdealloc(newpt); + return 0; // Reject the insertion of newpt. + } + } + } else if (pointtype(encpt) == FREEFACETVERTEX) { + face parentsh; + sdecode(point2sh(encpt), parentsh); + if (facetfacetadjacent(&parentsh, splitfac)) { + rp = getpointinsradius(encpt); + if (rv < rp) { + pointdealloc(newpt); + return 0; // Reject the insertion of newpt. + } + } + } } + } // if (useinsertradius) + // Search a subface which contains 'newpt'. + searchsh = *splitfac; + // Calculate an above point. It lies above the plane containing + // the subface [a,b,c], and save it in dummypoint. Moreover, + // the vector cent->dummypoint is the normal of the plane. + calculateabovepoint4(newpt, pa, pb, pc); + // Parameters: 'aflag' = 1, - above point exists. + // 'cflag' = 0, - non-convex, check co-planarity of the result. + // 'rflag' = 0, - no need to round the locating result. + ivf.iloc = (int) slocate(newpt, &searchsh, 1, 0, 0); + + if (!((ivf.iloc == (int) ONFACE) || (ivf.iloc == (int) ONEDGE))) { + pointdealloc(newpt); + return 0; + } + + + triface searchtet; + face *paryseg; + int splitflag; // Insert the point. stpivot(searchsh, searchtet); //assert((ivf.iloc == (int) ONFACE) || (ivf.iloc == (int) ONEDGE)); - // Split the subface by the Bowyer-Watson algorithm. - ivf.bowywat = 3; // Preserve segments and subfaces. - ivf.lawson = b->conforming ? 3 : 1; + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + ivf.lawson = 2; ivf.rejflag = 1; // Do check the encroachment of segments. - if (qflag == 0) { - ivf.rejflag |= 4; // Reject it if it encroached upon any vertex. + if (b->metric) { + ivf.rejflag |= 4; // Do check encroachment of protecting balls. } ivf.chkencflag = chkencflag; - ivf.sloc = ivf.iloc; - ivf.sbowywat = ivf.bowywat; + ivf.sloc = (int) INSTAR; // ivf.iloc; + ivf.sbowywat = 3; // ivf.bowywat; ivf.splitbdflag = 1; ivf.validflag = 1; ivf.respectbdflag = 1; - ivf.assignmeshsize = 1; + ivf.assignmeshsize = b->metric; ivf.refineflag = 2; ivf.refinesh = searchsh; + ivf.smlenflag = useinsertradius; // Update the insertion radius. - loc = insertvertex(newpt, &searchtet, &searchsh, NULL, &ivf); - if (loc == (int) ivf.iloc) { - if (b->verbose > 2) { - printf(" Point inserted successfully on facet.\n"); - } - // Flip not locally Delaunay link facets. - lawsonflip3d(newpt, 4, 0, chkencflag, 0); + if (insertpoint(newpt, &searchtet, &searchsh, NULL, &ivf)) { st_facref_count++; if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + // Update 'rv' (to be the shortest distance). + rv = ivf.smlen; + if (pointtype(ivf.parentpt) == FREESEGVERTEX) { + face parentseg, parentsh; + sdecode(point2sh(ivf.parentpt), parentseg); + sdecode(point2sh(newpt), parentsh); + if (segfacetadjacent(&parentseg, &parentsh)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < (sqrt(2.0) * rp)) { + rv = sqrt(2.0) * rp; // The relaxed insertion radius of 'newpt'. + } + } + } else if (pointtype(ivf.parentpt) == FREEFACETVERTEX) { + face parentsh1, parentsh2; + sdecode(point2sh(ivf.parentpt), parentsh1); + sdecode(point2sh(newpt), parentsh2); + if (facetfacetadjacent(&parentsh1, &parentsh2)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } + setpointinsradius(newpt, rv); + } // if (useinsertradius) + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); + } return 1; } else { // Point was not inserted. - if (loc == (int) ENCSEGMENT) { - if (b->verbose > 2) { - printf(" Point encroached upon %ld segments.\n", - encseglist->objects); - } - assert(encseglist->objects > 0); - pointdealloc(newpt); + pointdealloc(newpt); + if (ivf.iloc == (int) ENCSEGMENT) { // Select an encroached segment and split it. splitflag = 0; for (i = 0; i < encseglist->objects; i++) { paryseg = (face *) fastlookup(encseglist, i); - if (splitsegment(paryseg, NULL, qflag, chkencflag | 1)) { + if (splitsegment(paryseg, NULL, rv, encpt, encpt1, qflag, + chkencflag | 1)) { splitflag = 1; // A point is inserted on a segment. break; } @@ -1159,22 +947,18 @@ int tetgenmesh::splitsubface(face *splitfac, point encpt, int qflag, // Some segments may need to be repaired. repairencsegs(chkencflag | 1); // Queue this subface if it is still alive and not queued. - if (splitfac->sh[3] != NULL) { - if (!smarktest2ed(*splitfac)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = *splitfac; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(*splitfac); // An alive badface. - } - } + //if ((splitfac->sh != NULL) && (splitfac->sh[3] != NULL)) { + // // Only queue it if 'qflag' is set. + // if (qflag) { + // enqueuesubface(badsubfacs, splitfac); + // } + //} } return splitflag; } else { - pointdealloc(newpt); return 0; } } - } /////////////////////////////////////////////////////////////////////////////// @@ -1185,7 +969,7 @@ int tetgenmesh::splitsubface(face *splitfac, point encpt, int qflag, void tetgenmesh::repairencfacs(int chkencflag) { - badface *bface; + face *bface; point encpt = NULL; int qflag = 0; REAL ccent[3]; @@ -1194,20 +978,24 @@ void tetgenmesh::repairencfacs(int chkencflag) // if an unlimited number of Steiner points is allowed. while ((badsubfacs->items > 0) && (steinerleft != 0)) { badsubfacs->traversalinit(); - bface = badfacetraverse(badsubfacs); + bface = (face *) badsubfacs->traverse(); while ((bface != NULL) && (steinerleft != 0)) { - // A queued subface may have been deleted (split). - if (bface->ss.sh[3] != NULL) { - // A queued subface may have been processed. - if (smarktest2ed(bface->ss)) { - sunmarktest2(bface->ss); - if (checkfac4split(&(bface->ss), encpt, qflag, ccent)) { - splitsubface(&(bface->ss), encpt, qflag, ccent, chkencflag); + // Skip a deleted element. + if (bface->shver >= 0) { + // A queued subface may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued subface may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + if (checkfac4split(bface, encpt, qflag, ccent)) { + splitsubface(bface, encpt, NULL, qflag, ccent, chkencflag); + } } } + bface->shver = -1; // Signal it as a deleted element. + badsubfacs->dealloc((void *) bface); // Remove this entry from list. } - badfacedealloc(badsubfacs, bface); // Remove this entry from list. - bface = badfacetraverse(badsubfacs); + bface = (face *) badsubfacs->traverse(); } } @@ -1220,37 +1008,61 @@ void tetgenmesh::repairencfacs(int chkencflag) assert(0); // Unknown case. } badsubfacs->traversalinit(); - bface = badfacetraverse(badsubfacs); + bface = (face *) badsubfacs->traverse(); while (bface != NULL) { - if (bface->ss.sh[3] != NULL) { - if (smarktest2ed(bface->ss)) { - sunmarktest2(bface->ss); + // Skip a deleted element. + if (bface->shver >= 0) { + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } } } - bface = badfacetraverse(badsubfacs); + bface = (face *) badsubfacs->traverse(); } badsubfacs->restart(); } } +/////////////////////////////////////////////////////////////////////////////// +// // +// enqueuetetrahedron() Queue a tetrahedron for quality check. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::enqueuetetrahedron(triface *chktet) +{ + if (!marktest2ed(*chktet)) { + marktest2(*chktet); // Only queue it once. + triface *quetet = (triface *) badtetrahedrons->alloc(); + *quetet = *chktet; + } +} + /////////////////////////////////////////////////////////////////////////////// // // // checktet4split() Check if the tet needs to be split. // // // /////////////////////////////////////////////////////////////////////////////// - int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) { point pa, pb, pc, pd, *ppt; REAL vda[3], vdb[3], vdc[3]; REAL vab[3], vbc[3], vca[3]; REAL N[4][3], L[4], cosd[6], elen[6]; - REAL maxcosd, vol, volbnd, smlen, rd; + REAL maxcosd, vol, volbnd, smlen = 0, rd; REAL A[4][4], rhs[4], D; int indx[4]; int i, j; + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(chktet->tet, numelemattrib - 1) == -1.0) { + return 0; + } + } + qflag = 0; pd = (point) chktet->tet[7]; @@ -1262,11 +1074,6 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) pb = (point) chktet->tet[5]; pc = (point) chktet->tet[6]; - if (b->verbose > 2) { - printf(" Check tet (%d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd)); - } - // Get the edge vectors vda: d->a, vdb: d->b, vdc: d->c. // Set the matrix A = [vda, vdb, vdc]^T. for (i = 0; i < 3; i++) A[0][i] = vda[i] = pa[i] - pd[i]; @@ -1280,22 +1087,14 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) if (!lu_decmp(A, 3, indx, &D, 0)) { // A degenerated tet (vol = 0). - if (b->verbose > 3) { - printf(" Min dihed = 0 (degree)\n"); - } - // Return its barycenter. - for (i = 0; i < 3; i++) { - ccent[i] = 0.25 * (pa[i] + pb[i] + pc[i] + pd[i]); - } - return 1; + // This is possible due to the use of exact arithmetic. We temporarily + // leave this tet. It should be fixed by mesh optimization. + return 0; } // Check volume if '-a#' and '-a' options are used. if (b->varvolume || b->fixedvolume) { vol = fabs(A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2]) / 6.0; - if (b->verbose > 3) { - printf(" volume = %g.\n", vol); - } if (b->fixedvolume) { if (vol > b->maxvolume) { qflag = 1; @@ -1318,6 +1117,26 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) } } + if (b->metric) { // -m option. Check mesh size. + // Calculate the circumradius of this tet. + rhs[0] = 0.5 * dot(vda, vda); + rhs[1] = 0.5 * dot(vdb, vdb); + rhs[2] = 0.5 * dot(vdc, vdc); + lu_solve(A, 3, indx, rhs, 0); + for (i = 0; i < 3; i++) ccent[i] = pd[i] + rhs[i]; + rd = sqrt(dot(rhs, rhs)); + // Check if the ccent lies outside one of the prot.balls at vertices. + ppt = (point *) &(chktet->tet[4]); + for (i = 0; i < 4; i++) { + if (ppt[i][pointmtrindex] > 0) { + if (rd > ppt[i][pointmtrindex]) { + qflag = 1; // Enforce mesh size. + return 1; + } + } + } + } + if (in->tetunsuitable != NULL) { // Execute the user-defined meshing sizing evaluation. if ((*(in->tetunsuitable))(pa, pb, pc, pd, NULL, 0)) { @@ -1328,11 +1147,61 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) lu_solve(A, 3, indx, rhs, 0); for (i = 0; i < 3; i++) ccent[i] = pd[i] + rhs[i]; return 1; - } else { - return 0; // Do not split this tet. } } + if (useinsertradius) { + // Do not split this tet if the shortest edge is shorter than the + // insertion radius of one of its endpoints. + triface checkedge; + point e1, e2; + REAL rrv, smrrv; + + // Get the shortest edge of this tet. + checkedge.tet = chktet->tet; + for (i = 0; i < 6; i++) { + checkedge.ver = edge2ver[i]; + e1 = org(checkedge); + e2 = dest(checkedge); + elen[i] = distance(e1, e2); + if (i == 0) { + smlen = elen[i]; + j = 0; + } else { + if (elen[i] < smlen) { + smlen = elen[i]; + j = i; + } + } + } + // Check if the edge is too short. + checkedge.ver = edge2ver[j]; + // Get the smallest rrv of e1 and e2. + // Note: if rrv of e1 and e2 is zero. Do not use it. + e1 = org(checkedge); + smrrv = getpointinsradius(e1); + e2 = dest(checkedge); + rrv = getpointinsradius(e2); + if (rrv > 0) { + if (smrrv > 0) { + if (rrv < smrrv) { + smrrv = rrv; + } + } else { + smrrv = rrv; + } + } + if (smrrv > 0) { + // To avoid rounding error, round smrrv before doing comparison. + if ((fabs(smrrv - smlen) / smlen) < b->epsilon) { + smrrv = smlen; + } + if (smrrv > smlen) { + return 0; + } + } + } // if (useinsertradius) + // Check the radius-edge ratio. Set by -q#. if (b->minratio > 0) { // Calculate the circumcenter and radius of this tet. @@ -1342,24 +1211,23 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) lu_solve(A, 3, indx, rhs, 0); for (i = 0; i < 3; i++) ccent[i] = pd[i] + rhs[i]; rd = sqrt(dot(rhs, rhs)); - // Calculate the shortest edge length. - elen[0] = dot(vda, vda); - elen[1] = dot(vdb, vdb); - elen[2] = dot(vdc, vdc); - elen[3] = dot(vab, vab); - elen[4] = dot(vbc, vbc); - elen[5] = dot(vca, vca); - smlen = elen[0]; //sidx = 0; - for (i = 1; i < 6; i++) { - if (smlen > elen[i]) { - smlen = elen[i]; //sidx = i; + if (!useinsertradius) { + // Calculate the shortest edge length. + elen[0] = dot(vda, vda); + elen[1] = dot(vdb, vdb); + elen[2] = dot(vdc, vdc); + elen[3] = dot(vab, vab); + elen[4] = dot(vbc, vbc); + elen[5] = dot(vca, vca); + smlen = elen[0]; //sidx = 0; + for (i = 1; i < 6; i++) { + if (smlen > elen[i]) { + smlen = elen[i]; //sidx = i; + } } + smlen = sqrt(smlen); } - smlen = sqrt(smlen); D = rd / smlen; - if (b->verbose > 3) { - printf(" Ratio-edge ratio = %g, smlen = %g\n", D, smlen); - } if (D > b->minratio) { // A bad radius-edge ratio. return 1; @@ -1390,7 +1258,7 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) cosd[3] = -dot(N[1], N[2]); // Edge ad, ac cosd[4] = -dot(N[1], N[3]); cosd[5] = -dot(N[2], N[3]); // Edge ab - // Get the smallest diehedral angle. + // Get the smallest dihedral angle. //maxcosd = mincosd = cosd[0]; maxcosd = cosd[0]; for (i = 1; i < 6; i++) { @@ -1398,9 +1266,6 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) maxcosd = (cosd[i] > maxcosd ? cosd[i] : maxcosd); //mincosd = (cosd[i] < mincosd ? cosd[i] : maxcosd); } - if (b->verbose > 3) { - printf(" Min dihed = %g (degree)\n", acos(maxcosd) / PI * 180.0); - } if (maxcosd > cosmindihed) { // Calculate the circumcenter of this tet. // A bad dihedral angle. @@ -1416,26 +1281,6 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) } } - if (b->metric) { // -m option. Check mesh size. - // Calculate the circumradius of this tet. - rhs[0] = 0.5 * dot(vda, vda); - rhs[1] = 0.5 * dot(vdb, vdb); - rhs[2] = 0.5 * dot(vdc, vdc); - lu_solve(A, 3, indx, rhs, 0); - for (i = 0; i < 3; i++) ccent[i] = pd[i] + rhs[i]; - rd = sqrt(dot(rhs, rhs)); - // Check if the ccent lies outside one of the prot.balls at vertices. - ppt = (point *) &(chktet->tet[4]); - for (i = 0; i < 4; i++) { - if (ppt[i][pointmtrindex] > 0) { - if (rd > ppt[i][pointmtrindex]) { - qflag = 1; // Enforce mesh size. - return 1; - } - } - } - } - return 0; } @@ -1448,49 +1293,33 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) int tetgenmesh::splittetrahedron(triface* splittet, int qflag, REAL *ccent, int chkencflag) { - badface *bface; triface searchtet; - face checkseg, *paryseg; - point newpt, pa, *ppt = NULL; + face *paryseg; + point newpt; + badface *bface; insertvertexflags ivf; - REAL rd; int splitflag; - int loc; int i; - if (b->verbose > 2) { - ppt = (point *) &(splittet->tet[4]); - printf(" Split tet (%d, %d, %d, %d).\n", pointmark(ppt[0]), - pointmark(ppt[1]), pointmark(ppt[2]), pointmark(ppt[3])); - } - if (qflag == 0) { - // It is a bad quality tet (not due to mesh size). - // It can be split if 'ccent' does not encroach upon any prot. balls. - // Do a quick check if the 'ccent' lies inside the protect balls - // of one of the vertices of this tet. - ppt = (point *) &(splittet->tet[4]); - rd = distance(ccent, ppt[0]); - if ((rd <= ppt[0][pointmtrindex]) || (rd <= ppt[1][pointmtrindex]) || - (rd <= ppt[2][pointmtrindex]) || (rd <= ppt[3][pointmtrindex])) { - if (b->verbose > 2) { - printf(" Encroaching a protecting ball. Rejected.\n"); - } - return 0; - } - } + REAL rv = 0.; // Insertion radius of 'newpt'. makepoint(&newpt, FREEVOLVERTEX); for (i = 0; i < 3; i++) newpt[i] = ccent[i]; + if (useinsertradius) { + rv = distance(newpt, org(*splittet)); + setpointinsradius(newpt, rv); + } searchtet = *splittet; ivf.iloc = (int) OUTSIDE; - ivf.bowywat = 3; // Preserve subsegments and subfaces; - ivf.lawson = b->conforming ? 3 : 1; + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + ivf.lawson = 2; ivf.rejflag = 3; // Do check for encroached segments and subfaces. - if (qflag == 0) { + if (b->metric) { ivf.rejflag |= 4; // Reject it if it lies in some protecting balls. } ivf.chkencflag = chkencflag; @@ -1498,115 +1327,82 @@ int tetgenmesh::splittetrahedron(triface* splittet, int qflag, REAL *ccent, ivf.splitbdflag = 0; // No use. ivf.validflag = 1; ivf.respectbdflag = 1; - ivf.assignmeshsize = 1; + ivf.assignmeshsize = b->metric; ivf.refineflag = 1; ivf.refinetet = *splittet; - loc = insertvertex(newpt, &searchtet, NULL, NULL, &ivf); - if (loc == (int) ENCSEGMENT) { - if (b->verbose > 2) { - printf(" Point encroached upon %ld segments.\n", - encseglist->objects); - } - pointdealloc(newpt); - assert(encseglist->objects > 0); - splitflag = 0; - if (!b->nobisect) { // not -Y option - // Select an encroached segment and split it. - for (i = 0; i < encseglist->objects; i++) { - paryseg = (face *) fastlookup(encseglist, i); - if (splitsegment(paryseg, NULL, qflag, chkencflag | 3)) { - splitflag = 1; // A point is inserted on a segment. - break; - } - } - } // if (!b->nobisect) - encseglist->restart(); - if (splitflag) { - // Some segments may need to be repaired. - repairencsegs(chkencflag | 3); - // Some subfaces may need to be repaired. - repairencfacs(chkencflag | 2); - // Queue the tet if it is still alive and not queued. - if (splittet->tet[4] != NULL) { - if (!marktest2ed(*splittet)) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = *splittet; - marktest2(bface->tt); // Only queue it once. - bface->forg = org(*splittet); // An alive badface. - } - } - } - return splitflag; - } else if (loc == (int) ENCSUBFACE) { - if (b->verbose > 2) { - printf(" Point encroached upon %ld subfaces.\n", - encshlist->objects); - } - pointdealloc(newpt); - assert(encshlist->objects > 0); - splitflag = 0; - if (!b->nobisect) { // not -Y option - // Select an encroached subface and split it. - for (i = 0; i < encshlist->objects; i++) { - bface = (badface *) fastlookup(encshlist, i); - if (splitsubface(&(bface->ss),NULL,qflag,bface->cent,chkencflag | 2)) { - splitflag = 1; // A point is inserted on a subface or a segment. - break; - } - } - } // if (!b->nobisect) - encshlist->restart(); - if (splitflag) { - assert(badsubsegs->items == 0l); // repairencsegs(chkencflag | 3); - // Some subfaces may need to be repaired. - repairencfacs(chkencflag | 2); - // Queue the tet if it is still alive. - if (splittet->tet[4] != NULL) { - if (!marktest2ed(*splittet)) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = *splittet; - marktest2(bface->tt); // Only queue it once. - bface->forg = org(*splittet); // An alive badface. - } - } - } - return splitflag; - } else if (loc == (int) OUTSIDE) { - // There exists not boundary conforming segments/subfaces. - pointdealloc(newpt); - } else if (loc == (int) ONVERTEX) { - // Found a coincident vertex. It should be a Steiner point. - pa = org(searchtet); - assert(pointtype(pa) == FREEVOLVERTEX); - // Delete this new point. - pointdealloc(newpt); - } else if (loc == (int) NEARVERTEX) { - // The point lies very close to an existing point. - pa = point2ppt(newpt); - assert(pointtype(pa) == FREEVOLVERTEX); - // Delete this new point. - pointdealloc(newpt); - } else if (loc == (int) ENCVERTEX) { - // The new point encoraches upon some protecting balls. Rejected. - pointdealloc(newpt); - } else if (loc == (int) BADELEMENT) { - pointdealloc(newpt); - } else { - if (b->verbose > 2) { - printf(" Point inserted successfully.\n"); - } - // Recover Delaunayness. - lawsonflip3d(newpt, 4, 0, chkencflag, 0); + if (insertpoint(newpt, &searchtet, NULL, NULL, &ivf)) { // Vertex is inserted. st_volref_count++; if (steinerleft > 0) steinerleft--; + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); + } return 1; + } else { + // Point is not inserted. + pointdealloc(newpt); + // Check if there are encroached segments/subfaces. + if (ivf.iloc == (int) ENCSEGMENT) { + splitflag = 0; + //if (!b->nobisect) { // not -Y option + if (!b->nobisect || checkconstraints) { + // Select an encroached segment and split it. + for (i = 0; i < encseglist->objects; i++) { + paryseg = (face *) fastlookup(encseglist, i); + if (splitsegment(paryseg, NULL, rv, org(*splittet), NULL, qflag, + chkencflag | 3)) { + splitflag = 1; // A point is inserted on a segment. + break; + } + } + } // if (!b->nobisect) + encseglist->restart(); + if (splitflag) { + // Some segments may need to be repaired. + repairencsegs(chkencflag | 3); + // Some subfaces may need to be repaired. + repairencfacs(chkencflag | 2); + // Queue the tet if it is still alive and not queued. + if ((splittet->tet != NULL) && (splittet->tet[4] != NULL)) { + enqueuetetrahedron(splittet); + } + } + return splitflag; + } else if (ivf.iloc == (int) ENCSUBFACE) { + splitflag = 0; + //if (!b->nobisect) { // not -Y option + if (!b->nobisect || checkconstraints) { + // Select an encroached subface and split it. + for (i = 0; i < encshlist->objects; i++) { + bface = (badface *) fastlookup(encshlist, i); + if (splitsubface(&(bface->ss), NULL, org(*splittet), qflag, + bface->cent, chkencflag | 2)){ + splitflag = 1; // A point is inserted on a subface or a segment. + break; + } + } + } // if (!b->nobisect) + encshlist->restart(); + if (splitflag) { + assert(badsubsegs->items == 0l); + // Some subfaces may need to be repaired. + repairencfacs(chkencflag | 2); + // Queue the tet if it is still alive. + if ((splittet->tet != NULL) && (splittet->tet[4] != NULL)) { + enqueuetetrahedron(splittet); + } + } + return splitflag; + } + return 0; } - - return 0; } /////////////////////////////////////////////////////////////////////////////// @@ -1617,28 +1413,33 @@ int tetgenmesh::splittetrahedron(triface* splittet, int qflag, REAL *ccent, void tetgenmesh::repairbadtets(int chkencflag) { - badface *bface; + triface *bface; REAL ccent[3]; int qflag = 0; + // Loop until the pool 'badsubfacs' is empty. Note that steinerleft == -1 // if an unlimited number of Steiner points is allowed. while ((badtetrahedrons->items > 0) && (steinerleft != 0)) { badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); + bface = (triface *) badtetrahedrons->traverse(); while ((bface != NULL) && (steinerleft != 0)) { - // A queued tet may have been deleted. - if (!isdeadtet(bface->tt)) { - // A queued tet may have been processed. - if (marktest2ed(bface->tt)) { - unmarktest2(bface->tt); - if (checktet4split(&(bface->tt), qflag, ccent)) { - splittetrahedron(&(bface->tt), qflag, ccent, chkencflag); + // Skip a deleted element. + if (bface->ver >= 0) { + // A queued tet may have been deleted. + if (!isdeadtet(*bface)) { + // A queued tet may have been processed. + if (marktest2ed(*bface)) { + unmarktest2(*bface); + if (checktet4split(bface, qflag, ccent)) { + splittetrahedron(bface, qflag, ccent, chkencflag); + } } } + bface->ver = -1; // Signal it as a deleted element. + badtetrahedrons->dealloc((void *) bface); } - badfacedealloc(badtetrahedrons, bface); - bface = badfacetraverse(badtetrahedrons); + bface = (triface *) badtetrahedrons->traverse(); } } @@ -1652,30 +1453,31 @@ void tetgenmesh::repairbadtets(int chkencflag) } // Unmark all queued tet. badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); + bface = (triface *) badtetrahedrons->traverse(); while (bface != NULL) { - if (!isdeadtet(bface->tt)) { - if (marktest2ed(bface->tt)) { - unmarktest2(bface->tt); + // Skip a deleted element. + if (bface->ver >= 0) { + if (!isdeadtet(*bface)) { + if (marktest2ed(*bface)) { + unmarktest2(*bface); + } } } - bface = badfacetraverse(badtetrahedrons); + bface = (triface *) badtetrahedrons->traverse(); } // Clear the pool. badtetrahedrons->restart(); } } - /////////////////////////////////////////////////////////////////////////////// // // -// enforcequality() Refine the mesh. // +// delaunayrefinement() Refine the mesh by Delaunay refinement. // // // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::delaunayrefinement() { - badface *bface; triface checktet; face checksh; face checkseg; @@ -1711,19 +1513,20 @@ void tetgenmesh::delaunayrefinement() } } - if (b->refine || b->nobisect) { // '-r' or '-Y' option. - markacutevertices(); + if (useinsertradius) { + if ((b->plc && b->nobisect) || b->refine) { // '-pY' or '-r' option. + makesegmentendpointsmap(); + } + makefacetverticesmap(); } - marksharpsegments(); - - decidefeaturepointsizes(); - encseglist = new arraypool(sizeof(face), 8); encshlist = new arraypool(sizeof(badface), 8); - if (!b->nobisect) { // if no '-Y' option + + //if (!b->nobisect) { // if no '-Y' option + if (!b->nobisect || checkconstraints) { if (b->verbose) { printf(" Splitting encroached subsegments.\n"); } @@ -1732,17 +1535,14 @@ void tetgenmesh::delaunayrefinement() steinercount = points->items; // Initialize the pool of encroached subsegments. - badsubsegs = new memorypool(sizeof(badface), b->shellfaceperblock, - memorypool::POINTER, 0); + badsubsegs = new memorypool(sizeof(face), b->shellfaceperblock, + sizeof(void *), 0); // Add all segments into the pool. subsegs->traversalinit(); checkseg.sh = shellfacetraverse(subsegs); while (checkseg.sh != (shellface *) NULL) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. + enqueuesubface(badsubsegs, &checkseg); checkseg.sh = shellfacetraverse(subsegs); } @@ -1753,7 +1553,6 @@ void tetgenmesh::delaunayrefinement() printf(" Added %ld Steiner points.\n", points->items - steinercount); } - if (b->reflevel > 1) { // '-D2' option if (b->verbose) { printf(" Splitting encroached subfaces.\n"); @@ -1765,17 +1564,14 @@ void tetgenmesh::delaunayrefinement() bak_facref_count = st_facref_count; // Initialize the pool of encroached subfaces. - badsubfacs = new memorypool(sizeof(badface), b->shellfaceperblock, - memorypool::POINTER, 0); + badsubfacs = new memorypool(sizeof(face), b->shellfaceperblock, + sizeof(void *), 0); // Add all subfaces into the pool. subfaces->traversalinit(); checksh.sh = shellfacetraverse(subfaces); while (checksh.sh != (shellface *) NULL) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface. + enqueuesubface(badsubfacs, &checksh); checksh.sh = shellfacetraverse(subfaces); } @@ -1787,7 +1583,6 @@ void tetgenmesh::delaunayrefinement() points->items-steinercount, st_segref_count-bak_segref_count, st_facref_count-bak_facref_count); } - } // if (b->reflevel > 1) } // if (!b->nobisect) @@ -1806,17 +1601,13 @@ void tetgenmesh::delaunayrefinement() cosmindihed = cos(b->mindihedral / 180.0 * PI); // Initialize the pool of bad quality tetrahedra. - badtetrahedrons = new memorypool(sizeof(badface), b->tetrahedraperblock, - memorypool::POINTER, 0); - + badtetrahedrons = new memorypool(sizeof(triface), b->tetrahedraperblock, + sizeof(void *), 0); // Add all tetrahedra (no hull tets) into the pool. tetrahedrons->traversalinit(); checktet.tet = tetrahedrontraverse(); while (checktet.tet != NULL) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = checktet; - marktest2(bface->tt); // Only queue it once. - bface->forg = org(checktet); // An alive badface. + enqueuetetrahedron(&checktet); checktet.tet = tetrahedrontraverse(); } @@ -1829,10 +1620,15 @@ void tetgenmesh::delaunayrefinement() st_segref_count - bak_segref_count, st_facref_count - bak_facref_count, st_volref_count - bak_volref_count); + } + } // if (b->reflevel > 2) + + if (b->verbose) { + if (flip23count + flip32count + flip44count > bak_flipcount) { printf(" Performed %ld flips.\n", flip23count + flip32count + flip44count - bak_flipcount); } - } // if (b->reflevel > 2) + } if (steinerleft == 0) { if (!b->quiet) { @@ -1842,16 +1638,21 @@ void tetgenmesh::delaunayrefinement() } } + delete encseglist; delete encshlist; - if (!b->nobisect) { + //if (!b->nobisect) { + if (!b->nobisect || checkconstraints) { + totalworkmemory += (badsubsegs->maxitems * badsubsegs->itembytes); delete badsubsegs; if (b->reflevel > 1) { + totalworkmemory += (badsubfacs->maxitems * badsubfacs->itembytes); delete badsubfacs; } } if (b->reflevel > 2) { + totalworkmemory += (badtetrahedrons->maxitems*badtetrahedrons->itembytes); delete badtetrahedrons; } } diff --git a/external/tetgen/src/steiner.cxx b/external/tetgen/src/steiner.cxx index 174298270..e051bdab7 100644 --- a/external/tetgen/src/steiner.cxx +++ b/external/tetgen/src/steiner.cxx @@ -21,31 +21,23 @@ int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, int level, int edgepivot, flipconstraints* fc) { - int rejflag; - int i; - point tmppts[3]; - REAL normal[3], area, len; - REAL ori1, ori2; - REAL abovept[3]; - enum interresult dir; int types[2], poss[4]; int intflag; - - rejflag = 0; + int rejflag = 0; + int i; if (fc->seg[0] != NULL) { // A constraining edge is given (e.g., for edge recovery). if (fliptype == 1) { // A 2-to-3 flip: [a,b,c] => [e,d,a], [e,d,b], [e,d,c]. - if (pc != dummypoint) { - // Do not flip if the newly created faces intersect this edge in - // their interiors. - tmppts[0] = pa; - tmppts[1] = pb; - tmppts[2] = pc; - for (i = 0; i < 3 && !rejflag; i++) { + tmppts[0] = pa; + tmppts[1] = pb; + tmppts[2] = pc; + for (i = 0; i < 3 && !rejflag; i++) { + if (tmppts[i] != dummypoint) { + // Test if the face [e,d,#] intersects the edge. intflag = tri_edge_test(pe, pd, tmppts[i], fc->seg[0], fc->seg[1], NULL, 1, types, poss); if (intflag == 2) { @@ -71,134 +63,31 @@ int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, rejflag = 1; } } - } // if (intflag == 4) - } // i - } else { // pc == dummypoint - // Do not flip if the new hull edge [e,d] will intersect this edge - // in its interior. - // Comment: Here we actually need a 3D edge-edge test. - // We only do test if the edge in 'fc' is coplanar with the plane - // containing a,b,e,and d. - // Choose a better triangle [a,b,e] or [a,b,d]. - facenormal(pa, pb, pe, normal, 1, &len); - area = sqrt(DOT(normal, normal)); - facenormal(pa, pb, pd, normal, 1, &len); - len = sqrt(DOT(normal, normal)); // Re-use len as area. - if (area > len) { - // Choose [a,b,e] - ori1 = orient3d(pa, pb, pe, fc->seg[0]); - ori2 = orient3d(pa, pb, pe, fc->seg[1]); - } else { - // Choose [a,b,d] - ori1 = orient3d(pa, pb, pd, fc->seg[0]); - ori2 = orient3d(pa, pb, pd, fc->seg[1]); - } - if ((ori1 == 0) && (ori2 == 0)) { - calculateabovepoint4(pa, pb, pe, pd); - for (i = 0; i < 3; i++) { - abovept[i] = dummypoint[i]; } - intflag = tri_edge_test(pe, pd, abovept, fc->seg[0], fc->seg[1], - NULL, 1, types, poss); - if (intflag == 2) { - dir = (enum interresult) types[0]; - assert(dir != ACROSSFACE); - if (dir == ACROSSEDGE) { - if (poss[0] == 0) { - // The interior of [e,d] intersect the segment. - // Since [e,d] is the newly created edge. Reject this flip. - rejflag = 1; - } - } - } else if (intflag == 4) { - // [e,d,abovept] is coplanar with the constraining edge 'fc'. - // This is poissible if the edge in 'fc' is just the edge [e,d] - // (SHAREEDGE) or they share a common vertex (SHAREVEER) - dir = (enum interresult) types[0]; - if (dir == ACROSSEDGE) { - // This case can only happen if [e,d] is coplanar with 'fc'. - assert(0); // Not possible. - } - } - } - } // if (pc == dummypoint) + } // if (tmppts[0] != dummypoint) + } // i } else if (fliptype == 2) { // A 3-to-2 flip: [e,d,a], [e,d,b], [e,d,c] => [a,b,c] if (pc != dummypoint) { - if (!rejflag) { - // Check if the new face [a,b,c] intersect the edge in its interior. - intflag = tri_edge_test(pa, pb, pc, fc->seg[0], fc->seg[1], NULL, - 1, types, poss); - if (intflag == 2) { - // They intersect at a single point. - dir = (enum interresult) types[0]; - if (dir == ACROSSFACE) { - // The interior of [a,b,c] intersect the segment. - rejflag = 1; // Do not flip. - } else if (dir == ACROSSEDGE) { - // This case is possible since we allow a previous 2-to-3 flip - // even it will create a degenerate tet at edge [a,b]. - } - } else if (intflag == 4) { - // [a,b,c] is coplanar with the edge. - dir = (enum interresult) types[0]; - if (dir == ACROSSEDGE) { - // The boundary of [a,b,c] intersect the segment. - // An example is found in case 'camila.poly', during the recovery - // of segment [151, 161] (at linklevel = 2). See: 2011-06-10-a. - rejflag = 1; // Do not flip. - } + // Check if the new face [a,b,c] intersect the edge in its interior. + intflag = tri_edge_test(pa, pb, pc, fc->seg[0], fc->seg[1], NULL, + 1, types, poss); + if (intflag == 2) { + // They intersect at a single point. + dir = (enum interresult) types[0]; + if (dir == ACROSSFACE) { + // The interior of [a,b,c] intersect the segment. + rejflag = 1; // Do not flip. + } + } else if (intflag == 4) { + // [a,b,c] is coplanar with the edge. + dir = (enum interresult) types[0]; + if (dir == ACROSSEDGE) { + // The boundary of [a,b,c] intersect the segment. + rejflag = 1; // Do not flip. } - } // if (!relflag) - } else { // pc == dummypoint - // The flip 3-to-2 will replace [e,d] with a new hull edge [a,b]. - // Only do flip if [a,b] does not intersect the edge of 'fc'. - // Comment: Here we acutually need a 3D edge-edge intersection test. - // We only do test if the edge in 'fc' is coplanar with the plane - // containing a,b,e, and d. - // Choose a better triangle [a,b,e] or [a,b,d]. - facenormal(pa, pb, pe, normal, 1, &len); - area = sqrt(DOT(normal, normal)); - facenormal(pa, pb, pd, normal, 1, &len); - len = sqrt(DOT(normal, normal)); // Re-use len as area. - if (area > len) { - // Choose [a,b,e] - ori1 = orient3d(pa, pb, pe, fc->seg[0]); - ori2 = orient3d(pa, pb, pe, fc->seg[1]); - } else { - // Choose [a,b,d] - ori1 = orient3d(pa, pb, pd, fc->seg[0]); - ori2 = orient3d(pa, pb, pd, fc->seg[1]); } - if ((ori1 == 0) && (ori2 == 0)) { - // The edge in 'fc' is coplanar with the plane containing [a,b,e,d]. - calculateabovepoint4(pa, pb, pe, pd); - for (i = 0; i < 3; i++) { - abovept[i] = dummypoint[i]; - } - intflag = tri_edge_test(pa, pb, abovept, fc->seg[0], fc->seg[1], - NULL, 1, types, poss); - if (intflag == 2) { - dir = (enum interresult) types[0]; - assert(dir != ACROSSFACE); - if (dir == ACROSSEDGE) { - assert(0); // Check this case. - rejflag = 1; // Do not flip. - } - } else if (intflag == 4) { - // The edge 'fc' is coplanar with [a,b,abovept]. - // This is poissible if the edge in 'fc' is just the edge [a,b] - // (SHAREEDGE) or they share a common vertex (SHAREVEER) - dir = (enum interresult) types[0]; - if (dir == ACROSSEDGE) { - // This case can only happen if [a,b] is coplanar with 'fc'. - assert(0); // Not possible. - } - } - } // if (ori1 == 0 && ori2 == 0) - } - } else { - assert(0); // An unknown flip type. + } // if (pc != dummypoint) } } // if (fc->seg[0] != NULL) @@ -360,7 +249,7 @@ int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, // 'flipedge' is a non-convex or flat edge [a,b,#,#] to be removed. // // // // The return value is a positive integer, it indicates whether the edge is // -// removed or not. A value "2" means the edge is removed, othereise, the // +// removed or not. A value "2" means the edge is removed, otherwise, the // // edge is not removed and the value (must >= 3) is the current number of // // tets in the edge star. // // // @@ -369,27 +258,16 @@ int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) { triface *abtets, spintet; - face checkseg, *paryseg; + int t1ver; int n, nn, i; - if (b->verbose > 2) { - printf(" Removing edge (%d, %d)\n", pointmark(org(*flipedge)), - pointmark(dest(*flipedge))); - } - - fc->clearcounters(); - if (checksubsegflag) { // Do not flip a segment. - tsspivot1(*flipedge, checkseg); - if (checkseg.sh != NULL) { - if (b->verbose > 2) { - printf(" Can't flip a segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - fc->encsegcount++; + if (issubseg(*flipedge)) { if (fc->collectencsegflag) { + face checkseg, *paryseg; + tsspivot1(*flipedge, checkseg); if (!sinfected(checkseg)) { // Queue this segment in list. sinfect(checkseg); @@ -403,33 +281,16 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) // Count the number of tets at edge [a,b]. n = 0; - int counter = 0; // Sum of star counters. // SELF_CHECK. spintet = *flipedge; - i = 0; while (1) { - counter += elemcounter(spintet); - i++; + n++; fnextself(spintet); if (spintet.tet == flipedge->tet) break; } - //assert(i >= 3); - if (i < 3) { - // It is only possible when the mesh contains inverted tetrahedra. - assert(checkinverttetflag); - // Since "return 2" means success, we return 0. - return 0; - } - assert(counter == 0); // SELF_CHECK - n = i; + assert(n >= 3); - flipstarcount++; - // Record the maximum star size. - if (n > maxflipstarsize) { - maxflipstarsize = n; - } if ((b->flipstarsize > 0) && (n > b->flipstarsize)) { // The star size exceeds the limit. - skpflipstarcount++; return 0; // Do not flip it. } @@ -440,7 +301,7 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) i = 0; while (1) { abtets[i] = spintet; - setelemcounter(abtets[i], 1); // Marktest it (in Star(ab)). + setelemcounter(abtets[i], 1); i++; fnextself(spintet); if (spintet.tet == flipedge->tet) break; @@ -451,18 +312,9 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) nn = flipnm(abtets, n, 0, 0, fc); - if (nn == 2) { - // Edge is flipped. - if (b->verbose > 2) { - printf(" Edge is removed.\n"); - } - } else { - if (b->verbose > 2) { - printf(" Edge is not removed. n(%d), nn(%d).\n", n, nn); - } + if (nn > 2) { // Edge is not flipped. Unmarktest the remaining tets in Star(ab). for (i = 0; i < nn; i++) { - assert(elemcounter(abtets[i]) == 1); setelemcounter(abtets[i], 0); } // Restore the input edge (needed by Lawson's flip). @@ -473,20 +325,20 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) // NOTE: fc->unflip must be 0. int bakunflip = fc->unflip; fc->unflip = 0; - flipnm_post(abtets, n, nn, 0, fc); - fc->unflip = bakunflip; delete [] abtets; - return nn; //return nn == 2; + return nn; } /////////////////////////////////////////////////////////////////////////////// // // // removefacebyflips() Remove a face by flips. // // // +// Return 1 if the face is removed. Otherwise, return 0. // +// // // ASSUMPTIONS: // // - 'flipface' must not be a hull face. // // // @@ -494,22 +346,17 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) { - triface fliptets[3], flipedge; - face checksh; - point pa, pb, pc, pd, pe; - REAL ori; - int reducflag, rejflag; - if (checksubfaceflag) { - tspivot(*flipface, checksh); - if (checksh.sh != NULL) { - if (b->verbose > 2) { - printf(" Can't flip a subface.\n"); - } + if (issubface(*flipface)) { return 0; } } + triface fliptets[3], flipedge; + point pa, pb, pc, pd, pe; + REAL ori; + int reducflag = 0; + fliptets[0] = *flipface; fsym(*flipface, fliptets[1]); pa = org(fliptets[0]); @@ -518,13 +365,6 @@ int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) pd = oppo(fliptets[0]); pe = oppo(fliptets[1]); - if (b->verbose > 2) { - printf(" Removing face (%d, %d, %d) -- %d, %d\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pe)); - } - - reducflag = 0; - ori = orient3d(pa, pb, pd, pe); if (ori > 0) { ori = orient3d(pb, pc, pd, pe); @@ -545,23 +385,11 @@ int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) if (reducflag) { // A 2-to-3 flip is found. - rejflag = 0; - if (fc != NULL) { - //rejflag = checkflipeligibility(1, pa, pb, pc, pd, pe, fc); - } - if (!rejflag) { - flip23(fliptets, 0, 0, 0); - if (b->verbose > 2) { - printf(" Face is removed by a 2-to-3 flip.\n"); - } - return 1; - } + flip23(fliptets, 0, fc); + return 1; } else { // Try to flip the selected edge of this face. if (removeedgebyflips(&flipedge, fc) == 2) { - if (b->verbose > 2) { - printf(" Face is removed by removing an edge.\n"); - } return 1; } } @@ -577,7 +405,7 @@ int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) // If the edge is recovered, 'searchtet' returns a tet containing the edge. // // // // This edge may intersect a set of faces and edges in the mesh. All these // -// faces or edges are needed to be flipped. // +// faces or edges are needed to be removed. // // // // If the parameter 'fullsearch' is set, it tries to flip any face or edge // // that intersects the recovering edge. Otherwise, only the face or edge // @@ -588,21 +416,12 @@ int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) int tetgenmesh::recoveredgebyflips(point startpt, point endpt, triface* searchtet, int fullsearch) { - triface neightet, spintet; - point pa, pb, pc, pd; - badface bakface; - enum interresult dir, dir1; flipconstraints fc; - int types[2], poss[4], pos = 0; - int success; - int i, j; + enum interresult dir; - if (b->verbose > 2) { - printf(" Recovering edge (%d, %d)\n", pointmark(startpt), - pointmark(endpt)); - } fc.seg[0] = startpt; fc.seg[1] = endpt; + fc.checkflipeligibility = 1; // The mainloop of the edge reocvery. while (1) { // Loop I @@ -614,8 +433,7 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, if (dest(*searchtet) == endpt) { return 1; // Edge is recovered. } else { - // A PLC problem, or there is a Steiner point. - terminatetetgen(3); + terminatetetgen(this, 3); // // It may be a PLC problem. } } @@ -634,15 +452,21 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, continue; } } else { - terminatetetgen(3); //assert(0); // A PLC problem. + terminatetetgen(this, 3); // It may be a PLC problem. } // The edge is missing. if (fullsearch) { - // Try to flip one of the faces/edges which intersects the edge. - success = 0; + triface neightet, spintet; + point pa, pb, pc, pd; + badface bakface; + enum interresult dir1; + int types[2], poss[4], pos = 0; + int success = 0; + int t1ver; + int i, j; // Loop through the sequence of intersecting faces/edges from // 'startpt' to 'endpt'. @@ -775,7 +599,7 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, searchtet->tet = NULL; break; // Not find. } - } // while (1) + } // while (1) if (searchtet->tet != NULL) { if (oppo(*searchtet) != bakface.foppo) { fsymself(*searchtet); @@ -790,7 +614,7 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, searchtet->tet = NULL; // Not find. } if (searchtet->tet == NULL) { - success = 0; // This face/edge has been destroed. + success = 0; // This face/edge has been destroyed. break; // Loop I-I } } @@ -808,7 +632,6 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, } // while (1) // Loop I - // The edge is not recovered. return 0; } @@ -824,11 +647,6 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, // Such set of tets arises when we want to recover an edge from 'p0' to 'p_ // // (n-1)', and the number of tets at [a,b] can not be reduced by any flip. // // // -// The union of these tets is a polyhedron P. Obviously that P is a star- // -// shaped polyhedron. The midpoint of [a,b] is visible by all boundary faces // -// of P, push it slightly inside P does not change the visibilty. Indeed // -// every interior point of [a,b] is visible by the boundary faces of P. // -// // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, @@ -836,55 +654,46 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, { triface worktet, *parytet; triface faketet1, faketet2; - point pa, pb, pc, pd; - point p1, p2, p3; - point steinerpt; + point pc, pd, steinerpt; insertvertexflags ivf; optparameters opm; REAL vcd[3], sampt[3], smtpt[3]; REAL maxminvol = 0.0, minvol = 0.0, ori; int success, maxidx = 0; - int loc; int it, i; - if (b->verbose > 2) { - printf(" Find a Steiner in Schoenhardt polyhedron (n=%d).\n", n); - } - pa = org(abtets[0]); - pb = dest(abtets[0]); pc = apex(abtets[0]); // pc = p0 pd = oppo(abtets[n-1]); // pd = p_(n-1) + // Find an optimial point in edge [c,d]. It is visible by all outer faces // of 'abtets', and it maxmizes the min volume. // initialize the list of 2n boundary faces. for (i = 0; i < n; i++) { - eprev(abtets[i], worktet); - esymself(worktet); // [a,p_i,p_i+1]. + edestoppo(abtets[i], worktet); // [p_i,p_i+1,a] cavetetlist->newindex((void **) &parytet); *parytet = worktet; - enext(abtets[i], worktet); - esymself(worktet); // [p_i,b,p_i+1]. + eorgoppo(abtets[i], worktet); // [p_i+1,p_i,b] cavetetlist->newindex((void **) &parytet); *parytet = worktet; } + int N = 100; + REAL stepi = 0.01; + // Search the point along the edge [c,d]. for (i = 0; i < 3; i++) vcd[i] = pd[i] - pc[i]; - // Sample 100 points in edge [c,d]. - for (it = 1; it < 100; it++) { + // Sample N points in edge [c,d]. + for (it = 1; it < N; it++) { for (i = 0; i < 3; i++) { - sampt[i] = pc[i] + (0.01 * (double) it) * vcd[i]; + sampt[i] = pc[i] + (stepi * (double) it) * vcd[i]; } for (i = 0; i < cavetetlist->objects; i++) { parytet = (triface *) fastlookup(cavetetlist, i); - p1 = org(*parytet); - p2 = dest(*parytet); - p3 = apex(*parytet); - ori = orient3d(p2, p1, p3, sampt); + ori = orient3d(dest(*parytet), org(*parytet), apex(*parytet), sampt); if (i == 0) { minvol = ori; } else { @@ -903,26 +712,22 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, } // it if (maxminvol <= 0) { - if (b->verbose > 2) { - printf(" Unable to find a initial point: maxminvol = %g\n", - maxminvol); - } cavetetlist->restart(); return 0; } for (i = 0; i < 3; i++) { - smtpt[i] = pc[i] + (0.01 * (double) maxidx) * vcd[i]; + smtpt[i] = pc[i] + (stepi * (double) maxidx) * vcd[i]; } // Create two faked tets to hold the two non-existing boundary faces: // [d,c,a] and [c,d,b]. maketetrahedron(&faketet1); - setvertices(faketet1, pd, pc, pa, dummypoint); + setvertices(faketet1, pd, pc, org(abtets[0]), dummypoint); cavetetlist->newindex((void **) &parytet); *parytet = faketet1; maketetrahedron(&faketet2); - setvertices(faketet2, pc, pd, pb, dummypoint); + setvertices(faketet2, pc, pd, dest(abtets[0]), dummypoint); cavetetlist->newindex((void **) &parytet); *parytet = faketet2; @@ -955,9 +760,6 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, cavetetlist->restart(); if (!success) { - if (b->verbose > 2) { - printf(" Unable to relocate the initial point.\n"); - } return 0; } @@ -974,28 +776,23 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, } worktet = abtets[0]; // No need point location. ivf.iloc = (int) INSTAR; - ivf.bowywat = 0; // Do not use Bowyer-Watson algorithm. - ivf.lawson = 0; // Do not flip. - ivf.rejflag = 0; ivf.chkencflag = chkencflag; - ivf.sloc = 0; - ivf.sbowywat = 0; - ivf.splitbdflag = 0; - ivf.validflag = 0; - ivf.respectbdflag = 0; ivf.assignmeshsize = b->metric; + if (ivf.assignmeshsize) { + // Search the tet containing 'steinerpt' for size interpolation. + locate(steinerpt, &(abtets[0])); + worktet = abtets[0]; + } // Insert the new point into the tetrahedralization T. // Note that T is convex (nonconvex = 0). - loc = insertvertex(steinerpt, &worktet, NULL, NULL, &ivf); - - if (loc == (int) INSTAR) { + if (insertpoint(steinerpt, &worktet, NULL, NULL, &ivf)) { // The vertex has been inserted. st_volref_count++; if (steinerleft > 0) steinerleft--; return 1; } else { - // The Steiner point is too close to an existing vertex. Reject it. + // Not inserted. pointdealloc(steinerpt); return 0; } @@ -1003,7 +800,181 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, /////////////////////////////////////////////////////////////////////////////// // // -// addsteiner4recoversegment() Add a Steiner point for recoveing a seg. // +// add_steinerpt_in_segment() Add a Steiner point inside a segment. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::add_steinerpt_in_segment(face* misseg, int searchlevel) +{ + triface searchtet; + face *paryseg, candseg; + point startpt, endpt, pc, pd; + flipconstraints fc; + enum interresult dir; + REAL P[3], Q[3], tp, tq; + REAL len, smlen = 0, split = 0, split_q = 0; + int success; + int i; + + startpt = sorg(*misseg); + endpt = sdest(*misseg); + + fc.seg[0] = startpt; + fc.seg[1] = endpt; + fc.checkflipeligibility = 1; + fc.collectencsegflag = 1; + + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + //assert(dir != ACROSSVERT); + + // Try to flip the first intersecting face/edge. + enextesymself(searchtet); // Go to the opposite face. + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = searchlevel; + + if (dir == ACROSSFACE) { + // A face is intersected with the segment. Try to flip it. + success = removefacebyflips(&searchtet, &fc); + assert(success == 0); + } else if (dir == ACROSSEDGE) { + // An edge is intersected with the segment. Try to flip it. + success = removeedgebyflips(&searchtet, &fc); + assert(success != 2); + } else { + terminatetetgen(this, 3); // It may be a PLC problem. + } + + split = 0; + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + suninfect(*paryseg); + // Calculate the shortest edge between the two lines. + pc = sorg(*paryseg); + pd = sdest(*paryseg); + tp = tq = 0; + if (linelineint(startpt, endpt, pc, pd, P, Q, &tp, &tq)) { + // Does the shortest edge lie between the two segments? + // Round tp and tq. + if ((tp > 0) && (tq < 1)) { + if (tp < 0.5) { + if (tp < (b->epsilon * 1e+3)) tp = 0.0; + } else { + if ((1.0 - tp) < (b->epsilon * 1e+3)) tp = 1.0; + } + } + if ((tp <= 0) || (tp >= 1)) continue; + if ((tq > 0) && (tq < 1)) { + if (tq < 0.5) { + if (tq < (b->epsilon * 1e+3)) tq = 0.0; + } else { + if ((1.0 - tq) < (b->epsilon * 1e+3)) tq = 1.0; + } + } + if ((tq <= 0) || (tq >= 1)) continue; + // It is a valid shortest edge. Calculate its length. + len = distance(P, Q); + if (split == 0) { + smlen = len; + split = tp; + split_q = tq; + candseg = *paryseg; + } else { + if (len < smlen) { + smlen = len; + split = tp; + split_q = tq; + candseg = *paryseg; + } + } + } + } + + caveencseglist->restart(); + b->fliplinklevel = bak_fliplinklevel; + + if (split == 0) { + // Found no crossing segment. + return 0; + } + + face splitsh; + face splitseg; + point steinerpt, *parypt; + insertvertexflags ivf; + + if (b->addsteiner_algo == 1) { + // Split the segment at the closest point to a near segment. + makepoint(&steinerpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = startpt[i] + split * (endpt[i] - startpt[i]); + } + } else { // b->addsteiner_algo == 2 + for (i = 0; i < 3; i++) { + P[i] = startpt[i] + split * (endpt[i] - startpt[i]); + } + pc = sorg(candseg); + pd = sdest(candseg); + for (i = 0; i < 3; i++) { + Q[i] = pc[i] + split_q * (pd[i] - pc[i]); + } + makepoint(&steinerpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = 0.5 * (P[i] + Q[i]); + } + } + + // We need to locate the point. Start searching from 'searchtet'. + if (split < 0.5) { + point2tetorg(startpt, searchtet); + } else { + point2tetorg(endpt, searchtet); + } + if (b->addsteiner_algo == 1) { + splitseg = *misseg; + spivot(*misseg, splitsh); + } else { + splitsh.sh = NULL; + splitseg.sh = NULL; + } + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 1; + ivf.lawson = 0; + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (!insertpoint(steinerpt, &searchtet, &splitsh, &splitseg, &ivf)) { + pointdealloc(steinerpt); + return 0; + } + + if (b->addsteiner_algo == 1) { + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + st_segref_count++; + } else { // b->addsteiner_algo == 2 + // Queue the segment for recovery. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + st_volref_count++; + } + if (steinerleft > 0) steinerleft--; + + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// addsteiner4recoversegment() Add a Steiner point for recovering a seg. // // // /////////////////////////////////////////////////////////////////////////////// @@ -1011,16 +982,14 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) { triface *abtets, searchtet, spintet; face splitsh; - face checkseg; face *paryseg; point startpt, endpt; point pa, pb, pd, steinerpt, *parypt; enum interresult dir; insertvertexflags ivf; int types[2], poss[4]; - REAL ip[3], u; int n, endi, success; - int loc; + int t1ver; int i; startpt = sorg(*misseg); @@ -1033,10 +1002,6 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) // Try to recover the edge by adding Steiner points. point2tetorg(startpt, searchtet); dir = finddirection(&searchtet, endpt); - assert(dir != ACROSSVERT); - - // Get the first intersecting face/edge. - assert(!ishulltet(searchtet)); enextself(searchtet); //assert(apex(searchtet) == startpt); @@ -1044,13 +1009,8 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) // The segment is crossing at least 3 faces. Find the common edge of // the first 3 crossing faces. esymself(searchtet); - assert(oppo(searchtet) == startpt); fsym(searchtet, spintet); pd = oppo(spintet); - if (pd == endpt) { - // This should be possible. - assert(0); // Debug this case. - } for (i = 0; i < 3; i++) { pa = org(spintet); pb = dest(spintet); @@ -1066,8 +1026,9 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) } else { assert(dir == ACROSSEDGE); // PLC check. - tsspivot1(searchtet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(searchtet)) { + face checkseg; + tsspivot1(searchtet, checkseg); printf("Found two segments intersect each other.\n"); pa = farsorg(*misseg); pb = farsdest(*misseg); @@ -1077,7 +1038,7 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) pb = farsdest(checkseg); printf(" 2nd: [%d,%d] %d.\n", pointmark(pa), pointmark(pb), shellmark(checkseg)); - terminatetetgen(3); + terminatetetgen(this, 3); } } assert(apex(searchtet) == startpt); @@ -1104,8 +1065,6 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) abtets[i] = spintet; fnextself(spintet); } - assert(apex(abtets[0]) == startpt); - assert(apex(abtets[endi]) == endpt); success = 0; @@ -1138,10 +1097,10 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) // However, there will be invalid tets (either zero or negtive // volume). Otherwise, [c,d] should already be recovered by the // recoveredge() function. - assert(0); // DEBUG IT + terminatetetgen(this, 2); // Report a bug. } } else { - assert(0); // A PLC problem. + terminatetetgen(this, 10); // A PLC problem. } delete [] abtets; @@ -1162,99 +1121,22 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) printf(" Splitting segment (%d, %d)\n", pointmark(startpt), pointmark(endpt)); } + steinerpt = NULL; - if (endi == -1) { - // Let the missing segment be [a,b]. Let the edge [c,d] whose star contains - // a and intersects [a,b]. We choose the Steiner point at the intersection - // of the edge star of [c,d] and [a,b] (not a). - if (dir == ACROSSFACE) { - pa = org(searchtet); - pb = dest(searchtet); - - spintet = searchtet; - n = 0; endi = -1; - while (1) { - n++; // Count a tet in the star. - fnextself(spintet); - if (spintet.tet == searchtet.tet) break; - // Check if the segment leaves the edge star. - pd = apex(spintet); - assert(pd != endpt); - if (!tri_edge_test(pa, pb, pd, startpt, endpt, NULL, 1, types, poss)) { - if (endi == -1) endi = (n - 1); - } - } - assert(n >= 3); - assert(endi != -1); - - // 'abtets' is only for debug purpose. - abtets = new triface[endi]; - spintet = searchtet; - for (i = 0; i < endi; i++) { - abtets[i] = spintet; - fnextself(spintet); - } - searchtet = abtets[endi - 1]; - esymself(searchtet); // The exit face of [startpt, endpt]. - delete [] abtets; - } else { - assert(dir == ACROSSEDGE); - assert(apex(searchtet) == startpt); - esymself(searchtet); // The exit face of [startpt, endpt]. - //assert(oppo(searchtet) == startpt); - pa = org(searchtet); - pb = dest(searchtet); + if (b->addsteiner_algo > 0) { // -Y/1 or -Y/2 + if (add_steinerpt_in_segment(misseg, 3)) { + return 1; } - - pd = apex(searchtet); - // Get the intersection type (ACROSSFACE or ACROSSEDGE). - if (tri_edge_test(pa, pb, pd, startpt, endpt, NULL, 1, types, poss)) { - dir = (enum interresult) types[0]; - assert((dir == ACROSSFACE) || (dir == ACROSSEDGE)); - } else { - assert(0); // not possible. + sesymself(*misseg); + if (add_steinerpt_in_segment(misseg, 3)) { + return 1; } - - // Calculate the intersection of the face [a,b,d] and the segment. - planelineint(pa, pb, pd, startpt, endpt, ip, &u); - assert((u > 0) && (u < 1)); - - // Create a Steiner point. - makepoint(&steinerpt, FREESEGVERTEX); - for (i = 0; i < 3; i++) steinerpt[i] = ip[i]; - - - spivot(*misseg, splitsh); - if (dir == ACROSSFACE) { - ivf.iloc = (int) ONFACE; - } else { - ivf.iloc = (int) ONEDGE; - } - ivf.bowywat = 1; - ivf.lawson = 0; - ivf.rejflag = 0; - ivf.chkencflag = 0; - ivf.sloc = (int) ONEDGE; - ivf.sbowywat = 1; - ivf.splitbdflag = 0; - ivf.validflag = 1; - ivf.respectbdflag = 1; - ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, &splitsh, misseg, &ivf); - - if (loc != ivf.iloc) { - if (loc == (int) NEARVERTEX) { - // The vertex is rejected. Too close to an existing vertex. - pointdealloc(steinerpt); - steinerpt = NULL; - } else { - assert(0); // Unknown case. - } - } - } else { // if (endi > 0) - steinerpt = NULL; + sesymself(*misseg); } + + + if (steinerpt == NULL) { // Split the segment at its midpoint. makepoint(&steinerpt, FREESEGVERTEX); @@ -1276,10 +1158,9 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) ivf.validflag = 1; ivf.respectbdflag = 1; ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, &splitsh, misseg, &ivf); - - assert(loc != (int) ONVERTEX); - assert(loc != (int) NEARVERTEX); + if (!insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { + assert(0); + } } // if (endi > 0) // Save this Steiner point (for removal). @@ -1309,11 +1190,12 @@ int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, int steinerflag) { triface searchtet, spintet; - face sseg, checkseg, *paryseg; + face sseg, *paryseg; point startpt, endpt; int success; - + int t1ver; long bak_inpoly_count = st_volref_count; + long bak_segref_count = st_segref_count; if (b->verbose > 1) { printf(" Recover segments [%s level = %2d] #: %ld.\n", @@ -1362,8 +1244,6 @@ int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, if (success) { // Segment is recovered. Insert it. - tsspivot1(searchtet, checkseg); // SELF_CHECK - assert(checkseg.sh == NULL); // Let the segment remember an adjacent tet. sstbond1(sseg, searchtet); // Bond the segment to all tets containing it. @@ -1402,6 +1282,10 @@ int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, printf(" Add %ld Steiner points in volume.\n", st_volref_count - bak_inpoly_count); } + if (st_segref_count > bak_segref_count) { + printf(" Add %ld Steiner points in segments.\n", + st_segref_count - bak_segref_count); + } } } @@ -1421,24 +1305,19 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, face *searchsh, triface* searchtet) { triface spintet, flipedge; - face checkseg; point pd, pe; enum interresult dir; flipconstraints fc; + int types[2], poss[4], intflag; int success, success1; + int t1ver; int i, j; - int intflag; - int types[2], poss[4]; - - if (b->verbose > 2) { - printf(" Recovering face (%d, %d, %d) by flips\n", pointmark(pa), - pointmark(pb), pointmark(pc)); - } fc.fac[0] = pa; fc.fac[1] = pb; fc.fac[2] = pc; + fc.checkflipeligibility = 1; success = 0; for (i = 0; i < 3 && !success; i++) { @@ -1482,14 +1361,13 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, dir = (enum interresult) types[0]; if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { // Go to the edge [d,e]. - eprev(spintet, flipedge); - esymself(flipedge); - enextself(flipedge); // [d,e,a,b]. + edestoppo(spintet, flipedge); // [d,e,a,b] if (searchsh != NULL) { // Check if [e,d] is a segment. - tsspivot1(flipedge, checkseg); - if (checkseg.sh != NULL) { - if (!b->quiet) { + if (issubseg(flipedge)) { + if (!b->quiet) { + face checkseg; + tsspivot1(flipedge, checkseg); printf("Found a segment and a subface intersect.\n"); pd = farsorg(checkseg); pe = farsdest(checkseg); @@ -1497,16 +1375,16 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, pointmark(pe), shellmark(checkseg)); printf(" 2nd: [%d,%d,%d] %d\n", pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*searchsh)); - } - terminatetetgen(3); - } + } + terminatetetgen(this, 3); + } } // Try to flip the edge [d,e]. success1 = (removeedgebyflips(&flipedge, &fc) == 2); } else { if (dir == TOUCHFACE) { point touchpt, *parypt; - if (poss[0] == 0) { + if (poss[1] == 0) { touchpt = pd; // pd is a coplanar vertex. } else { touchpt = pe; // pe is a coplanar vertex. @@ -1514,17 +1392,11 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, if (pointtype(touchpt) == FREEVOLVERTEX) { // A volume Steiner point was added in this subface. // Split this subface by this point. - if (b->verbose > 2) { - printf(" Shift volume Steiner point %d to facet.\n", - pointmark(touchpt)); - } face checksh, *parysh; int siloc = (int) ONFACE; int sbowat = 0; // Only split this subface. - - sinsertvertex(touchpt, searchsh, NULL, siloc, sbowat); - setpointtype(touchpt, FREEFACETVERTEX); + sinsertvertex(touchpt, searchsh, NULL, siloc, sbowat, 0); st_volref_count--; st_facref_count++; // Queue this vertex for removal. @@ -1538,12 +1410,6 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, spivot(*parysh, checksh); // The new subface [a, b, p]. // Do not recover a deleted new face (degenerated). if (checksh.sh[3] != NULL) { - if (b->verbose > 3) { - printf(" Queue new subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - //sdissolve(checksh); // It has not been connected yet. subfacstack->newindex((void **) &parysh); *parysh = checksh; } @@ -1569,7 +1435,7 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, } else if (pointtype(touchpt) == FREEFACETVERTEX) { // Two facets self-intersect. } - terminatetetgen(3); + terminatetetgen(this, 3); } } else { assert(0); // Unknown cases. Debug. @@ -1602,13 +1468,13 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) { triface searchtet, neightet, spintet; face searchsh, neighsh, neineish, *parysh; - face bdsegs[3], checkseg; + face bdsegs[3]; point startpt, endpt, apexpt, *parypt; point steinerpt; enum interresult dir; insertvertexflags ivf; int success; - int loc; + int t1ver; int i, j; if (b->verbose > 1) { @@ -1632,7 +1498,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) if (b->verbose > 2) { - printf(" Recover subface (%d, %d, %d).\n", pointmark(sorg(searchsh)), + printf(" Recover subface (%d, %d, %d).\n",pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), pointmark(sapex(searchsh))); } @@ -1652,14 +1518,13 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) startpt = sorg(searchsh); endpt = sdest(searchsh); point2tetorg(startpt, searchtet); - assert(org(searchtet) == startpt); // SELF_CHECK dir = finddirection(&searchtet, endpt); if (dir == ACROSSVERT) { if (dest(searchtet) == endpt) { success = 1; } else { //assert(0); // A PLC problem. - terminatetetgen(3); + terminatetetgen(this, 3); } } else { // The edge is missing. Try to recover it. @@ -1673,13 +1538,8 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) } if (success) { // Insert a temporary segment to protect this edge. - if (b->verbose > 2) { - printf(" Insert a temp segment to protect edge [%d, %d].\n", - pointmark(startpt), pointmark(endpt)); - } makeshellface(subsegs, &(bdsegs[i])); setshvertices(bdsegs[i], startpt, endpt, NULL); - //setshellmark(bdsegs[i], -2); // It's a temporary segment. smarktest2(bdsegs[i]); // It's a temporary segment. // Insert this segment into surface mesh. ssbond(searchsh, bdsegs[i]); @@ -1688,8 +1548,6 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) ssbond(neighsh, bdsegs[i]); } // Insert this segment into tetrahedralization. - tsspivot1(searchtet, checkseg); // SELF_CHECK - assert(checkseg.sh == NULL); sstbond1(bdsegs[i], searchtet); // Bond the segment to all tets containing it. spintet = searchtet; @@ -1701,11 +1559,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) // An edge of this subface is missing. Can't recover this subface. // Delete any temporary segment that has been created. for (j = (i - 1); j >= 0; j--) { - if (smarktest2ed(bdsegs[j])) { // if (shellmark(bdsegs[j]) == -2) { - if (b->verbose > 2) { - printf(" Remove a temp segment (%d, %d).\n", - pointmark(sorg(bdsegs[j])), pointmark(sdest(bdsegs[j]))); - } + if (smarktest2ed(bdsegs[j])) { spivot(bdsegs[j], neineish); assert(neineish.sh != NULL); //if (neineish.sh != NULL) { @@ -1717,7 +1571,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) spivotself(neighsh); // SELF_CHECK assert(neighsh.sh == neineish.sh); } - //} + //} sstpivot1(bdsegs[j], searchtet); assert(searchtet.tet != NULL); //if (searchtet.tet != NULL) { @@ -1727,7 +1581,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) fnextself(spintet); if (spintet.tet == searchtet.tet) break; } - //} + //} shellfacedealloc(subsegs, bdsegs[j].sh); } } // j @@ -1754,9 +1608,9 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) ivf.validflag = 1; ivf.respectbdflag = 1; ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, &searchsh, NULL, &ivf); - assert(loc != (int) OUTSIDE); - + if (!insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { + assert(0); + } // Save this Steiner point (for removal). // Re-use the array 'subvertstack'. subvertstack->newindex((void **) &parypt); @@ -1781,11 +1635,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) // Delete any temporary segment that has been created. for (j = 0; j < 3; j++) { - if (smarktest2ed(bdsegs[j])) { //if (shellmark(bdsegs[j]) == -2) { - if (b->verbose > 2) { - printf(" Remove a temp segment (%d, %d).\n", - pointmark(sorg(bdsegs[j])), pointmark(sdest(bdsegs[j]))); - } + if (smarktest2ed(bdsegs[j])) { spivot(bdsegs[j], neineish); assert(neineish.sh != NULL); //if (neineish.sh != NULL) { @@ -1797,7 +1647,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) spivotself(neighsh); // SELF_CHECK assert(neighsh.sh == neineish.sh); } - //} + //} sstpivot1(bdsegs[j], neightet); assert(neightet.tet != NULL); //if (neightet.tet != NULL) { @@ -1807,7 +1657,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) fnextself(spintet); if (spintet.tet == neightet.tet) break; } - //} + //} shellfacedealloc(subsegs, bdsegs[j].sh); } } // j @@ -1844,9 +1694,9 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) ivf.validflag = 1; ivf.respectbdflag = 1; ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, &searchsh, NULL, &ivf); - assert(loc != (int) OUTSIDE); - + if (!insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { + assert(0); + } // Save this Steiner point (for removal). // Re-use the array 'subvertstack'. subvertstack->newindex((void **) &parypt); @@ -1862,11 +1712,6 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) if (!success) { if (misshlist != NULL) { - if (b->verbose > 2) { - printf(" Subface (%d, %d, %d) is missing.\n", - pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), - pointmark(sapex(searchsh))); - } // Save this subface. misshlist->newindex((void **) &parysh); *parysh = searchsh; @@ -1886,7 +1731,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) // Otherwise, only a part of the star which is bounded by facets is returned.// // // // 'tetlist' returns the list of tets in the star of the vertex 'searchpt'. // -// Every tet in 'tetlist' is at the face oppsiting to 'searchpt'. // +// Every tet in 'tetlist' is at the face opposing to 'searchpt'. // // // // 'vertlist' returns the list of vertices in the star (exclude 'searchpt'). // // // @@ -1900,54 +1745,48 @@ int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, { triface searchtet, neightet, *parytet; face checksh, *parysh; - //face checkseg; point pt, *parypt; int collectflag; + int t1ver; int i, j; - if (b->verbose > 2) { - printf(" Form the star of vertex %d.\n", pointmark(searchpt)); - } - point2tetorg(searchpt, searchtet); // Go to the opposite face (the link face) of the vertex. - enextself(searchtet); - esymself(searchtet); + enextesymself(searchtet); //assert(oppo(searchtet) == searchpt); infect(searchtet); // Collect this tet (link face). tetlist->newindex((void **) &parytet); *parytet = searchtet; if (vertlist != NULL) { // Collect three (link) vertices. - for (i = 0; i < 3; i++) { - pt = org(searchtet); + j = (searchtet.ver & 3); // The current vertex index. + for (i = 1; i < 4; i++) { + pt = (point) searchtet.tet[4 + ((j + i) % 4)]; pinfect(pt); vertlist->newindex((void **) &parypt); *parypt = pt; - enextself(searchtet); } } collectflag = 1; esym(searchtet, neightet); - tspivot(neightet, checksh); - if (checksh.sh != NULL) { + if (issubface(neightet)) { if (shlist != NULL) { + tspivot(neightet, checksh); if (!sinfected(checksh)) { // Collect this subface (link edge). sinfected(checksh); shlist->newindex((void **) &parysh); *parysh = checksh; } - } // if (checksh.sh != NULL) + } if (!fullstar) { collectflag = 0; } } if (collectflag) { fsymself(neightet); // Goto the adj tet of this face. - assert(neightet.tet != NULL); esymself(neightet); // Goto the oppo face of this vertex. // assert(oppo(neightet) == searchpt); infect(neightet); // Collect this tet (link face). @@ -1967,15 +1806,14 @@ int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, searchtet = * (triface *) fastlookup(tetlist, i); // Note that 'searchtet' is a face opposite to 'searchpt', and the neighbor // tet at the current edge is already collected. - // Check the neighors at the other two edges of this face. + // Check the neighbors at the other two edges of this face. for (j = 0; j < 2; j++) { collectflag = 1; enextself(searchtet); - //fnext(searchtet, neightet); esym(searchtet, neightet); - tspivot(neightet, checksh); - if (checksh.sh != NULL) { + if (issubface(neightet)) { if (shlist != NULL) { + tspivot(neightet, checksh); if (!sinfected(checksh)) { // Collect this subface (link edge). sinfected(checksh); @@ -1989,7 +1827,6 @@ int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, } if (collectflag) { fsymself(neightet); - assert(neightet.tet != NULL); if (!infected(neightet)) { esymself(neightet); // Go to the face opposite to 'searchpt'. infect(neightet); @@ -2009,16 +1846,6 @@ int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, } // j } // i - if (b->verbose > 2) { - printf(" Collected %ld tets", tetlist->objects); - if (vertlist != NULL) { - printf(", %ld vertices", vertlist->objects); - } - if (shlist != NULL) { - printf(", %ld subfaces", shlist->objects); - } - printf(".\n"); - } // Uninfect the list of tets and vertices. for (i = 0; i < tetlist->objects; i++) { @@ -2098,20 +1925,18 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) // Go to the link face of e1. point2tetorg(e1, searchtet); - enextself(searchtet); - esymself(searchtet); + enextesymself(searchtet); //assert(oppo(searchtet) == e1); - assert(cavetetlist->objects == 0l); // It will re-use this list. + assert(cavebdrylist->objects == 0l); // It will re-use this list. + arraypool *tetlist = cavebdrylist; // Search e2. for (i = 0; i < 3; i++) { pt = apex(searchtet); if (pt == e2) { // Found. 'searchtet' is [#,#,e2,e1]. - enext(searchtet, *tedge); - esymself(*tedge); - eprevself(*tedge); // [e1,e2,#,#]. + eorgoppo(searchtet, *tedge); // [e1,e2,#,#]. return 1; } enextself(searchtet); @@ -2124,24 +1949,22 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) pt = apex(neightet); if (pt == e2) { // Found. 'neightet' is [#,#,e2,e1]. - enext(neightet, *tedge); - esymself(*tedge); - eprevself(*tedge); // [e1,e2,#,#]. + eorgoppo(neightet, *tedge); // [e1,e2,#,#]. return 1; } // Continue searching in the link face of e1. infect(searchtet); - cavetetlist->newindex((void **) &parytet); + tetlist->newindex((void **) &parytet); *parytet = searchtet; infect(neightet); - cavetetlist->newindex((void **) &parytet); + tetlist->newindex((void **) &parytet); *parytet = neightet; done = 0; - for (i = 0; (i < cavetetlist->objects) && !done; i++) { - parytet = (triface *) fastlookup(cavetetlist, i); + for (i = 0; (i < tetlist->objects) && !done; i++) { + parytet = (triface *) fastlookup(tetlist, i); searchtet = *parytet; for (j = 0; (j < 2) && !done; j++) { enextself(searchtet); @@ -2151,13 +1974,11 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) pt = apex(neightet); if (pt == e2) { // Found. 'neightet' is [#,#,e2,e1]. - enext(neightet, *tedge); - esymself(*tedge); - eprevself(*tedge); // [e1,e2,#,#]. + eorgoppo(neightet, *tedge); done = 1; } else { infect(neightet); - cavetetlist->newindex((void **) &parytet); + tetlist->newindex((void **) &parytet); *parytet = neightet; } } @@ -2165,11 +1986,11 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) } // i // Uninfect the list of visited tets. - for (i = 0; i < cavetetlist->objects; i++) { - parytet = (triface *) fastlookup(cavetetlist, i); + for (i = 0; i < tetlist->objects; i++) { + parytet = (triface *) fastlookup(tetlist, i); uninfect(*parytet); } - cavetetlist->restart(); + tetlist->restart(); return done; } @@ -2185,7 +2006,6 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) { triface searchtet; - face checkseg; point *pendpt, *parypt; enum interresult dir; flipconstraints fc; @@ -2193,13 +2013,9 @@ int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) int count; int n, i, j; - if (b->verbose > 2) { - printf(" Initial edge degree = %ld.\n", endptlist->objects); - } - assert(endptlist->objects >= 4l); - // Reduce the number of edges. fc.remvert = startpt; + fc.checkflipeligibility = 1; while (1) { @@ -2226,8 +2042,7 @@ int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) if (dir == ACROSSVERT) { if (dest(searchtet) == *pendpt) { // Do not flip a segment. - tsspivot1(searchtet, checkseg); - if (checkseg.sh == NULL) { + if (!issubseg(searchtet)) { n = removeedgebyflips(&searchtet, &fc); if (n == 2) { reduceflag = 1; @@ -2258,10 +2073,6 @@ int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) } // while (1) - if (b->verbose > 2) { - printf(" Final edge degree = %ld.\n", endptlist->objects); - } - return (int) endptlist->objects; } @@ -2269,8 +2080,8 @@ int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) // // // removevertexbyflips() Remove a vertex by flips. // // // -// This routine attempts to remove the given vertex 'rempt' (p) from the cur-// -// rent tetrahedralization (T) by a sequence of elementary flips. // +// This routine attempts to remove the given vertex 'rempt' (p) from the // +// tetrahedralization (T) by a sequence of flips. // // // // The algorithm used here is a simple edge reduce method. Suppose there are // // n edges connected at p. We try to reduce the number of edges by flipping // @@ -2287,11 +2098,13 @@ int tetgenmesh::removevertexbyflips(point steinerpt) triface searchtet, spintet, neightet; face parentsh, spinsh, checksh; face leftseg, rightseg, checkseg; - point lpt = NULL, rpt = NULL, apexpt, *parypt; + point lpt = NULL, rpt = NULL, apexpt; //, *parypt; + flipconstraints fc; enum verttype vt; enum locateresult loc; int valence, removeflag; int slawson; + int t1ver; int n, i; vt = pointtype(steinerpt); @@ -2332,6 +2145,11 @@ int tetgenmesh::removevertexbyflips(point steinerpt) printf(" Removing Steiner point %d in volume.\n", pointmark(steinerpt)); } + } else if (vt == VOLVERTEX) { + if (b->verbose > 2) { + printf(" Removing a point %d in volume.\n", + pointmark(steinerpt)); + } } else { // It is not a Steiner point. return 0; @@ -2350,24 +2168,7 @@ int tetgenmesh::removevertexbyflips(point steinerpt) removeflag = 0; - if (valence < 3) { - assert(0); // Unknown cases. - } - - if (valence == 3) { - // Only three edges at this vertex. This is only possible when there are - // Inverted elements. - getvertexstar(1, steinerpt, cavetetlist, NULL, NULL); - if (cavetetlist->objects == 2) { - printf("to be continued...\n"); - assert(0); - } else { - assert(0); // Unknown cases. - } - cavetetlist->restart(); - loc = OUTSIDE; - removeflag = 1; - } else if (valence == 4) { + if (valence == 4) { // Only 4 vertices (4 tets) left! 'p' is inside the convex hull of the 4 // vertices. This case is due to that 'p' is not exactly on the segment. point2tetorg(steinerpt, searchtet); @@ -2400,19 +2201,24 @@ int tetgenmesh::removevertexbyflips(point steinerpt) // There are 4 tets sharing at [p,lpt]. There must be 4 tets sharing // at [p,rpt]. There must be a face [p, lpt, rpt]. if (apex(neightet) == rpt) { - // The edge (segment) has been already recovered! At first, this is - // due to the same reason as the case 'valence == 4'. Second, - // there are 4 vertices (including p, lpt, rpt) exactly coplanar. - // We can do a 6-to-2 flip to remove p and recover a face - // [lpt, rpt, c] = [a,b,c]. + // The edge (segment) has been already recovered! + // Check if a 6-to-2 flip is possible (to remove 'p'). // Let 'searchtet' be [p,d,a,b] esym(neightet, searchtet); enextself(searchtet); - loc = ONFACE; - removeflag = 1; + // Check if there are exactly three tets at edge [p,d]. + wrktets[0] = searchtet; // [p,d,a,b] + for (i = 0; i < 2; i++) { + fnext(wrktets[i], wrktets[i+1]); // [p,d,b,c], [p,d,c,a] + } + if (apex(wrktets[0]) == oppo(wrktets[2])) { + loc = ONFACE; + removeflag = 1; + } } } } else if (vt == FREEFACETVERTEX) { + // It is possible to do a 6-to-2 flip to remove the vertex. point2tetorg(steinerpt, searchtet); // Get the three faces of 'searchtet' which share at p. // All faces has p as origin. @@ -2423,29 +2229,35 @@ int tetgenmesh::removevertexbyflips(point steinerpt) wrktets[2] = searchtet; eprevself(wrktets[2]); esymself(wrktets[2]); - // Get the one which has a subface (should be only 1). - n = -1; - valence = 0; // Re-use it as a counter. + // All internal edges of the six tets have valance either 3 or 4. + // Get one edge which has valance 3. + searchtet.tet = NULL; for (i = 0; i < 3; i++) { - tspivot(wrktets[i], checksh); - if (checksh.sh != NULL) { - n = i; - valence++; + spintet = wrktets[i]; + valence = 0; + while (1) { + valence++; + fnextself(spintet); + if (spintet.tet == wrktets[i].tet) break; + } + if (valence == 3) { + // Found the edge. + searchtet = wrktets[i]; + break; + } else { + assert(valence == 4); } } - assert(valence == 1); - searchtet = wrktets[n]; - esymself(searchtet); - enextself(searchtet); + assert(searchtet.tet != NULL); + // Note, we do not detach the three subfaces at p. + // They will be removed within a 4-to-1 flip. loc = ONFACE; removeflag = 1; } else { // assert(0); DEBUG IT } //removeflag = 1; - } else { // valence > 5. - - } // if (valence > 5) + } if (!removeflag) { if (vt == FREESEGVERTEX) { @@ -2490,7 +2302,7 @@ int tetgenmesh::removevertexbyflips(point steinerpt) // Remove the vertex from the surface mesh. // This will re-create the segment [lpt, rpt] and re-triangulate // all the facets at the segment. - // Detach the subsegments from their surronding tets. + // Detach the subsegments from their surrounding tets. for (i = 0; i < 2; i++) { checkseg = (i == 0) ? leftseg : rightseg; sstpivot1(checkseg, neightet); @@ -2523,9 +2335,6 @@ int tetgenmesh::removevertexbyflips(point steinerpt) setpointtype(steinerpt, FREEVOLVERTEX); st_segref_count--; st_volref_count++; - // Save this Steiner points in (global) list. - suppsteinerptlist->newindex((void **) &parypt); - *parypt = steinerpt; return 1; } // if (!checksubfaceflag) } // if (getedge(...)) @@ -2533,10 +2342,6 @@ int tetgenmesh::removevertexbyflips(point steinerpt) } // if (!removeflag) if (!removeflag) { - if (b->verbose > 2) { - printf(" Unable to remove Steiner point %d val(%d).\n", - pointmark(steinerpt), valence); - } return 0; } @@ -2593,7 +2398,8 @@ int tetgenmesh::removevertexbyflips(point steinerpt) eprevself(fliptets[3]); esymself(fliptets[3]); // [a,b,c,p]. // Remove p by a 4-to-1 flip. - flip41(fliptets, 1, 0, 0); + //flip41(fliptets, 1, 0, 0); + flip41(fliptets, 1, &fc); //recenttet = fliptets[0]; } else if (loc == ONFACE) { // Let the original two tets be [a,b,c,d] and [b,a,c,e]. And p is in @@ -2611,6 +2417,32 @@ int tetgenmesh::removevertexbyflips(point steinerpt) for (i = 3; i < 5; i++) { fnext(fliptets[i], fliptets[i+1]); // [e,p,b,c], [e,p,c,a] } + if (vt == FREEFACETVERTEX) { + // We need to determine the location of three subfaces at p. + valence = 0; // Re-use it. + // Check if subfaces are all located in the lower three tets. + // i.e., [e,p,a,b], [e,p,b,c], and [e,p,c,a]. + for (i = 3; i < 6; i++) { + if (issubface(fliptets[i])) valence++; + } + if (valence > 0) { + assert(valence == 2); + // We must do 3-to-2 flip in the upper part. We simply re-arrange + // the six tets. + for (i = 0; i < 3; i++) { + esym(fliptets[i+3], wrktets[i]); + esym(fliptets[i], fliptets[i+3]); + fliptets[i] = wrktets[i]; + } + // Swap the last two pairs, i.e., [1]<->[[2], and [4]<->[5] + wrktets[1] = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = wrktets[1]; + wrktets[1] = fliptets[4]; + fliptets[4] = fliptets[5]; + fliptets[5] = wrktets[1]; + } + } // Remove p by a 6-to-2 flip, which is a combination of two flips: // a 3-to-2 (deletes the edge [e,p]), and // a 4-to-1 (deletes the vertex p). @@ -2618,10 +2450,12 @@ int tetgenmesh::removevertexbyflips(point steinerpt) // two new tets: [a,b,c,p] and [b,a,c,e]. The new tet [a,b,c,p] is // degenerate (has zero volume). It will be deleted in the followed // 4-to-1 flip. - flip32(&(fliptets[3]), 1, 0, 0); + //flip32(&(fliptets[3]), 1, 0, 0); + flip32(&(fliptets[3]), 1, &fc); // Second do a 4-to-1 flip on [p,d,a,b],[p,d,b,c],[p,d,c,a],[a,b,c,p]. // This creates a new tet [a,b,c,d]. - flip41(fliptets, 1, 0, 0); + //flip41(fliptets, 1, 0, 0); + flip41(fliptets, 1, &fc); //recenttet = fliptets[0]; } else if (loc == ONEDGE) { // Let the original edge be [e,d] and p is in [e,d]. Assume there are n @@ -2665,7 +2499,8 @@ int tetgenmesh::removevertexbyflips(point steinerpt) enextself(wrktets[1]); // [p,p_0,e,p_1] esymself(wrktets[1]); // [p_0,p,p_1,e] eprevself(wrktets[1]); // [p_1,p_0,p,e] [1] - flip23(wrktets, 1, 0, 0); + //flip23(wrktets, 1, 0, 0); + flip23(wrktets, 1, &fc); // Save the new tet [e,d,p,p_0] (degenerated). fliptets[n] = wrktets[2]; // Save the new tet [e,d,p_0,p_1]. @@ -2688,7 +2523,8 @@ int tetgenmesh::removevertexbyflips(point steinerpt) wrktets[2] = fliptets[i]; // [p,d,p_i,p_i+1] eprevself(wrktets[2]); // [p_i,p,d,p_i+1] esymself(wrktets[2]); // [p,p_i,p_i+1,d] [2] - flip32(wrktets, 1, 0, 0); + //flip32(wrktets, 1, 0, 0); + flip32(wrktets, 1, &fc); // Save the new tet [e,d,p_i,p_i+1]. // FOR DEBUG ONLY fliptets[i] = wrktets[0]; // [d,e,p_i+1,p_i] // FOR DEBUG ONLY esymself(fliptets[i]); // [e,d,p_i,p_i+1] // FOR DEBUG ONLY @@ -2713,7 +2549,8 @@ int tetgenmesh::removevertexbyflips(point steinerpt) enextself(wrktets[2]); // [p_p_n-1,e,p_0] esymself(wrktets[2]); // [p_n-1,p,p_0,e] enextself(wrktets[2]); // [p,p_0,p_n-1,e] [2] - flip41(wrktets, 1, 0, 0); + //flip41(wrktets, 1, 0, 0); + flip41(wrktets, 1, &fc); // Save the new tet [e,d,p_n-1,p_0] // FOR DEBUG ONLY fliptets[n-1] = wrktets[0]; // [e,d,p_n-1,p_0] // FOR DEBUG ONLY //recenttet = fliptets[0]; @@ -2789,51 +2626,42 @@ int tetgenmesh::removevertexbyflips(point steinerpt) } // if (vt == FREESEGVERTEX) // The point has been removed. - setpointtype(steinerpt, UNUSEDVERTEX); - unuverts++; - // Update the correspinding counters. - if (vt == FREESEGVERTEX) { - st_segref_count--; - } else if (vt == FREEFACETVERTEX) { - st_facref_count--; - } else if (vt == FREEVOLVERTEX) { - st_volref_count--; + if (pointtype(steinerpt) != UNUSEDVERTEX) { + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + } + if (vt != VOLVERTEX) { + // Update the correspinding counters. + if (vt == FREESEGVERTEX) { + st_segref_count--; + } else if (vt == FREEFACETVERTEX) { + st_facref_count--; + } else if (vt == FREEVOLVERTEX) { + st_volref_count--; + } + if (steinerleft > 0) steinerleft++; } - if (steinerleft > 0) steinerleft++; return 1; } /////////////////////////////////////////////////////////////////////////////// // // -// suppresssteinerpoint() Suppress a Steiner point. // -// // -// Remove a Steiner point 'p' from the segment it lies on. It is replaced by // -// a set of volume Steiner points in each sector at the segment. // -// // -// The list of volume Steiner points is returned in 'suppsteinerptlist'. // +// suppressbdrysteinerpoint() Suppress a boundary Steiner point // // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::suppressssteinerpoint(point steinerpt) +int tetgenmesh::suppressbdrysteinerpoint(point steinerpt) { - triface searchtet, neightet, spintet, *parytet; - triface newtet, newface; face parentsh, spinsh, *parysh; - face newsh, neighsh; - face leftseg, rightseg, checkseg, *splitseg; - point lpt = NULL, rpt = NULL, newpt, *parypt; - point pa, pb, pc; - verttype vt; - long bak_supp_steiners; - int slawson; - int i, j, k; + face leftseg, rightseg; + point lpt = NULL, rpt = NULL; + int i; - vt = pointtype(steinerpt); + verttype vt = pointtype(steinerpt); if (vt == FREESEGVERTEX) { sdecode(point2sh(steinerpt), leftseg); - assert(leftseg.sh != NULL); leftseg.shver = 0; if (sdest(leftseg) == steinerpt) { senext(leftseg, rightseg); @@ -2853,46 +2681,17 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) lpt = sorg(leftseg); rpt = sdest(rightseg); if (b->verbose > 2) { - printf(" Suppressing point %d from segment (%d, %d).\n", + printf(" Suppressing Steiner point %d in segment (%d, %d).\n", pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); } - } else if (vt == FREEFACETVERTEX) { - if (b->verbose > 2) { - printf(" Suppressing point %d from facet.\n", - pointmark(steinerpt)); - } - //point2shorg(steinerpt, parentsh); - getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); - parysh = (face *) fastlookup(caveshlist, 0); - parentsh = *parysh; - //assert(sapex(parentsh) == steinerpt); - senext2self(parentsh); - assert(sorg(parentsh) == steinerpt); - cavetetlist->restart(); - caveshlist->restart(); - } else { - // Do nothing. - return 0; - } - - if (vt == FREESEGVERTEX) { - // Check if this edge [lpt, rpt] already exists. - if (getedge(lpt, rpt, &searchtet)) { - tsspivot1(searchtet, checkseg); // SELF_CHECK - assert(checkseg.sh == NULL); - return 0; - } - } - - bak_supp_steiners = suppsteinerptlist->objects; - - if (vt == FREESEGVERTEX) { // Get all subfaces at the left segment [lpt, steinerpt]. spivot(leftseg, parentsh); spinsh = parentsh; while (1) { cavesegshlist->newindex((void **) &parysh); *parysh = spinsh; + // Orient the face consistently. + if (sorg(*parysh)!= sorg(parentsh)) sesymself(*parysh); spivotself(spinsh); if (spinsh.sh == NULL) break; if (spinsh.sh == parentsh.sh) break; @@ -2902,6 +2701,198 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) cavesegshlist->restart(); return 0; } + } else if (vt == FREEFACETVERTEX) { + if (b->verbose > 2) { + printf(" Suppressing Steiner point %d from facet.\n", + pointmark(steinerpt)); + } + sdecode(point2sh(steinerpt), parentsh); + // A facet Steiner point. There are exactly two sectors. + for (i = 0; i < 2; i++) { + cavesegshlist->newindex((void **) &parysh); + *parysh = parentsh; + sesymself(parentsh); + } + } else { + return 0; + } + + triface searchtet, neightet, *parytet; + point pa, pb, pc, pd; + REAL v1[3], v2[3], len, u; + + REAL startpt[3] = {0,}, samplept[3] = {0,}, candpt[3] = {0,}; + REAL ori, minvol, smallvol; + int samplesize; + int it, j, k; + + int n = (int) cavesegshlist->objects; + point *newsteiners = new point[n]; + for (i = 0; i < n; i++) newsteiners[i] = NULL; + + // Search for each sector an interior vertex. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + stpivot(*parysh, searchtet); + // Skip it if it is outside. + if (ishulltet(searchtet)) continue; + // Get the "half-ball". Tets in 'cavetetlist' all contain 'steinerpt' as + // opposite. Subfaces in 'caveshlist' all contain 'steinerpt' as apex. + // Moreover, subfaces are oriented towards the interior of the ball. + setpoint2tet(steinerpt, encode(searchtet)); + getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); + // Calculate the searching vector. + pa = sorg(*parysh); + pb = sdest(*parysh); + pc = sapex(*parysh); + facenormal(pa, pb, pc, v1, 1, NULL); + len = sqrt(dot(v1, v1)); + assert(len > 0.0); + v1[0] /= len; + v1[1] /= len; + v1[2] /= len; + if (vt == FREESEGVERTEX) { + parysh = (face *) fastlookup(cavesegshlist, (i + 1) % n); + pd = sapex(*parysh); + facenormal(pb, pa, pd, v2, 1, NULL); + len = sqrt(dot(v2, v2)); + assert(len > 0.0); + v2[0] /= len; + v2[1] /= len; + v2[2] /= len; + // Average the two vectors. + v1[0] = 0.5 * (v1[0] + v2[0]); + v1[1] = 0.5 * (v1[1] + v2[1]); + v1[2] = 0.5 * (v1[2] + v2[2]); + } + // Search the intersection of the ray starting from 'steinerpt' to + // the search direction 'v1' and the shell of the half-ball. + // - Construct an endpoint. + len = distance(pa, pb); + v2[0] = steinerpt[0] + len * v1[0]; + v2[1] = steinerpt[1] + len * v1[1]; + v2[2] = steinerpt[2] + len * v1[2]; + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + // Test if the ray startpt->v2 lies in the cone: where 'steinerpt' + // is the apex, and three sides are defined by the triangle + // [pa, pb, pc]. + ori = orient3d(steinerpt, pa, pb, v2); + if (ori >= 0) { + ori = orient3d(steinerpt, pb, pc, v2); + if (ori >= 0) { + ori = orient3d(steinerpt, pc, pa, v2); + if (ori >= 0) { + // Found! Calculate the intersection. + planelineint(pa, pb, pc, steinerpt, v2, startpt, &u); + assert(u != 0.0); + break; + } + } + } + } // j + assert(j < cavetetlist->objects); // There must be an intersection. + // Close the ball by adding the subfaces. + for (j = 0; j < caveshlist->objects; j++) { + parysh = (face *) fastlookup(caveshlist, j); + stpivot(*parysh, neightet); + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + // Search a best point inside the segment [startpt, steinerpt]. + it = 0; + samplesize = 100; + v1[0] = steinerpt[0] - startpt[0]; + v1[1] = steinerpt[1] - startpt[1]; + v1[2] = steinerpt[2] - startpt[2]; + minvol = -1.0; + while (it < 3) { + for (j = 1; j < samplesize - 1; j++) { + samplept[0] = startpt[0] + ((REAL) j / (REAL) samplesize) * v1[0]; + samplept[1] = startpt[1] + ((REAL) j / (REAL) samplesize) * v1[1]; + samplept[2] = startpt[2] + ((REAL) j / (REAL) samplesize) * v1[2]; + // Find the minimum volume for 'samplept'. + smallvol = -1; + for (k = 0; k < cavetetlist->objects; k++) { + parytet = (triface *) fastlookup(cavetetlist, k); + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + ori = orient3d(pb, pa, pc, samplept); + if (ori <= 0) { + break; // An invalid tet. + } + if (smallvol == -1) { + smallvol = ori; + } else { + if (ori < smallvol) smallvol = ori; + } + } // k + if (k == cavetetlist->objects) { + // Found a valid point. Remember it. + if (minvol == -1.0) { + candpt[0] = samplept[0]; + candpt[1] = samplept[1]; + candpt[2] = samplept[2]; + minvol = smallvol; + } else { + if (minvol < smallvol) { + // It is a better location. Remember it. + candpt[0] = samplept[0]; + candpt[1] = samplept[1]; + candpt[2] = samplept[2]; + minvol = smallvol; + } else { + // No improvement of smallest volume. + // Since we are searching along the line [startpt, steinerpy], + // The smallest volume can only be decreased later. + break; + } + } + } + } // j + if (minvol > 0) break; + samplesize *= 10; + it++; + } // while (it < 3) + if (minvol == -1.0) { + // Failed to find a valid point. + cavetetlist->restart(); + caveshlist->restart(); + break; + } + // Create a new Steiner point inside this section. + makepoint(&(newsteiners[i]), FREEVOLVERTEX); + newsteiners[i][0] = candpt[0]; + newsteiners[i][1] = candpt[1]; + newsteiners[i][2] = candpt[2]; + cavetetlist->restart(); + caveshlist->restart(); + } // i + + if (i < cavesegshlist->objects) { + // Failed to suppress the vertex. + for (; i > 0; i--) { + if (newsteiners[i - 1] != NULL) { + pointdealloc(newsteiners[i - 1]); + } + } + delete [] newsteiners; + cavesegshlist->restart(); + return 0; + } + + // Remove p from the segment or the facet. + triface newtet, newface, spintet; + face newsh, neighsh; + face *splitseg, checkseg; + int slawson = 0; // Do not do flip afterword. + int t1ver; + + if (vt == FREESEGVERTEX) { // Detach 'leftseg' and 'rightseg' from their adjacent tets. // These two subsegments will be deleted. sstpivot1(leftseg, neightet); @@ -2918,13 +2909,6 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) fnextself(spintet); if (spintet.tet == neightet.tet) break; } - } else { // vt == FREEFACETVERTEX - // A facet Steiner point. There are exactly two sectors. - for (i = 0; i < 2; i++) { - cavesegshlist->newindex((void **) &parysh); - *parysh = parentsh; - sesymself(parentsh); - } } // Loop through all sectors bounded by facets at this segment. @@ -2937,27 +2921,25 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) // Get all tets in this sector. setpoint2tet(steinerpt, encode(neightet)); getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); - assert(caveshlist->objects > 0); - // Create a new vertex 'np'. - makepoint(&newpt, FREEVOLVERTEX); - st_volref_count++; - // Init 'np' at the same location of 'p'. - for (j = 0; j < 3; j++) newpt[j] = steinerpt[j]; - // Within the tet, replace 'p' by 'np'. - for (j = 0; j < cavetetlist->objects; j++) { - parytet = (triface *) fastlookup(cavetetlist, j); - setoppo(*parytet, newpt); - } // j - // Save the new Steiner point in list. - suppsteinerptlist->newindex((void **) &parypt); - *parypt = newpt; + if (!ishulltet(neightet)) { + // Within each tet in the ball, replace 'p' by 'np'. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + setoppo(*parytet, newsteiners[i]); + } // j + // Point to a parent tet. + parytet = (triface *) fastlookup(cavetetlist, 0); + setpoint2tet(newsteiners[i], (tetrahedron) (parytet->tet)); + st_volref_count++; + if (steinerleft > 0) steinerleft--; + } // Disconnect the set of boundary faces. They're temporarily open faces. // They will be connected to the new tets after 'p' is removed. for (j = 0; j < caveshlist->objects; j++) { // Get a boundary face. parysh = (face *) fastlookup(caveshlist, j); stpivot(*parysh, neightet); - assert(apex(neightet) == newpt); + //assert(apex(neightet) == newpt); // Clear the connection at this face. dissolve(neightet); tsdissolve(neightet); @@ -2968,12 +2950,15 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) } // i cavesegshlist->restart(); - // Remove p from the segment. - slawson = 0; // Do not do flip afterword. if (vt == FREESEGVERTEX) { spivot(rightseg, parentsh); // 'rightseg' has p as its origin. splitseg = &rightseg; } else { + if (sdest(parentsh) == steinerpt) { + senextself(parentsh); + } else if (sapex(parentsh) == steinerpt) { + senext2self(parentsh); + } assert(sorg(parentsh) == steinerpt); splitseg = NULL; } @@ -2982,12 +2967,7 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) if (vt == FREESEGVERTEX) { // The original segment is returned in 'rightseg'. rightseg.shver = 0; - assert(sorg(rightseg) == lpt); - assert(sdest(rightseg) == rpt); } - // The set of new subfaces are found in 'caveshbdlist'. - assert(caveshbdlist->objects > 0); - // For each new subface, create two new tets at each side of it. // Both of the two new tets have its opposite be dummypoint. @@ -3007,6 +2987,8 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) sesymself(newsh); tsbond(neightet, newsh); } + // Temporarily increase the hullsize. + hullsize += (caveshbdlist->objects * 2l); if (vt == FREESEGVERTEX) { // Connecting new tets at the recovered segment. @@ -3014,30 +2996,20 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) assert(parentsh.sh != NULL); spinsh = parentsh; while (1) { - assert(sinfected(spinsh)); if (sorg(spinsh) != lpt) sesymself(spinsh); - assert(sorg(spinsh) == lpt); - assert(sdest(spinsh) == rpt); // Get the new tet at this subface. stpivot(spinsh, newtet); - assert(oppo(newtet) == dummypoint); tssbond1(newtet, rightseg); // Go to the other face at this segment. - esymself(newtet); - assert(org(newtet) == rpt); - assert(newtet.tet[newtet.ver & 3] == NULL); - // Get the adjacent tet at this segment. spivot(spinsh, neighsh); if (sorg(neighsh) != lpt) sesymself(neighsh); sesymself(neighsh); stpivot(neighsh, neightet); - assert(oppo(neightet) == dummypoint); tssbond1(neightet, rightseg); sstbond1(rightseg, neightet); - // Go to the other face at this segment. + // Connecting two adjacent tets at this segment. + esymself(newtet); esymself(neightet); - assert(org(neightet) == lpt); - assert(neightet.tet[neightet.ver & 3] == NULL); // Connect the two tets (at rightseg) together. bond(newtet, neightet); // Go to the next subface. @@ -3062,9 +3034,8 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) sspivot(newsh, checkseg); if (checkseg.sh != NULL) { // A segment. It must not be the recovered segment. - assert(checkseg.sh != rightseg.sh); tssbond1(newtet, checkseg); - //sstbond1(checkseg, newtet); + sstbond1(checkseg, newtet); } spivot(newsh, neighsh); if (neighsh.sh != NULL) { @@ -3107,10 +3078,23 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) neightet = searchtet; } pc = apex(newface); - if (pc == dummypoint) { - setapex(newface, apex(neightet)); + if (apex(neightet) == steinerpt) { + // Exterior case. The 'neightet' is a hull tet which contain + // 'steinerpt'. It will be deleted after 'steinerpt' is removed. + assert(pc == dummypoint); + caveoldtetlist->newindex((void **) &parytet); + *parytet = neightet; + // Connect newface to the adjacent hull tet of 'neightet', which + // has the same edge as 'newface', and does not has 'steinerpt'. + fnextself(neightet); } else { - assert(pc == apex(neightet)); + if (pc == dummypoint) { + if (apex(neightet) != dummypoint) { + setapex(newface, apex(neightet)); + // A hull tet has turned into an interior tet. + hullsize--; // Must update the hullsize. + } + } } bond(newface, neightet); } // if (newface.tet[newface.ver & 3] == NULL) @@ -3128,6 +3112,16 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) } caveshbdlist->restart(); + if (caveoldtetlist->objects > 0l) { + // Delete hull tets which contain 'steinerpt'. + for (i = 0; i < caveoldtetlist->objects; i++) { + parytet = (triface *) fastlookup(caveoldtetlist, i); + tetrahedrondealloc(parytet->tet); + } + // Must update the hullsize. + hullsize -= caveoldtetlist->objects; + caveoldtetlist->restart(); + } setpointtype(steinerpt, UNUSEDVERTEX); unuverts++; @@ -3136,163 +3130,201 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) } else { // vt == FREEFACETVERTEX st_facref_count--; } - if (steinerleft > 0) steinerleft++; + if (steinerleft > 0) steinerleft++; // We've removed a Steiner points. - if (b->verbose > 2) { - printf(" Duplicated %ld Steiner points.\n", - suppsteinerptlist->objects - bak_supp_steiners); + + point *parypt; + int steinercount = 0; + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + + // Try to remove newly added Steiner points. + for (i = 0; i < n; i++) { + if (newsteiners[i] != NULL) { + if (!removevertexbyflips(newsteiners[i])) { + if (b->nobisect_param > 0) { // Not -Y0 + // Save it in subvertstack for removal. + subvertstack->newindex((void **) &parypt); + *parypt = newsteiners[i]; + } + steinercount++; + } + } } + b->fliplinklevel = bak_fliplinklevel; + + if (steinercount > 0) { + if (b->verbose > 2) { + printf(" Added %d interior Steiner points.\n", steinercount); + } + } + + delete [] newsteiners; + return 1; } + /////////////////////////////////////////////////////////////////////////////// // // // suppresssteinerpoints() Suppress Steiner points. // // // +// All Steiner points have been saved in 'subvertstack' in the routines // +// carveholes() and suppresssteinerpoint(). // // Each Steiner point is either removed or shifted into the interior. // // // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::suppresssteinerpoints() { - triface *parytet; - point rempt, *parypt, *plastpt, *ppt; - optparameters opm; - REAL ori; - int bak_fliplinklevel; - int remcount, smtcount; - int count, nt; - int i, j; if (!b->quiet) { printf("Suppressing Steiner points ...\n"); } - bak_fliplinklevel = b->fliplinklevel; - b->fliplinklevel = 100000; // Unlimited flip level. - remcount = 0; + point rempt, *parypt; - if (b->nobisect_param > 1) { // -Y2 - // Try to remove all the Steiner points. + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + int suppcount = 0, remcount = 0; + int i; + + // Try to suppress boundary Steiner points. + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) != UNUSEDVERTEX) { + if ((pointtype(rempt) == FREESEGVERTEX) || + (pointtype(rempt) == FREEFACETVERTEX)) { + if (suppressbdrysteinerpoint(rempt)) { + suppcount++; + } + } + } + } // i + + if (suppcount > 0) { + if (b->verbose) { + printf(" Suppressed %d boundary Steiner points.\n", suppcount); + } + } + + if (b->nobisect_param > 0) { // -Y1 for (i = 0; i < subvertstack->objects; i++) { parypt = (point *) fastlookup(subvertstack, i); rempt = *parypt; if (pointtype(rempt) != UNUSEDVERTEX) { - if (removevertexbyflips(rempt)) { - remcount++; - } - } - } - if (b->verbose) { - if (remcount > 0) { - printf(" Removed %d Steiner points.\n", remcount); - } - } - subvertstack->restart(); - } - - remcount = smtcount = 0; - - // Try to remove the suppressed Steiner points. - for (i = 0; i < suppsteinerptlist->objects; i++) { - // Get the Steiner point. - parypt = (point *) fastlookup(suppsteinerptlist, i); - rempt = *parypt; - if (pointtype(rempt) != UNUSEDVERTEX) { - assert((pointtype(rempt) == FREESEGVERTEX) || - (pointtype(rempt) == FREEFACETVERTEX) || - (pointtype(rempt) == FREEVOLVERTEX)); - if (removevertexbyflips(rempt)) { - // Move the last entry to fill the current one. - j = (int) (suppsteinerptlist->objects - 1); - plastpt = (point *) fastlookup(suppsteinerptlist, j); - *parypt = *plastpt; - suppsteinerptlist->objects--; - i--; - remcount++; - } - } else { - // The point has been removed. - // Move the last entry to fill the current one. - j = (int) (suppsteinerptlist->objects - 1); - plastpt = (point *) fastlookup(suppsteinerptlist, j); - *parypt = *plastpt; - suppsteinerptlist->objects--; - i--; - } - } // i - - if (b->verbose) { - if (remcount > 0) { - printf(" Removed %d suppressed Steiner points.\n", remcount); - } - } - - if (suppsteinerptlist->objects == 0l) { - b->fliplinklevel = bak_fliplinklevel; - return remcount; - } - - // Point smooth options. - opm.max_min_volume = 1; - opm.numofsearchdirs = 20; - opm.searchstep = 0.001; - - nt = 0; - - while (1) { - // Try to smooth volume Steiner points. - count = 0; - - for (i = 0; i < suppsteinerptlist->objects; i++) { - parypt = (point *) fastlookup(suppsteinerptlist, i); - rempt = *parypt; - if (pointtype(rempt) == FREEVOLVERTEX) { - getvertexstar(1, rempt, cavetetlist, NULL, NULL); - // Calculate the initial smallest volume (maybe zero or negative). - for (j = 0; j < cavetetlist->objects; j++) { - parytet = (triface *) fastlookup(cavetetlist, j); - ppt = (point *) &(parytet->tet[4]); - ori = orient3d(ppt[1], ppt[0], ppt[2], ppt[3]); - if (j == 0) { - opm.initval = ori; - } else { - if (opm.initval > ori) opm.initval = ori; + if (pointtype(rempt) == FREEVOLVERTEX) { + if (removevertexbyflips(rempt)) { + remcount++; } } - if (smoothpoint(rempt, cavetetlist, 1, &opm)) { - count++; - } - cavetetlist->restart(); } - } // i - - smtcount += count; - - if (count == 0) { - // No point has been smoothed. - break; } + } - nt++; - if (nt > 2) { - break; // Already three iterations. - } - } // while - - // The mesh should not contain inverted (or degenrrated) tets now. - checkinverttetflag = 0; - - if (b->verbose) { - if (smtcount > 0) { - printf(" Smoothed %d Steiner points.\n", smtcount); + if (remcount > 0) { + if (b->verbose) { + printf(" Removed %d interior Steiner points.\n", remcount); } } b->fliplinklevel = bak_fliplinklevel; - return smtcount; + if (b->nobisect_param > 1) { // -Y2 + // Smooth interior Steiner points. + optparameters opm; + triface *parytet; + point *ppt; + REAL ori; + int smtcount, count, ivcount; + int nt, j; + + // Point smooth options. + opm.max_min_volume = 1; + opm.numofsearchdirs = 20; + opm.searchstep = 0.001; + opm.maxiter = 30; // Limit the maximum iterations. + + smtcount = 0; + + do { + + nt = 0; + + while (1) { + count = 0; + ivcount = 0; // Clear the inverted count. + + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) == FREEVOLVERTEX) { + getvertexstar(1, rempt, cavetetlist, NULL, NULL); + // Calculate the initial smallest volume (maybe zero or negative). + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + ppt = (point *) &(parytet->tet[4]); + ori = orient3dfast(ppt[1], ppt[0], ppt[2], ppt[3]); + if (j == 0) { + opm.initval = ori; + } else { + if (opm.initval > ori) opm.initval = ori; + } + } + if (smoothpoint(rempt, cavetetlist, 1, &opm)) { + count++; + } + if (opm.imprval <= 0.0) { + ivcount++; // The mesh contains inverted elements. + } + cavetetlist->restart(); + } + } // i + + smtcount += count; + + if (count == 0) { + // No point has been smoothed. + break; + } + + nt++; + if (nt > 2) { + break; // Already three iterations. + } + } // while + + if (ivcount > 0) { + // There are inverted elements! + if (opm.maxiter > 0) { + // Set unlimited smoothing steps. Try again. + opm.numofsearchdirs = 30; + opm.searchstep = 0.0001; + opm.maxiter = -1; + continue; + } + } + + break; + } while (1); // Additional loop for (ivcount > 0) + + if (ivcount > 0) { + printf("BUG Report! The mesh contain inverted elements.\n"); + } + + if (b->verbose) { + if (smtcount > 0) { + printf(" Smoothed %d Steiner points.\n", smtcount); + } + } + } // -Y2 + + subvertstack->restart(); + + return 1; } /////////////////////////////////////////////////////////////////////////////// @@ -3314,16 +3346,11 @@ void tetgenmesh::recoverboundary(clock_t& tv) // Counters. long bak_segref_count, bak_facref_count, bak_volref_count; - long bak_supp_count; if (!b->quiet) { printf("Recovering boundaries...\n"); } - if (b->verbose) { - printf(" Flip link level = %d\n", b->fliplinklevel); - } - if (b->verbose) { printf(" Recovering segments.\n"); @@ -3335,19 +3362,18 @@ void tetgenmesh::recoverboundary(clock_t& tv) misseglist = new arraypool(sizeof(face), 8); bdrysteinerptlist = new arraypool(sizeof(point), 8); - // In random order. - subsegs->traversalinit(); - for (i = 0; i < subsegs->items; i++) { - s = randomnation(i + 1); - // Move the s-th seg to the i-th. - subsegstack->newindex((void **) &paryseg); - *paryseg = * (face *) fastlookup(subsegstack, s); - // Put i-th seg to be the s-th. - searchseg.sh = shellfacetraverse(subsegs); - //sinfect(searchseg); // Only save it once. - paryseg = (face *) fastlookup(subsegstack, s); - *paryseg = searchseg; - } + // In random order. + subsegs->traversalinit(); + for (i = 0; i < subsegs->items; i++) { + s = randomnation(i + 1); + // Move the s-th seg to the i-th. + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + // Put i-th seg to be the s-th. + searchseg.sh = shellfacetraverse(subsegs); + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = searchseg; + } // The init number of missing segments. ms = subsegs->items; @@ -3356,6 +3382,7 @@ void tetgenmesh::recoverboundary(clock_t& tv) autofliplinklevel = 1; // Init value. } + // First, trying to recover segments by only doing flips. while (1) { recoversegments(misseglist, 0, 0); @@ -3395,8 +3422,7 @@ void tetgenmesh::recoverboundary(clock_t& tv) } if (misseglist->objects > 0) { - // There are missing segments. Increase the fliplevel. - nit = 0; + // Second, trying to recover segments by doing more flips (fullsearch). while (misseglist->objects > 0) { ms = misseglist->objects; for (i = 0; i < misseglist->objects; i++) { @@ -3405,17 +3431,13 @@ void tetgenmesh::recoverboundary(clock_t& tv) } misseglist->restart(); - // Recover the missing segments by doing more flips. recoversegments(misseglist, 1, 0); if (misseglist->objects < ms) { // The number of missing segments is reduced. continue; } else { - nit++; - if (nit >= 3) { - break; - } + break; } } if (b->verbose) { @@ -3425,8 +3447,8 @@ void tetgenmesh::recoverboundary(clock_t& tv) } if (misseglist->objects > 0) { - // There are missing segments. Add Steiner points in volume. - nit = 0; + // Third, trying to recover segments by doing more flips (fullsearch) + // and adding Steiner points in the volume. while (misseglist->objects > 0) { ms = misseglist->objects; for (i = 0; i < misseglist->objects; i++) { @@ -3435,17 +3457,13 @@ void tetgenmesh::recoverboundary(clock_t& tv) } misseglist->restart(); - // Recover the missing segments (with Steiner points). recoversegments(misseglist, 1, 1); if (misseglist->objects < ms) { // The number of missing segments is reduced. continue; } else { - nit++; - if (nit >= 3) { - break; - } + break; } } if (b->verbose) { @@ -3454,7 +3472,8 @@ void tetgenmesh::recoverboundary(clock_t& tv) } if (misseglist->objects > 0) { - // There are missing segments. Add Steiner points to split them. + // Last, trying to recover segments by doing more flips (fullsearch), + // and adding Steiner points in the volume, and splitting segments. long bak_inpoly_count = st_volref_count; //st_inpoly_count; for (i = 0; i < misseglist->objects; i++) { subsegstack->newindex((void **) &paryseg); @@ -3462,7 +3481,6 @@ void tetgenmesh::recoverboundary(clock_t& tv) } misseglist->restart(); - // Recover the missing segments (with Steiner points). recoversegments(misseglist, 1, 2); if (b->verbose) { @@ -3541,6 +3559,7 @@ void tetgenmesh::recoverboundary(clock_t& tv) while (1) { recoversubfaces(misshlist, 0); + if (misshlist->objects > 0) { if (b->fliplinklevel >= 0) { break; @@ -3615,32 +3634,18 @@ void tetgenmesh::recoverboundary(clock_t& tv) } - if ((bdrysteinerptlist->objects > 0) && (b->nobisect_param > 0)) { // -Y1 - bak_supp_count = 0; - b->fliplinklevel = 100000; // Unlimited flip levels. - do { - // Suppress boundary Steiner points. - for (i = 0; i < bdrysteinerptlist->objects; i++) { - parypt = (point *) fastlookup(bdrysteinerptlist, i); - rempt = *parypt; - suppressssteinerpoint(rempt); - bak_supp_count++; - } - bdrysteinerptlist->restart(); - // There may be subfaces need to be recover. - if (subfacstack->objects > 0l) { - recoversubfaces(NULL, 1); - } - } while (bdrysteinerptlist->objects > 0); + if (bdrysteinerptlist->objects > 0) { if (b->verbose) { - printf(" Suppressed %ld Steiner points from boundary.\n", - bak_supp_count); + printf(" %ld Steiner points remained in boundary.\n", + bdrysteinerptlist->objects); } - // The mesh contains inverted (or degenrrated) tets now. - checkinverttetflag = 1; } // if + // Accumulate the dynamic memory. + totalworkmemory += (misseglist->totalmemory + misshlist->totalmemory + + bdrysteinerptlist->totalmemory); + delete bdrysteinerptlist; delete misseglist; delete misshlist; diff --git a/external/tetgen/src/surface.cxx b/external/tetgen/src/surface.cxx index 212eb7d9e..6817699ef 100644 --- a/external/tetgen/src/surface.cxx +++ b/external/tetgen/src/surface.cxx @@ -3,123 +3,6 @@ //// //// //// //// -/////////////////////////////////////////////////////////////////////////////// -// // -// calculateabovepoint() Calculate a point above a facet in 'dummypoint'. // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh::calculateabovepoint(arraypool *facpoints, point *ppa, - point *ppb, point *ppc) -{ - point *ppt, pa, pb, pc; - REAL v1[3], v2[3], n[3]; - REAL lab, len, A, area; - REAL x, y, z; - int i; - - ppt = (point *) fastlookup(facpoints, 0); - pa = *ppt; // a is the first point. - pb = pc = NULL; // Avoid compiler warnings. - - // Get a point b s.t. the length of [a, b] is maximal. - lab = 0; - for (i = 1; i < facpoints->objects; i++) { - ppt = (point *) fastlookup(facpoints, i); - x = (*ppt)[0] - pa[0]; - y = (*ppt)[1] - pa[1]; - z = (*ppt)[2] - pa[2]; - len = x * x + y * y + z * z; - if (len > lab) { - lab = len; - pb = *ppt; - } - } - lab = sqrt(lab); - if (lab == 0) { - if (!b->quiet) { - printf("Warning: All points of a facet are coincident with %d.\n", - pointmark(pa)); - } - return false; - } - - // Get a point c s.t. the area of [a, b, c] is maximal. - v1[0] = pb[0] - pa[0]; - v1[1] = pb[1] - pa[1]; - v1[2] = pb[2] - pa[2]; - A = 0; - for (i = 1; i < facpoints->objects; i++) { - ppt = (point *) fastlookup(facpoints, i); - v2[0] = (*ppt)[0] - pa[0]; - v2[1] = (*ppt)[1] - pa[1]; - v2[2] = (*ppt)[2] - pa[2]; - CROSS(v1, v2, n); - area = DOT(n, n); - if (area > A) { - A = area; - pc = *ppt; - } - } - if (A == 0) { - // All points are collinear. No above point. - if (!b->quiet) { - printf("Warning: All points of a facet are collinaer with [%d, %d].\n", - pointmark(pa), pointmark(pb)); - } - return false; - } - - // Calculate an above point of this facet. - facenormal(pa, pb, pc, n, 1, NULL); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - lab /= 2.0; // Half the maximal length. - dummypoint[0] = pa[0] + lab * n[0]; - dummypoint[1] = pa[1] + lab * n[1]; - dummypoint[2] = pa[2] + lab * n[2]; - - if (ppa != NULL) { - // Return the three points. - *ppa = pa; - *ppb = pb; - *ppc = pc; - } - - return true; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// Calculate an above point. It lies above the plane containing the subface // -// [a,b,c], and save it in dummypoint. Moreover, the vector pa->dummypoint // -// is the normal of the plane. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::calculateabovepoint4(point pa, point pb, point pc, point pd) -{ - arraypool *ptarray; - point *parypt; - - ptarray = new arraypool(sizeof(point), 4); - - ptarray->newindex((void **) &parypt); - *parypt = pa; - ptarray->newindex((void **) &parypt); - *parypt = pb; - ptarray->newindex((void **) &parypt); - *parypt = pc; - ptarray->newindex((void **) &parypt); - *parypt = pd; - - calculateabovepoint(ptarray, NULL, NULL, NULL); - - delete ptarray; -} - /////////////////////////////////////////////////////////////////////////////// // // // flipshpush() Push a facet edge into flip stack. // @@ -140,19 +23,20 @@ void tetgenmesh::flipshpush(face* flipedge) /////////////////////////////////////////////////////////////////////////////// // // -// flip22() Remove an edge by transforming 2-to-2 subfaces. // +// flip22() Perform a 2-to-2 flip in surface mesh. // // // -// 'flipfaces' contains two faces: abc and bad. This routine removes these 2 // -// faces and replaces them by two new faces: cdb and dca. // +// 'flipfaces' is an array of two subfaces. On input, they are [a,b,c] and // +// [b,a,d]. On output, they are [c,d,b] and [d,c,a]. As a result, edge [a,b] // +// is replaced by edge [c,d]. // // // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) { - face bdedges[4], outfaces[4], infaces[4], bdsegs[4]; - face checkface, checkseg; + face bdedges[4], outfaces[4], infaces[4]; + face bdsegs[4]; + face checkface; point pa, pb, pc, pd; - badface *bface; int i; pa = sorg(flipfaces[0]); @@ -164,10 +48,6 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) sesymself(flipfaces[1]); } - if (b->verbose > 3) { - printf(" flip 2-to-2: (%d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd)); - } flip22count++; // Collect the four boundary edges. @@ -182,8 +62,7 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) infaces[i] = outfaces[i]; sspivot(bdedges[i], bdsegs[i]); if (outfaces[i].sh != NULL) { - sspivot(bdedges[i], checkseg); - if (checkseg.sh != NULL) { + if (isshsubseg(bdedges[i])) { spivot(infaces[i], checkface); while (checkface.sh != bdedges[i].sh) { infaces[i] = checkface; @@ -197,9 +76,9 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) // Shellmark does not change. // area constraint does not change. - // Transform abc -> cdb. + // Transform [a,b,c] -> [c,d,b]. setshvertices(flipfaces[0], pc, pd, pb); - // Transform bad -> dca. + // Transform [b,a,d] -> [d,c,a]. setshvertices(flipfaces[1], pd, pc, pa); // Update the point-to-subface map. @@ -235,12 +114,7 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) ssbond(bdedges[i], bdsegs[(3 + i) % 4]); if (chkencflag & 1) { // Queue this segment for encroaching check. - if (!smarktest2ed(bdsegs[(3 + i) % 4])) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = bdsegs[(3 + i) % 4]; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(bface->ss); // An alive badface. - } + enqueuesubface(badsubsegs, &(bdsegs[(3 + i) % 4])); } } else { ssdissolve(bdedges[i]); @@ -250,12 +124,7 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) if (chkencflag & 2) { // Queue the flipped subfaces for quality/encroaching checks. for (i = 0; i < 2; i++) { - if (!smarktest2ed(flipfaces[i])) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = flipfaces[i]; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(bface->ss); // An alive badface. - } + enqueuesubface(badsubfacs, &(flipfaces[i])); } } @@ -286,22 +155,17 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) void tetgenmesh::flip31(face* flipfaces, int flipflag) { - face bdedges[3], outfaces[3], infaces[3], bdsegs[3]; - face checkface, checkseg; - point pa, pb, pc, delpt; - REAL area; + face bdedges[3], outfaces[3], infaces[3]; + face bdsegs[3]; + face checkface; + point pa, pb, pc; int i; - delpt = sorg(flipfaces[0]); pa = sdest(flipfaces[0]); pb = sdest(flipfaces[1]); pc = sdest(flipfaces[2]); - if (b->verbose > 3) { - printf(" flip 3-to-1: (%d, %d, %d) - %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(delpt)); - } - // flip31count++; + flip31count++; // Collect all infos at the three boundary edges. for (i = 0; i < 3; i++) { @@ -310,8 +174,7 @@ void tetgenmesh::flip31(face* flipfaces, int flipflag) infaces[i] = outfaces[i]; sspivot(bdedges[i], bdsegs[i]); if (outfaces[i].sh != NULL) { - sspivot(bdedges[i], checkseg); - if (checkseg.sh != NULL) { + if (isshsubseg(bdedges[i])) { spivot(infaces[i], checkface); while (checkface.sh != bdedges[i].sh) { infaces[i] = checkface; @@ -326,8 +189,11 @@ void tetgenmesh::flip31(face* flipfaces, int flipflag) setshvertices(flipfaces[3], pa, pb,pc); setshellmark(flipfaces[3], shellmark(flipfaces[0])); if (checkconstraints) { - area = areabound(flipfaces[0]); - setareabound(flipfaces[3], area); + //area = areabound(flipfaces[0]); + setareabound(flipfaces[3], areabound(flipfaces[0])); + } + if (useinsertradius) { + setfacetindex(flipfaces[3], getfacetindex(flipfaces[0])); } // Update the point-to-subface map. @@ -384,15 +250,13 @@ long tetgenmesh::lawsonflip() { badface *popface; face flipfaces[2]; - face checkseg; point pa, pb, pc, pd; REAL sign; - long flipcount; + long flipcount = 0; if (b->verbose > 2) { printf(" Lawson flip %ld edges.\n", flippool->items); } - flipcount = flip22count; while (flipstack != (badface *) NULL) { @@ -409,8 +273,7 @@ long tetgenmesh::lawsonflip() // Skip it if it is not the same edge as we saved. if ((sorg(flipfaces[0]) != pa) || (sdest(flipfaces[0]) != pb)) continue; // Skip it if it is a subsegment. - sspivot(flipfaces[0], checkseg); - if (checkseg.sh != NULL) continue; + if (isshsubseg(flipfaces[0])) continue; // Get the adjacent face. spivot(flipfaces[0], flipfaces[1]); @@ -423,16 +286,15 @@ long tetgenmesh::lawsonflip() if (sign < 0) { // It is non-locally Delaunay. Flip it. flip22(flipfaces, 1, 0); + flipcount++; } } if (b->verbose > 2) { - printf(" %ld edges stacked, %ld flips.\n", flippool->items, - flip22count - flipcount); + printf(" Performed %ld flips.\n", flipcount); } - assert(flippool->items == 0l); // SELF_CHECK - return flip22count - flipcount; + return flipcount; } /////////////////////////////////////////////////////////////////////////////// @@ -444,75 +306,72 @@ long tetgenmesh::lawsonflip() // 'caveshbdlist' contains new subfaces in C(p). If the new point lies on a // // segment, 'cavesegshlist' returns the two new subsegments. // // // -// NOTE: the old subfaces in C(p) are not deleted. Theyare needed in case we // -// want to remove the new point immedately. // +// 'iloc' suggests the location of the point. If it is OUTSIDE, this routine // +// will first locate the point. It starts searching from 'searchsh' or 'rec- // +// entsh' if 'searchsh' is NULL. // +// // +// If 'bowywat' is set (1), the Bowyer-Watson algorithm is used to insert // +// the vertex. Otherwise, only insert the vertex in the initial cavity. // +// // +// If 'iloc' is 'INSTAR', this means the cavity of this vertex was already // +// provided in the list 'caveshlist'. // +// // +// If 'splitseg' is not NULL, the new vertex lies on the segment and it will // +// be split. 'iloc' must be either 'ONEDGE' or 'INSTAR'. // +// // +// 'rflag' (rounding) is a parameter passed to slocate() function. If it is // +// set, after the location of the point is found, either ONEDGE or ONFACE, // +// round the result using an epsilon. // +// // +// NOTE: the old subfaces in C(p) are not deleted. They're needed in case we // +// want to remove the new point immediately. // // // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::sinsertvertex(point insertpt, face *searchsh, face *splitseg, - int iloc, int bowywat) + int iloc, int bowywat, int rflag) { - triface adjtet; face cavesh, neighsh, *parysh; face newsh, casout, casin; - face aseg, bseg, aoutseg, boutseg; face checkseg; - point pa, pb, pc; - enum locateresult loc; - REAL sign, ori, area; + point pa, pb; + enum locateresult loc = OUTSIDE; + REAL sign, ori; int i, j; if (b->verbose > 2) { printf(" Insert facet point %d.\n", pointmark(insertpt)); } - if (splitseg != NULL) { + if (bowywat == 3) { + loc = INSTAR; + } + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { // A segment is going to be split, no point location. spivot(*splitseg, *searchsh); - loc = ONEDGE; + if (loc != INSTAR) loc = ONEDGE; } else { - loc = (enum locateresult) iloc; + if (loc != INSTAR) loc = (enum locateresult) iloc; if (loc == OUTSIDE) { // Do point location in surface mesh. if (searchsh->sh == NULL) { *searchsh = recentsh; } - // Start searching from 'searchsh'. - loc = slocate(insertpt, searchsh, 1, 1, 0); + // Search the vertex. An above point must be provided ('aflag' = 1). + loc = slocate(insertpt, searchsh, 1, 1, rflag); } } - if (b->verbose > 2) { - if (searchsh->sh != NULL) { - pa = sorg(*searchsh); - pb = sdest(*searchsh); - pc = sapex(*searchsh); - printf(" Located subface (%d, %d, %d).\n", pointmark(pa), - pointmark(pb), pointmark(pc)); - } else { - assert(splitseg != NULL); - pa = sorg(*splitseg); - pb = sdest(*splitseg); - printf(" Located segment (%d, %d).\n", pointmark(pa),pointmark(pb)); - } - } - -if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { // Form the initial sC(p). if (loc == ONFACE) { - if (b->verbose > 2) { - printf(" Inside face.\n"); - } // Add the face into list (in B-W cavity). smarktest(*searchsh); caveshlist->newindex((void **) &parysh); *parysh = *searchsh; } else if (loc == ONEDGE) { - if (b->verbose > 2) { - printf(" On edge.\n"); - } - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { splitseg->shver = 0; pa = sorg(*splitseg); } else { @@ -523,9 +382,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { neighsh = *searchsh; while (1) { // Adjust the origin of its edge to be 'pa'. - if (sorg(neighsh) != pa) { - sesymself(neighsh); - } + if (sorg(neighsh) != pa) sesymself(neighsh); // Add this face into list (in B-W cavity). smarktest(neighsh); caveshlist->newindex((void **) &parysh); @@ -541,18 +398,12 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } } // If (not a non-dangling segment). } else if (loc == ONVERTEX) { - if (b->verbose > 2) { - printf(" On vertex.\n"); - } return (int) loc; } else if (loc == OUTSIDE) { // Comment: This should only happen during the surface meshing step. // Enlarge the convex hull of the triangulation by including p. // An above point of the facet is set in 'dummypoint' to replace // orient2d tests by orient3d tests. - if (b->verbose > 2) { - printf(" Outside face.\n"); - } // Imagine that the current edge a->b (in 'searchsh') is horizontal in a // plane, and a->b is directed from left to right, p lies above a->b. // Find the right-most edge of the triangulation which is visible by p. @@ -562,9 +413,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { spivot(neighsh, casout); if (casout.sh == NULL) { // A convex hull edge. Is it visible by p. - pa = sorg(neighsh); - pb = sdest(neighsh); - ori = orient3d(pa, pb, dummypoint, insertpt); + ori = orient3d(sorg(neighsh), sdest(neighsh), dummypoint, insertpt); if (ori < 0) { *searchsh = neighsh; // Visible, update 'searchsh'. } else { @@ -585,8 +434,11 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { setshvertices(newsh, pb, pa, insertpt); setshellmark(newsh, shellmark(*searchsh)); if (checkconstraints) { - area = areabound(*searchsh); - setareabound(newsh, area); + //area = areabound(*searchsh); + setareabound(newsh, areabound(*searchsh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(*searchsh)); } // Connect the new subface to the bottom subfaces. sbond1(newsh, *searchsh); @@ -622,47 +474,30 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { // Finish the process if p is not visible by the hull edge. if (ori >= 0) break; } + } else if (loc == INSTAR) { + // Under this case, the sub-cavity sC(p) has already been formed in + // insertvertex(). } -} else { - - // Under this case, the sub-cavity sC(p) has already been formed in - // insertvertex(). Check it. - // FOR DEBUG ONLY. - for (i = 0; i < caveshlist->objects; i++) { - cavesh = * (face *) fastlookup(caveshlist, i); - assert(smarktested(cavesh)); - } - if (splitseg != NULL) { - assert(smarktested(*splitseg)); - } - - -}// if (bowywat < 3) - // Form the Bowyer-Watson cavity sC(p). for (i = 0; i < caveshlist->objects; i++) { cavesh = * (face *) fastlookup(caveshlist, i); for (j = 0; j < 3; j++) { - sspivot(cavesh, checkseg); - if (checkseg.sh == NULL) { + if (!isshsubseg(cavesh)) { spivot(cavesh, neighsh); if (neighsh.sh != NULL) { // The adjacent face exists. if (!smarktested(neighsh)) { if (bowywat) { - if (bowywat > 2) { + if (loc == INSTAR) { // if (bowywat > 2) { // It must be a boundary edge. sign = 1; } else { // Check if this subface is connected to adjacent tet(s). - stpivot(neighsh, adjtet); - if (adjtet.tet == NULL) { + if (!isshtet(neighsh)) { // Check if the subface is non-Delaunay wrt. the new pt. - pa = sorg(neighsh); - pb = sdest(neighsh); - pc = sapex(neighsh); - sign = incircle3d(pa, pb, pc, insertpt); + sign = incircle3d(sorg(neighsh), sdest(neighsh), + sapex(neighsh), insertpt); } else { // It is connected to an adjacent tet. A boundary edge. sign = 1; @@ -706,10 +541,6 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } // j } // i - if (b->verbose > 3) { - printf(" Size of cavity: %ld faces, %ld bdry edges.\n", - caveshlist->objects, caveshbdlist->objects); - } // Creating new subfaces. for (i = 0; i < caveshbdlist->objects; i++) { @@ -722,10 +553,12 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { makeshellface(subfaces, &newsh); setshvertices(newsh, pa, pb, insertpt); setshellmark(newsh, shellmark(*parysh)); - setshelltype(newsh, shelltype(*parysh)); if (checkconstraints) { - area = areabound(*parysh); - setareabound(newsh, area); + //area = areabound(*parysh); + setareabound(newsh, areabound(*parysh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(*parysh)); } // Update the point-to-subface map. if (pointtype(pa) == FREEFACETVERTEX) { @@ -743,7 +576,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { checkseg.shver = 0; if (sorg(newsh) != sorg(checkseg)) { sesymself(newsh); - sesymself(*parysh); // This side should also be inversed. + sesymself(*parysh); // This side should also be inverse. } spivot(casin, neighsh); while (neighsh.sh != parysh->sh) { @@ -762,8 +595,10 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { sbond1(*parysh, newsh); } - // Set a handle for searching. - recentsh = newsh; + if (newsh.sh != NULL) { + // Set a handle for searching. + recentsh = newsh; + } // Update the point-to-subface map. if (pointtype(insertpt) == FREEFACETVERTEX) { @@ -791,8 +626,6 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { if (neighsh.sh != NULL) { // Now 'neighsh' is a new subface at edge [b, #]. if (sorg(neighsh) != pb) sesymself(neighsh); - assert(sorg(neighsh) == pb); // SELF_CHECK - assert(sapex(neighsh) == insertpt); // SELF_CHECK senext2self(neighsh); // Go to the open edge [p, b]. sbond(newsh, neighsh); } else { @@ -817,8 +650,6 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { if (neighsh.sh != NULL) { // Now 'neighsh' is a new subface at edge [#, a]. if (sdest(neighsh) != pa) sesymself(neighsh); - assert(sdest(neighsh) == pa); // SELF_CHECK - assert(sapex(neighsh) == insertpt); // SELF_CHECK senextself(neighsh); // Go to the open edge [a, p]. sbond(newsh, neighsh); } else { @@ -828,13 +659,15 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } } - if (loc == ONEDGE) { - + if ((loc == ONEDGE) || ((splitseg != NULL) && (splitseg->sh != NULL)) + || (cavesegshlist->objects > 0l)) { // An edge is being split. We distinguish two cases: // (1) the edge is not on the boundary of the cavity; // (2) the edge is on the boundary of the cavity. // In case (2), the edge is either a segment or a hull edge. There are // degenerated new faces in the cavity. They must be removed. + face aseg, bseg, aoutseg, boutseg; + for (i = 0; i < cavesegshlist->objects; i++) { // Get the saved old subface. parysh = (face *) fastlookup(cavesegshlist, i); @@ -854,7 +687,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } assert(sapex(neighsh) == insertpt); // SELF_CHECK // Connect adjacent faces at two other edges of cavesh and neighsh. - // As a result, the two degenrated new faces are squessed from the + // As a result, the two degenerated new faces are squeezed from the // new triangulation of the cavity. Note that the squeezed faces // still hold the adjacent informations which will be used in // re-connecting subsegments (if they exist). @@ -866,7 +699,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { sbond1(newsh, casout); // newsh <- casout. } } else { - // There is only one subface containing this edge [a,b]. Squeese the + // There is only one subface containing this edge [a,b]. Squeeze the // degenerated new face [a,b,c] by disconnecting it from its two // adjacent subfaces at edges [b,c] and [c,a]. Note that the face // [a,b,c] still hold the connection to them. @@ -876,7 +709,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { sdissolve(newsh); } } - recentsh = newsh; + //recentsh = newsh; // Update the point-to-subface map. if (pointtype(insertpt) == FREEFACETVERTEX) { setpoint2sh(insertpt, sencode(newsh)); @@ -884,18 +717,14 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } } - if (splitseg != NULL) { - if (bowywat < 3) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + if (loc != INSTAR) { // if (bowywat < 3) { smarktest(*splitseg); // Mark it as being processed. } aseg = *splitseg; pa = sorg(*splitseg); pb = sdest(*splitseg); - if (b->verbose > 2) { - printf(" Split seg (%d, %d) by %d.\n", pointmark(pa), - pointmark(pb), pointmark(insertpt)); - } // Insert the new point p. makeshellface(subsegs, &aseg); @@ -905,12 +734,14 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { setshvertices(bseg, insertpt, pb, NULL); setshellmark(aseg, shellmark(*splitseg)); setshellmark(bseg, shellmark(*splitseg)); - setshelltype(aseg, shelltype(*splitseg)); - setshelltype(bseg, shelltype(*splitseg)); if (checkconstraints) { setareabound(aseg, areabound(*splitseg)); setareabound(bseg, areabound(*splitseg)); } + if (useinsertradius) { + setfacetindex(aseg, getfacetindex(*splitseg)); + setfacetindex(bseg, getfacetindex(*splitseg)); + } // Connect [#, a]<->[a, p]. senext2(*splitseg, boutseg); // Temporarily use boutseg. @@ -932,7 +763,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { sbond(aoutseg, boutseg); // Connect subsegs [a, p] and [p, b] to adjacent new subfaces. - // Although the degenerated new faces have been squeesed. They still + // Although the degenerated new faces have been squeezed. They still // hold the connections to the actual new faces. for (i = 0; i < cavesegshlist->objects; i++) { parysh = (face *) fastlookup(cavesegshlist, i); @@ -951,11 +782,17 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { // Let the point remember the segment it lies on. - setpoint2sh(insertpt, sencode(aseg)); + if (pointtype(insertpt) == FREESEGVERTEX) { + setpoint2sh(insertpt, sencode(aseg)); + } // Update the point-to-seg map. - setpoint2sh(pa, sencode(aseg)); - setpoint2sh(pb, sencode(bseg)); - } // if (splitseg != NULL) + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(aseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(bseg)); + } + } // if ((splitseg != NULL) && (splitseg->sh != NULL)) // Delete all degenerated new faces. for (i = 0; i < cavesegshlist->objects; i++) { @@ -967,7 +804,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } cavesegshlist->restart(); - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { // Return the two new subsegments (for further process). // Re-use 'cavesegshlist'. cavesegshlist->newindex((void **) &parysh); @@ -975,7 +812,6 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { cavesegshlist->newindex((void **) &parysh); *parysh = bseg; } - } // if (loc == ONEDGE) @@ -990,39 +826,34 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { // a segment vertex, and the origin of 'parentseg' is p. Otherwise, p is a // // facet vertex, and the origin of 'parentsh' is p. // // // -// If 'lawson' > 0, the Lawson flip algorithm is used to recover Delaunay- // -// ness after p is removed. // -// // // Within each facet, we first use a sequence of 2-to-2 flips to flip any // // edge at p, finally use a 3-to-1 flip to remove p. // // // // All new created subfaces are returned in the global array 'caveshbdlist'. // // The new segment (when p is on segment) is returned in 'parentseg'. // // // +// If 'lawson' > 0, the Lawson flip algorithm is used to recover Delaunay- // +// ness after p is removed. // +// // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, int lawson) { - face flipfaces[4], *parysh; - face spinsh, startsh, neighsh, nextsh, fakesh; - face abseg, prevseg, checkseg; - face adjseg1, adjseg2; + face flipfaces[4], spinsh, *parysh; point pa, pb, pc, pd; + REAL ori1, ori2; int it, i, j; - REAL *norm, n1[3], n2[3]; - REAL len, len1, len2; - REAL ori1, ori2; - if (parentseg != NULL) { - assert(sorg(*parentseg) == delpt); - assert(parentseg->shver == 0); // 'delpt' (p) should be a Steiner point inserted in a segment [a,b], // where 'parentseg' should be [p,b]. Find the segment [a,p]. + face startsh, neighsh, nextsh; + face abseg, prevseg, checkseg; + face adjseg1, adjseg2; + face fakesh; senext2(*parentseg, prevseg); spivotself(prevseg); - assert(prevseg.sh != NULL); prevseg.shver = 0; assert(sdest(prevseg) == delpt); // Restore the original segment [a,b]. @@ -1035,10 +866,12 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, makeshellface(subsegs, &abseg); setshvertices(abseg, pa, pb, NULL); setshellmark(abseg, shellmark(*parentseg)); - setshelltype(abseg, shelltype(*parentseg)); if (checkconstraints) { setareabound(abseg, areabound(*parentseg)); } + if (useinsertradius) { + setfacetindex(abseg, getfacetindex(*parentseg)); + } // Connect [#, a]<->[a, b]. senext2(prevseg, adjseg1); spivotself(adjseg1); @@ -1066,16 +899,17 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, // Get the faces in face ring at segment [p, b]. // Re-use array 'caveshlist'. spivot(*parentseg, *parentsh); - spinsh = *parentsh; - while (1) { - // Save this face in list. - caveshlist->newindex((void **) &parysh); - *parysh = spinsh; - // Go to the next face in the ring. - spivotself(spinsh); - if (spinsh.sh == NULL) break; - if (spinsh.sh == parentsh->sh) break; - } + if (parentsh->sh != NULL) { + spinsh = *parentsh; + while (1) { + // Save this face in list. + caveshlist->newindex((void **) &parysh); + *parysh = spinsh; + // Go to the next face in the ring. + spivotself(spinsh); + if (spinsh.sh == parentsh->sh) break; + } + } // Create the face ring of the new segment [a,b]. Each face in the ring // is [a,b,p] (degenerated!). It will be removed (automatically). @@ -1127,12 +961,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, // Since we will re-connect the face ring using the faked subfaces. // We put the adjacent face of [a,b,p] to the list. spivot(neighsh, startsh); // The original adjacent subface. - if (sorg(startsh) != pa) { - sesymself(startsh); - } - assert(sorg(startsh) == pa); - assert(sdest(startsh) == pb); - assert(sapex(startsh) != delpt); + if (sorg(startsh) != pa) sesymself(startsh); sdissolve(startsh); // Connect fakesh to the segment [a,b]. ssbond(startsh, abseg); @@ -1183,9 +1012,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, parentsh = (face *) fastlookup(cavesegshlist, it); // [a,b,p] senextself(*parentsh); // [b,p,a]. spivotself(*parentsh); - if (sorg(*parentsh) != delpt) { - sesymself(*parentsh); - } + if (sorg(*parentsh) != delpt) sesymself(*parentsh); // now parentsh is [p,b,#]. if (sorg(*parentsh) != delpt) { // The vertex has already been removed in above special case. @@ -1203,10 +1030,8 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, spivotself(spinsh); assert(spinsh.sh != NULL); if (spinsh.sh == parentsh->sh) break; - if (sorg(spinsh) != delpt) { - sesymself(spinsh); - assert(sorg(spinsh) == delpt); - } + if (sorg(spinsh) != delpt) sesymself(spinsh); + assert(sorg(spinsh) == delpt); } // while (1) if (caveshlist->objects == 3) { @@ -1225,9 +1050,6 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, *parysh = flipfaces[3]; // The vertex is removed. break; - } else { - // There should be more than 3 subfaces in list. - assert(caveshlist->objects > 3); } // Search an edge to flip. @@ -1235,35 +1057,15 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, parysh = (face *) fastlookup(caveshlist, i); flipfaces[0] = *parysh; spivot(flipfaces[0], flipfaces[1]); - if (sorg(flipfaces[0]) != sdest(flipfaces[1])) { + if (sorg(flipfaces[0]) != sdest(flipfaces[1])) sesymself(flipfaces[1]); - } // Skip this edge if it belongs to a faked subface. if (!smarktested(flipfaces[0]) && !smarktested(flipfaces[1])) { pa = sorg(flipfaces[0]); pb = sdest(flipfaces[0]); pc = sapex(flipfaces[0]); pd = sapex(flipfaces[1]); - // Select a base. - facenormal(pa, pb, pc, n1, 1, NULL); - len1 = sqrt(DOT(n1, n1)); - facenormal(pa, pb, pd, n2, 1, NULL); - len2 = sqrt(DOT(n2, n2)); - if (len1 > len2) { - norm = n1; - len = len1; - } else { - norm = n2; - len = len2; - } - assert(len > 0); - norm[0] /= len; - norm[1] /= len; - norm[2] /= len; - len = DIST(pa, pb); - dummypoint[0] = pa[0] + len * norm[0]; - dummypoint[1] = pa[1] + len * norm[1]; - dummypoint[2] = pa[2] + len * norm[2]; + calculateabovepoint4(pa, pb, pc, pd); // Check if a 2-to-2 flip is possible. ori1 = orient3d(pc, pd, dummypoint, pa); ori2 = orient3d(pc, pd, dummypoint, pb); @@ -1280,6 +1082,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, } } // } // i + if (i == caveshlist->objects) { // This can happen only if there are 4 edges at p, and they are // orthogonal to each other, see Fig. 2010-11-01. @@ -1297,6 +1100,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, caveshbdlist->newindex((void **) &parysh); *parysh = flipfaces[0]; } + // The edge list at p are changed. caveshlist->restart(); } // while (1) @@ -1335,7 +1139,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, // If 'rflag' (rounding) is set, after the location of the point is found, // // either ONEDGE or ONFACE, round the result using an epsilon. // // // -// The returned value inducates the following cases: // +// The returned value indicates the following cases: // // - ONVERTEX, p is the origin of 'searchsh'. // // - ONEDGE, p lies on the edge of 'searchsh'. // // - ONFACE, p lies in the interior of 'searchsh'. // @@ -1348,35 +1152,19 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, face* searchsh, int aflag, int cflag, int rflag) { face neighsh; - face checkseg; - point pa, pb, pc, pd, *parypt; + point pa, pb, pc; enum locateresult loc; enum {MOVE_BC, MOVE_CA} nextmove; REAL ori, ori_bc, ori_ca; - REAL dist_bc, dist_ca; int i; - // For finding an approximate location. - //REAL n[3], len, len3; - REAL n[3], area_abc, area_abp, area_bcp, area_cap; - pa = sorg(*searchsh); pb = sdest(*searchsh); pc = sapex(*searchsh); if (!aflag) { // No above point is given. Calculate an above point for this facet. - // Re-use the 'cavetetvertlist'. - cavetetvertlist->newindex((void **) &parypt); - *parypt = pa; - cavetetvertlist->newindex((void **) &parypt); - *parypt = pb; - cavetetvertlist->newindex((void **) &parypt); - *parypt = pc; - cavetetvertlist->newindex((void **) &parypt); - *parypt = searchpt; - calculateabovepoint(cavetetvertlist, NULL, NULL, NULL); - cavetetvertlist->restart(); + calculateabovepoint4(pa, pb, pc, searchpt); } // 'dummypoint' is given. Make sure it is above [a,b,c] @@ -1411,25 +1199,7 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, if (ori_bc < 0) { if (ori_ca < 0) { // (--) // Any of the edges is a viable move. - senext(*searchsh, neighsh); // At edge [b, c]. - spivotself(neighsh); - if (neighsh.sh != NULL) { - pd = sapex(neighsh); - dist_bc = NORM2(searchpt[0] - pd[0], searchpt[1] - pd[1], - searchpt[2] - pd[2]); - } else { - dist_bc = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - senext2(*searchsh, neighsh); // At edge [c, a]. - spivotself(neighsh); - if (neighsh.sh != NULL) { - pd = sapex(neighsh); - dist_ca = NORM2(searchpt[0] - pd[0], searchpt[1] - pd[1], - searchpt[2] - pd[2]); - } else { - dist_ca = dist_bc; - } - if (dist_ca < dist_bc) { + if (randomnation(2)) { nextmove = MOVE_CA; } else { nextmove = MOVE_BC; @@ -1474,8 +1244,7 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, } if (!cflag) { // NON-convex case. Check if we will cross a boundary. - sspivot(*searchsh, checkseg); - if (checkseg.sh != NULL) { + if (isshsubseg(*searchsh)) { return ENCSEGMENT; } } @@ -1507,6 +1276,8 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, if (rflag) { // Round the locate result before return. + REAL n[3], area_abc, area_abp, area_bcp, area_cap; + pa = sorg(*searchsh); pb = sdest(*searchsh); pc = sapex(*searchsh); @@ -1574,23 +1345,21 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, // The segment is given by the origin of 'searchsh' and 'endpt'. Assume the // // orientation of 'searchsh' is CCW w.r.t. the above point. // // // -// If an edge in T is found matching this segment, the segment is "locaked" // +// If an edge in T is found matching this segment, the segment is "locked" // // in T at the edge. Otherwise, flip the first edge in T that the segment // // crosses. Continue the search from the flipped face. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::interresult - tetgenmesh::sscoutsegment(face *searchsh, point endpt) +enum tetgenmesh::interresult tetgenmesh::sscoutsegment(face *searchsh, + point endpt) { face flipshs[2], neighsh; - face newseg, checkseg; + face newseg; point startpt, pa, pb, pc, pd; enum interresult dir; enum {MOVE_AB, MOVE_CA} nextmove; - REAL ori_ab, ori_ca; - REAL dist_b, dist_c; - int shmark = 0; + REAL ori_ab, ori_ca, len; // The origin of 'searchsh' is fixed. startpt = sorg(*searchsh); // pa = startpt; @@ -1600,6 +1369,7 @@ enum tetgenmesh::interresult printf(" Scout segment (%d, %d).\n", pointmark(startpt), pointmark(endpt)); } + len = distance(startpt, endpt); // Search an edge in 'searchsh' on the path of this segment. while (1) { @@ -1618,22 +1388,22 @@ enum tetgenmesh::interresult break; } - ori_ab = orient3d(startpt, pb, dummypoint, endpt); - ori_ca = orient3d(pc, startpt, dummypoint, endpt); + // Round the results. + if ((sqrt(triarea(startpt, pb, endpt)) / len) < b->epsilon) { + ori_ab = 0.0; + } else { + ori_ab = orient3d(startpt, pb, dummypoint, endpt); + } + if ((sqrt(triarea(pc, startpt, endpt)) / len) < b->epsilon) { + ori_ca = 0.0; + } else { + ori_ca = orient3d(pc, startpt, dummypoint, endpt); + } if (ori_ab < 0) { if (ori_ca < 0) { // (--) // Both sides are viable moves. - spivot(*searchsh, neighsh); // At edge [a, b]. - assert(neighsh.sh != NULL); // SELF_CHECK - pd = sapex(neighsh); - dist_b = NORM2(endpt[0] - pd[0], endpt[1] - pd[1], endpt[2] - pd[2]); - senext2(*searchsh, neighsh); // At edge [c, a]. - spivotself(neighsh); - assert(neighsh.sh != NULL); // SELF_CHECK - pd = sapex(neighsh); - dist_c = NORM2(endpt[0] - pd[0], endpt[1] - pd[1], endpt[2] - pd[2]); - if (dist_c < dist_b) { + if (randomnation(2)) { nextmove = MOVE_CA; } else { nextmove = MOVE_AB; @@ -1673,13 +1443,32 @@ enum tetgenmesh::interresult // Move 'searchsh' to the next face, keep the origin unchanged. if (nextmove == MOVE_AB) { spivot(*searchsh, neighsh); - if (sorg(neighsh) != pb) sesymself(neighsh); - senext(neighsh, *searchsh); + if (neighsh.sh != NULL) { + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } else { + // This side (startpt->pb) is outside. It is caused by rounding error. + // Try the next side, i.e., (pc->startpt). + senext2(*searchsh, neighsh); + spivotself(neighsh); + assert(neighsh.sh != NULL); + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } } else { senext2(*searchsh, neighsh); spivotself(neighsh); - if (sdest(neighsh) != pc) sesymself(neighsh); - *searchsh = neighsh; + if (neighsh.sh != NULL) { + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } else { + // The same reason as above. + // Try the next side, i.e., (startpt->pb). + spivot(*searchsh, neighsh); + assert(neighsh.sh != NULL); + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } } assert(sorg(*searchsh) == startpt); // SELF_CHECK @@ -1689,11 +1478,8 @@ enum tetgenmesh::interresult // Insert the segment into the triangulation. makeshellface(subsegs, &newseg); setshvertices(newseg, startpt, endpt, NULL); - // Set the actual segment marker. - if (in->facetmarkerlist != NULL) { - shmark = shellmark(*searchsh); - setshellmark(newseg, in->facetmarkerlist[shmark - 1]); - } + // Set the default segment marker. + setshellmark(newseg, 1); ssbond(*searchsh, newseg); spivot(*searchsh, neighsh); if (neighsh.sh != NULL) { @@ -1710,21 +1496,20 @@ enum tetgenmesh::interresult if (dir == ACROSSEDGE) { // Edge [b, c] intersects with the segment. senext(*searchsh, flipshs[0]); - sspivot(flipshs[0], checkseg); - if (checkseg.sh != NULL) { + if (isshsubseg(flipshs[0])) { printf("Error: Invalid PLC.\n"); pb = sorg(flipshs[0]); pc = sdest(flipshs[0]); printf(" Two segments (%d, %d) and (%d, %d) intersect.\n", pointmark(startpt), pointmark(endpt), pointmark(pb), pointmark(pc)); - terminatetetgen(3); + terminatetetgen(this, 3); } // Flip edge [b, c], queue unflipped edges (for Delaunay checks). spivot(flipshs[0], flipshs[1]); assert(flipshs[1].sh != NULL); // SELF_CHECK if (sorg(flipshs[1]) != sdest(flipshs[0])) sesymself(flipshs[1]); flip22(flipshs, 1, 0); - // The flip may create an invered triangle, check it. + // The flip may create an inverted triangle, check it. pa = sapex(flipshs[1]); pb = sapex(flipshs[0]); pc = sorg(flipshs[0]); @@ -1735,16 +1520,8 @@ enum tetgenmesh::interresult ori_ca = orient3d(pd, pc, dummypoint, pa); //assert(ori_ab * ori_ca != 0); // SELF_CHECK if (ori_ab < 0) { - if (b->verbose > 2) { - printf(" Queue an inversed triangle (%d, %d, %d) %d\n", - pointmark(pc), pointmark(pd), pointmark(pb), pointmark(pa)); - } flipshpush(&(flipshs[0])); // push it to 'flipstack' } else if (ori_ca < 0) { - if (b->verbose > 2) { - printf(" Queue an inversed triangle (%d, %d, %d) %d\n", - pointmark(pd), pointmark(pc), pointmark(pa), pointmark(pb)); - } flipshpush(&(flipshs[1])); // // push it to 'flipstack' } // Set 'searchsh' s.t. its origin is 'startpt'. @@ -1766,7 +1543,6 @@ enum tetgenmesh::interresult void tetgenmesh::scarveholes(int holes, REAL* holelist) { face *parysh, searchsh, neighsh; - face checkseg; enum locateresult loc; int i, j; @@ -1789,8 +1565,7 @@ void tetgenmesh::scarveholes(int holes, REAL* holelist) } } else { // A hull side. Check if it is protected by a segment. - sspivot(searchsh, checkseg); - if (checkseg.sh == NULL) { + if (!isshsubseg(searchsh)) { // Not protected. Save this face. if (!sinfected(searchsh)) { sinfect(searchsh); @@ -1822,8 +1597,7 @@ void tetgenmesh::scarveholes(int holes, REAL* holelist) for (j = 0; j < 3; j++) { spivot(searchsh, neighsh); if (neighsh.sh != NULL) { - sspivot(searchsh, checkseg); - if (checkseg.sh == NULL) { + if (!isshsubseg(searchsh)) { if (!sinfected(neighsh)) { sinfect(neighsh); caveshbdlist->newindex((void **) &parysh); @@ -1855,6 +1629,9 @@ void tetgenmesh::scarveholes(int holes, REAL* holelist) // // // triangulate() Create a CDT for the facet. // // // +// All vertices of the triangulation have type FACETVERTEX. The actual type // +// of boundary vertices are set by the routine unifysements(). // +// // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, @@ -1863,13 +1640,9 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, face searchsh, newsh, *parysh; face newseg; point pa, pb, pc, *ppt, *cons; - enum locateresult loc; int iloc; int i, j; - int idx, fmarker; - REAL area; - if (b->verbose > 2) { printf(" f%d: %ld vertices, %ld segments", shmark, ptlist->objects, conlist->objects); @@ -1882,61 +1655,37 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, if (ptlist->objects < 2l) { // Not a segment or a facet. return; - } if (ptlist->objects == 2l) { + } + + if (ptlist->objects == 2l) { pa = * (point *) fastlookup(ptlist, 0); pb = * (point *) fastlookup(ptlist, 1); if (distance(pa, pb) > 0) { // It is a single segment. makeshellface(subsegs, &newseg); setshvertices(newseg, pa, pb, NULL); - // Set the actual segment marker. - if (in->facetmarkerlist != NULL) { - setshellmark(newseg, in->facetmarkerlist[shmark - 1]); - } + // Set the default segment marker '1'. + setshellmark(newseg, 1); } if (pointtype(pa) == VOLVERTEX) { - setpointtype(pa, RIDGEVERTEX); + setpointtype(pa, FACETVERTEX); } if (pointtype(pb) == VOLVERTEX) { - setpointtype(pb, RIDGEVERTEX); + setpointtype(pb, FACETVERTEX); } return; - } if (ptlist->objects == 3l) { - // The facet has only one triangle. + } + + + if (ptlist->objects == 3) { pa = * (point *) fastlookup(ptlist, 0); pb = * (point *) fastlookup(ptlist, 1); pc = * (point *) fastlookup(ptlist, 2); - if (triarea(pa, pb, pc) > 0) { - makeshellface(subfaces, &newsh); - setshvertices(newsh, pa, pb, pc); - setshellmark(newsh, shmark); - // Create three new segments. - for (i = 0; i < 3; i++) { - makeshellface(subsegs, &newseg); - setshvertices(newseg, sorg(newsh), sdest(newsh), NULL); - // Set the actual segment marker. - if (in->facetmarkerlist != NULL) { - setshellmark(newseg, in->facetmarkerlist[shmark - 1]); - } - ssbond(newsh, newseg); - senextself(newsh); - } - if (pointtype(pa) == VOLVERTEX) { - setpointtype(pa, FACETVERTEX); - } - if (pointtype(pb) == VOLVERTEX) { - setpointtype(pb, FACETVERTEX); - } - if (pointtype(pc) == VOLVERTEX) { - setpointtype(pc, FACETVERTEX); - } + } else { + // Calculate an above point of this facet. + if (!calculateabovepoint(ptlist, &pa, &pb, &pc)) { + return; // The point set is degenerate. } - return; - } - - // Calulcate an above point of this facet. - if (!calculateabovepoint(ptlist, &pa, &pb, &pc)) { - return; // The point set is degenerate. } // Create an initial triangulation. @@ -1957,6 +1706,8 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, // Are there area constraints? if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { + int idx, fmarker; + REAL area; idx = in->facetmarkerlist[shmark - 1]; // The actual facet marker. for (i = 0; i < in->numberoffacetconstraints; i++) { fmarker = (int) in->facetconstraintlist[i * 2]; @@ -1968,6 +1719,19 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, } } + if (ptlist->objects == 3) { + // The triangulation only has one element. + for (i = 0; i < 3; i++) { + makeshellface(subsegs, &newseg); + setshvertices(newseg, sorg(newsh), sdest(newsh), NULL); + // Set the default segment marker '1'. + setshellmark(newseg, 1); + ssbond(newsh, newseg); + senextself(newsh); + } + return; + } + // Incrementally build the triangulation. pinfect(pa); pinfect(pb); @@ -1977,9 +1741,8 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, if (!pinfected(*ppt)) { searchsh = recentsh; // Start from 'recentsh'. iloc = (int) OUTSIDE; - if (b->verbose > 2) printf(" # %d", i); - loc = (enum locateresult) sinsertvertex(*ppt, &searchsh, NULL, iloc, 1); - assert(loc != ONVERTEX); // SELF_CHECK + // Insert the vertex. Use Bowyer-Watson algo. Round the location. + iloc = sinsertvertex(*ppt, &searchsh, NULL, iloc, 1, 1); if (pointtype(*ppt) == VOLVERTEX) { setpointtype(*ppt, FACETVERTEX); } @@ -2001,8 +1764,26 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, for (i = 0; i < conlist->objects; i++) { cons = (point *) fastlookup(conlist, i); searchsh = recentsh; - loc = slocate(cons[0], &searchsh, 1, 1, 0); - assert(loc == ONVERTEX); // SELF_CHECK + iloc = (int) slocate(cons[0], &searchsh, 1, 1, 0); + if (iloc != (enum locateresult) ONVERTEX) { + // Not found due to roundoff errors. Do a brute-force search. + subfaces->traversalinit(); + searchsh.sh = shellfacetraverse(subfaces); + while (searchsh.sh != NULL) { + // Only search the subface in the same facet. + if (shellmark(searchsh) == shmark) { + if ((point) searchsh.sh[3] == cons[0]) { + searchsh.shver = 0; break; + } else if ((point) searchsh.sh[4] == cons[0]) { + searchsh.shver = 2; break; + } else if ((point) searchsh.sh[5] == cons[0]) { + searchsh.shver = 4; break; + } + } + searchsh.sh = shellfacetraverse(subfaces); + } + assert(searchsh.sh != NULL); + } // Recover the segment. Some edges may be flipped. sscoutsegment(&searchsh, cons[1]); if (flipstack != NULL) { @@ -2023,17 +1804,20 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, // If c = d, then f1 and f2 are identical. Otherwise, these two subfaces // // intersect, and the mesher is stopped. // // // -// If the two subfaces are indentical, we try to replace f2 by f1, i.e, all // +// If the two subfaces are identical, we try to replace f2 by f1, i.e, all // // neighbors of f2 are re-connected to f1. // // // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::unifysubfaces(face *f1, face *f2) { - face casout, casin, neighsh; - face sseg, checkseg; + if (b->psc) { + // In this case, it is possible that two subfaces are identical. + // While they must belong to two different surfaces. + return; + } + point pa, pb, pc, pd; - int i; pa = sorg(*f1); pb = sdest(*f1); @@ -2043,86 +1827,28 @@ void tetgenmesh::unifysubfaces(face *f1, face *f2) if (pc != pd) { printf("Found two facets intersect each other.\n"); printf(" 1st: [%d, %d, %d] #%d\n", - pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); + pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); printf(" 2nd: [%d, %d, %d] #%d\n", - pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); - terminatetetgen(3); + pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); + terminatetetgen(this, 3); } else { printf("Found two duplicated facets.\n"); printf(" 1st: [%d, %d, %d] #%d\n", - pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); + pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); printf(" 2nd: [%d, %d, %d] #%d\n", - pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); - terminatetetgen(3); + pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); + terminatetetgen(this, 3); } - // f1 and f2 are identical, replace f2 by f1. - if (!b->quiet) { - printf("Warning: Facet #%d is duplicated with Facet #%d. Removed!\n", - shellmark(*f2), shellmark(*f1)); - } - - // Make possible disconnections/reconnections at neighbors of f2. - for (i = 0; i < 3; i++) { - spivot(*f1, casout); - if (casout.sh == NULL) { - // f1 has no adjacent subfaces yet. - spivot(*f2, casout); - if (casout.sh != NULL) { - // Re-direct the adjacent connections of f2 to f1. - casin = casout; - spivot(casin, neighsh); - while (neighsh.sh != f2->sh) { - casin = neighsh; - spivot(casin, neighsh); - } - // Connect casout <= f1 <= casin. - sbond1(*f1, casout); - sbond1(casin, *f1); - } - } - sspivot(*f2, sseg); - if (sseg.sh != NULL) { - // f2 has a segment. It must be different to f1's. - // Disconnect bonds of subfaces to this segment. - spivot(*f2, casout); - if (casout.sh != NULL) { - casin = casout; - ssdissolve(casin); - spivot(casin, neighsh); - while (neighsh.sh != f2->sh) { - casin = neighsh; - ssdissolve(casin); - spivot(casin, neighsh); - } - } - // Delete the segment. - shellfacedealloc(subsegs, sseg.sh); - } - spivot(*f2, casout); - if (casout.sh != NULL) { - // Find the subface (casin) pointing to f2. - casin = casout; - spivot(casin, neighsh); - while (neighsh.sh != f2->sh) { - casin = neighsh; - spivot(casin, neighsh); - } - // Disconnect f2 <= casin. - sdissolve(casin); - } - senextself(*f1); - senextself(*f2); - } // i - - // Delete f2. - shellfacedealloc(subfaces, f2->sh); } /////////////////////////////////////////////////////////////////////////////// // // // unifysegments() Remove redundant segments and create face links. // // // +// After this routine, although segments are unique, but some of them may be // +// removed later by mergefacet(). All vertices still have type FACETVERTEX. // +// // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::unifysegments() @@ -2136,9 +1862,6 @@ void tetgenmesh::unifysegments() int *idx2faclist; int idx, k, m; - int e1, e2; - REAL len; - if (b->verbose > 1) { printf(" Unifying segments.\n"); } @@ -2146,6 +1869,79 @@ void tetgenmesh::unifysegments() // Create a mapping from vertices to subfaces. makepoint2submap(subfaces, idx2faclist, facperverlist); + if (b->psc) { + face sface1; + face seg, seg1; + int fmarker, fmarker1; + // First only connect subfaces which belong to the same surfaces. + subsegloop.shver = 0; + subsegs->traversalinit(); + subsegloop.sh = shellfacetraverse(subsegs); + while (subsegloop.sh != (shellface *) NULL) { + torg = sorg(subsegloop); + tdest = sdest(subsegloop); + + idx = pointmark(torg) - in->firstnumber; + for (k = idx2faclist[idx]; k < idx2faclist[idx + 1]; k++) { + sface = facperverlist[k]; + // The face may be deleted if it is a duplicated face. + if (sface.sh[3] == NULL) continue; + // Search the edge torg->tdest. + assert(sorg(sface) == torg); // SELF_CHECK + if (sdest(sface) != tdest) { + senext2self(sface); + sesymself(sface); + } + if (sdest(sface) != tdest) continue; + + sspivot(sface, seg); + if (seg.sh == NULL) continue; + // assert(seg.sh != NULL); It may or may not be subsegloop. + + // Find the adjacent subface on the same facet. + fmarker = in->facetmarkerlist[shellmark(sface) - 1]; + sface1.sh = NULL; + k++; + for (; k < idx2faclist[idx + 1]; k++) { + sface1 = facperverlist[k]; + // The face may be deleted if it is a duplicated face. + if (sface1.sh[3] == NULL) continue; + // Search the edge torg->tdest. + assert(sorg(sface1) == torg); // SELF_CHECK + if (sdest(sface1) != tdest) { + senext2self(sface1); + sesymself(sface1); + } + if (sdest(sface1) != tdest) continue; + // Found a subface sharing at the same edge. + fmarker1 = in->facetmarkerlist[shellmark(sface1) - 1]; + if (fmarker1 == fmarker) { + // Found a pair of adjacent subfaces. Connect them. + // Delete a redundent segment. + sspivot(sface1, seg1); + assert(seg1.sh != NULL); // SELF_CHECK + shellfacedealloc(subsegs, seg.sh); + shellfacedealloc(subsegs, seg1.sh); + ssdissolve(sface); + ssdissolve(sface1); + // Connect them. + sbond(sface, sface1); + // Set Steiner point -to- subface map. + if (pointtype(torg) == FREEFACETVERTEX) { + setpoint2sh(torg, sencode(sface)); + } + if (pointtype(tdest) == FREEFACETVERTEX) { + setpoint2sh(tdest, sencode(sface)); + } + break; + } + } + break; + } + subsegloop.sh = shellfacetraverse(subsegs); + } + } // if (b->psc) + subsegloop.shver = 0; subsegs->traversalinit(); subsegloop.sh = shellfacetraverse(subsegs); @@ -2248,7 +2044,7 @@ void tetgenmesh::unifysegments() // f is either codirection with f1 or is codirection with f2. facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); facenormal(torg, tdest, sapex(sface), n2, 1, NULL); - if (DOT(n1, n2) > 0) { + if (dot(n1, n2) > 0) { unifysubfaces(&(f1->ss), &sface); } else { unifysubfaces(&(f2->ss), &sface); @@ -2274,7 +2070,7 @@ void tetgenmesh::unifysegments() // f is coplanar with f1 (see Fig. 8). facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); facenormal(torg, tdest, sapex(sface), n2, 1, NULL); - if (DOT(n1, n2) > 0) { + if (dot(n1, n2) > 0) { // The two faces are codirectional as well. unifysubfaces(&(f1->ss), &sface); } @@ -2296,17 +2092,16 @@ void tetgenmesh::unifysegments() } } // for (k = idx2faclist[idx]; ...) - if (b->verbose > 2) { - printf(" Found %ld segments at (%d %d).\n", flippool->items, - pointmark(torg), pointmark(tdest)); + if (b->psc) { + // Set Steiner point -to- segment map. + if (pointtype(torg) == FREESEGVERTEX) { + setpoint2sh(torg, sencode(subsegloop)); + } + if (pointtype(tdest) == FREESEGVERTEX) { + setpoint2sh(tdest, sencode(subsegloop)); + } } - //if (b->nobisect || b->nomerge) { // -Y or -M - // Set the vertex types of the endpoints of the segment. - setpointtype(torg, RIDGEVERTEX); - setpointtype(tdest, RIDGEVERTEX); - //} - // Set the connection between this segment and faces containing it, // at the same time, remove redundant segments. f1 = facelink; @@ -2326,11 +2121,6 @@ void tetgenmesh::unifysegments() f1 = facelink; for (k = 1; k <= flippool->items; k++) { k < flippool->items ? f2 = f1->nextitem : f2 = facelink; - if (b->verbose > 3) { - printf(" Bond subfaces (%d, %d, %d) and (%d, %d, %d).\n", - pointmark(torg), pointmark(tdest), pointmark(sapex(f1->ss)), - pointmark(torg), pointmark(tdest), pointmark(sapex(f2->ss))); - } sbond1(f1->ss, f2->ss); f1 = f2; } @@ -2341,6 +2131,8 @@ void tetgenmesh::unifysegments() // Are there length constraints? if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + int e1, e2; + REAL len; for (k = 0; k < in->numberofsegmentconstraints; k++) { e1 = (int) in->segmentconstraintlist[k * 3]; e2 = (int) in->segmentconstraintlist[k * 3 + 1]; @@ -2417,11 +2209,6 @@ void tetgenmesh::mergefacets() ang = facedihedral(pa, pb, pc, pd); if (ang > PI) ang = (2 * PI - ang); if (ang > ang_tol) { - if (b->verbose > 2) { - printf(" Merge at segment (%d, %d)-(%d, %d) ang = %g\n", - pointmark(pa), pointmark(pb), pointmark(pc), - pointmark(pd), ang / PI * 180.0); - } remsegcount++; ssdissolve(parentsh); ssdissolve(neighsh); @@ -2441,7 +2228,6 @@ void tetgenmesh::mergefacets() lawsonflip(); // Recover Delaunayness. } - if (b->verbose > 1) { printf(" %d segments are removed.\n", remsegcount); } @@ -2466,7 +2252,7 @@ void tetgenmesh::identifypscedges(point *idx2verlist) int* idx2shlist; face searchsh, neighsh; face segloop, checkseg, newseg; - point checkpt, pa, pb; + point checkpt, pa = NULL, pb = NULL; int *endpts; int edgemarker; int idx, i, j; @@ -2478,8 +2264,18 @@ void tetgenmesh::identifypscedges(point *idx2verlist) printf("Inserting edges ...\n"); } - // All identified segments have the initial marker '0'. - // All segments inserted here should have a non-zero marker. + // All identified segments have the initial marker '1'. + // All segments inserted here should have a marker 'k >= 0'. + + if (b->psc) { + // First mark all segments of the mesh with a marker '-1'. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + setshellmark(segloop, -1); + segloop.sh = shellfacetraverse(subsegs); + } + } // Construct a map from points to subfaces. makepoint2submap(subfaces, idx2shlist, shperverlist); @@ -2487,6 +2283,8 @@ void tetgenmesh::identifypscedges(point *idx2verlist) // Process the set of PSC edges. for (i = 0; i < in->numberofedges; i++) { endpts = &(in->edgelist[(i << 1)]); + edgemarker = in->edgemarkerlist ? in->edgemarkerlist[i] : 0; + // Find a face contains the edge. newseg.sh = NULL; searchsh.sh = NULL; @@ -2505,43 +2303,31 @@ void tetgenmesh::identifypscedges(point *idx2verlist) } } } // j - edgemarker = 0; - if (in->edgemarkerlist) { - edgemarker = in->edgemarkerlist[i]; - } - if (edgemarker == 0) { - edgemarker = 1; - } - // We should find a subface having this edge. + if (searchsh.sh != NULL) { // Check if this edge is already a segment of the mesh. sspivot(searchsh, checkseg); if (checkseg.sh != NULL) { - // There should be no duplicated edges. - assert(shellmark(checkseg) == 0); - setshellmark(checkseg, edgemarker); + // This segment already exist. + newseg = checkseg; } else { // Create a new segment at this edge. pa = sorg(searchsh); pb = sdest(searchsh); - if (b->verbose > 2) { - printf(" Create a new segment (%d, %d).\n", - pointmark(pa), pointmark(pb)); - } makeshellface(subsegs, &newseg); setshvertices(newseg, pa, pb, NULL); - setshellmark(newseg, edgemarker); ssbond(searchsh, newseg); spivot(searchsh, neighsh); if (neighsh.sh != NULL) { ssbond(neighsh, newseg); - // There should be only two subfaces at this segment. - spivotself(neighsh); // SELF_CHECK - assert(neighsh.sh == searchsh.sh); } - if (!b->psc) { - setpointtype(pa, RIDGEVERTEX); - setpointtype(pb, RIDGEVERTEX); + if (b->psc) { + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(newseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(newseg)); + } } } } else { @@ -2549,64 +2335,91 @@ void tetgenmesh::identifypscedges(point *idx2verlist) // Get the two endpoints of this segment. pa = idx2verlist[endpts[0]]; pb = idx2verlist[endpts[1]]; - if (b->verbose > 2) { - printf(" Create a new segment (%d, %d) - dangling.\n", - pointmark(pa), pointmark(pb)); + // Check if segment [a,b] already exists. + // TODO: Change the brute-force search. Slow! + point *ppt; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + ppt = (point *) &(segloop.sh[3]); + if (((ppt[0] == pa) && (ppt[1] == pb)) || + ((ppt[0] == pb) && (ppt[1] == pa))) { + // Found! + newseg = segloop; + break; + } + segloop.sh = shellfacetraverse(subsegs); + } + if (newseg.sh == NULL) { + makeshellface(subsegs, &newseg); + setshvertices(newseg, pa, pb, NULL); + if (b->psc) { + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(newseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(newseg)); + } + } } - makeshellface(subsegs, &newseg); - setshvertices(newseg, pa, pb, NULL); - setshellmark(newseg, edgemarker); - //if (!b->psc) { - setpointtype(pa, RIDGEVERTEX); - setpointtype(pb, RIDGEVERTEX); - //} } - if (newseg.sh != NULL) { - if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { - for (i = 0; i < in->numberofsegmentconstraints; i++) { - e1 = (int) in->segmentconstraintlist[i * 3]; - e2 = (int) in->segmentconstraintlist[i * 3 + 1]; - if (((pointmark(pa) == e1) && (pointmark(pb) == e2)) || - ((pointmark(pa) == e2) && (pointmark(pb) == e1))) { - len = in->segmentconstraintlist[i * 3 + 2]; - setareabound(newseg, len); - break; - } + setshellmark(newseg, edgemarker); + + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + for (i = 0; i < in->numberofsegmentconstraints; i++) { + e1 = (int) in->segmentconstraintlist[i * 3]; + e2 = (int) in->segmentconstraintlist[i * 3 + 1]; + if (((pointmark(pa) == e1) && (pointmark(pb) == e2)) || + ((pointmark(pa) == e2) && (pointmark(pb) == e1))) { + len = in->segmentconstraintlist[i * 3 + 2]; + setareabound(newseg, len); + break; } } } } // i + + delete [] shperverlist; + delete [] idx2shlist; + if (b->psc) { - // Delete all segments of the mesh with a marker '0'. + // Removing all segments with a marker '-1'. subsegs->traversalinit(); segloop.sh = shellfacetraverse(subsegs); while (segloop.sh != NULL) { - if (shellmark(segloop) == 0) { - if (b->verbose > 2) { - printf(" Remove a segment (%d, %d).\n", - pointmark(sorg(segloop)), pointmark(sdest(segloop))); - } - spivot(segloop, searchsh); - if (searchsh.sh != NULL) { - ssdissolve(searchsh); - spivot(searchsh, neighsh); - if (neighsh.sh != NULL) { - ssdissolve(neighsh); - // There should be only two subfaces at this segment. - spivotself(neighsh); // SELF_CHECK - assert(neighsh.sh == searchsh.sh); - } - } + if (shellmark(segloop) == -1) { shellfacedealloc(subsegs, segloop.sh); } segloop.sh = shellfacetraverse(subsegs); } - } + + // Connecting subsegments at Steiner points. + face seg1, seg2; + // Re-use 'idx2shlist' and 'shperverlist'. + makepoint2submap(subsegs, idx2shlist, shperverlist); - delete [] shperverlist; - delete [] idx2shlist; + points->traversalinit(); + pa = pointtraverse(); + while (pa != NULL) { + if (pointtype(pa) == FREESEGVERTEX) { + idx = pointmark(pa) - in->firstnumber; + // There must be only two segments containing this vertex. + assert((idx2shlist[idx + 1] - idx2shlist[idx]) == 2); + i = idx2shlist[idx]; + seg1 = shperverlist[i]; + seg2 = shperverlist[i+1]; + senextself(seg1); + senextself(seg2); + sbond(seg1, seg2); + } + pa = pointtraverse(); + } + + delete [] shperverlist; + delete [] idx2shlist; + } } /////////////////////////////////////////////////////////////////////////////// @@ -2749,16 +2562,27 @@ void tetgenmesh::meshsurface() if (!b->diagnose) { // Remove redundant segments and build the face links. unifysegments(); - } - - if (!b->nomerge && !b->nobisect && !b->diagnose) { - // Merge adjacent coplanar facets. - mergefacets(); - } - - if (in->numberofedges > 0) { // if (b->psc) - // There are segments specified by the user. Read and create them. - identifypscedges(idx2verlist); + if (!b->psc && !b->nomergefacet && !b->nobisect) { + // Merge adjacent coplanar facets. + mergefacets(); + } + if (in->numberofedges > 0) { // if (b->psc) + // There are segments specified by the user. Read and create them. + identifypscedges(idx2verlist); + } + if (!b->psc) { + // Mark all segment vertices to be RIDGEVERTEX. + face segloop; + point *ppt; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + ppt = (point *) &(segloop.sh[3]); + setpointtype(ppt[0], RIDGEVERTEX); + setpointtype(ppt[1], RIDGEVERTEX); + segloop.sh = shellfacetraverse(subsegs); + } + } } if (b->object == tetgenbehavior::STL) { @@ -2784,7 +2608,7 @@ void tetgenmesh::meshsurface() // interecursive() Recursively do intersection test on a set of triangles.// // // // Recursively split the set 'subfacearray' of subfaces into two sets using // -// a cut plane parallel to x-, or, y-, or z-axies. The split criteria are // +// a cut plane parallel to x-, or, y-, or z-axis. The split criteria are // // follows. Assume the cut plane is H, and H+ denotes the left halfspace of // // H, and H- denotes the right halfspace of H; and s be a subface: // // // @@ -2826,11 +2650,11 @@ void tetgenmesh::interecursive(shellface** subfacearray, int arraysize, leftarray = new shellface*[arraysize]; if (leftarray == NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } rightarray = new shellface*[arraysize]; if (rightarray == NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } leftsize = rightsize = 0; @@ -2997,7 +2821,7 @@ void tetgenmesh::detectinterfaces() internum = 0; // Recursively split the set of triangles into two sets using a cut plane - // parallel to x-, or, y-, or z-axies. Stop splitting when the number + // parallel to x-, or, y-, or z-axis. Stop splitting when the number // of subfaces is not decreasing anymore. Do tests on the current set. interecursive(subfacearray, subfaces->items, 0, xmin, xmax, ymin, ymax, zmin, zmax, &internum); diff --git a/external/tetgen/tetgen.cxx b/external/tetgen/tetgen.cxx index 305d61b60..da4ef3d73 100644 --- a/external/tetgen/tetgen.cxx +++ b/external/tetgen/tetgen.cxx @@ -2,17 +2,10 @@ // // // TetGen // // // -// A Quality Tetrahedral Mesh Generator and 3D Delaunay Triangulator // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // // // // Version 1.5 // -// October 06, 2012 // -// // -// Copyright (C) 2002--2012 // -// Hang Si // -// Research Group: Numerical Mathematics and Scientific Computing // -// Weierstrass Institute for Applied Analysis and Stochastics (WIAS) // -// Mohrenstr. 39, 10117 Berlin, Germany // -// Hang.Si@wias-berlin.de // +// November 4, 2013 // // // // TetGen is freely available through the website: http://www.tetgen.org. // // It may be copied, modified, and redistributed for non-commercial use. // @@ -54,24 +47,24 @@ bool tetgenio::load_node_call(FILE* infile, int markers, int uvflag, // Initialize 'pointlist', 'pointattributelist', and 'pointmarkerlist'. pointlist = new REAL[numberofpoints * 3]; if (pointlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } if (numberofpointattributes > 0) { pointattributelist = new REAL[numberofpoints * numberofpointattributes]; if (pointattributelist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } if (markers) { pointmarkerlist = new int[numberofpoints]; if (pointmarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } if (uvflag) { pointparamlist = new pointparam[numberofpoints]; if (pointparamlist == NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } @@ -219,11 +212,11 @@ bool tetgenio::load_node(char* filebasename) mesh_dim = 3; numberofpointattributes = 0; // no point attribute. markers = 0; // no boundary marker. - uvflag = 0; // no uv parameters (reuqired by a PSC). + uvflag = 0; // no uv parameters (required by a PSC). // Read the first line of the file. stringptr = readnumberline(inputline, infile, innodefilename); - // Does this file contain an index colume? + // Does this file contain an index column? stringptr = strstr(inputline, "rbox"); if (stringptr == NULL) { // Read number of points, number of dimensions, number of point @@ -298,7 +291,7 @@ bool tetgenio::load_edge(char* filebasename) if (numberofedges > 0) { edgelist = new int[numberofedges * 2]; if (edgelist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } stringptr = findnextnumber(stringptr); if (*stringptr == '\0') { @@ -321,13 +314,13 @@ bool tetgenio::load_edge(char* filebasename) if (*stringptr == '\0') { printf("Error: Edge %d is missing vertex %d in %s.\n", i + firstnumber, j + 1, inedgefilename); - terminatetetgen(1); + terminatetetgen(NULL, 1); } corner = (int) strtol(stringptr, &stringptr, 0); if (corner < firstnumber || corner >= numberofpoints + firstnumber) { printf("Error: Edge %d has an invalid vertex index.\n", i + firstnumber); - terminatetetgen(1); + terminatetetgen(NULL, 1); } edgelist[index++] = corner; } @@ -389,12 +382,12 @@ bool tetgenio::load_face(char* filebasename) if (numberoftrifaces > 0) { trifacelist = new int[numberoftrifaces * 3]; if (trifacelist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } if (markers) { trifacemarkerlist = new int[numberoftrifaces]; if (trifacemarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } } @@ -409,13 +402,13 @@ bool tetgenio::load_face(char* filebasename) if (*stringptr == '\0') { printf("Error: Face %d is missing vertex %d in %s.\n", i + firstnumber, j + 1, infilename); - terminatetetgen(1); + terminatetetgen(NULL, 1); } corner = (int) strtol(stringptr, &stringptr, 0); if (corner < firstnumber || corner >= numberofpoints + firstnumber) { printf("Error: Face %d has an invalid vertex index.\n", i + firstnumber); - terminatetetgen(1); + terminatetetgen(NULL, 1); } trifacelist[index++] = corner; } @@ -500,14 +493,14 @@ bool tetgenio::load_tet(char* filebasename) // Allocate memory for tetrahedra. tetrahedronlist = new int[numberoftetrahedra * numberofcorners]; if (tetrahedronlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } // Allocate memory for output tetrahedron attributes if necessary. if (numberoftetrahedronattributes > 0) { tetrahedronattributelist = new REAL[numberoftetrahedra * numberoftetrahedronattributes]; if (tetrahedronattributelist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } } @@ -522,13 +515,13 @@ bool tetgenio::load_tet(char* filebasename) if (*stringptr == '\0') { printf("Error: Tetrahedron %d is missing vertex %d in %s.\n", i + firstnumber, j + 1, infilename); - terminatetetgen(1); + terminatetetgen(NULL, 1); } corner = (int) strtol(stringptr, &stringptr, 0); if (corner < firstnumber || corner >= numberofpoints + firstnumber) { printf("Error: Tetrahedron %d has an invalid vertex index.\n", i + firstnumber); - terminatetetgen(1); + terminatetetgen(NULL, 1); } tetrahedronlist[index++] = corner; } @@ -590,7 +583,7 @@ bool tetgenio::load_vol(char* filebasename) tetrahedronvolumelist = new REAL[volelements]; if (tetrahedronvolumelist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } // Read the list of volume constraints. @@ -769,7 +762,7 @@ bool tetgenio::load_mtr(char* filebasename) // Allocate space for pointmtrlist. pointmtrlist = new REAL[numberofpoints * numberofpointmtrs]; if (pointmtrlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } mtrindex = 0; for (i = 0; i < numberofpoints; i++) { @@ -779,7 +772,7 @@ bool tetgenio::load_mtr(char* filebasename) if (*stringptr == '\0') { printf("Error: Metric %d is missing value #%d in %s.\n", i + firstnumber, j + 1, mtrfilename); - terminatetetgen(1); + terminatetetgen(NULL, 1); } mtr = (REAL) strtod(stringptr, &stringptr); pointmtrlist[mtrindex++] = mtr; @@ -836,10 +829,10 @@ bool tetgenio::load_poly(char* filebasename) } // Initialize the default values. - mesh_dim = 3; // Three-dimemsional accoordinates. + mesh_dim = 3; // Three-dimensional coordinates. numberofpointattributes = 0; // no point attribute. markers = 0; // no boundary marker. - uvflag = 0; // no uv parameters (reuqired by a PSC). + uvflag = 0; // no uv parameters (required by a PSC). // Read number of points, number of dimensions, number of point // attributes, and number of boundary markers. @@ -2090,7 +2083,7 @@ bool tetgenio::load_medit(char* filebasename, int istetmesh) // // // load_vtk() Load VTK surface mesh from file (.vtk ascii or binary). // // // -// This function is contributed by: Bryn Lloyd, Computer Vision Laborator, // +// This function is contributed by: Bryn Lloyd, Computer Vision Laboratory, // // ETH, Zuerich. May 7, 2007. // // // /////////////////////////////////////////////////////////////////////////////// @@ -2903,20 +2896,21 @@ char* tetgenio::findnextnumber(char *string) void tetgenbehavior::syntax() { - printf(" tetgen [-pYq_Aa_mriO_S_T_XMwcdzfenvgKJBNEFICQVh] input_file\n"); + printf(" tetgen [-pYrq_Aa_miO_S_T_XMwcdzfenvgkJBNEFICQVh] input_file\n"); printf(" -p Tetrahedralizes a piecewise linear complex (PLC).\n"); printf(" -Y Preserves the input surface mesh (does not modify it).\n"); + printf(" -r Reconstructs a previously generated mesh.\n"); printf(" -q Refines mesh (to improve mesh quality).\n"); + printf(" -R Mesh coarsening (to reduce the mesh elements).\n"); printf(" -A Assigns attributes to tetrahedra in different regions.\n"); printf(" -a Applies a maximum tetrahedron volume constraint.\n"); printf(" -m Applies a mesh sizing function.\n"); - printf(" -r Reconstructs a previously generated mesh.\n"); printf(" -i Inserts a list of additional points.\n"); printf(" -O Specifies the level of mesh optimization.\n"); printf(" -S Specifies maximum number of added points.\n"); printf(" -T Sets a tolerance for coplanar test (default 1e-8).\n"); printf(" -X Suppresses use of exact arithmetic.\n"); - printf(" -M No merge of coplanar facets.\n"); + printf(" -M No merge of coplanar facets or very close vertices.\n"); printf(" -w Generates weighted Delaunay (regular) triangulation.\n"); printf(" -c Retains the convex hull of the PLC.\n"); printf(" -d Detects self-intersections of facets of the PLC.\n"); @@ -2926,12 +2920,12 @@ void tetgenbehavior::syntax() printf(" -n Outputs tetrahedra neighbors to .neigh file.\n"); printf(" -v Outputs Voronoi diagram to files.\n"); printf(" -g Outputs mesh to .mesh file for viewing by Medit.\n"); - printf(" -K Outputs mesh to .vtk file for viewing by Paraview.\n"); + printf(" -k Outputs mesh to .vtk file for viewing by Paraview.\n"); printf(" -J No jettison of unused vertices from output .node file.\n"); printf(" -B Suppresses output of boundary information.\n"); printf(" -N Suppresses output of .node file.\n"); printf(" -E Suppresses output of .ele file.\n"); - printf(" -F Suppresses output of .face file.\n"); + printf(" -F Suppresses output of .face and .edge file.\n"); printf(" -I Suppresses mesh iteration numbers.\n"); printf(" -C Checks the consistency of the final mesh.\n"); printf(" -Q Quiet: No terminal output except errors.\n"); @@ -2950,20 +2944,13 @@ void tetgenbehavior::usage() printf("TetGen\n"); printf("A Quality Tetrahedral Mesh Generator and 3D Delaunay "); printf("Triangulator\n"); - printf("Version 1.5 (February 21, 2012).\n"); - printf("\n"); - printf("Copyright (C) 2002 - 2012\n"); - printf("Hang Si\n"); - printf("Mohrenstr. 39, 10117 Berlin, Germany\n"); - printf("Hang.Si@wias-berlin.de\n"); + printf("Version 1.5\n"); + printf("November 4, 2013\n"); printf("\n"); printf("What Can TetGen Do?\n"); printf("\n"); - printf(" TetGen generates exact Delaunay tetrahedralizations, exact\n"); - printf(" constrained Delaunay tetrahedralizations, and quality "); - printf("tetrahedral\n meshes. The latter are nicely graded and whose "); - printf("tetrahedra have\n radius-edge ratio bounded, thus are suitable "); - printf("for finite element and\n finite volume analysis.\n"); + printf(" TetGen generates Delaunay tetrahedralizations, constrained\n"); + printf(" Delaunay tetrahedralizations, and quality tetrahedral meshes.\n"); printf("\n"); printf("Command Line Syntax:\n"); printf("\n"); @@ -2984,22 +2971,24 @@ void tetgenbehavior::usage() printf("Examples of How to Use TetGen:\n"); printf("\n"); printf(" \'tetgen object\' reads vertices from object.node, and writes "); - printf("their\n Delaunay tetrahedralization to object.1.node and "); - printf("object.1.ele.\n"); + printf("their\n Delaunay tetrahedralization to object.1.node, "); + printf("object.1.ele\n (tetrahedra), and object.1.face"); + printf(" (convex hull faces).\n"); printf("\n"); printf(" \'tetgen -p object\' reads a PLC from object.poly or object."); printf("smesh (and\n possibly object.node) and writes its constrained "); - printf("Delaunay\n tetrahedralization to object.1.node, object.1.ele and "); - printf("object.1.face.\n"); + printf("Delaunay\n tetrahedralization to object.1.node, object.1.ele, "); + printf("object.1.face,\n"); + printf(" (boundary faces) and object.1.edge (boundary edges).\n"); printf("\n"); printf(" \'tetgen -pq1.414a.1 object\' reads a PLC from object.poly or\n"); printf(" object.smesh (and possibly object.node), generates a mesh "); printf("whose\n tetrahedra have radius-edge ratio smaller than 1.414 and "); printf("have volume\n of 0.1 or less, and writes the mesh to "); - printf("object.1.node, object.1.ele\n and object.1.face.\n"); + printf("object.1.node, object.1.ele,\n object.1.face, and object.1.edge\n"); printf("\n"); printf("Please send bugs/comments to Hang Si \n"); - terminatetetgen(0); + terminatetetgen(NULL, 0); } /////////////////////////////////////////////////////////////////////////////// @@ -3068,6 +3057,13 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) nobisect_param = (argv[i][j + 1] - '0'); j++; } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + addsteiner_algo = (argv[i][j + 1] - '0'); + j++; + } + } } else if (argv[i][j] == 'r') { refine = 1; } else if (argv[i][j] == 'q') { @@ -3114,6 +3110,27 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) optmaxdihedral = (REAL) strtod(workstring, (char **) NULL); } } + } else if (argv[i][j] == 'R') { + coarsen = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { + coarsen_param = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + coarsen_percent = (REAL) strtod(workstring, (char **) NULL); + } + } } else if (argv[i][j] == 'w') { weighted = 1; if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) { @@ -3121,10 +3138,71 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) j++; } } else if (argv[i][j] == 'b') { + // -b(brio_threshold/brio_ratio/hilbert_limit/hilbert_order) brio_hilbert = 1; - if (argv[i][j + 1] == '0') { // -b0 - brio_hilbert = 0; // Turn off BRIO sorting. + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + brio_threshold = (int) strtol(workstring, (char **) &workstring, 0); + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + brio_ratio = (REAL) strtod(workstring, (char **) NULL); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + hilbert_limit = (int) strtol(workstring, (char **) &workstring, 0); + } + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == '-')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + hilbert_order = (REAL) strtod(workstring, (char **) NULL); + } + } + if (brio_threshold == 0) { // -b0 + brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. + } + if (brio_ratio >= 1.0) { // -b/1 + no_sort = 1; + brio_hilbert = 0; // Turn off BRIO-Hilbert sorting. } } else if (argv[i][j] == 'l') { incrflip = 1; @@ -3164,7 +3242,19 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) } else if (argv[i][j] == 'c') { convex = 1; } else if (argv[i][j] == 'M') { - nomerge = 1; + nomergefacet = 1; + nomergevertex = 1; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { + nomergefacet = (argv[i][j + 1] - '0'); + j++; + } + if ((argv[i][j + 1] == '/') || (argv[i][j + 1] == ',')) { + j++; + if ((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '1')) { + nomergevertex = (argv[i][j + 1] - '0'); + j++; + } + } } else if (argv[i][j] == 'X') { if (argv[i][j + 1] == '1') { nostaticfilter = 1; @@ -3266,6 +3356,26 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) quiet = 1; } else if (argv[i][j] == 'V') { verbose++; + } else if (argv[i][j] == 'x') { + if (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.')) { + k = 0; + while (((argv[i][j + 1] >= '0') && (argv[i][j + 1] <= '9')) || + (argv[i][j + 1] == '.') || (argv[i][j + 1] == 'e') || + (argv[i][j + 1] == '-') || (argv[i][j + 1] == '+')) { + j++; + workstring[k] = argv[i][j]; + k++; + } + workstring[k] = '\0'; + tetrahedraperblock = (int) strtol(workstring, (char **) NULL, 0); + if (tetrahedraperblock > 8188) { + vertexperblock = tetrahedraperblock / 2; + shellfaceperblock = vertexperblock / 2; + } else { + tetrahedraperblock = 8188; + } + } } else if ((argv[i][j] == 'h') || (argv[i][j] == 'H') || (argv[i][j] == '?')) { usage(); @@ -3282,7 +3392,7 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) if (infilename[0] == '\0') { // No input file name. Print the syntax and exit. syntax(); - terminatetetgen(0); + terminatetetgen(NULL, 0); } // Recognize the object from file extension if it is available. if (!strcmp(&infilename[strlen(infilename) - 5], ".node")) { @@ -3332,36 +3442,41 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) if (diagnose && !plc) { // -d plc = 1; } - if (plc && !quality && !nobisect) { // -p only - // Create a CDT, do not do mesh optimization. + if (refine && !quality) { // -r only + // Reconstruct a mesh, no mesh optimization. optlevel = 0; } + if (insertaddpoints && (optlevel == 0)) { // with -i option + optlevel = 2; + } + if (coarsen && (optlevel == 0)) { // with -R option + optlevel = 2; + } // Detect improper combinations of switches. - if (plc && refine) { - printf("Error: Switch -r cannot use together with -p.\n"); - return false; - } - if (refine && (plc || noiterationnum)) { - printf("Error: Switches %s cannot use together with -r.\n", - "-p, -d, and -I"); - return false; - } if ((refine || plc) && weighted) { printf("Error: Switches -w cannot use together with -p or -r.\n"); return false; } - // Be careful not to allocate space for element area constraints that - // will never be assigned any value (other than the default -1.0). - if (!refine && !plc) { - varvolume = 0; + if (convex) { // -c + if (plc && !regionattrib) { + // -A (region attribute) is needed for marking exterior tets (-1). + regionattrib = 1; + } } + + // Note: -A must not used together with -r option. // Be careful not to add an extra attribute to each element unless the // input supports it (PLC in, but not refining a preexisting mesh). if (refine || !plc) { regionattrib = 0; } + // Be careful not to allocate space for element area constraints that + // will never be assigned any value (other than the default -1.0). + if (!refine && !plc) { + varvolume = 0; + } // If '-a' or '-aa' is in use, enable '-q' option too. if (fixedvolume || varvolume) { if (quality == 0) { @@ -3374,7 +3489,11 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) // No user-specified dihedral angle bound. Use default ones. if (!quality) { if (optmaxdihedral < 179.0) { - optmaxdihedral = 179.0; + if (nobisect) { // with -Y option + optmaxdihedral = 179.0; + } else { // -p only + optmaxdihedral = 179.999; + } } if (optminsmtdihed < 179.999) { optminsmtdihed = 179.999; @@ -3436,16 +3555,25 @@ bool tetgenbehavior::parse_commandline(int argc, char **argv) // Initialize fast lookup tables for mesh maniplulation primitives. -int tetgenmesh::mod12[36] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; -int tetgenmesh::mod6[18] = {0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, - 0, 1, 2, 3, 4, 5}; +int tetgenmesh::bondtbl[12][12] = {{0,},}; +int tetgenmesh::enexttbl[12] = {0,}; +int tetgenmesh::eprevtbl[12] = {0,}; +int tetgenmesh::enextesymtbl[12] = {0,}; +int tetgenmesh::eprevesymtbl[12] = {0,}; +int tetgenmesh::eorgoppotbl[12] = {0,}; +int tetgenmesh::edestoppotbl[12] = {0,}; +int tetgenmesh::fsymtbl[12][12] = {{0,},}; +int tetgenmesh::facepivot1[12] = {0,}; +int tetgenmesh::facepivot2[12][12] = {{0,},}; +int tetgenmesh::tsbondtbl[12][6] = {{0,},}; +int tetgenmesh::stbondtbl[12][6] = {{0,},}; +int tetgenmesh::tspivottbl[12][6] = {{0,},}; +int tetgenmesh::stpivottbl[12][6] = {{0,},}; -// Table 'edgepivot' takes an directed edge (version) as input, returns the +// Table 'esymtbl' takes an directed edge (version) as input, returns the // inversed edge (version) of it. -int tetgenmesh::edgepivot[12] = {9, 6, 11, 4, 3, 7, 1, 5, 10, 0, 8, 2}; +int tetgenmesh::esymtbl[12] = {9, 6, 11, 4, 3, 7, 1, 5, 10, 0, 8, 2}; // The following four tables give the 12 permutations of the set {0,1,2,3}. // An offset 4 is added to each element for a direct access of the points @@ -3462,6 +3590,11 @@ int tetgenmesh::oppopivot[12] = {4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7}; int tetgenmesh::ver2edge[12] = {0, 1, 2, 3, 3, 5, 1, 5, 4, 0, 4, 2}; int tetgenmesh::edge2ver[ 6] = {0, 1, 2, 3, 8, 5}; +// Edge versions whose apex or opposite may be dummypoint. + +int tetgenmesh::epivot[12] = {4, 5, 2, 11, 4, 5, 2, 11, 4, 5, 2, 11}; + + // Table 'snextpivot' takes an edge version as input, returns the next edge // version in the same edge ring. @@ -3475,9 +3608,91 @@ int tetgenmesh::sorgpivot [6] = {3, 4, 4, 5, 5, 3}; int tetgenmesh::sdestpivot[6] = {4, 3, 5, 4, 3, 5}; int tetgenmesh::sapexpivot[6] = {5, 5, 3, 3, 4, 4}; -// Edge versions whose apex or opposite may be dummypoint. +/////////////////////////////////////////////////////////////////////////////// +// // +// inittable() Initialize the look-up tables. // +// // +/////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::epivot[4] = {4, 5, 2, 11}; +void tetgenmesh::inittables() +{ + int i, j; + + + // i = t1.ver; j = t2.ver; + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + bondtbl[i][j] = (j & 3) + (((i & 12) + (j & 12)) % 12); + } + } + + + // i = t1.ver; j = t2.ver + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + fsymtbl[i][j] = (j + 12 - (i & 12)) % 12; + } + } + + + for (i = 0; i < 12; i++) { + facepivot1[i] = (esymtbl[i] & 3); + } + + for (i = 0; i < 12; i++) { + for (j = 0; j < 12; j++) { + facepivot2[i][j] = fsymtbl[esymtbl[i]][j]; + } + } + + for (i = 0; i < 12; i++) { + enexttbl[i] = (i + 4) % 12; + eprevtbl[i] = (i + 8) % 12; + } + + for (i = 0; i < 12; i++) { + enextesymtbl[i] = esymtbl[enexttbl[i]]; + eprevesymtbl[i] = esymtbl[eprevtbl[i]]; + } + + for (i = 0; i < 12; i++) { + eorgoppotbl [i] = eprevtbl[esymtbl[enexttbl[i]]]; + edestoppotbl[i] = enexttbl[esymtbl[eprevtbl[i]]]; + } + + int soffset, toffset; + + // i = t.ver, j = s.shver + for (i = 0; i < 12; i++) { + for (j = 0; j < 6; j++) { + if ((j & 1) == 0) { + soffset = (6 - ((i & 12) >> 1)) % 6; + toffset = (12 - ((j & 6) << 1)) % 12; + } else { + soffset = (i & 12) >> 1; + toffset = (j & 6) << 1; + } + tsbondtbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); + stbondtbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); + } + } + + + // i = t.ver, j = s.shver + for (i = 0; i < 12; i++) { + for (j = 0; j < 6; j++) { + if ((j & 1) == 0) { + soffset = (i & 12) >> 1; + toffset = (j & 6) << 1; + } else { + soffset = (6 - ((i & 12) >> 1)) % 6; + toffset = (12 - ((j & 6) << 1)) % 12; + } + tspivottbl[i][j] = (j & 1) + (((j & 6) + soffset) % 6); + stpivottbl[i][j] = (i & 3) + (((i & 12) + toffset) % 12); + } + } +} /////////////////////////////////////////////////////////////////////////////// // // @@ -3511,6 +3726,7 @@ void tetgenmesh::arraypool::poolinit(int sizeofobject, int log2objperblk) log2objectsperblock = log2objperblk; // Compute the number of objects in each block. objectsperblock = ((int) 1) << log2objectsperblock; + objectsperblockmark = objectsperblock - 1; // No memory has been allocated. totalmemory = 0l; @@ -3668,26 +3884,22 @@ void* tetgenmesh::arraypool::lookup(int objectindex) // // // newindex() Allocate space for a fresh object from the pool. // // // +// 'newptr' returns a pointer to the new object (it must not be a NULL). // +// // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::arraypool::newindex(void **newptr) { - void *newobject; - int newindex; - // Allocate an object at index 'firstvirgin'. - newindex = objects; - newobject = (void *) (getblock(objects) + + int newindex = objects; + *newptr = (void *) (getblock(objects) + (objects & (objectsperblock - 1)) * objectbytes); objects++; - // If 'newptr' is not NULL, use it to return a pointer to the object. - if (newptr != (void **) NULL) { - *newptr = newobject; - } return newindex; } + /////////////////////////////////////////////////////////////////////////////// // // // memorypool() The constructors of memorypool. // @@ -3701,7 +3913,6 @@ tetgenmesh::memorypool::memorypool() deaditemstack = (void *) NULL; pathblock = (void **) NULL; pathitem = (void *) NULL; - itemwordtype = POINTER; alignbytes = 0; itembytes = itemwords = 0; itemsperblock = 0; @@ -3710,10 +3921,10 @@ tetgenmesh::memorypool::memorypool() pathitemsleft = 0; } -tetgenmesh::memorypool:: -memorypool(int bytecount, int itemcount, enum wordtype wtype, int alignment) +tetgenmesh::memorypool::memorypool(int bytecount, int itemcount, int wsize, + int alignment) { - poolinit(bytecount, itemcount, wtype, alignment); + poolinit(bytecount, itemcount, wsize, alignment); } /////////////////////////////////////////////////////////////////////////////// @@ -3747,14 +3958,9 @@ tetgenmesh::memorypool::~memorypool() // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::memorypool:: -poolinit(int bytecount, int itemcount, enum wordtype wtype, int alignment) +void tetgenmesh::memorypool::poolinit(int bytecount,int itemcount,int wordsize, + int alignment) { - int wordsize; - - // Initialize values in the pool. - itemwordtype = wtype; - wordsize = (itemwordtype == POINTER) ? sizeof(void *) : sizeof(REAL); // Find the proper alignment, which must be at least as large as: // - The parameter `alignment'. // - The primary word type, to avoid unaligned accesses. @@ -3779,7 +3985,7 @@ poolinit(int bytecount, int itemcount, enum wordtype wtype, int alignment) firstblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + alignbytes); if (firstblock == (void **) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } // Set the next block pointer to NULL. *(firstblock) = (void *) NULL; @@ -3843,7 +4049,7 @@ void* tetgenmesh::memorypool::alloc() newblock = (void **) malloc(itemsperblock * itembytes + sizeof(void *) + alignbytes); if (newblock == (void **) NULL) { - terminatetetgen(1); + terminatetetgen(NULL, 1); } *nowblock = (void *) newblock; // The next block pointer is NULL. @@ -3864,11 +4070,7 @@ void* tetgenmesh::memorypool::alloc() // Allocate a new item. newitem = nextitem; // Advance `nextitem' pointer to next free item in block. - if (itemwordtype == POINTER) { - nextitem = (void *) ((void **) nextitem + itemwords); - } else { - nextitem = (void *) ((REAL *) nextitem + itemwords); - } + nextitem = (void *) ((uintptr_t) nextitem + itembytes); unallocateditems--; maxitems++; } @@ -3952,11 +4154,7 @@ void* tetgenmesh::memorypool::traverse() } newitem = pathitem; // Find the next item in the block. - if (itemwordtype == POINTER) { - pathitem = (void *) ((void **) pathitem + itemwords); - } else { - pathitem = (void *) ((REAL *) pathitem + itemwords); - } + pathitem = (void *) ((uintptr_t) pathitem + itembytes); pathitemsleft--; return newitem; } @@ -3967,8 +4165,7 @@ void* tetgenmesh::memorypool::traverse() // // // 'idx2verlist' returns the created map. Traverse all vertices, a pointer // // to each vertex is set into the array. The pointer to the first vertex is // -// saved in 'idx2verlist[0]'. Don't forget to minus 'in->firstnumber' when // -// to get the vertex form its index. // +// saved in 'idx2verlist[in->firstnumber]'. // // // /////////////////////////////////////////////////////////////////////////////// @@ -3985,7 +4182,7 @@ void tetgenmesh::makeindex2pointmap(point*& idx2verlist) points->traversalinit(); pointloop = pointtraverse(); - idx = in->firstnumber;; + idx = in->firstnumber; while (pointloop != (point) NULL) { idx2verlist[idx++] = pointloop; pointloop = pointtraverse(); @@ -4174,38 +4371,6 @@ tetgenmesh::shellface* tetgenmesh::shellfacetraverse(memorypool *pool) return newshellface; } -/////////////////////////////////////////////////////////////////////////////// -// // -// badfacedealloc() Deallocate space for a badface, marking it dead. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::badfacedealloc(memorypool *pool, badface *dying) -{ - // Set badface's forg to NULL. This makes it possible to detect dead - // ones when traversing the list of all items. - dying->forg = (point) NULL; - pool->dealloc((void *) dying); -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// badfacetraverse() Traverse the pools, skipping dead ones. // -// // -/////////////////////////////////////////////////////////////////////////////// - -tetgenmesh::badface* tetgenmesh::badfacetraverse(memorypool *pool) -{ - badface *newsh; - - do { - newsh = (badface *) pool->traverse(); - if (newsh == (badface *) NULL) { - return (badface *) NULL; - } - } while (newsh->forg == (point) NULL); // Skip dead ones. - return newsh; -} /////////////////////////////////////////////////////////////////////////////// // // @@ -4260,7 +4425,7 @@ void tetgenmesh::maketetrahedron(triface *newtet) newtet->tet[5] = NULL; newtet->tet[6] = NULL; newtet->tet[7] = NULL; - // No attached segments and sbfaces yet. + // No attached segments and subfaces yet. newtet->tet[8] = NULL; newtet->tet[9] = NULL; // Initialize the marker (clear all flags). @@ -4279,7 +4444,7 @@ void tetgenmesh::maketetrahedron(triface *newtet) /////////////////////////////////////////////////////////////////////////////// // // // makeshellface() Create a new shellface with version zero. Used for // -// both subfaces and seusegments. // +// both subfaces and subsegments. // // // /////////////////////////////////////////////////////////////////////////////// @@ -4302,18 +4467,17 @@ void tetgenmesh::makeshellface(memorypool *pool, face *newface) // No adjoining tetrahedra. newface->sh[9] = NULL; newface->sh[10] = NULL; - if (b->quality && checkconstraints) { + if (checkconstraints) { // Initialize the maximum area bound. setareabound(*newface, 0.0); } - // Clear the infection and marktest bits. ((int *) (newface->sh))[shmarkindex + 1] = 0; - + if (useinsertradius) { + setfacetindex(*newface, 0); + } // Set the boundary marker to zero. setshellmark(*newface, 0); - // Set the default face type. - setshelltype(*newface, NSHARP); newface->shver = 0; } @@ -4340,7 +4504,7 @@ void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) } setpoint2tet(*pnewpoint, NULL); setpoint2ppt(*pnewpoint, NULL); - if (b->plc || b->psc || b->refine) { + if (b->plc || b->refine) { // Initialize the point-to-simplex field. setpoint2sh(*pnewpoint, NULL); if (b->metric && (bgm != NULL)) { @@ -4348,20 +4512,11 @@ void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) } } // Initialize the point marker (starting from in->firstnumber). - i = (int) points->items - (in->firstnumber == 1 ? 0 : 1); - setpointmark(*pnewpoint, i); - // Initialize the point type. + setpointmark(*pnewpoint, (int) (points->items) - (!in->firstnumber)); + // Clear all flags. + ((int *) (*pnewpoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. setpointtype(*pnewpoint, vtype); - // Clear the point flags. - puninfect(*pnewpoint); - punmarktest(*pnewpoint); - if (b->psc) { - // Initialize the u,v coordinates. - setpointgeomuv(*pnewpoint, 0, 0); - setpointgeomuv(*pnewpoint, 1, 0); - // Initialize the geometry tag. - setpointgeomtag(*pnewpoint, 0); - } } /////////////////////////////////////////////////////////////////////////////// @@ -4377,13 +4532,16 @@ void tetgenmesh::makepoint(point* pnewpoint, enum verttype vtype) void tetgenmesh::initializepools() { - enum memorypool::wordtype wtype; - int pointsize, elesize, shsize; + int pointsize = 0, elesize = 0, shsize = 0; + int i; if (b->verbose) { printf(" Initializing memorypools.\n"); + printf(" tetrahedron per block: %d.\n", b->tetrahedraperblock); } + inittables(); + // There are three input point lists available, which are in, addin, // and bgm->in. These point lists may have different number of // attributes. Decide the maximum number. @@ -4410,17 +4568,25 @@ void tetgenmesh::initializepools() if (in->segmentconstraintlist || in->facetconstraintlist) { checkconstraints = 1; } + if (b->plc || b->refine) { + // Save the insertion radius for Steiner points if boundaries + // are allowed be split. + if (!b->nobisect || checkconstraints) { + useinsertradius = 1; + } + } // The index within each point at which its metric tensor is found. // Each vertex has three coordinates. if (b->psc) { // '-s' option (PSC), the u,v coordinates are provided. pointmtrindex = 5 + numpointattrib; + // The index within each point at which its u, v coordinates are found. + // Comment: They are saved after the list of point attributes. + pointparamindex = pointmtrindex - 2; } else { pointmtrindex = 3 + numpointattrib; } - // The index within each point at which its u, v coordinates are found. - pointparamindex = 3 + (numpointattrib > 0); // For '-m' option. A tensor field is provided (*.mtr or *.b.mtr file). if (b->metric) { // Decide the size (1, 3, or 6) of the metric tensor. @@ -4438,6 +4604,11 @@ void tetgenmesh::initializepools() // For '-q' option. Make sure to have space for saving a scalar value. sizeoftensor = b->quality ? 1 : 0; } + if (useinsertradius) { + // Increase a space (REAL) for saving point insertion radius, it is + // saved directly after the metric. + sizeoftensor++; + } // The index within each point at which an element pointer is found, where // the index is measured in pointers. Ensure the index is aligned to a // sizeof(tetrahedron)-byte address. @@ -4463,17 +4634,14 @@ void tetgenmesh::initializepools() // The index within each point at which the boundary marker is found, // Ensure the point marker is aligned to a sizeof(int)-byte address. pointmarkindex = (pointsize + sizeof(int) - 1) / sizeof(int); - // Now point size is the ints (inidcated by pointmarkindex) plus: + // Now point size is the ints (indicated by pointmarkindex) plus: // - an integer for boundary marker; // - an integer for vertex type; // - an integer for geometry tag (optional, -s option). pointsize = (pointmarkindex + 2 + (b->psc ? 1 : 0)) * sizeof(tetrahedron); - // Decide the wordtype used in vertex pool. - wtype = (sizeof(REAL) >= sizeof(tetrahedron)) ? - memorypool::FLOATINGPOINT : memorypool::POINTER; // Initialize the pool of vertices. - points = new memorypool(pointsize, b->vertexperblock, wtype, 0); + points = new memorypool(pointsize, b->vertexperblock, sizeof(REAL), 0); if (b->verbose) { printf(" Size of a point: %d bytes.\n", points->itembytes); @@ -4481,7 +4649,32 @@ void tetgenmesh::initializepools() // Initialize the infinite vertex. dummypoint = (point) new char[pointsize]; - setpointmark(dummypoint, -1); + // Initialize all fields of this point. + dummypoint[0] = 0.0; + dummypoint[1] = 0.0; + dummypoint[2] = 0.0; + for (i = 0; i < numpointattrib; i++) { + dummypoint[3 + i] = 0.0; + } + // Initialize the metric tensor. + for (i = 0; i < sizeoftensor; i++) { + dummypoint[pointmtrindex + i] = 0.0; + } + setpoint2tet(dummypoint, NULL); + setpoint2ppt(dummypoint, NULL); + if (b->plc || b->psc || b->refine) { + // Initialize the point-to-simplex field. + setpoint2sh(dummypoint, NULL); + if (b->metric && (bgm != NULL)) { + setpoint2bgmtet(dummypoint, NULL); + } + } + // Initialize the point marker (starting from in->firstnumber). + setpointmark(dummypoint, -1); // The unique marker for dummypoint. + // Clear all flags. + ((int *) (dummypoint))[pointmarkindex + 1] = 0; + // Initialize (set) the point type. + setpointtype(dummypoint, UNUSEDVERTEX); // Does not matter. // The number of bytes occupied by a tetrahedron is varying by the user- // specified options. The contents of the first 12 pointers are listed @@ -4514,7 +4707,7 @@ void tetgenmesh::initializepools() // The index within each element at which its attributes are found, where // the index is measured in REALs. elemattribindex = (elesize + sizeof(REAL) - 1) / sizeof(REAL); - // The index within each element at which the maximum voulme bound is + // The index within each element at which the maximum volume bound is // found, where the index is measured in REALs. volumeboundindex = elemattribindex + numelemattrib; // If element attributes or an constraint are needed, increase the number @@ -4527,8 +4720,8 @@ void tetgenmesh::initializepools() // Having determined the memory size of an element, initialize the pool. - tetrahedrons = new memorypool(elesize, b->tetrahedraperblock, - memorypool::POINTER, 16); + tetrahedrons = new memorypool(elesize, b->tetrahedraperblock, sizeof(void *), + 16); if (b->verbose) { printf(" Size of a tetrahedron: %d (%d) bytes.\n", elesize, @@ -4545,7 +4738,7 @@ void tetgenmesh::initializepools() areaboundindex = (shsize + sizeof(REAL) - 1) / sizeof(REAL); // If -q switch is in use, increase the number of bytes occupied by // a subface for saving maximum area bound. - if (b->quality && checkconstraints) { + if (checkconstraints) { shsize = (areaboundindex + 1) * sizeof(REAL); } else { shsize = areaboundindex * sizeof(REAL); @@ -4556,12 +4749,16 @@ void tetgenmesh::initializepools() // Increase the number of bytes by two or three integers, one for facet // marker, one for shellface type, and optionally one for pbc group. shsize = (shmarkindex + 2) * sizeof(shellface); + if (useinsertradius) { + // Increase the number of byte by one integer for storing facet index. + // set/read by setfacetindex() and getfacetindex. + shsize = (shmarkindex + 3) * sizeof(shellface); + } // Initialize the pool of subfaces. Each subface record is eight-byte // aligned so it has room to store an edge version (from 0 to 5) in // the least three bits. - subfaces = new memorypool(shsize, b->shellfaceperblock, - memorypool::POINTER, 8); + subfaces = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); if (b->verbose) { printf(" Size of a shellface: %d (%d) bytes.\n", shsize, @@ -4570,40 +4767,36 @@ void tetgenmesh::initializepools() // Initialize the pool of subsegments. The subsegment's record is same // with subface. - subsegs = new memorypool(shsize, b->shellfaceperblock, - memorypool::POINTER, 8); + subsegs = new memorypool(shsize, b->shellfaceperblock, sizeof(void *), 8); // Initialize the pool for tet-subseg connections. tet2segpool = new memorypool(6 * sizeof(shellface), b->shellfaceperblock, - memorypool::POINTER, 0); + sizeof(void *), 0); // Initialize the pool for tet-subface connections. tet2subpool = new memorypool(4 * sizeof(shellface), b->shellfaceperblock, - memorypool::POINTER, 0); + sizeof(void *), 0); // Initialize arraypools for segment & facet recovery. subsegstack = new arraypool(sizeof(face), 10); subfacstack = new arraypool(sizeof(face), 10); subvertstack = new arraypool(sizeof(point), 8); - suppsteinerptlist = new arraypool(sizeof(point), 8); - - // Initialize arraypools for surface Bowyer-Watson algorithm. + // Initialize arraypools for surface point insertion/deletion. caveshlist = new arraypool(sizeof(face), 8); caveshbdlist = new arraypool(sizeof(face), 8); cavesegshlist = new arraypool(sizeof(face), 4); cavetetshlist = new arraypool(sizeof(face), 8); cavetetseglist = new arraypool(sizeof(face), 8); - caveencshlist = new arraypool(sizeof(face), 8); caveencseglist = new arraypool(sizeof(face), 8); } // Initialize the pools for flips. - flippool = new memorypool(sizeof(badface), 1024, memorypool::POINTER, 0); + flippool = new memorypool(sizeof(badface), 1024, sizeof(void *), 0); unflipqueue = new arraypool(sizeof(badface), 10); - // Initialize the arraypools for Bowyer-Watson algorithm. + // Initialize the arraypools for point insertion. cavetetlist = new arraypool(sizeof(triface), 10); cavebdrylist = new arraypool(sizeof(triface), 10); caveoldtetlist = new arraypool(sizeof(triface), 10); @@ -4619,9 +4812,148 @@ void tetgenmesh::initializepools() //// //// // PI is the ratio of a circle's circumference to its diameter. - REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; +/////////////////////////////////////////////////////////////////////////////// +// // +// insphere_s() Insphere test with symbolic perturbation. // +// // +// Given four points pa, pb, pc, and pd, test if the point pe lies inside or // +// outside the circumscribed sphere of the four points. // +// // +// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // +// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // +// points pa, pb, and pc. Otherwise, the returned sign is flipped. // +// // +// Return a positive value (> 0) if pe lies inside, a negative value (< 0) // +// if pe lies outside the sphere, the returned value will not be zero. // +// // +/////////////////////////////////////////////////////////////////////////////// + +REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) +{ + REAL sign; + + sign = insphere(pa, pb, pc, pd, pe); + if (sign != 0.0) { + return sign; + } + + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; + + pt[0] = pa; + pt[1] = pb; + pt[2] = pc; + pt[3] = pd; + pt[4] = pe; + + // Sort the five points such that their indices are in the increasing + // order. An optimized bubble sort algorithm is used, i.e., it has + // the worst case O(n^2) runtime, but it is usually much faster. + swaps = 0; // Record the total number of swaps. + n = 5; + do { + count = 0; + n = n - 1; + for (i = 0; i < n; i++) { + if (pointmark(pt[i]) > pointmark(pt[i+1])) { + swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; + count++; + } + } + swaps += count; + } while (count > 0); // Continue if some points are swapped. + + oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); + if (oriA != 0.0) { + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriA = -oriA; + return oriA; + } + + oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); + assert(oriB != 0.0); // SELF_CHECK + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// orient4d_s() 4d orientation test with symbolic perturbation. // +// // +// Given four lifted points pa', pb', pc', and pd' in R^4,test if the lifted // +// point pe' in R^4 lies below or above the hyperplane passing through the // +// four points pa', pb', pc', and pd'. // +// // +// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // +// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // +// the points pa, pb, and pc. Otherwise, the returned sign is flipped. // +// // +// Return a positive value (> 0) if pe' lies below, a negative value (< 0) // +// if pe' lies above the hyperplane, the returned value should not be zero. // +// // +/////////////////////////////////////////////////////////////////////////////// + +REAL tetgenmesh::orient4d_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, + REAL aheight, REAL bheight, REAL cheight, + REAL dheight, REAL eheight) +{ + REAL sign; + + sign = orient4d(pa, pb, pc, pd, pe, + aheight, bheight, cheight, dheight, eheight); + if (sign != 0.0) { + return sign; + } + + // Symbolic perturbation. + point pt[5], swappt; + REAL oriA, oriB; + int swaps, count; + int n, i; + + pt[0] = pa; + pt[1] = pb; + pt[2] = pc; + pt[3] = pd; + pt[4] = pe; + + // Sort the five points such that their indices are in the increasing + // order. An optimized bubble sort algorithm is used, i.e., it has + // the worst case O(n^2) runtime, but it is usually much faster. + swaps = 0; // Record the total number of swaps. + n = 5; + do { + count = 0; + n = n - 1; + for (i = 0; i < n; i++) { + if (pointmark(pt[i]) > pointmark(pt[i+1])) { + swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; + count++; + } + } + swaps += count; + } while (count > 0); // Continue if some points are swapped. + + oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); + if (oriA != 0.0) { + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriA = -oriA; + return oriA; + } + + oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); + assert(oriB != 0.0); // SELF_CHECK + // Flip the sign if there are odd number of swaps. + if ((swaps % 2) != 0) oriB = -oriB; + return oriB; +} + /////////////////////////////////////////////////////////////////////////////// // // // tri_edge_test() Triangle-edge intersection test. // @@ -4635,7 +4967,7 @@ REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; // If T and E intersect each other, they may intersect in different ways. If // // 'level' > 0, their intersection type will be reported 'types' and 'pos'. // // // -// The retrun value indicates one of the following cases: // +// The return value indicates one of the following cases: // // - 0, T and E are disjoint. // // - 1, T and E intersect each other. // // - 2, T and E are not coplanar. They intersect at a single point. // @@ -4644,6 +4976,10 @@ REAL tetgenmesh::PI = 3.14159265358979323846264338327950288419716939937510582; // // /////////////////////////////////////////////////////////////////////////////// +#define SETVECTOR3(V, a0, a1, a2) (V)[0] = (a0); (V)[1] = (a1); (V)[2] = (a2) + +#define SWAP2(a0, a1, tmp) (tmp) = (a0); (a0) = (a1); (a1) = (tmp) + int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, point R, int level, int *types, int *pos) { @@ -4660,14 +4996,14 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, REAL n[3], len; // Calculate a lift point, saved in dummypoint. facenormal(A, B, C, n, 1, NULL); - len = sqrt(DOT(n, n)); + len = sqrt(dot(n, n)); if (len != 0) { n[0] /= len; n[1] /= len; n[2] /= len; - len = DIST(A, B); - len += DIST(B, C); - len += DIST(C, A); + len = distance(A, B); + len += distance(B, C); + len += distance(C, A); len /= 3.0; R = abovept; //dummypoint; R[0] = A[0] + len * n[0]; @@ -4688,7 +5024,6 @@ int tetgenmesh::tri_edge_2d(point A, point B, point C, point P, point Q, sB = orient3d(P, Q, R, B); sC = orient3d(P, Q, R, C); - triedgcopcount++; if (sA < 0) { if (sB < 0) { @@ -5272,7 +5607,6 @@ int tetgenmesh::tri_edge_tail(point A,point B,point C,point P,point Q,point R, REAL s1, s2, s3; int z1; - triedgcount++; if (sP < 0) { if (sQ < 0) { // (--) disjoint @@ -5767,6 +6101,9 @@ void tetgenmesh::lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N) // Return a negative value if pd is inside the circumcircle of the triangle // // pa, pb, and pc. // // // +// IMPORTANT: It assumes that [a,b] is the common edge, i.e., the two input // +// triangles are [a,b,c] and [b,a,d]. // +// // /////////////////////////////////////////////////////////////////////////////// REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) @@ -5776,19 +6113,19 @@ REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) // Calculate the areas of the two triangles [a, b, c] and [b, a, d]. facenormal(pa, pb, pc, n1, 1, NULL); - area2[0] = DOT(n1, n1); + area2[0] = dot(n1, n1); facenormal(pb, pa, pd, n2, 1, NULL); - area2[1] = DOT(n2, n2); + area2[1] = dot(n2, n2); if (area2[0] > area2[1]) { // Choose [a, b, c] as the base triangle. circumsphere(pa, pb, pc, NULL, c, &r); - d = DIST(c, pd); + d = distance(c, pd); } else { // Choose [b, a, d] as the base triangle. if (area2[1] > 0) { circumsphere(pb, pa, pd, NULL, c, &r); - d = DIST(c, pc); + d = distance(c, pc); } else { // The four points are collinear. This case only happens on the boundary. return 0; // Return "not inside". @@ -5803,150 +6140,6 @@ REAL tetgenmesh::incircle3d(point pa, point pb, point pc, point pd) return sign; } -/////////////////////////////////////////////////////////////////////////////// -// // -// insphere_s() Insphere test with symbolic perturbation. // -// // -// Given four points pa, pb, pc, and pd, test if the point pe lies inside or // -// outside the circumscirbed sphere of the four points. // -// // -// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // -// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // -// points pa, pb, and pc. Otherwise, the returned sign is flipped. // -// // -// Return a positive value (> 0) if pe lies inside, a negative value (< 0) // -// if pe lies outside the sphere, the returned value will not be zero. // -// // -/////////////////////////////////////////////////////////////////////////////// - -REAL tetgenmesh::insphere_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe) -{ - REAL sign; - - sign = insphere(pa, pb, pc, pd, pe); - if (sign != 0.0) { - return sign; - } - - insphere_sos_count++; - - // Symbolic perturbation. - point pt[5], swappt; - REAL oriA, oriB; - int swaps, count; - int n, i; - - pt[0] = pa; - pt[1] = pb; - pt[2] = pc; - pt[3] = pd; - pt[4] = pe; - - // Sort the five points such that their indices are in the increasing - // order. An optimized bubble sort algorithm is used, i.e., it has - // the worst case O(n^2) runtime, but it is usually much faster. - swaps = 0; // Record the total number of swaps. - n = 5; - do { - count = 0; - n = n - 1; - for (i = 0; i < n; i++) { - if (pointmark(pt[i]) > pointmark(pt[i+1])) { - swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; - count++; - } - } - swaps += count; - } while (count > 0); // Continue if some points are swapped. - - oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); - if (oriA != 0.0) { - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriA = -oriA; - return oriA; - } - - oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); - assert(oriB != 0.0); // SELF_CHECK - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriB = -oriB; - return oriB; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// orient4d_s() 4d orientation test with symbolic perturbation. // -// // -// Given four lifted points pa', pb', pc', and pd' in R^4,test if the lifted // -// point pe' in R^4 lies below or above the hyperplance passing through the // -// four points pa', pb', pc', and pd'. // -// // -// Here we assume that the 3d orientation of the point sequence {pa, pb, pc, // -// pd} is positive (NOT zero), i.e., pd lies above the plane passing through // -// the points pa, pb, and pc. Otherwise, the returned sign is flipped. // -// // -// Return a positive value (> 0) if pe' lies below, a negative value (< 0) // -// if pe' lies above the hyperplane, the returned value should not be zero. // -// // -/////////////////////////////////////////////////////////////////////////////// - -REAL tetgenmesh::orient4d_s(REAL* pa, REAL* pb, REAL* pc, REAL* pd, REAL* pe, - REAL aheight, REAL bheight, REAL cheight, - REAL dheight, REAL eheight) -{ - REAL sign; - - sign = orient4d(pa, pb, pc, pd, pe, - aheight, bheight, cheight, dheight, eheight); - if (sign != 0.0) { - return sign; - } - - orient4d_sos_count++; - - // Symbolic perturbation. - point pt[5], swappt; - REAL oriA, oriB; - int swaps, count; - int n, i; - - pt[0] = pa; - pt[1] = pb; - pt[2] = pc; - pt[3] = pd; - pt[4] = pe; - - // Sort the five points such that their indices are in the increasing - // order. An optimized bubble sort algorithm is used, i.e., it has - // the worst case O(n^2) runtime, but it is usually much faster. - swaps = 0; // Record the total number of swaps. - n = 5; - do { - count = 0; - n = n - 1; - for (i = 0; i < n; i++) { - if (pointmark(pt[i]) > pointmark(pt[i+1])) { - swappt = pt[i]; pt[i] = pt[i+1]; pt[i+1] = swappt; - count++; - } - } - swaps += count; - } while (count > 0); // Continue if some points are swapped. - - oriA = orient3d(pt[1], pt[2], pt[3], pt[4]); - if (oriA != 0.0) { - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriA = -oriA; - return oriA; - } - - oriB = -orient3d(pt[0], pt[2], pt[3], pt[4]); - assert(oriB != 0.0); // SELF_CHECK - // Flip the sign if there are odd number of swaps. - if ((swaps % 2) != 0) oriB = -oriB; - return oriB; -} - /////////////////////////////////////////////////////////////////////////////// // // // facenormal() Calculate the normal of the face. // @@ -5983,9 +6176,9 @@ void tetgenmesh::facenormal(point pa, point pb, point pc, REAL *n, int pivot, v3[0] = pc[0] - pb[0]; // edge vector v3: b->c v3[1] = pc[1] - pb[1]; v3[2] = pc[2] - pb[2]; - L1 = DOT(v1, v1); - L2 = DOT(v2, v2); - L3 = DOT(v3, v3); + L1 = dot(v1, v1); + L2 = dot(v2, v2); + L3 = dot(v3, v3); // Sort the three edge lengths. if (L1 < L2) { if (L2 < L3) { @@ -6009,7 +6202,7 @@ void tetgenmesh::facenormal(point pa, point pb, point pc, REAL *n, int pivot, } // Calculate the face normal. - CROSS(pv1, pv2, n); + cross(pv1, pv2, n); // Inverse the direction; n[0] = -n[0]; n[1] = -n[1]; @@ -6043,9 +6236,8 @@ REAL tetgenmesh::shortdistance(REAL* p, REAL* e1, REAL* e2) v2[2] = p[2] - e1[2]; len = sqrt(dot(v1, v1)); -#ifdef SELF_CHECK assert(len != 0.0); -#endif + v1[0] /= len; v1[1] /= len; v1[2] /= len; @@ -6077,6 +6269,27 @@ REAL tetgenmesh::triarea(REAL* pa, REAL* pb, REAL* pc) return 0.5 * sqrt(dot(A[2], A[2])); // The area of [a,b,c]. } +REAL tetgenmesh::orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd) +{ + REAL adx, bdx, cdx; + REAL ady, bdy, cdy; + REAL adz, bdz, cdz; + + adx = pa[0] - pd[0]; + bdx = pb[0] - pd[0]; + cdx = pc[0] - pd[0]; + ady = pa[1] - pd[1]; + bdy = pb[1] - pd[1]; + cdy = pc[1] - pd[1]; + adz = pa[2] - pd[2]; + bdz = pb[2] - pd[2]; + cdz = pc[2] - pd[2]; + + return adx * (bdy * cdz - bdz * cdy) + + bdx * (cdy * adz - cdz * ady) + + cdx * (ady * bdz - adz * bdy); +} + /////////////////////////////////////////////////////////////////////////////// // // // interiorangle() Return the interior angle (0 - 2 * PI) between vectors // @@ -6106,9 +6319,8 @@ REAL tetgenmesh::interiorangle(REAL* o, REAL* p1, REAL* p2, REAL* n) len1 = sqrt(dot(v1, v1)); len2 = sqrt(dot(v2, v2)); lenlen = len1 * len2; -#ifdef SELF_CHECK assert(lenlen != 0.0); -#endif + costheta = dot(v1, v2) / lenlen; if (costheta > 1.0) { costheta = 1.0; // Roundoff. @@ -6245,7 +6457,7 @@ bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, REAL* cosdd, REAL* cosmaxd, REAL* cosmind) { REAL N[4][3], vol, cosd, len; - int f1, f2, i, j; + int f1 = 0, f2 = 0, i, j; vol = 0; // Check if the tet is valid or not. @@ -6301,7 +6513,7 @@ bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, } } - // Calculate the consine of the dihedral angles of the edges. + // Calculate the cosine of the dihedral angles of the edges. for (i = 0; i < 6; i++) { switch (i) { case 0: f1 = 0; f2 = 1; break; // [c,d]. @@ -6313,6 +6525,7 @@ bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, } cosd = -dot(N[f1], N[f2]); if (cosd < -1.0) cosd = -1.0; // Rounding. + if (cosd > 1.0) cosd = 1.0; // Rounding. if (cosdd) cosdd[i] = cosd; if (cosmaxd || cosmind) { if (i == 0) { @@ -6330,7 +6543,7 @@ bool tetgenmesh::tetalldihedral(point pa, point pb, point pc, point pd, /////////////////////////////////////////////////////////////////////////////// // // -// tetallnormal() Get the in-noramls of the four faces of a given tet. // +// tetallnormal() Get the in-normals of the four faces of a given tet. // // // // Let tet be abcd. N[4][3] returns the four normals, which are: N[0] cbd, // // N[1] acd, N[2] bad, N[3] abc (exactly corresponding to the face indices // @@ -6449,7 +6662,7 @@ REAL tetgenmesh::tetaspectratio(point pa, point pb, point pc, point pd) // // // Return TRUE if the input points are not degenerate and the circumcenter // // and circumradius are returned in 'cent' and 'radius' respectively if they // -// are not NULLs. Otherwise, return FALSE indicated the points are degenrate.// +// are not NULLs. Otherwise, return FALSE, the four points are co-planar. // // // /////////////////////////////////////////////////////////////////////////////// @@ -6601,6 +6814,56 @@ void tetgenmesh::planelineint(REAL* pa, REAL* pb, REAL* pc, REAL* e1, REAL* e2, } } +/////////////////////////////////////////////////////////////////////////////// +// // +// linelineint() Calculate the intersection(s) of two line segments. // +// // +// Calculate the line segment [P, Q] that is the shortest route between two // +// lines from A to B and C to D. Calculate also the values of tp and tq // +// where: P = A + tp (B - A), and Q = C + tq (D - C). // +// // +// Return 1 if the line segment exists. Otherwise, return 0. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::linelineint(REAL* A, REAL* B, REAL* C, REAL* D, REAL* P, + REAL* Q, REAL* tp, REAL* tq) +{ + REAL vab[3], vcd[3], vca[3]; + REAL vab_vab, vcd_vcd, vab_vcd; + REAL vca_vab, vca_vcd; + REAL det, eps; + int i; + + for (i = 0; i < 3; i++) { + vab[i] = B[i] - A[i]; + vcd[i] = D[i] - C[i]; + vca[i] = A[i] - C[i]; + } + + vab_vab = dot(vab, vab); + vcd_vcd = dot(vcd, vcd); + vab_vcd = dot(vab, vcd); + + det = vab_vab * vcd_vcd - vab_vcd * vab_vcd; + // Round the result. + eps = det / (fabs(vab_vab * vcd_vcd) + fabs(vab_vcd * vab_vcd)); + if (eps < b->epsilon) { + return 0; + } + + vca_vab = dot(vca, vab); + vca_vcd = dot(vca, vcd); + + *tp = (vcd_vcd * (- vca_vab) + vab_vcd * vca_vcd) / det; + *tq = (vab_vcd * (- vca_vab) + vab_vab * vca_vcd) / det; + + for (i = 0; i < 3; i++) P[i] = A[i] + (*tp) * vab[i]; + for (i = 0; i < 3; i++) Q[i] = C[i] + (*tq) * vcd[i]; + + return 1; +} + /////////////////////////////////////////////////////////////////////////////// // // // tetprismvol() Calculate the volume of a tetrahedral prism in 4D. // @@ -6648,6 +6911,129 @@ REAL tetgenmesh::tetprismvol(REAL* p0, REAL* p1, REAL* p2, REAL* p3) return fabs(vol[0]) + fabs(vol[1]) + fabs(vol[2]) + fabs(vol[3]); } +/////////////////////////////////////////////////////////////////////////////// +// // +// calculateabovepoint() Calculate a point above a facet in 'dummypoint'. // +// // +/////////////////////////////////////////////////////////////////////////////// + +bool tetgenmesh::calculateabovepoint(arraypool *facpoints, point *ppa, + point *ppb, point *ppc) +{ + point *ppt, pa, pb, pc; + REAL v1[3], v2[3], n[3]; + REAL lab, len, A, area; + REAL x, y, z; + int i; + + ppt = (point *) fastlookup(facpoints, 0); + pa = *ppt; // a is the first point. + pb = pc = NULL; // Avoid compiler warnings. + + // Get a point b s.t. the length of [a, b] is maximal. + lab = 0; + for (i = 1; i < facpoints->objects; i++) { + ppt = (point *) fastlookup(facpoints, i); + x = (*ppt)[0] - pa[0]; + y = (*ppt)[1] - pa[1]; + z = (*ppt)[2] - pa[2]; + len = x * x + y * y + z * z; + if (len > lab) { + lab = len; + pb = *ppt; + } + } + lab = sqrt(lab); + if (lab == 0) { + if (!b->quiet) { + printf("Warning: All points of a facet are coincident with %d.\n", + pointmark(pa)); + } + return false; + } + + // Get a point c s.t. the area of [a, b, c] is maximal. + v1[0] = pb[0] - pa[0]; + v1[1] = pb[1] - pa[1]; + v1[2] = pb[2] - pa[2]; + A = 0; + for (i = 1; i < facpoints->objects; i++) { + ppt = (point *) fastlookup(facpoints, i); + v2[0] = (*ppt)[0] - pa[0]; + v2[1] = (*ppt)[1] - pa[1]; + v2[2] = (*ppt)[2] - pa[2]; + cross(v1, v2, n); + area = dot(n, n); + if (area > A) { + A = area; + pc = *ppt; + } + } + if (A == 0) { + // All points are collinear. No above point. + if (!b->quiet) { + printf("Warning: All points of a facet are collinaer with [%d, %d].\n", + pointmark(pa), pointmark(pb)); + } + return false; + } + + // Calculate an above point of this facet. + facenormal(pa, pb, pc, n, 1, NULL); + len = sqrt(dot(n, n)); + n[0] /= len; + n[1] /= len; + n[2] /= len; + lab /= 2.0; // Half the maximal length. + dummypoint[0] = pa[0] + lab * n[0]; + dummypoint[1] = pa[1] + lab * n[1]; + dummypoint[2] = pa[2] + lab * n[2]; + + if (ppa != NULL) { + // Return the three points. + *ppa = pa; + *ppb = pb; + *ppc = pc; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Calculate an above point. It lies above the plane containing the subface // +// [a,b,c], and save it in dummypoint. Moreover, the vector pa->dummypoint // +// is the normal of the plane. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::calculateabovepoint4(point pa, point pb, point pc, point pd) +{ + REAL n1[3], n2[3], *norm; + REAL len, len1, len2; + + // Select a base. + facenormal(pa, pb, pc, n1, 1, NULL); + len1 = sqrt(dot(n1, n1)); + facenormal(pa, pb, pd, n2, 1, NULL); + len2 = sqrt(dot(n2, n2)); + if (len1 > len2) { + norm = n1; + len = len1; + } else { + norm = n2; + len = len2; + } + assert(len > 0); + norm[0] /= len; + norm[1] /= len; + norm[2] /= len; + len = distance(pa, pb); + dummypoint[0] = pa[0] + len * norm[0]; + dummypoint[1] = pa[1] + len * norm[1]; + dummypoint[2] = pa[2] + len * norm[2]; +} + //// //// //// //// //// geom_cxx ///////////////////////////////////////////////////////////////// @@ -6656,36 +7042,14 @@ REAL tetgenmesh::tetprismvol(REAL* p0, REAL* p1, REAL* p2, REAL* p3) //// //// //// //// -/////////////////////////////////////////////////////////////////////////////// -// // -// flippush() Push a face (possibly will be flipped) into flipstack. // -// // -// The face is marked. The flag is used to check the validity of the face on // -// its popup. Some other flips may change it already. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::flippush(badface*& fstack, triface* flipface) -{ - badface *newflipface; - - if (!facemarked(*flipface)) { - newflipface = (badface *) flippool->alloc(); - newflipface->tt = *flipface; - markface(newflipface->tt); - // Push this face into stack. - newflipface->nextitem = fstack; - fstack = newflipface; - } -} - /////////////////////////////////////////////////////////////////////////////// // // // flip23() Perform a 2-to-3 flip (face-to-edge flip). // // // -// 'fliptets' is an array of tetrahedra. On input it contains two tets // -// [a,b,c,d] and [b,a,c,e]. It returns three new tets: [e,d,a,b], [e,d,b,c], // -// [e,d,c,a]. The face [a,b,c] is removed, and the edge [d,e] is created. // +// 'fliptets' is an array of three tets (handles), where the [0] and [1] are // +// [a,b,c,d] and [b,a,c,e]. The three new tets: [e,d,a,b], [e,d,b,c], and // +// [e,d,c,a] are returned in [0], [1], and [2] of 'fliptets'. As a result, // +// The face [a,b,c] is removed, and the edge [d,e] is created. // // // // If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // // the five vertices may be 'dummypoint'. There are two canonical cases: // @@ -6696,27 +7060,18 @@ void tetgenmesh::flippush(badface*& fstack, triface* flipface) // rotate the three input tets counterclockwisely (right-hand rule) // // until a or b is in c's position. // // // -// If 'flipflag > 0', faces on the convex hull of the five vertices might // -// need to be flipped, e.g., for incremental DT construction or mesh quality // -// improvement. They will be queued in 'flipstack'. // -// // -// If 'flipflag = 1', it is in the process of incrmental flip DT algorithm, // -// and we assume that 'd' must be the newly inserted vertex. In such case, // -// only the link faces at 'd', i.e., three faces [a,b,e], [b,c,e], and [c,a, // -// e] needs to be queued ([Edelsbrunner & Shah'1996] and [M\"ucke'1998]). // +// If 'fc->enqflag' is set, convex hull faces will be queued for flipping. // +// In particular, if 'fc->enqflag' is 1, it is called by incrementalflip() // +// after the insertion of a new point. It is assumed that 'd' is the new // +// point. IN this case, only link faces of 'd' are queued. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, - int chkencflag) +void tetgenmesh::flip23(triface* fliptets, int hullflag, flipconstraints *fc) { triface topcastets[3], botcastets[3]; triface newface, casface; - face checksh; - face checkseg; - badface *bface; // used by chkencflag point pa, pb, pc, pd, pe; - REAL volneg[2], volpos[3], vol_diff; // volumes of involved tet-prisms. REAL attrib, volume; int dummyflag = 0; // range = {-1, 0, 1, 2}. int i; @@ -6745,16 +7100,12 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, } } - pa = org(fliptets[0]); + pa = org(fliptets[0]); pb = dest(fliptets[0]); pc = apex(fliptets[0]); pd = oppo(fliptets[0]); pe = oppo(fliptets[1]); - if (b->verbose > 3) { - printf(" flip 2-to-3: (%d, %d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pe)); - } flip23count++; // Get the outer boundary faces. @@ -6839,7 +7190,8 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, setvertices(fliptets[2], pe, pd, pc, pa); // [e,d,c,a] * } - if (calc_tetprism_vol) { + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[2], volpos[3], vol_diff; if (pd != dummypoint) { if (pc != dummypoint) { volpos[0] = tetprismvol(pe, pd, pa, pb); @@ -6862,8 +7214,8 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, volneg[1] = tetprismvol(pb, pa, pc, pe); } vol_diff = volpos[0] + volpos[1] + volpos[2] - volneg[0] - volneg[1]; - tetprism_vol_sum += vol_diff; // Update the total sum. - } // if (check_tetprism_vol_diff) + fc->tetprism_vol_sum += vol_diff; // Update the total sum. + } // Bond three new tets together. for (i = 0; i < 3; i++) { @@ -6872,43 +7224,36 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, } // Bond to top outer boundary faces (at [a,b,c,d]). for (i = 0; i < 3; i++) { - enextesym(fliptets[i], newface); - eprevself(newface); // At edges [b,a], [c,b], [a,c]. + eorgoppo(fliptets[i], newface); // At edges [b,a], [c,b], [a,c]. bond(newface, topcastets[i]); } // Bond bottom outer boundary faces (at [b,a,c,e]). for (i = 0; i < 3; i++) { - eprevesym(fliptets[i], newface); - enextself(newface); // At edges [a,b], [b,c], [c,a]. + edestoppo(fliptets[i], newface); // At edges [a,b], [b,c], [c,a]. bond(newface, botcastets[i]); } - // Bond 15 subsegments if there are. if (checksubsegflag) { + // Bond subsegments if there are. + // Each new tet has 5 edges to be checked (except the edge [e,d]). + face checkseg; // The middle three: [a,b], [b,c], [c,a]. - for (i = 0; i < 3; i++) { - tsspivot1(topcastets[i], checkseg); - if (checkseg.sh != NULL) { - enextesym(fliptets[i], newface); - eprevself(newface); // At edges [b,a], [c,b], [a,c]. + for (i = 0; i < 3; i++) { + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); + eorgoppo(fliptets[i], newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } } // The top three: [d,a], [d,b], [d,c]. Two tets per edge. for (i = 0; i < 3; i++) { - eprev(topcastets[i], casface); - tsspivot1(casface, checkseg); - if (checkseg.sh != NULL) { + eprev(topcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); enext(fliptets[i], newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); @@ -6916,22 +7261,16 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, eprevself(newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } } // The bot three: [a,e], [b,e], [c,e]. Two tets per edge. for (i = 0; i < 3; i++) { enext(botcastets[i], casface); - tsspivot1(casface, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(casface)) { + tsspivot1(casface, checkseg); eprev(fliptets[i], newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); @@ -6939,75 +7278,53 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, enextself(newface); tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } } - } + } // if (checksubsegflag) - // Bond 6 subfaces if there are. if (checksubfaceflag) { - for (i = 0; i < 3; i++) { - tspivot(topcastets[i], checksh); - if (checksh.sh != NULL) { - enextesym(fliptets[i], newface); - eprevself(newface); // At edge [b,a], [c,b], [a,c]. + // Bond 6 subfaces if there are. + face checksh; + for (i = 0; i < 3; i++) { + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); + eorgoppo(fliptets[i], newface); sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } } for (i = 0; i < 3; i++) { - tspivot(botcastets[i], checksh); - if (checksh.sh != NULL) { - eprevesym(fliptets[i], newface); - enextself(newface); // At edge [a,b], [b,c], [c,a] + if (issubface(botcastets[i])) { + tspivot(botcastets[i], checksh); + edestoppo(fliptets[i], newface); sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } } - } + } // if (checksubfaceflag) - if (chkencflag & 4) { + if (fc->chkencflag & 4) { // Put three new tets into check list. for (i = 0; i < 3; i++) { - if (!marktest2ed(fliptets[i])) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = fliptets[i]; - marktest2(bface->tt); - bface->forg = org(fliptets[i]); - } + enqueuetetrahedron(&(fliptets[i])); } } // Update the point-to-tet map. - setpoint2tet(pa, encode(fliptets[0])); - setpoint2tet(pb, encode(fliptets[0])); - setpoint2tet(pc, encode(fliptets[1])); - setpoint2tet(pd, encode(fliptets[0])); - setpoint2tet(pe, encode(fliptets[0])); + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[1].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + setpoint2tet(pe, (tetrahedron) fliptets[0].tet); if (hullflag > 0) { if (dummyflag != 0) { @@ -7040,13 +7357,13 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, } } - if (flipflag > 0) { + if (fc->enqflag > 0) { // Queue faces which may be locally non-Delaunay. for (i = 0; i < 3; i++) { eprevesym(fliptets[i], newface); flippush(flipstack, &newface); } - if (flipflag > 1) { + if (fc->enqflag > 1) { for (i = 0; i < 3; i++) { enextesym(fliptets[i], newface); flippush(flipstack, &newface); @@ -7061,9 +7378,10 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, // // // flip32() Perform a 3-to-2 flip (edge-to-face flip). // // // -// 'fliptets' is an array of three tetrahedra. On input, it contains three // -// tets: [e,d,a,b], [e,d,b,c], and [e,d,c,a]. It returns tw tets: [a,b,c,d], // -// and [b,a,c,e]. The edge [e,d] is replaced by the face [a,b,c]. // +// 'fliptets' is an array of three tets (handles), which are [e,d,a,b], // +// [e,d,b,c], and [e,d,c,a]. The two new tets: [a,b,c,d] and [b,a,c,e] are // +// returned in [0] and [1] of 'fliptets'. As a result, the edge [e,d] is // +// replaced by the face [a,b,c]. // // // // If 'hullflag' > 0, hull tets may be involved in this flip, i.e., one of // // the five vertices may be 'dummypoint'. There are two canonical cases: // @@ -7074,47 +7392,33 @@ void tetgenmesh::flip23(triface* fliptets, int hullflag, int flipflag, // three old tets counterclockwisely (right-hand rule) until a or b // // is in c's position. // // // -// If 'flipflag > 0', faces on the convex hull of the five vertices might // -// need to be flipped, e.g., for incremental DT construction or mesh quality // -// improvement. They will be queued in 'flipstack'. // -// // -// If 'flipflag = 1', it is in the process of incrmental flip DT algorithm, // -// and we assume that 'a' must be the newly inserted vertex. In such case, // -// only the link faces at 'a', i.e., two faces [c,b,d] and [b,c,e] needs to // -// be queued ( [Edelsbrunner & Shah'1996] and [M\"ucke'1998]). // +// If 'fc->enqflag' is set, convex hull faces will be queued for flipping. // +// In particular, if 'fc->enqflag' is 1, it is called by incrementalflip() // +// after the insertion of a new point. It is assumed that 'a' is the new // +// point. In this case, only link faces of 'a' are queued. // // // // If 'checksubfaceflag' is on (global variable), and assume [e,d] is not a // // segment. There may be two (interior) subfaces sharing at [e,d], which are // // [e,d,p] and [e,d,q], where the pair (p,q) may be either (a,b), or (b,c), // // or (c,a) In such case, a 2-to-2 flip is performed on these two subfaces // // and two new subfaces [p,q,e] and [p,q,d] are created. They are inserted // -// back into the tetrahedralization. However, it is possible that the new // -// subface ([p,q,e] or [p,q,d] already exists. In such case, we just delete // -// the conflict subface. As a result, either 'd' or 'e' is removed from the // -// surface mesh. A better solution would be to detect and perform a 3-to-1 // -// flip to remove 'd' or 'e' (see also 2011-11-15). // +// back into the tetrahedralization. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, - int chkencflag) +void tetgenmesh::flip32(triface* fliptets, int hullflag, flipconstraints *fc) { triface topcastets[3], botcastets[3]; triface newface, casface; - face checksh; + face flipshs[3]; face checkseg; - badface *bface; // used by chkencflag point pa, pb, pc, pd, pe; - REAL volneg[3], volpos[2], vol_diff; // volumes of involved tet-prisms. REAL attrib, volume; int dummyflag = 0; // Rangle = {-1, 0, 1, 2} + int spivot = -1, scount = 0; // for flip22() + int t1ver; int i, j; - // For 2-to-2 flip (subfaces). - face flipshs[3], flipfaces[2]; - point rempt; - int spivot = -1, scount = 0; - if (hullflag > 0) { // Check if e is 'dummypoint'. if (org(fliptets[0]) == dummypoint) { @@ -7153,36 +7457,24 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, pd = dest(fliptets[0]); pe = org(fliptets[0]); - if (b->verbose > 3) { - printf(" flip 3-to-2: (%d, %d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pe)); - } flip32count++; // Get the outer boundary faces. for (i = 0; i < 3; i++) { - enextesym(fliptets[i], casface); - eprevself(casface); + eorgoppo(fliptets[i], casface); fsym(casface, topcastets[i]); } for (i = 0; i < 3; i++) { - eprevesym(fliptets[i], casface); - enextself(casface); + edestoppo(fliptets[i], casface); fsym(casface, botcastets[i]); } if (checksubfaceflag) { // Check if there are interior subfaces at the edge [e,d]. - spivot = -1; - scount = 0; for (i = 0; i < 3; i++) { tspivot(fliptets[i], flipshs[i]); if (flipshs[i].sh != NULL) { - if (b->verbose > 3) { - printf(" Found an interior subface (%d, %d, %d).\n", - pointmark(sorg(flipshs[i])), pointmark(sdest(flipshs[i])), - pointmark(sapex(flipshs[i]))); - } + // Found an interior subface. stdissolve(flipshs[i]); // Disconnect the sub-tet bond. scount++; } else { @@ -7196,28 +7488,6 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, fliptets[1].ver = 11; setelemmarker(fliptets[0].tet, 0); // Clear all flags. setelemmarker(fliptets[1].tet, 0); - // NOTE: the element attributes and volume constraint must be set correctly. - if (checksubfaceflag) { - if (scount > 0) { - // There are two subfaces involved in this flip. The three tets are - // separated into two different regions, one may be exterior. The - // first region has two tets, and the second region has only one. - // The two created tets must be in the same region as the first region. - // The element attributes and volume constraint must be set correctly. - //assert(spivot != -1); - // The tet fliptets[spivot] is in the first region. - for (j = 0; j < 2; j++) { - for (i = 0; i < numelemattrib; i++) { - attrib = elemattribute(fliptets[spivot].tet, i); - setelemattribute(fliptets[j].tet, i, attrib); - } - if (b->varvolume) { - volume = volumebound(fliptets[spivot].tet); - setvolumebound(fliptets[j].tet, volume); - } - } - } - } if (checksubsegflag) { // Dealloc the space to subsegments. if (fliptets[0].tet[8] != NULL) { @@ -7240,7 +7510,28 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, fliptets[1].tet[9] = NULL; } } - + if (checksubfaceflag) { + if (scount > 0) { + // The element attributes and volume constraint must be set correctly. + // There are two subfaces involved in this flip. The three tets are + // separated into two different regions, one may be exterior. The + // first region has two tets, and the second region has only one. + // The two created tets must be in the same region as the first region. + // The element attributes and volume constraint must be set correctly. + //assert(spivot != -1); + // The tet fliptets[spivot] is in the first region. + for (j = 0; j < 2; j++) { + for (i = 0; i < numelemattrib; i++) { + attrib = elemattribute(fliptets[spivot].tet, i); + setelemattribute(fliptets[j].tet, i, attrib); + } + if (b->varvolume) { + volume = volumebound(fliptets[spivot].tet); + setvolumebound(fliptets[j].tet, volume); + } + } + } + } // Delete an old tet. tetrahedrondealloc(fliptets[2].tet); @@ -7264,14 +7555,15 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, esymself(fliptets[0]); // Adjust abec -> bace. esymself(fliptets[1]); - // The hullsize does not changle. + // The hullsize does not change. } } else { setvertices(fliptets[0], pa, pb, pc, pd); setvertices(fliptets[1], pb, pa, pc, pe); } - if (calc_tetprism_vol) { + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[3], volpos[2], vol_diff; if (pc != dummypoint) { if (pd != dummypoint) { volneg[0] = tetprismvol(pe, pd, pa, pb); @@ -7294,7 +7586,7 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, volpos[1] = 0.; } vol_diff = volpos[0] + volpos[1] - volneg[0] - volneg[1] - volneg[2]; - tetprism_vol_sum += vol_diff; // Update the total sum. + fc->tetprism_vol_sum += vol_diff; // Update the total sum. } // Bond abcd <==> bace. @@ -7313,133 +7605,89 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, } if (checksubsegflag) { - // Bond segments to new (flipped) tets. - for (i = 0; i < 3; i++) { - tsspivot1(topcastets[i], checkseg); - if (checkseg.sh != NULL) { + // Bond 9 segments to new (flipped) tets. + for (i = 0; i < 3; i++) { // edges a->b, b->c, c->a. + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); tssbond1(fliptets[0], checkseg); sstbond1(checkseg, fliptets[0]); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } - } - } - enextself(fliptets[0]); - } - // The three top edges. - for (i = 0; i < 3; i++) { - esym(fliptets[0], newface); - eprevself(newface); // edge b->d, c->d, a->d. - enext(topcastets[i], casface); - tsspivot1(casface, checkseg); - if (checkseg.sh != NULL) { - tssbond1(newface, checkseg); - sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } - } - } - enextself(fliptets[0]); - } - // Process the bottom tet bace. - for (i = 0; i < 3; i++) { - tsspivot1(botcastets[i], checkseg); - if (checkseg.sh != NULL) { tssbond1(fliptets[1], checkseg); sstbond1(checkseg, fliptets[1]); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } + enextself(fliptets[0]); eprevself(fliptets[1]); } - // The three bot edges. - for (i = 0; i < 3; i++) { - esym(fliptets[1], newface); - enextself(newface); // edge b<-e, c<-e, a<-e. - eprev(botcastets[i], casface); - tsspivot1(casface, checkseg); - if (checkseg.sh != NULL) { + // The three top edges. + for (i = 0; i < 3; i++) { // edges b->d, c->d, a->d. + esym(fliptets[0], newface); + eprevself(newface); + enext(topcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); + } + } + enextself(fliptets[0]); + } + // The three bot edges. + for (i = 0; i < 3; i++) { // edges b<-e, c<-e, a<-e. + esym(fliptets[1], newface); + enextself(newface); + eprev(botcastets[i], casface); + if (issubseg(casface)) { + tsspivot1(casface, checkseg); + tssbond1(newface, checkseg); + sstbond1(checkseg, newface); + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } eprevself(fliptets[1]); } - } + } // if (checksubsegflag) if (checksubfaceflag) { + face checksh; // Bond the top three casing subfaces. - for (i = 0; i < 3; i++) { - tspivot(topcastets[i], checksh); - if (checksh.sh != NULL) { - esym(fliptets[0], newface); // At edge [b,a], [c,b], [a,c] + for (i = 0; i < 3; i++) { // At edges [b,a], [c,b], [a,c] + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); + esym(fliptets[0], newface); sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } enextself(fliptets[0]); } // Bond the bottom three casing subfaces. - for (i = 0; i < 3; i++) { - tspivot(botcastets[i], checksh); - if (checksh.sh != NULL) { - esym(fliptets[1], newface); // // At edge [a,b], [b,c], [c,a] + for (i = 0; i < 3; i++) { // At edges [a,b], [b,c], [c,a] + if (issubface(botcastets[i])) { + tspivot(botcastets[i], checksh); + esym(fliptets[1], newface); sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } eprevself(fliptets[1]); } - } - if (checksubfaceflag) { if (scount > 0) { - assert(spivot != -1); // spivot = i, in {0,1,2} + face flipfaces[2]; // Perform a 2-to-2 flip in subfaces. flipfaces[0] = flipshs[(spivot + 1) % 3]; flipfaces[1] = flipshs[(spivot + 2) % 3]; sesymself(flipfaces[1]); - flip22(flipfaces, 0, chkencflag); + flip22(flipfaces, 0, fc->chkencflag); // Connect the flipped subfaces to flipped tets. // First go to the corresponding flipping edge. // Re-use top- and botcastets[0]. @@ -7460,49 +7708,9 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, sesymself(flipfaces[0]); tsbond(topcastets[0], flipfaces[0]); } else { - // Found two subfaces are duplicated at the same tet face. - // Due to the same reason explained below. - assert(sapex(checksh) == sapex(flipfaces[0])); - sspivot(checksh, checkseg); - assert(checkseg.sh == NULL); - // Delete the two duplicated subfaces. - rempt = sapex(checksh); - if (b->verbose > 2) { - printf(" Remove vertex %d from surface.\n", pointmark(rempt)); - } - // Make sure we do not delete a Steiner points in segment. - assert(pointtype(rempt) == FREEFACETVERTEX); - setpointtype(rempt, FREEVOLVERTEX); - // Re-use flipshs. - //spivot(checksh, flipshs[0]); - flipshs[0] = checksh; - spivotself(flipshs[0]); - if (flipshs[0].sh == flipfaces[0].sh) { - sesym(checksh, flipshs[0]); - spivotself(flipshs[0]); - } - assert(flipshs[0].sh != flipfaces[0].sh); - //spivot(flipfaces[0], flipshs[1]); - flipshs[1] = flipfaces[0]; - spivotself(flipshs[1]); - if (flipshs[1].sh == checksh.sh) { - sesym(flipfaces[0], flipshs[1]); - spivotself(flipshs[1]); - } - assert(flipshs[1].sh != checksh.sh); - // Bond the two subfaces together. - sbond(flipshs[0], flipshs[1]); - // Detach 'checksh' from the adjacent tets. - tsdissolve(topcastets[0]); - fsymself(topcastets[0]); - tsdissolve(topcastets[0]); - // Delete the two duplicated subfaces. - shellfacedealloc(subfaces, checksh.sh); - shellfacedealloc(subfaces, flipfaces[0].sh); + // An invalid 2-to-2 flip. Report a bug. + terminatetetgen(this, 2); } - // // Push topcastets[0] into queue for checking new sliver. - // assert(oppo(topcastets[0]) != dummypoint); - // flippush(&(topcastets[0]), oppo(topcastets[0])); // Connect the bot subface to the bottom tets. esymself(botcastets[0]); sesymself(flipfaces[1]); @@ -7514,74 +7722,24 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, sesymself(flipfaces[1]); tsbond(botcastets[0], flipfaces[1]); } else { - // Found two subfaces are duplicated at the same tet face. - assert(sapex(checksh) == sapex(flipfaces[1])); - // This happens in case when a Steiner point is not exactly coplanar - // or collinear with the subface or subedge where it was added. - // See figs illustrated in 2011-11-09. - sspivot(checksh, checkseg); - assert(checkseg.sh == NULL); - // Since the edge [p,q] is not a segment, both subfaces must be - // removed. The effect is that the Steiner point is removed from - // the surface triangulation. - // Delete the two duplicated subfaces. - rempt = sapex(checksh); - if (b->verbose > 2) { - printf(" Remove vertex %d from surface.\n", pointmark(rempt)); - } - // Make sure we do not delete a Steiner points in segment. - assert(pointtype(rempt) == FREEFACETVERTEX); - setpointtype(rempt, FREEVOLVERTEX); - // Re-use flipshs. - //spivot(checksh, flipshs[0]); - flipshs[0] = checksh; - spivotself(flipshs[0]); - if (flipshs[0].sh == flipfaces[1].sh) { - sesym(checksh, flipshs[0]); - spivotself(flipshs[0]); - } - assert(flipshs[0].sh != flipfaces[1].sh); - //spivot(flipfaces[1], flipshs[1]); - flipshs[1] = flipfaces[1]; - spivotself(flipshs[1]); - if (flipshs[1].sh == checksh.sh) { - sesym(flipfaces[1], flipshs[1]); - spivotself(flipshs[1]); - } - assert(flipshs[1].sh != checksh.sh); - // Bond the two subfaces together. - sbond(flipshs[0], flipshs[1]); - // Detach 'checksh' from the adjacent tets. - tsdissolve(botcastets[0]); - fsymself(botcastets[0]); - tsdissolve(botcastets[0]); - // Delete the two duplicated subfaces. - shellfacedealloc(subfaces, checksh.sh); - shellfacedealloc(subfaces, flipfaces[1].sh); + // An invalid 2-to-2 flip. Report a bug. + terminatetetgen(this, 2); } - // // Push botcastets[0] into queue for checking new sliver. - // assert(oppo(botcastets[0]) != dummypoint); - // flippush(&(botcastets[0]), oppo(botcastets[0])); - } - } + } // if (scount > 0) + } // if (checksubfaceflag) - if (chkencflag & 4) { + if (fc->chkencflag & 4) { // Put two new tets into check list. for (i = 0; i < 2; i++) { - if (!marktest2ed(fliptets[i])) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = fliptets[i]; - marktest2(bface->tt); - bface->forg = org(fliptets[i]); - } + enqueuetetrahedron(&(fliptets[i])); } } - setpoint2tet(pa, encode(fliptets[0])); - setpoint2tet(pb, encode(fliptets[0])); - setpoint2tet(pc, encode(fliptets[0])); - setpoint2tet(pd, encode(fliptets[0])); - setpoint2tet(pe, encode(fliptets[1])); + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[0].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); + setpoint2tet(pe, (tetrahedron) fliptets[1].tet); if (hullflag > 0) { if (dummyflag != 0) { @@ -7604,14 +7762,14 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, } } - if (flipflag > 0) { + if (fc->enqflag > 0) { // Queue faces which may be locally non-Delaunay. // pa = org(fliptets[0]); // 'a' may be a new vertex. enextesym(fliptets[0], newface); flippush(flipstack, &newface); eprevesym(fliptets[1], newface); flippush(flipstack, &newface); - if (flipflag > 1) { + if (fc->enqflag > 1) { //pb = dest(fliptets[0]); eprevesym(fliptets[0], newface); flippush(flipstack, &newface); @@ -7637,28 +7795,28 @@ void tetgenmesh::flip32(triface* fliptets, int hullflag, int flipflag, // four tets in 'fliptets' are: [p,d,a,b], [p,d,b,c], [p,d,c,a], and [a,b,c, // // p]. On return, 'fliptets[0]' is the new tet [a,b,c,d]. // // // -// If 'hullflag' is set (> 0), one of the four vertices may be 'duumypoint'. // -// The 'hullsize' may be changed. // +// If 'hullflag' is set (> 0), one of the five vertices may be 'dummypoint'. // +// The 'hullsize' may be changed. Note that p may be dummypoint. In this // +// case, four hull tets are replaced by one real tet. // // // // If 'checksubface' flag is set (>0), it is possible that there are three // // interior subfaces connecting at p. If so, a 3-to-1 flip is performed to // // to remove p from the surface triangulation. // // // +// If it is called by the routine incrementalflip(), we assume that d is the // +// newly inserted vertex. // +// // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, - int chkencflag) +void tetgenmesh::flip41(triface* fliptets, int hullflag, flipconstraints *fc) { triface topcastets[3], botcastet; triface newface, neightet; face flipshs[4]; - face checksh; - face checkseg; point pa, pb, pc, pd, pp; - badface *bface; // used by chkencflag - REAL volneg[4], volpos[1], vol_diff; // volumes of involved tet-prisms. int dummyflag = 0; // in {0, 1, 2, 3, 4} int spivot = -1, scount = 0; + int t1ver; int i; pa = org(fliptets[3]); @@ -7667,11 +7825,7 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, pd = dest(fliptets[0]); pp = org(fliptets[0]); // The removing vertex. - if (b->verbose > 3) { - printf(" flip 4-to-1: (%d, %d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pp)); - } - // flip41count++; + flip41count++; // Get the outer boundary faces. for (i = 0; i < 3; i++) { @@ -7684,8 +7838,6 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, if (checksubfaceflag) { // Check if there are three subfaces at 'p'. // Re-use 'newface'. - spivot = -1; - scount = 0; for (i = 0; i < 3; i++) { fnext(fliptets[3], newface); // [a,b,p,d],[b,c,p,d],[c,a,p,d]. tspivot(newface, flipshs[i]); @@ -7715,6 +7867,7 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, } } // if (checksubfaceflag) + // Re-use fliptets[0] for [a,b,c,d]. fliptets[0].ver = 11; setelemmarker(fliptets[0].tet, 0); // Clean all flags. @@ -7733,19 +7886,20 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, fliptets[0].tet[9] = NULL; } } - // Delete the other three tets. for (i = 1; i < 4; i++) { tetrahedrondealloc(fliptets[i].tet); } - // Mark the point pp as unused. - setpointtype(pp, UNUSEDVERTEX); - unuverts++; + if (pp != dummypoint) { + // Mark the point pp as unused. + setpointtype(pp, UNUSEDVERTEX); + unuverts++; + } // Create the new tet [a,b,c,d]. if (hullflag > 0) { - // One of the four vertices may be 'dummypoint'. + // One of the five vertices may be 'dummypoint'. if (pa == dummypoint) { // pa is dummypoint. setvertices(fliptets[0], pc, pb, pd, pa); @@ -7766,17 +7920,26 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, dummyflag = 4; } else { setvertices(fliptets[0], pa, pb, pc, pd); - dummyflag = 0; + if (pp == dummypoint) { + dummyflag = -1; + } else { + dummyflag = 0; + } } if (dummyflag > 0) { - // We delete 3 hull tets, and create 1 hull tet. + // We deleted 3 hull tets, and create 1 hull tet. hullsize -= 2; + } else if (dummyflag < 0) { + // We deleted 4 hull tets. + hullsize -= 4; + // meshedges does not change. } } else { setvertices(fliptets[0], pa, pb, pc, pd); } - if (calc_tetprism_vol) { + if (fc->remove_ndelaunay_edge) { // calc_tetprism_vol + REAL volneg[4], volpos[1], vol_diff; if (dummyflag > 0) { if (pa == dummypoint) { volneg[0] = 0.; @@ -7800,6 +7963,12 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, volneg[3] = tetprismvol(pa, pb, pc, pp); } volpos[0] = 0.; + } else if (dummyflag < 0) { + volneg[0] = 0.; + volneg[1] = 0.; + volneg[2] = 0.; + volneg[3] = 0.; + volpos[0] = tetprismvol(pa, pb, pc, pd); } else { volneg[0] = tetprismvol(pp, pd, pa, pb); volneg[1] = tetprismvol(pp, pd, pb, pc); @@ -7808,7 +7977,7 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, volpos[0] = tetprismvol(pa, pb, pc, pd); } vol_diff = volpos[0] - volneg[0] - volneg[1] - volneg[2] - volneg[3]; - tetprism_vol_sum += vol_diff; // Update the total sum. + fc->tetprism_vol_sum += vol_diff; // Update the total sum. } // Bond the new tet to adjacent tets. @@ -7820,40 +7989,29 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, bond(fliptets[0], botcastet); if (checksubsegflag) { + face checkseg; // Bond 6 segments (at edges of [a,b,c,d]) if there there are. for (i = 0; i < 3; i++) { eprev(topcastets[i], newface); // At edges [d,a],[d,b],[d,c]. - tsspivot1(newface, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(newface)) { + tsspivot1(newface, checkseg); esym(fliptets[0], newface); enextself(newface); // At edges [a,d], [b,d], [c,d]. tssbond1(newface, checkseg); sstbond1(checkseg, newface); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } enextself(fliptets[0]); } for (i = 0; i < 3; i++) { - tsspivot1(topcastets[i], checkseg); // At edges [a,b],[b,c],[c,a]. - if (checkseg.sh != NULL) { + if (issubseg(topcastets[i])) { + tsspivot1(topcastets[i], checkseg); // At edges [a,b],[b,c],[c,a]. tssbond1(fliptets[0], checkseg); sstbond1(checkseg, fliptets[0]); - if (chkencflag & 1) { - // Skip it if it has already queued. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } + if (fc->chkencflag & 1) { + enqueuesubface(badsubsegs, &checkseg); } } enextself(fliptets[0]); @@ -7861,40 +8019,29 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, } if (checksubfaceflag) { + face checksh; // Bond 4 subfaces (at faces of [a,b,c,d]) if there are. for (i = 0; i < 3; i++) { - tspivot(topcastets[i], checksh); // At faces [a,b,d],[b,c,d],[c,a,d] - if (checksh.sh != NULL) { + if (issubface(topcastets[i])) { + tspivot(topcastets[i], checksh); // At faces [a,b,d],[b,c,d],[c,a,d] esym(fliptets[0], newface); // At faces [b,a,d],[c,b,d],[a,c,d] sesymself(checksh); tsbond(newface, checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } enextself(fliptets[0]); } - tspivot(botcastet, checksh); // At face [b,a,c] - if (checksh.sh != NULL) { + if (issubface(botcastet)) { + tspivot(botcastet, checksh); // At face [b,a,c] sesymself(checksh); tsbond(fliptets[0], checksh); - if (chkencflag & 2) { - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface - } + if (fc->chkencflag & 2) { + enqueuesubface(badsubfacs, &checksh); } } - } - if (checksubfaceflag) { if (spivot >= 0) { // Perform a 3-to-1 flip in surface triangulation. // Depending on the value of 'spivot', the three subfaces are: @@ -7928,30 +8075,26 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, } // if (spivot > 0) } // if (checksubfaceflag) - if (chkencflag & 4) { - // Put the new tet into check list. - if (!marktest2ed(fliptets[0])) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = fliptets[0]; - marktest2(bface->tt); - bface->forg = org(fliptets[0]); - } + if (fc->chkencflag & 4) { + enqueuetetrahedron(&(fliptets[0])); } // Update the point-to-tet map. - setpoint2tet(pa, encode(fliptets[0])); - setpoint2tet(pb, encode(fliptets[0])); - setpoint2tet(pc, encode(fliptets[0])); - setpoint2tet(pd, encode(fliptets[0])); + setpoint2tet(pa, (tetrahedron) fliptets[0].tet); + setpoint2tet(pb, (tetrahedron) fliptets[0].tet); + setpoint2tet(pc, (tetrahedron) fliptets[0].tet); + setpoint2tet(pd, (tetrahedron) fliptets[0].tet); - if (flipflag > 0) { + if (fc->enqflag > 0) { // Queue faces which may be locally non-Delaunay. - for (i = 0; i < 3; i++) { - esym(fliptets[0], newface); - flippush(flipstack, &newface); - enextself(fliptets[0]); + flippush(flipstack, &(fliptets[0])); // [a,b,c] (opposite to new point). + if (fc->enqflag > 1) { + for (i = 0; i < 3; i++) { + esym(fliptets[0], newface); + flippush(flipstack, &newface); + enextself(fliptets[0]); + } } - flippush(flipstack, &(fliptets[0])); } recenttet = fliptets[0]; @@ -7959,7 +8102,7 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, /////////////////////////////////////////////////////////////////////////////// // // -// flipnm() Try to flip an edge through a sequence of elementary flips. // +// flipnm() Flip an edge through a sequence of elementary flips. // // // // 'abtets' is an array of 'n' tets in the star of edge [a,b].These tets are // // ordered in a counterclockwise cycle with respect to the vector a->b, i.e.,// @@ -7984,7 +8127,6 @@ void tetgenmesh::flip41(triface* fliptets, int hullflag, int flipflag, // - Neither a nor b is 'dummypoint'. // // - [a,b] must not be a segment. // // // -// // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, @@ -7992,27 +8134,19 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, { triface fliptets[3], spintet, flipedge; triface *tmpabtets, *parytet; - face checksh; - face checkseg, *paryseg; point pa, pb, pc, pd, pe, pf; - point tmppts[3]; - REAL abovept[3]; - REAL ori, ori1, ori2; + REAL ori; + int hullflag, hulledgeflag; int reducflag, rejflag; - int hullflag; int reflexlinkedgecount; int edgepivot; int n1, nn; + int t1ver; int i, j; pa = org(abtets[0]); pb = dest(abtets[0]); - if (b->verbose > 2) { - printf(" flipnm(%d): (%d, %d) - n(%d), e(%d).\n", level, pointmark(pa), - pointmark(pb), n, abedgepivot); - } - if (n > 3) { // Try to reduce the size of the Star(ab) by flipping a face in it. reflexlinkedgecount = 0; @@ -8020,9 +8154,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, for (i = 0; i < n; i++) { // Let the face of 'abtets[i]' be [a,b,c]. if (checksubfaceflag) { - // Do not flip this face if it is a constraining face. - tspivot(abtets[i], checksh); - if (checksh.sh != NULL) { + if (issubface(abtets[i])) { continue; // Skip a subface. } } @@ -8031,32 +8163,20 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, (elemcounter(abtets[(i - 1 + n) % n]) > 1)) { continue; } + pc = apex(abtets[i]); pd = apex(abtets[(i + 1) % n]); pe = apex(abtets[(i - 1 + n) % n]); if ((pd == dummypoint) || (pe == dummypoint)) { - // [a,b,c] is a hull face, it is not flipable. - continue; + continue; // [a,b,c] is a hull face. } - if (checkinverttetflag) { - // The mesh contains inverted (or degenerated) elements. - // Only do check if both elements are valid. - if (pc != dummypoint) { - ori = orient3d(pa, pb, pc, pd); - if (ori < 0) { - ori = orient3d(pb, pa, pc, pe); - } - if (ori >= 0) { - continue; // An invalid tet. - } - } else { - continue; - } - } // if (checkinverttetflag) - reducflag = 0; // Not reducible. + + // Decide whether [a,b,c] is flippable or not. + reducflag = 0; hullflag = (pc == dummypoint); // pc may be dummypoint. + hulledgeflag = 0; if (hullflag == 0) { ori = orient3d(pb, pc, pd, pe); // Is [b,c] locally convex? if (ori > 0) { @@ -8070,8 +8190,11 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, } else if (ori == 0) { // [a,b] is flat. if (n == 4) { - // The "flat" tet can be removed immedately by a 3-to-2 flip. + // The "flat" tet can be removed immediately by a 3-to-2 flip. reducflag = 1; + // Check if [e,d] is a hull edge. + pf = apex(abtets[(i + 2) % n]); + hulledgeflag = (pf == dummypoint); } } } @@ -8096,16 +8219,27 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, if (ori < 0) { // Found a 4-to-4 flip: [a,b] => [e,d] reducflag = 1; - ori = 0; // Signal as a 4-to-4 flip (like a co-palanar case). + ori = 0; // Signal as a 4-to-4 flip (like a co-planar case). + hulledgeflag = 1; // [e,d] is a hull edge. } } } } // if (hullflag) + if (reducflag) { + if (nonconvex && hulledgeflag) { + // We will create a hull edge [e,d]. Make sure it does not exist. + if (getedge(pe, pd, &spintet)) { + // The 2-to-3 flip is not a topological valid flip. + reducflag = 0; + } + } + } + if (reducflag) { // [a,b,c] could be removed by a 2-to-3 flip. rejflag = 0; - if (fc != NULL) { + if (fc->checkflipeligibility) { // Check if the flip can be performed. rejflag = checkflipeligibility(1, pa, pb, pc, pd, pe, level, abedgepivot, fc); @@ -8114,7 +8248,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Do flip: [a,b,c] => [e,d]. fliptets[0] = abtets[i]; fsym(fliptets[0], fliptets[1]); // abtets[i-1]. - flip23(fliptets, hullflag, 0, 0); + flip23(fliptets, hullflag, fc); // Shrink the array 'abtets', maintain the original order. // Two tets 'abtets[i-1] ([a,b,e,c])' and 'abtets[i] ([a,b,c,d])' @@ -8132,21 +8266,19 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // [n-2] |___________| [n-2] |___________| // [n-1] |___________| [n-1] |_[i]_2-t-3_| // - eprevself(fliptets[0]); - esymself(fliptets[0]); - enextself(fliptets[0]); // [a,b,e,d] + edestoppoself(fliptets[0]); // [a,b,e,d] // Increase the counter of this new tet (it is in Star(ab)). - increaseelemcounter(fliptets[0]); //marktest(fliptets[0]); + increaseelemcounter(fliptets[0]); abtets[(i - 1 + n) % n] = fliptets[0]; for (j = i; j < n - 1; j++) { abtets[j] = abtets[j + 1]; // Upshift } // The last entry 'abtets[n-1]' is empty. It is used in two ways: - // (i) it remebers the vertex 'c' (in 'abtets[n-1].tet'), and - // (ii) it remebers the position [i] where this flip took place. + // (i) it remembers the vertex 'c' (in 'abtets[n-1].tet'), and + // (ii) it remembers the position [i] where this flip took place. // These informations let us to either undo this flip or recover // the original edge link (for collecting new created tets). - //abtets[n - 1] = fliptets[1]; // [e,d,b,c] is remebered. + //abtets[n - 1] = fliptets[1]; // [e,d,b,c] is remembered. abtets[n - 1].tet = (tetrahedron *) pc; abtets[n - 1].ver = 0; // Clear it. // 'abtets[n - 1].ver' is in range [0,11] -- only uses 4 bits. @@ -8167,27 +8299,28 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Star(ab) is reduced. Try to flip the edge [a,b]. nn = flipnm(abtets, n - 1, level, abedgepivot, fc); - if (nn > 2) { + if (nn == 2) { + // The edge has been flipped. + return nn; + } else { // if (nn > 2) // The edge is not flipped. if (fc->unflip || (ori == 0)) { // Undo the previous 2-to-3 flip, i.e., do a 3-to-2 flip to // transform [e,d] => [a,b,c]. - // 'ori == 0' means that the previous flip created a degenrated + // 'ori == 0' means that the previous flip created a degenerated // tet. It must be removed. - // Remeber that 'abtets[i-1]' is [a,b,e,d]. We can use it to + // Remember that 'abtets[i-1]' is [a,b,e,d]. We can use it to // find another two tets [e,d,b,c] and [e,d,c,a]. fliptets[0] = abtets[(i-1 + (n-1)) % (n-1)]; // [a,b,e,d] - eprevself(fliptets[0]); - esymself(fliptets[0]); - enextself(fliptets[0]); // [e,d,a,b] + edestoppoself(fliptets[0]); // [e,d,a,b] fnext(fliptets[0], fliptets[1]); // [1] is [e,d,b,c] fnext(fliptets[1], fliptets[2]); // [2] is [e,d,c,a] assert(apex(fliptets[0]) == oppo(fliptets[2])); // SELF_CHECK // Restore the two original tets in Star(ab). - flip32(fliptets, hullflag, 0, 0); + flip32(fliptets, hullflag, fc); // Marktest the two restored tets in Star(ab). for (j = 0; j < 2; j++) { - increaseelemcounter(fliptets[j]); //marktest(fliptets[j]); + increaseelemcounter(fliptets[j]); } // Expand the array 'abtets', maintain the original order. for (j = n - 2; j>= i; j--) { @@ -8203,30 +8336,17 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Pop two (flipped) tets from the stack. cavetetlist->objects -= 2; } - } // if (upflip || (ori == 0)) + } // if (unflip || (ori == 0)) } // if (nn > 2) - if (nn == 2) { //if ((nn == 2) || !fullsearch) { - // The edge has been flipped. - return nn; - } if (!fc->unflip) { // The flips are not reversed. The current Star(ab) can not be - // further reduced. Return its size (# of tets). + // further reduced. Return its current size (# of tets). return nn; } // unflip is set. // Continue the search for flips. - } else { - if (b->verbose > 2) { - printf(" -- Reject a 2-to-3 flip at star face (%d, %d, %d)", - pointmark(pa), pointmark(pb), pointmark(pc)); - printf(", link (%d)\n", level); - } - if (fc != NULL) { - fc->rejf23count++; - } - } // if (rejflag) + } } // if (reducflag) } // i @@ -8235,16 +8355,6 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // There are reflex edges in the Link(ab). if (((b->fliplinklevel < 0) && (level < autofliplinklevel)) || ((b->fliplinklevel >= 0) && (level < b->fliplinklevel))) { - // Record the largest level. - if ((level + 1) > maxfliplinklevel) { - maxfliplinklevel = level + 1; - } - if (fc != NULL) { - // Increase the link level counter. - if ((level + 1) > fc->maxflippedlinklevelcount) { - fc->maxflippedlinklevelcount = level + 1; - } - } // Try to reduce the Star(ab) by flipping a reflex edge in Link(ab). for (i = 0; i < n; i++) { // Do not flip this face [a,b,c] if there are two Stars involved. @@ -8254,25 +8364,14 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, } pc = apex(abtets[i]); if (pc == dummypoint) { - continue; // [a,b,dummypoint] is a hull edge. + continue; // [a,b] is a hull edge. } pd = apex(abtets[(i + 1) % n]); pe = apex(abtets[(i - 1 + n) % n]); if ((pd == dummypoint) || (pe == dummypoint)) { continue; // [a,b,c] is a hull face. } - if (checkinverttetflag) { - // The mesh contains inverted (or degenerated) elements. - // Only do check if both elements are valid. - // assert(pc != dummypoint); - ori = orient3d(pa, pb, pc, pd); - if (ori < 0) { - ori = orient3d(pb, pa, pc, pe); - } - if (ori >= 0) { - continue; // An invalid tet. - } - } // if (checkinverttetflag) + edgepivot = 0; // No edge is selected yet. @@ -8298,21 +8397,15 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // An edge is selected. if (checksubsegflag) { // Do not flip it if it is a segment. - tsspivot1(flipedge, checkseg); - if (checkseg.sh != NULL) { - if (b->verbose > 2) { - printf(" -- Can't flip a link(%d) segment (%d, %d).\n", - level, pointmark(org(flipedge)), pointmark(dest(flipedge))); - } - if (fc != NULL) { - fc->encsegcount++; - if (fc->collectencsegflag) { - if (!sinfected(checkseg)) { - // Queue this segment in list. - sinfect(checkseg); - caveencseglist->newindex((void **) &paryseg); - *paryseg = checkseg; - } + if (issubseg(flipedge)) { + if (fc->collectencsegflag) { + face checkseg, *paryseg; + tsspivot1(flipedge, checkseg); + if (!sinfected(checkseg)) { + // Queue this segment in list. + sinfect(checkseg); + caveencseglist->newindex((void **) &paryseg); + *paryseg = checkseg; } } continue; @@ -8327,7 +8420,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, spintet = flipedge; while (1) { n1++; - j += (elemcounter(spintet)); //if (marktested(spintet)) j++; + j += (elemcounter(spintet)); fnextself(spintet); if (spintet.tet == flipedge.tet) break; } @@ -8339,14 +8432,8 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Only two tets can be marktested. assert(j == 2); - flipstarcount++; - // Record the maximum star size. - if (n1 > maxflipstarsize) { - maxflipstarsize = n1; - } if ((b->flipstarsize > 0) && (n1 > b->flipstarsize)) { - // The star size exceeds the given limit (-LL__). - skpflipstarcount++; + // The star size exceeds the given limit. continue; // Do not flip it. } @@ -8363,15 +8450,6 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, fnextself(spintet); if (spintet.tet == flipedge.tet) break; } - // SELF_CHECK BEGIN - // These two tets are inside both of the Stars. - assert(elemcounter(tmpabtets[0]) == 2); - assert(elemcounter(tmpabtets[1]) == 2); - // Marktest the tets in Star(flipedge) but not in Star(ab). - for (j = 2; j < n1; j++) { - assert(elemcounter(tmpabtets[j]) == 1); - //marktest(tmpabtets[j]); - } // Try to flip the selected edge away. nn = flipnm(tmpabtets, n1, level + 1, edgepivot, fc); @@ -8392,10 +8470,8 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, esymself(spintet); eprevself(spintet); // [a,b,e,d] } // edgepivot == 2 - //assert(!marktested(spintet)); // It's a new tet. - assert(elemcounter(spintet) == 0); - //marktest(spintet); // It is in Star(ab). - increaseelemcounter(spintet); + assert(elemcounter(spintet) == 0); // It's a new tet. + increaseelemcounter(spintet); // It is in Star(ab). // Put the new tet at [i-1]-th entry. abtets[(i - 1 + n) % n] = spintet; for (j = i; j < n - 1; j++) { @@ -8422,7 +8498,10 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // Continue to flip the edge [a,b]. nn = flipnm(abtets, n - 1, level, abedgepivot, fc); - if (nn > 2) { + if (nn == 2) { + // The edge has been flipped. + return nn; + } else { // if (nn > 2) { // The edge is not flipped. if (fc->unflip) { // Recover the flipped edge ([c,b] or [a,c]). @@ -8476,7 +8555,6 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, enextself(fliptets[1]); // [a,b,c,d] } // edgepivot == 2 for (j = 0; j < 2; j++) { - assert(elemcounter(fliptets[j]) == 0); // SELF_CHECK increaseelemcounter(fliptets[j]); } // Insert the two recovered tets into Star(ab). @@ -8488,10 +8566,6 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, } // if (unflip) } // if (nn > 2) - if (nn == 2) { //if ((nn == 2) || !fullsearch) { - // The edge has been flipped. - return nn; - } if (!fc->unflip) { // The flips are not reversed. The current Star(ab) can not be // further reduced. Return its size (# of tets). @@ -8500,7 +8574,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // unflip is set. // Continue the search for flips. } else { - // The seclected edge is not flipped. + // The selected edge is not flipped. if (fc->unflip) { // The memory should already be freed. assert(nn == n1); @@ -8517,72 +8591,34 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, delete [] tmpabtets; } } // i - } else { - if (b->verbose > 2) { - printf(" -- Maximal link level (%d) reached at edge (%d, %d).\n", - level, pointmark(org(abtets[0])), pointmark(dest(abtets[0]))); - } - if (fc != NULL) { - fc->misfliplinklevelcount++; - } } // if (level...) } // if (reflexlinkedgecount > 0) } else { // Check if a 3-to-2 flip is possible. - pc = apex(abtets[0]); - pd = apex(abtets[1]); - pe = apex(abtets[2]); - - // Check if one of them is dummypoint. If so, we rearrange the vertices - // c, d, and e into p0, p1, and p2, such that p2 is the dummypoint. + // Let the three apexes be c, d,and e. Hull tets may be involved. If so, + // we rearrange them such that the vertex e is dummypoint. hullflag = 0; - if (pc == dummypoint) { + + if (apex(abtets[0]) == dummypoint) { + pc = apex(abtets[1]); + pd = apex(abtets[2]); + pe = apex(abtets[0]); hullflag = 1; - tmppts[0] = pd; - tmppts[1] = pe; - tmppts[2] = pc; - } else if (pd == dummypoint) { - hullflag = 1; - tmppts[0] = pe; - tmppts[1] = pc; - tmppts[2] = pd; - } else if (pe == dummypoint) { - hullflag = 1; - tmppts[0] = pc; - tmppts[1] = pd; - tmppts[2] = pe; + } else if (apex(abtets[1]) == dummypoint) { + pc = apex(abtets[2]); + pd = apex(abtets[0]); + pe = apex(abtets[1]); + hullflag = 2; } else { - tmppts[0] = pc; - tmppts[1] = pd; - tmppts[2] = pe; + pc = apex(abtets[0]); + pd = apex(abtets[1]); + pe = apex(abtets[2]); + hullflag = (pe == dummypoint) ? 3 : 0; } reducflag = 0; rejflag = 0; - if (checkinverttetflag) { - // Only do flip if no tet is inverted (or degenerated). - if (hullflag == 0) { - ori = orient3d(pa, pb, pc, pd); - if (ori < 0) { - ori = orient3d(pa, pb, pd, pe); - if (ori < 0) { - ori = orient3d(pa, pb, pe, pc); - } - } - } else { - ori = orient3d(pa, pb, tmppts[0], tmppts[1]); - } - if (ori >= 0) { - if (b->verbose > 2) { - printf(" -- Hit a non-valid tet (%d, %d) - (%d, %d, %d)", - pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), - pointmark(pe)); - printf(" at link(%d)\n", level); - } - return 3; - } - } // if (checkinverttetflag) if (hullflag == 0) { // Make sure that no inverted tet will be created, i.e. the new tets @@ -8593,98 +8629,63 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, if (ori < 0) { reducflag = 1; } - } else { - if (b->verbose > 2) { - printf(" -- Hit a chrismastree (%d, %d) - (%d, %d, %d)", - pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), - pointmark(pe)); - printf(" at link(%d)\n", level); - } - if (fc != NULL) { - fc->chrismastreecount++; - } } } else { - // [a,b] is a hull edge. Moreover, the tet [a,b,p0,p1] is a hull tet - // ([a,b,p0] and [a,b,p1] are two hull faces). - // This can happen when it is in the middle of a 4-to-4 flip. - // Note that [a,b] may even be a non-convex hull edge. + // [a,b] is a hull edge. + // Note: This can happen when it is in the middle of a 4-to-4 flip. + // Note: [a,b] may even be a non-convex hull edge. if (!nonconvex) { - // [a,b], [a,b,p0] and [a,b,p1] are on the convex hull. - ori = orient3d(pa, pb, tmppts[0], tmppts[1]); + // The mesh is convex, only do flip if it is a coplanar hull edge. + ori = orient3d(pa, pb, pc, pd); if (ori == 0) { - // They four vertices are coplanar. A 2-to-2 flip is possible if - // [a,b] and [p0,p1] are intersecting each other. - // NOTE: The following test is not robust, should be replaced in - // the future. 2011-12-01. - calculateabovepoint4(pa, pb, tmppts[0], tmppts[1]); - for (j = 0; j < 3; j++) { - abovept[j] = dummypoint[j]; - } - // Make sure that no inverted face will be created, i.e., [p1,p0, - // abvpt,pa] and [p0,p1,abvpt,pb] must be valid tets. - ori1 = orient3d(tmppts[0], tmppts[1], abovept, pa); - ori2 = orient3d(tmppts[0], tmppts[1], abovept, pb); - if (ori1 * ori2 < 0) { - reducflag = 1; // Flipable. - } - if (!reducflag) { - if (b->verbose > 2) { - printf(" -- Hit a degenerate chrismastree (%d, %d)", - pointmark(pa), pointmark(pb)); - printf(" - (%d, %d, -1) at link(%d)\n", - pointmark(tmppts[0]), pointmark(tmppts[1]), level); - } - if (fc != NULL) { - fc->chrismastreecount++; - } - } - } else { - if (b->verbose > 2) { - printf(" -- Hit a convex hull edge (%d, %d) at link(%d).\n", - pointmark(pa), pointmark(pb), level); - } - if (fc != NULL) { - fc->convexhulledgecount++; - } - } - } else { // if (nonconvex) - // [a,b,p0] and [a,b,p1] must be two subfaces. - // Since [a,b] is not a segment. A 3-to-2 flip (including a 2-to-2 - // flip) is possible. - // Here we only do flip if there are exactly three tets containing - // the edge [p0,p1]. In this case, the other two tets at [p0,p1] - // (not [a,b,p0,p1]) must be valid. Since they already exist. - for (j = 0; j < 3; j++) { - if (apex(abtets[j]) == dummypoint) { - flipedge = abtets[(j + 1) % 3]; // [a,b,p0,p1]. - break; - } - } - // assert(j < 3); - eprevself(flipedge); - esymself(flipedge); - enextself(flipedge); // [p0,p1,a,b]. - assert(apex(flipedge) == pa); - spintet = flipedge; - j = 0; - while (1) { - j++; - fnextself(spintet); - if (spintet.tet == flipedge.tet) break; - } - if (j == 3) { reducflag = 1; - } else { - if (b->verbose > 2) { - printf(" -- Hit a hull edge (%d, %d) at link(%d).\n", - pointmark(pa), pointmark(pb), level); - } - //if (fc != NULL) { - // fc->convexhulledgecount++; - //} } + } else { // nonconvex + reducflag = 1; } + if (reducflag == 1) { + // [a,b], [a,b,c] and [a,b,d] are on the convex hull. + // Make sure that no inverted tet will be created. + point searchpt = NULL, chkpt; + REAL bigvol = 0.0, ori1, ori2; + // Search an interior vertex which is an apex of edge [c,d]. + // In principle, it can be arbitrary interior vertex. To avoid + // numerical issue, we choose the vertex which belongs to a tet + // 't' at edge [c,d] and 't' has the biggest volume. + fliptets[0] = abtets[hullflag % 3]; // [a,b,c,d]. + eorgoppoself(fliptets[0]); // [d,c,b,a] + spintet = fliptets[0]; + while (1) { + fnextself(spintet); + chkpt = oppo(spintet); + if (chkpt == pb) break; + if ((chkpt != dummypoint) && (apex(spintet) != dummypoint)) { + ori = -orient3d(pd, pc, apex(spintet), chkpt); + assert(ori > 0); + if (ori > bigvol) { + bigvol = ori; + searchpt = chkpt; + } + } + } + if (searchpt != NULL) { + // Now valid the configuration. + ori1 = orient3d(pd, pc, searchpt, pa); + ori2 = orient3d(pd, pc, searchpt, pb); + if (ori1 * ori2 >= 0.0) { + reducflag = 0; // Not valid. + } else { + ori1 = orient3d(pa, pb, searchpt, pc); + ori2 = orient3d(pa, pb, searchpt, pd); + if (ori1 * ori2 >= 0.0) { + reducflag = 0; // Not valid. + } + } + } else { + // No valid searchpt is found. + reducflag = 0; // Do not flip it. + } + } // if (reducflag == 1) } // if (hullflag == 1) if (reducflag) { @@ -8695,10 +8696,12 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // the surface mesh will be automatically performed within the // 3-to-2 flip. nn = 0; + edgepivot = -1; // Re-use it. for (j = 0; j < 3; j++) { - tspivot(abtets[j], checksh); - if (checksh.sh != NULL) { + if (issubface(abtets[j])) { nn++; // Found a subface. + } else { + edgepivot = j; } } assert(nn < 3); @@ -8707,32 +8710,39 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, // the boundary recovery phase. The neighbor subface is not yet // recovered. This edge should not be flipped at this moment. rejflag = 1; + } else if (nn == 2) { + // Found two subfaces. A 2-to-2 flip is possible. Validate it. + // Below we check if the two faces [p,q,a] and [p,q,b] are subfaces. + eorgoppo(abtets[(edgepivot + 1) % 3], spintet); // [q,p,b,a] + if (issubface(spintet)) { + rejflag = 1; // Conflict to a 2-to-2 flip. + } else { + esymself(spintet); + if (issubface(spintet)) { + rejflag = 1; // Conflict to a 2-to-2 flip. + } + } } } - if (!rejflag && (fc != NULL)) { + if (!rejflag && fc->checkflipeligibility) { // Here we must exchange 'a' and 'b'. Since in the check... function, // we assume the following point sequence, 'a,b,c,d,e', where // the face [a,b,c] will be flipped and the edge [e,d] will be // created. The two new tets are [a,b,c,d] and [b,a,c,e]. - rejflag = checkflipeligibility(2, tmppts[0], tmppts[1], tmppts[2], - pb, pa, level, abedgepivot, fc); + rejflag = checkflipeligibility(2, pc, pd, pe, pb, pa, level, + abedgepivot, fc); } if (!rejflag) { // Do flip: [a,b] => [c,d,e] - flip32(abtets, hullflag, 0, 0); - sucflipstarcount++; + flip32(abtets, hullflag, fc); if (fc->remove_ndelaunay_edge) { if (level == 0) { - // It is the desired removing edge. - if (tetprism_vol_sum >= fc->bak_tetprism_vol) { - if (b->verbose > 2) { - printf(" -- Reject to flip (%d, %d) at link(%d)\n", - pointmark(pa), pointmark(pb), level); - printf(" due to an increased volume (%.17g).\n", - tetprism_vol_sum - fc->bak_tetprism_vol); - } - // flip back: [c,d,e] => [a,b]. - flip23(abtets, hullflag, 0, 0); + // It is the desired removing edge. Check if we have improved + // the objective function. + if ((fc->tetprism_vol_sum >= 0.0) || + (fabs(fc->tetprism_vol_sum) < fc->bak_tetprism_vol)) { + // No improvement! flip back: [c,d,e] => [a,b]. + flip23(abtets, hullflag, fc); // Increase the element counter -- They are in cavity. for (j = 0; j < 3; j++) { increaseelemcounter(abtets[j]); @@ -8762,15 +8772,7 @@ int tetgenmesh::flipnm(triface* abtets, int n, int level, int abedgepivot, } } // if (fc->collectnewtets) return 2; - } else { - if (b->verbose > 2) { - printf(" -- Reject a 3-to-2 flip (%d, %d) at link(%d).\n", - pointmark(pa), pointmark(pb), level); - } - if (fc != NULL) { - fc->rejf32count++; - } - } // if (rejflag) + } } // if (reducflag) } // if (n == 3) @@ -8825,7 +8827,7 @@ int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, // 'abtets[1]' is [d,c,e,a] or [#,#,#,a]. if (fc->unflip) { // Do a 2-to-3 flip to recover the edge [a,b]. There may be hull tets. - flip23(abtets, 1, 0, 0); + flip23(abtets, 1, fc); if (fc->collectnewtets) { // Pop up new (flipped) tets from the stack. if (abedgepivot == 0) { @@ -8865,7 +8867,7 @@ int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, fnext(fliptets[1], fliptets[2]); // [e,d,c,a] // Do a 3-to-2 flip: [e,d] => [a,b,c]. // NOTE: hull tets may be invloved. - flip32(fliptets, 1, 0, 0); + flip32(fliptets, 1, fc); // Expand the array 'abtets', maintain the original order. // The new array length is (i+1). for (j = i - 1; j >= t; j--) { @@ -8952,9 +8954,6 @@ int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, printf(" Release %d spaces at f[%d].\n", n1, i); } delete [] tmpabtets; - } else { - assert(fliptype == 0); // Not a saved flip. - assert(0); // Should be not possible. } } // i @@ -8963,668 +8962,7 @@ int tetgenmesh::flipnm_post(triface* abtets, int n, int nn, int abedgepivot, /////////////////////////////////////////////////////////////////////////////// // // -// lawsonflip3d() A three-dimensional Lawson's flip algorithm. // -// // -// The basic idea of Lawson's algorithm is to flip every face of the triang- // -// ulation which is not locally Delaunay until no such face exists, then the // -// triangulation is a DT. However, in 3D, it is common that a face which is // -// not locally Delaunay and is not flippable. Hence, Laowson's algorithm may // -// get stuck. It is still an open problem, whether there exists a flip algo- // -// rithm which has a guarantee to create a DT in 3D. // -// // -// If only one vertex is added into a DT, then Lawson's flip algorithm is // -// guaranteed to transform it into a new DT [Joe'91]. Moreover, an arbitrary // -// order of flips is sufficient [Edelsbrunner & Shah'96]. // -// // -// In practice, it is desired to remove not locally Delaunay faces by flips // -// as many as possible. For this purpose, a second queue is used to store // -// the not locally Delaunay faces which are not flippable, and try them at a // -// later time. // -// // -// If 'newpt' (p) is not NULL, it is a new vertex just inserted into the // -// tetrahedralization T. // -// // -// 'flipflag' indicates the property of the tetrahedralization 'T' which // -// does not include 'p' yet. // -// // -// If 'peelsliverflag' is set, the purpose of calling Lawson's flip is to // -// remove "hull slivers". This flag only works with a non-convex mesh, i.e., // -// the mesh must contains boundaries (segments and subfaces). // -// // -// 'chkencflag' indicates whether segments, subfaces, and tets should be // -// checked (for encroaching and quality) after flips. // -// // -/////////////////////////////////////////////////////////////////////////////// - -long tetgenmesh::lawsonflip3d(point newpt, int flipflag, int peelsliverflag, - int chkencflag, int flipedgeflag) -{ - badface *popface, *bface; - triface fliptets[5], baktets[2]; - triface fliptet, neightet, *parytet; - face checksh, *parysh; - face checkseg, *paryseg; - point *ppt, pd, pe, pf; - long flipcount; - REAL sign, ori; - int convflag; - int n, i; - - // For removing hull slivers. - face neighsh; - point p1, p2; - point pa, pb, pc, rempt; - REAL ang; - long tetpeelcount; - int remflag; - - flipconstraints fc; - - if (b->verbose > 2) { - printf(" Lawson flip %ld faces.\n", flippool->items); - } - - flipcount = flip23count + flip32count + flip44count; - tetpeelcount = opt_sliver_peels; - - if (flipedgeflag) { - fc.remove_ndelaunay_edge = 1; - fc.unflip = 1; // Unflip if the edge is not flipped. - fc.collectnewtets = 1; - assert(cavetetlist->objects == 0l); - assert(calc_tetprism_vol == 1); // Swith on. - } else { - assert(unflipqueue->objects == 0); // The second queue must be empty. - } - - while (1) { - - while (flipstack != (badface *) NULL) { - - // Pop a face from the stack. - popface = flipstack; - flipstack = flipstack->nextitem; // The next top item in stack. - fliptet = popface->tt; - flippool->dealloc((void *) popface); - - // Skip it if it is a dead tet (destroyed by previous flips). - if (isdeadtet(fliptet)) continue; - // Skip it if it is not the same tet as we saved. - if (!facemarked(fliptet)) continue; - - unmarkface(fliptet); - - if (ishulltet(fliptet)) { - // It is a hull tet. - if (((flipflag == 4) || peelsliverflag) && !b->convex) { - fliptet.ver = epivot[fliptet.ver & 3]; - if (oppo(fliptet) == dummypoint) { - // It's a hull face (oppo(fliptet) == dummypoint). - // Check if there exists a "hull sliver". - fsymself(fliptet); - tspivot(fliptet, checksh); - assert(checksh.sh != NULL); - for (i = 0; i < 3; i++) { - sspivot(checksh, checkseg); - if (checkseg.sh == NULL) { - spivot(checksh, neighsh); - assert(neighsh.sh != NULL); - if (sorg(checksh) != sdest(neighsh)) { - sesymself(neighsh); - } - stpivot(neighsh, neightet); - if (neightet.tet == fliptet.tet) { - // Found a hull sliver 'neightet' [d,e,a,b], where [d,e,a] - // and [e,d,b] are two hull faces. Normally, a 3-to-2 flip - // (including a 2-to-2 flip on hull subfaces) can remove - // this hull sliver. - // A special case is the existence of a hull tet [b,a,d,-1] - // or [a,b,e,-1]. It was creared by a previous hull tet - // removal. Moreover, 'd' or 'e' might be Steiner points - // on segments [a,b]. In this case, eithe [a,d],[b,d] or - // [a,e],[b,e] are subsegments. If so, a 4-to-1 flip - // (including a 3-to-1, and maybe a 2-to-1 flip) should be - // applied to remove an exterior vertex. - // First check if the face [b,a,d] is a hull face. - eprev(neightet, fliptets[0]); - esymself(fliptets[0]); // [d,a,b,e] - enextself(fliptets[0]); // [a,b,d,e] - fsymself(fliptets[0]); // [b,a,d,#] - if (oppo(fliptets[0]) != dummypoint) { - // Second check if the face [a,b,e] is a hull face. - enext(neightet, fliptets[0]); - esymself(fliptets[0]); // [a,e,b,d] - eprevself(fliptets[0]); // [b,a,e,d] - fsymself(fliptets[0]); // [b,a,e,#] - } - - if (oppo(fliptets[0]) != dummypoint) { - // Make sure we do not create an "inverted triangle" in the - // boundary, i.e., in exactly planar case, d and e must - // lie in the different sides of the edge [a,b]. - // If the dihedral angle formed by [a,b,e] and [a,b,d] is - // larger than 90 degree, we can remove [a,b,e,d]. - fliptets[0] = neightet; // [e,d,a,b] - eprevself(fliptets[0]); - esymself(fliptets[0]); - enextself(fliptets[0]); // [a,b,e,d]. - pa = org(fliptets[0]); - pb = dest(fliptets[0]); - p1 = apex(fliptets[0]); // pe - p2 = oppo(fliptets[0]); // pd - ang = facedihedral(pa, pb, p1, p2); - ang *= 2.0; - if (ang > PI) { - if (b->verbose > 2) { - printf(" Remove a hull sliver (%d, %d, %d, %d).\n", - pointmark(org(fliptet)), pointmark(dest(fliptet)), - pointmark(apex(fliptet)), pointmark(oppo(fliptet))); - } - // Remove the ill tet from bounday. - fliptets[0] = neightet; // [e,d,a,b] - fnext(fliptets[0], fliptets[1]); // [e,d,b,c] - fnext(fliptets[1], fliptets[2]); // [e,d,c,a] - // FOR DEBUG - fnext(fliptets[2], fliptets[3]); - assert(fliptets[3].tet == neightet.tet); - assert(oppo(fliptets[1]) == dummypoint); - // Do a 3-to-2 flip to remove the ill tet. Two hull tets - // are removed toether. Two hull subfaces are flipped. - flip32(fliptets, 1, flipflag, 0); - // Update counters. - flip32count--; - flip22count--; - opt_sliver_peels++; - } - } else { - // There exists a thrid hull tet at vertex. - rempt = apex(fliptets[0]); - if (pointmark(rempt) > - (in->numberofpoints - (in->firstnumber ? 0 : 1))) { - if (pointtype(rempt) == FREESEGVERTEX) { - st_segref_count--; - } else if (pointtype(rempt) == FREEFACETVERTEX) { - st_facref_count--; - } else { - assert(0); // Impossible. - } - if (b->verbose > 2) { - printf(" Remove an exterior Steiner vertex %d.\n", - pointmark(rempt)); - } - if (removevertexbyflips(rempt)) { - // exsteinercount++; - } else { - assert(0); // Not possible. - } - } else { - //if (b->verbose > 2) { - // printf(" Remove an exterior input vertex %d.\n", - // pointmark(rempt)); - //} - // Comment: We do not remove an input point. - } - } - break; - } - } // if (checkseg.sh == NULL) - senextself(checksh); - } // i - } else { - // It's a hull edge. - assert(apex(fliptet) == dummypoint); - if (!peelsliverflag) { - // The hull edge may be not locally Delaunay. Put interior - // faces at this edge into 'flipstack' for flipping. - neightet = fliptet; // [a,b,c,d] ('c' is dummypoint). - fnextself(neightet); // [a,b,d,#1] ([a,b,d] is a hull face). - while (1) { - fnextself(neightet); // [a,b,#1,#2] - if (oppo(neightet) != dummypoint) { - // It is an interior face. - flippush(flipstack, &neightet); - } else { - // We assume the interior of the domain is connected. - // Hence we can hit hull faces only twice. - break; - } - } // while (1) - } // if (!peelsliverflag) - } - } // if ((flipflag == 4) || peelsliverflag) - - // Do not flip a hull face/edge UNLESS it is in the process of - // incrementally creating a DT in which the convex hull may be - // enlarged by the flips (when p lies outside of it). - if (flipflag != 1) { - continue; - } - } // if (ishulltet(fliptet)) - - if (peelsliverflag) { - continue; // Only check hull tets. - } - - // Let 'fliptet' be [a,b,c,d], the face [a,b,c] is the flip face. - // Get its opposite tet [b,a,c,e]. - fsym(fliptet, neightet); - - if (ishulltet(neightet)) { - // It is a hull tet. - if (flipflag == 1) { - // Check if the new point is visible by the hull face. - ppt = (point *) neightet.tet; - ori = orient3d(ppt[4], ppt[5], ppt[6], newpt); - if (ori < 0) { - // Visible. Perform a 2-to-3 flip on the flip face. - fliptets[0] = fliptet; // [a,b,c,d], d = newpt. - fliptets[1] = neightet; // [b,a,c,e], c = dummypoint. - flip23(fliptets, 1, flipflag, chkencflag); // flip a hull tet. - //recenttet = fliptets[0]; - } else if (ori == 0) { - // Handle degenerate case ori == 0. - if (oppo(neightet) == newpt) { - // Two hull tets have the same base face. - if (b->verbose > 2) { - printf(" Close an open face (%d, %d, %d)\n", - pointmark(org(fliptet)), pointmark(dest(fliptet)), - pointmark(apex(fliptet))); - } - // The following code connect adjacent tets at corresponding - // sides of the two hull tets. It is hard to understand. - // See an example in 2011-11-11. - // First infect the two hull tets (they will be deleted). - infect(fliptet); - infect(neightet); - // Connect the actual adjacent tets. - for (i = 0; i < 3; i++) { - fnext(fliptet, fliptets[0]); - fnext(neightet, fliptets[1]); - if (!infected(fliptets[0])) { - assert(!infected(fliptets[1])); - bond(fliptets[0], fliptets[1]); - // Update the point-to-tet map. - pa = org(fliptet); - pb = dest(fliptet); - setpoint2tet(pa, encode(fliptets[0])); - setpoint2tet(pb, encode(fliptets[0])); - // Remeber a recent tet for point location. - recenttet = fliptets[0]; - // apex(fliptets[0]) is the new point. The opposite face may - // be not locally Delaunay. Put it in flip stack. - assert(apex(fliptets[0]) == newpt); // SELF_CHECK - esymself(fliptets[0]); - flippush(flipstack, &(fliptets[0])); - assert(apex(fliptets[1]) == newpt); // SELF_CHECK - esymself(fliptets[1]); - flippush(flipstack, &(fliptets[1])); - } - enextself(fliptet); - eprevself(neightet); - } - // Delete the two tets. - tetrahedrondealloc(fliptet.tet); - tetrahedrondealloc(neightet.tet); - // Update the hull size. - hullsize -= 2; - } - } - } // if (flipflag == 1) - - continue; // Do not flip a hull face. - } // if (ishulltet(neightet)) - - if (ishulltet(fliptet)) { - continue; // Do not flip a hull tet. - } - - if ((flipflag == 3) || (flipflag == 4)) { - if (checksubfaceflag) { - // Do not flip a subface. - tspivot(fliptet, checksh); - if (checksh.sh != NULL) { - if (chkencflag & 2) { - // Mesh refinement. - // Put this subface into list. - if (!smarktest2ed(checksh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(checksh); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface. - } - } - continue; - } - } - } // if ((flipflag == 3) || (flipflag == 4)) - - ppt = (point *) fliptet.tet; - pe = oppo(neightet); - - sign = insphere_s(ppt[4], ppt[5], ppt[6], ppt[7], pe); - - if (sign < 0) { - if (b->verbose > 3) { - printf(" A non-Delaunay face (%d, %d, %d) - %d, %d\n", - pointmark(org(fliptet)), pointmark(dest(fliptet)), - pointmark(apex(fliptet)), pointmark(oppo(fliptet)), - pointmark(pe)); - } - - // Try to flip this face. - pd = oppo(fliptet); - // Check the convexity of its three edges. - convflag = 1; - for (i = 0; i < 3; i++) { - p1 = org(fliptet); - p2 = dest(fliptet); - ori = orient3d(p1, p2, pd, pe); - if (ori < 0) { - // A locally non-convex edge. - convflag = -1; - break; - } else if (ori == 0) { - // A locally flat edge. - convflag = 0; - break; - } - enextself(fliptet); - } - - if (convflag > 0) { - // A 2-to-3 flip is found. - fliptets[0] = fliptet; // abcd, d may be the new vertex. - fliptets[1] = neightet; // bace. - if ((flipflag == 1) || (flipflag == 2)) { // CDT boundary recovery. - if (checksubfaceflag) { - // Check if a subface will be flipped. - tspivot(fliptets[0], checksh); - if (checksh.sh != NULL) { - assert(flipflag < 3); // 1 or 2. - // It is updateing a conforming DT or a CDT. - if (b->verbose > 3) { - printf(" Queue a flipped subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - for (i = 0; i < 2; i++) { - tsdissolve(fliptets[i]); // Disconnect the tet->sub bond. - } - stdissolve(checksh); // Disconnect the sub->tet bond. - // Add the missing subface into list. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } // if (checksh.sh != NULL) - } - } // if ((flipflag == 1) || (flipflag == 2)) - flip23(fliptets, 0, flipflag, chkencflag); - //recenttet = fliptets[0]; // for point location. - } else { - // The edge ('fliptet') is non-convex or flat. - if ((flipflag == 3) || (flipflag == 4)) { - // Do not flip a subsegment. - tsspivot1(fliptet, checkseg); - if (checkseg.sh != NULL) { - if (b->verbose > 3) { - printf(" Found a non-Delaunay segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - // Comment: this should be only possible when a new Steiner - // point is inserted on a segment nearby. - if (chkencflag & 1) { - // Put this segment into list. - if (!smarktest2ed(checkseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(checkseg); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. - } - } - continue; - } - } - - // A 3-to-2 or 4-to-4 may be possible. - esym(fliptet, fliptets[0]); // [b,a,d,c] - // assert(apex(fliptets[0]) == pd); - n = 0; - do { - fnext(fliptets[n], fliptets[n + 1]); - n++; - } while ((fliptets[n].tet != fliptet.tet) && (n < 5)); - - if (n == 3) { - // Found a 3-to-2 flip. - if ((flipflag == 1) || (flipflag == 2)) { // CDT boundary recovery. - if (checksubsegflag) { - // Check if the flip edge is subsegment. - tsspivot1(fliptets[0], checkseg); - if (checkseg.sh != NULL) { - if (!sinfected(checkseg)) { - // This subsegment will be flipped. Queue it. - if (b->verbose > 3) { - printf(" Queue a flipped segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - sinfect(checkseg); // Only save it once. - subsegstack->newindex((void **) &paryseg); - *paryseg = checkseg; - } - // Clean tet-to-seg pointers. - for (i = 0; i < 3; i++) { - tssdissolve1(fliptets[i]); - } - // Clean the seg-to-tet pointer. - sstdissolve1(checkseg); - } - } - if (checksubfaceflag) { - // Check if there are subfaces to be flipped. - for (i = 0; i < 3; i++) { - tspivot(fliptets[i], checksh); - if (checksh.sh != NULL) {//if (flipshs[i].sh != NULL) { - if (b->verbose > 2) { - printf(" Queue a flipped subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - tsdissolve(fliptets[i]); // Disconnect the tet->sub bond. - stdissolve(checksh); // Disconnect the sub->tet bond. - // Add the missing subface into list. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } - } - } - } // if ((flipflag == 1) || (flipflag == 2)) - - // Now flip the edge. - flip32(fliptets, 0, flipflag, chkencflag); - //recenttet = fliptets[0]; // for point location. - } else { - // There are more than 3 tets shared at this edge. - if ((n == 4) && (convflag < 0)) { - // Check if a 4-to-4 flip is possible. - pf = apex(fliptets[3]); - if (pf == dummypoint) { - // It is a non-convex hull edge shared by four tets (two hull - // tets and two interior tets). - // Let the two interior tets be [a,b,c,d] and [b,a,c,e] where - // [a,b] be the hull edge, [a,b,c] be the interior face. - // [a,b,d] and [a,b,e] are two hull faces. - // A 4-to-4 flip is possible if the two new tets [e,d,b,c] - // and [e,d,c,a] are valid tets. - // Current status: - // 'fliptets[0]' is [a,b,e,c] - // 'fliptets[1]' is [a,b,c,d] - // 'fliptets[2]' is [a,b,d,f] (hull tet) - // 'fliptets[3]' is [a,b,f,e] (hull tet) - pa = org(fliptets[1]); - pb = dest(fliptets[1]); - pc = apex(fliptets[1]); - p1 = oppo(fliptets[1]); // pd - p2 = apex(fliptets[0]); // pe - ori = orient3d(p2, p1, pb, pc); - if (ori < 0) { - ori = orient3d(p2, p1, pc, pa); - if (ori < 0) { - convflag = -2; // A 4-to-4 flip is possible. - } - } - } - } // if ((n == 4) && (convflag < 0)) - if ((n == 4) && ((convflag == 0) || (convflag == -2))) { - // Found a 4-to-4 flip. - if (b->verbose > 3) { - printf(" A 4-to-4 flip (%d, %d) - (%d, %d).\n", - pointmark(org(fliptet)), pointmark(dest(fliptet)), - pointmark(pd), pointmark(pe)); - } - if ((flipflag == 1) || (flipflag == 2)) { // CDT boundary recovery - if (checksubsegflag) { - // Check if the flip edge is subsegment. - tsspivot1(fliptets[0], checkseg); - if (checkseg.sh != NULL) { - if (!sinfected(checkseg)) { - // This subsegment will be flipped. Queue it. - if (b->verbose > 3) { - printf(" Queue a flipped segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - sinfect(checkseg); // Only save it once. - subsegstack->newindex((void **) &paryseg); - *paryseg = checkseg; - } - // Clean the tet-to-seg pointers. - for (i = 0; i < 4; i++) { - tssdissolve1(fliptets[i]); - } - // Clean the seg-to-tet pointer. - sstdissolve1(checkseg); - } - } - if (checksubfaceflag) { - // Check if there are subfaces to be flipped. - for (i = 0; i < 4; i++) { - tspivot(fliptets[i], checksh); - if (checksh.sh != NULL) { - if (b->verbose > 3) { - printf(" Queue a flipped subface (%d,%d,%d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - tsdissolve(fliptets[i]); // Disconnect the tet->sub bond. - stdissolve(checksh); // Disconnect the sub->tet bond. - // Add the missing subface into list. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } - } - } - } // if ((flipflag == 1) || (flipflag == 2)) - - // First do a 2-to-3 flip. - // Comment: This flip temporarily creates either a degenerated - // tet (convflag == 0) or an inverted tet (convflag < 0). - // It is removed by the followed 3-to-2 flip. - fliptets[0] = fliptet; // tet abcd, d is the new vertex. - baktets[0] = fliptets[2]; - baktets[1] = fliptets[3]; - // The flip may involve hull tets. - flip23(fliptets, 1, flipflag, chkencflag); - // Then do a 3-to-2 flip. - enextesymself(fliptets[0]); // fliptets[0] is edab. - eprevself(fliptets[0]); // tet badc, d is the new vertex. - fliptets[1] = baktets[0]; - fliptets[2] = baktets[1]; - flip32(fliptets, 1, flipflag, chkencflag); - flip23count--; - flip32count--; - flip44count++; - //recenttet = fliptets[0]; // for point location. - } else { - // This edge is shared by more than 4 tets. - if (b->verbose > 2) { - printf(" An unflippable non-Delaunay edge (%d,%d).\n", - pointmark(org(fliptet)), pointmark(dest(fliptet))); - } - remflag = 0; - if (flipedgeflag == 2) { - // Try to flip this edge by my edge flip algorithm. - // Remember the the objective value (volume of all tetprisms). - fc.bak_tetprism_vol = tetprism_vol_sum; - if (removeedgebyflips(&fliptet, &fc) == 2) { - if (b->verbose > 2) { - printf(" Decreased quantity: %.17g.\n", - fc.bak_tetprism_vol - tetprism_vol_sum); - } - // Queue new faces in flipstack. - for (i = 0; i < cavetetlist->objects; i++) { - parytet = (triface *) fastlookup(cavetetlist, i); - if (!isdeadtet(*parytet)) { // Skip a dead tet. - for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { - // Avoid queue a face twice. - fsym(*parytet, neightet); - if (!facemarked(neightet)) { - //flippush(flipstack, parytet); - bface = (badface *) flippool->alloc(); - bface->tt = *parytet; - markface(bface->tt); - bface->forg = org(bface->tt); // An alive badface. - bface->fdest = dest(bface->tt); - bface->fapex = apex(bface->tt); - // bface->foppo = oppo(bface->tt); - // Push this face into stack. - bface->nextitem = flipstack; - flipstack = bface; - } - } // parytet->ver - } - } // i - cavetetlist->restart(); - remflag = 1; - } - } - if (!remflag) { - // Found an unflippable non-Delaunay edge. - if (flipedgeflag > 0) { // if (flipflag > 1) { - // Save this face (of the edge) in a second queue. - unflipqueue->newindex((void **) &bface); - bface->tt = fliptet; - bface->forg = org(fliptet); - bface->fdest = dest(fliptet); - bface->fapex = apex(fliptet); // FOR DEBUG. - } - } - } - } // if (n > 3) - } // if (convflag <= 0) - } // if (sign < 0) - - } // while (flipstack != NULL) - - - break; - - } // while (1) - - - if (b->verbose > 2) { - printf(" Total %ld flips", flip23count + flip32count + flip44count - - flipcount); - if ((flipflag == 4) || peelsliverflag) { - printf(", %ld sliver peels", opt_sliver_peels - tetpeelcount); - } - printf("\n"); - } - - - return flip23count + flip32count + flip44count - flipcount; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// insertvertex() Insert a point into current tetrahedralization. // +// insertpoint() Insert a point into current tetrahedralization. // // // // The Bowyer-Watson (B-W) algorithm is used to add a new point p into the // // tetrahedralization T. It first finds a "cavity", denoted as C, in T, C // @@ -9635,74 +8973,47 @@ long tetgenmesh::lawsonflip3d(point newpt, int flipflag, int peelsliverflag, // C and p. If T is not a DT, then C may be not star-shaped. It must be // // modified so that it becomes star-shaped. // // // -// // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, - face *splitseg, insertvertexflags *ivf) +int tetgenmesh::insertpoint(point insertpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf) { - arraypool *swaplist; // for updating cavity. + arraypool *swaplist; triface *cavetet, spintet, neightet, neineitet, *parytet; triface oldtet, newtet, newneitet; - face checksh, *parysh, neighsh, spinsh; + face checksh, neighsh, *parysh; face checkseg, *paryseg; point *pts, pa, pb, pc, *parypt; - badface *bface; - enum locateresult loc; + enum locateresult loc = OUTSIDE; REAL sign, ori; - REAL rd, cent[3]; REAL attrib, volume; - long cutcount, cutshcount, tetcount = 0; - long bakhullsize; bool enqflag; + int t1ver; int i, j, k, s; - int rejptflag, encptflag; // for protecting balls. - int bgmloc; - - if (b->verbose > 2) { printf(" Insert point %d\n", pointmark(insertpt)); } - // Locate the point. - loc = OUTSIDE; // Set a default value. - if (searchtet->tet != NULL) { loc = (enum locateresult) ivf->iloc; } if (loc == OUTSIDE) { - tetcount = ptloc_count; // Count the number of visited tets. if (searchtet->tet == NULL) { if (!b->weighted) { - if (b->brio_hilbert) { // -b - *searchtet = recenttet; - } else { // -b0 - randomsample(insertpt, searchtet); - } + randomsample(insertpt, searchtet); } else { - // There may exist dangling vertex. + // Weighted DT. There may exist dangling vertex. *searchtet = recenttet; } } // Locate the point. - loc = locate(insertpt, searchtet, ivf->chkencflag); - if (b->verbose > 3) { - printf(" Walk distance (# tets): %ld\n", ptloc_count-tetcount); - } - if (ptloc_max_count < (ptloc_count - tetcount)) { - ptloc_max_count = (ptloc_count - tetcount); - } - } - - if (b->verbose > 3) { - printf(" Located tet (%d, %d, %d, %d).\n", - pointmark(org(*searchtet)), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); + loc = locate(insertpt, searchtet); } + ivf->iloc = (int) loc; // The return value. if (b->weighted) { if (loc != OUTSIDE) { @@ -9714,92 +9025,71 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, insertpt[3]); if (sign > 0) { // This new vertex does not lie below the lower hull. Skip it. - if (b->verbose > 1) { - printf(" Point #%d is non-regular, skipped.\n", - pointmark(insertpt)); - } setpointtype(insertpt, NREGULARVERTEX); nonregularcount++; - return NONREGULAR; + ivf->iloc = (int) NONREGULAR; + return 0; } } } - // Create the initial cavity C(p) which contains all tetrahedra directly - // intersect with p. - - // Remember the current hullsize. It is used to restore the hullsize - // if the new point is rejected for insertion. - bakhullsize = hullsize; + // Create the initial cavity C(p) which contains all tetrahedra that + // intersect p. It may include 1, 2, or n tetrahedra. + // If p lies on a segment or subface, also create the initial sub-cavity + // sC(p) which contains all subfaces (and segment) which intersect p. if (loc == OUTSIDE) { - if (b->verbose > 3) { - printf(" Outside hull.\n"); - } + flip14count++; // The current hull will be enlarged. // Add four adjacent boundary tets into list. for (i = 0; i < 4; i++) { decode(searchtet->tet[i], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } - if ((point) searchtet->tet[7] == dummypoint) hullsize--; - // tetrahedrondealloc(searchtet->tet); infect(*searchtet); caveoldtetlist->newindex((void **) &parytet); *parytet = *searchtet; - flip14count++; } else if (loc == INTETRAHEDRON) { - if (b->verbose > 3) { - printf(" Inside tet.\n"); - } + flip14count++; // Add four adjacent boundary tets into list. for (i = 0; i < 4; i++) { decode(searchtet->tet[i], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } - // tetrahedrondealloc(searchtet->tet); infect(*searchtet); caveoldtetlist->newindex((void **) &parytet); *parytet = *searchtet; - flip14count++; } else if (loc == ONFACE) { - if (b->verbose > 3) { - printf(" On face.\n"); - } + flip26count++; // Add six adjacent boundary tets into list. j = (searchtet->ver & 3); // The current face number. for (i = 1; i < 4; i++) { decode(searchtet->tet[(j + i) % 4], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } decode(searchtet->tet[j], spintet); j = (spintet.ver & 3); // The current face number. for (i = 1; i < 4; i++) { decode(spintet.tet[(j + i) % 4], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } - if ((point) spintet.tet[7] == dummypoint) hullsize--; - if ((point) searchtet->tet[7] == dummypoint) hullsize--; - // tetrahedrondealloc(spintet.tet); infect(spintet); caveoldtetlist->newindex((void **) &parytet); *parytet = spintet; - // tetrahedrondealloc(searchtet->tet); infect(*searchtet); caveoldtetlist->newindex((void **) &parytet); *parytet = *searchtet; - flip26count++; - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitsh != NULL) { + if (ivf->splitbdflag) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { // Create the initial sub-cavity sC(p). smarktest(*splitsh); caveshlist->newindex((void **) &parysh); @@ -9807,35 +9097,30 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // if (splitbdflag) } else if (loc == ONEDGE) { - if (b->verbose > 3) { - printf(" On edge.\n"); - } + flipn2ncount++; // Add all adjacent boundary tets into list. spintet = *searchtet; while (1) { - enextesym(spintet, neightet); - fsymself(neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + eorgoppo(spintet, neightet); + decode(neightet.tet[neightet.ver & 3], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; - eprevesym(spintet, neightet); - fsymself(neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + edestoppo(spintet, neightet); + decode(neightet.tet[neightet.ver & 3], neightet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; - if ((point) spintet.tet[7] == dummypoint) hullsize--; - // tetrahedrondealloc(spintet.tet); infect(spintet); caveoldtetlist->newindex((void **) &parytet); *parytet = spintet; fnextself(spintet); if (spintet.tet == searchtet->tet) break; } // while (1) - flipn2ncount++; - if (ivf->splitbdflag) { //if (bowywat > 2) { + if (ivf->splitbdflag) { // Create the initial sub-cavity sC(p). - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { smarktest(*splitseg); splitseg->shver = 0; spivot(*splitseg, *splitsh); @@ -9867,9 +9152,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // if (splitbdflag) } else if (loc == INSTAR) { - if (b->verbose > 3) { - printf(" Inside star.\n"); - } // We assume that all tets in the star are given in 'caveoldtetlist', // and they are all infected. assert(caveoldtetlist->objects > 0); @@ -9881,75 +9163,24 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, decode(cavetet->tet[j], neightet); if (!infected(neightet)) { // It's a boundary face. - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); + neightet.ver = epivot[neightet.ver]; + cavebdrylist->newindex((void **) &parytet); *parytet = neightet; } } } } else if (loc == ONVERTEX) { - pa = org(*searchtet); - if (b->verbose > 3) { - printf(" On vertex %d.\n", pointmark(pa)); - } - if (insertpt != pa) { - // Remember it is a duplicated point. - setpointtype(insertpt, DUPLICATEDVERTEX); - // Set a pointer to the point it duplicates. - setpoint2ppt(insertpt, pa); - } // The point already exist. Do nothing and return. - return (int) loc; - } else if (loc == ENCSUBFACE) { - if (b->verbose > 3) { - printf(" Beyond boundary.\n"); - } - // The vertex lies outside of the region boundary. - if (ivf->rejflag & 2) { - // Check if this vertex lies very close to the boundary face. - // This case needs to be handled due to the rounding off error. - tspivot(*searchtet, checksh); - assert(checksh.sh != NULL); - pa = sorg(checksh); - pb = sdest(checksh); - pc = sapex(checksh); - ori = orient3d(pa, pb, pc, insertpt); - // Re-use cent[3]. - cent[0] = distance(pa, insertpt); - cent[1] = distance(pb, insertpt); - cent[2] = distance(pb, insertpt); - // Choose the largest distance. - if (cent[0] < cent[1]) cent[0] = cent[1]; - if (cent[0] < cent[2]) cent[0] = cent[2]; - if (fabs(ori) / (cent[0] * cent[0] * cent[0]) < b->epsilon) { - // A nearly co-planar subface. We treat this case as coplanar, so - // the insertion point does not lie outside of the domain. - // Queue an encroached subface. - // Calculate the circumcenter of this subface (for refinement). - circumsphere(pa, pb, pc, NULL, cent, &rd); - encshlist->newindex((void **) &bface); - bface->ss = checksh; - bface->forg = pa; // Not a dad one. - for (j = 0; j < 3; j++) bface->cent[j] = cent[j]; - bface->key = rd; - return (int) ENCSUBFACE; - } - } - // Treated it as outside - loc = OUTSIDE; - return (int) loc; - } else { - assert(0); // Unknown type. - } + return 0; + } if (ivf->assignmeshsize) { // Assign mesh size for the new point. if (bgm != NULL) { // Interpolate the mesh size from the background mesh. - pa = org(*searchtet); - bgm->decode(point2bgmtet(pa), neightet); // neightet is in 'bgm'! - bgmloc = bgm->scoutpoint(insertpt, &neightet, 0); // randflag = 0 + bgm->decode(point2bgmtet(org(*searchtet)), neightet); + int bgmloc = (int) bgm->scoutpoint(insertpt, &neightet, 0); if (bgmloc != (int) OUTSIDE) { insertpt[pointmtrindex] = bgm->getpointmeshsize(insertpt, &neightet, bgmloc); @@ -9960,84 +9191,21 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // if (assignmeshsize) - if (ivf->validflag) { //if (bowywat > 2) { - // Validate the initial C(p). Enlarge it at a face which is not visible - // by p. This removes (interior) slivers. Re-use 'cavebdrylist'. - tetcount = 0l; - + if (ivf->bowywat) { + // Update the cavity C(p) using the Bowyer-Watson algorithm. + swaplist = cavetetlist; + cavetetlist = cavebdrylist; + cavebdrylist = swaplist; for (i = 0; i < cavetetlist->objects; i++) { + // 'cavetet' is an adjacent tet at outside of the cavity. cavetet = (triface *) fastlookup(cavetetlist, i); - // Other expansions may make this face inside C(p). + // The tet may be tested and included in the (enlarged) cavity. if (!infected(*cavetet)) { - pc = apex(*cavetet); - // Do valid if it is a face (not a hull edge). - if (pc != dummypoint) { - pa = org(*cavetet); - pb = dest(*cavetet); - ori = orient3d(pa, pb, pc, insertpt); - if (ori <= 0) { - // An invalid face. Enlarge the cavity. - if (b->verbose > 3) { - printf(" Enlarge cavity at (%d, %d, %d)\n", - pointmark(pa), pointmark(pb), pointmark(pc)); - } - // Add the other three faces into list. - j = (cavetet->ver & 3); // The current face number. - for (k = 1; k < 4; k++) { - decode(cavetet->tet[(j + k) % 4], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; - } - if ((point) cavetet->tet[7] == dummypoint) hullsize--; - infect(*cavetet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = *cavetet; - tetcount++; - } else { - // A valid face. - cavebdrylist->newindex((void **) &parytet); - *parytet = *cavetet; - } - } else { - // A hull edge is valid. - cavebdrylist->newindex((void **) &parytet); - *parytet = *cavetet; - } - } // if (!infected(*cavetet)) - } // i - - if (tetcount > 0l) { - // The cavity has been enlarged. Update it. - cavetetlist->restart(); - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - if (!infected(*cavetet)) { - cavetetlist->newindex((void **) &parytet); - *parytet = *cavetet; - } - } // i - } // if (tetcount) - - cavebdrylist->restart(); - tetcount = 0l; - } // if (bowywat > 2) - - // Update the cavity C(p) using the Bowyer-Watson approach (bowywat > 0). - - for (i = 0; i < cavetetlist->objects; i++) { - // 'cavetet' is an adjacent tet at outside of the cavity. - cavetet = (triface *) fastlookup(cavetetlist, i); - // The tet may be tested and included in the (enlarged) cavity. - if (!infected(*cavetet)) { - // Check for two possible cases for this tet: - // (1) It is a cavity tet, or - // (2) it is a cavity boundary face. - // In case (1), this tet is grabbed in the cavity and three adjacent - // tets on other faces of this tet are added into 'cavetetlist'. - enqflag = false; - if (!marktested(*cavetet)) { - if (ivf->bowywat) { + // Check for two possible cases for this tet: + // (1) It is a cavity tet, or + // (2) it is a cavity boundary face. + enqflag = false; + if (!marktested(*cavetet)) { // Do Delaunay (in-sphere) test. pts = (point *) cavetet->tet; if (pts[7] != dummypoint) { @@ -10059,14 +9227,12 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, //if (!nonconvex) { // Include it in the cavity. The convex hull will be enlarged. enqflag = true; // (ori < 0.0); - //} + //} } else if (ori == 0.0) { // A coplanar hull face. We need to test if this hull face is // Delaunay or not. We test if the adjacent tet (not faked) // of this hull face is Delaunay or not. - neightet = *cavetet; - neightet.ver = 3; // The face opposite to dummypoint. - fsym(neightet, neineitet); + decode(cavetet->tet[3], neineitet); if (!infected(neineitet)) { if (!marktested(neineitet)) { // Do Delaunay test on this tet. @@ -10080,12 +9246,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); } enqflag = (sign < 0.0); - } else { - // The adjacent tet has been tested (marktested), and it - // is Delaunay (not get infected). Hence the the hull - // face is Delaunay as well. - // enqflag = false; - } + } } else { // The adjacent tet is non-Delaunay. The hull face is non- // Delaunay as well. Include it in the cavity. @@ -10094,15 +9255,11 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } // if (ori == 0.0) } else { // A hull face (must be a subface). - assert(checksubfaceflag); - assert(ivf->validflag); // We FIRST include it in the initial cavity if the adjacent tet // (not faked) of this hull face is not Delaunay wrt p. // Whether it belongs to the final cavity will be determined // during the validation process. 'validflag'. - neightet = *cavetet; - neightet.ver = 3; // The face opposite to dummypoint. - fsym(neightet, neineitet); + decode(cavetet->tet[3], neineitet); if (!infected(neineitet)) { if (!marktested(neineitet)) { // Do Delaunay test on this tet. @@ -10116,12 +9273,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sign = insphere_s(pts[4],pts[5],pts[6],pts[7], insertpt); } enqflag = (sign < 0.0); - } else { - // The adjacent tet has been tested (marktested), and it - // is Delaunay (not get infected). Hence the the hull - // face is Delaunay as well. - // enqflag = false; - } // if (marktested(neineitet)) + } } else { // The adjacent tet is non-Delaunay. The hull face is non- // Delaunay as well. Include it in the cavity. @@ -10129,200 +9281,131 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } // if (infected(neineitet)) } // if (nonconvex) } // if (pts[7] != dummypoint) - } // if (bowywat) - marktest(*cavetet); // Only test it once. - } // if (!marktested(*cavetet)) + marktest(*cavetet); // Only test it once. + } // if (!marktested(*cavetet)) - if (enqflag) { - // Found a tet in the cavity. Put other three faces in check list. - k = (cavetet->ver & 3); // The current face number - for (j = 1; j < 4; j++) { - decode(cavetet->tet[(j + k) % 4], neightet); - neightet.ver = epivot[neightet.ver & 3]; - cavetetlist->newindex((void **) &parytet); - *parytet = neightet; + if (enqflag) { + // Found a tet in the cavity. Put other three faces in check list. + k = (cavetet->ver & 3); // The current face number + for (j = 1; j < 4; j++) { + decode(cavetet->tet[(j + k) % 4], neightet); + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + infect(*cavetet); + caveoldtetlist->newindex((void **) &parytet); + *parytet = *cavetet; + } else { + // Found a boundary face of the cavity. + cavetet->ver = epivot[cavetet->ver]; + cavebdrylist->newindex((void **) &parytet); + *parytet = *cavetet; } - if ((point) cavetet->tet[7] == dummypoint) hullsize--; - // tetrahedrondealloc(cavetet->tet); - infect(*cavetet); - caveoldtetlist->newindex((void **) &parytet); - *parytet = *cavetet; - } else { - // Found a boundary face of the cavity. It may be a face of a hull - // tet which contains 'dummypoint'. Choose the edge in the face - // such that its endpoints are not 'dummypoint', while its apex - // may be 'dummypoint'. - //j = (cavetet->ver & 3); // j is the face number. - //cavetet->ver = epivot[j]; // [4,5,2,11] - cavebdrylist->newindex((void **) &parytet); - *parytet = *cavetet; - } - } // if (!infected(*cavetet)) - } // i - - if (b->verbose > 3) { - printf(" Initial cavity size: %ld tets, %ld faces.\n", - caveoldtetlist->objects, cavebdrylist->objects); - } + } // if (!infected(*cavetet)) + } // i + cavetetlist->restart(); // Clear the working list. + } // if (ivf->bowywat) if (checksubsegflag) { // Collect all segments of C(p). + shellface *ssptr; for (i = 0; i < caveoldtetlist->objects; i++) { cavetet = (triface *) fastlookup(caveoldtetlist, i); - for (j = 0; j < 6; j++) { - cavetet->ver = edge2ver[j]; - tsspivot1(*cavetet, checkseg); - if (checkseg.sh != NULL) { - if (!sinfected(checkseg)) { - sinfect(checkseg); - cavetetseglist->newindex((void **) &paryseg); - *paryseg = checkseg; + if ((ssptr = (shellface*) cavetet->tet[8]) != NULL) { + for (j = 0; j < 6; j++) { + if (ssptr[j]) { + sdecode(ssptr[j], checkseg); + if (!sinfected(checkseg)) { + sinfect(checkseg); + cavetetseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } } - } + } // j } - } + } // i // Uninfect collected segments. for (i = 0; i < cavetetseglist->objects; i++) { - checkseg = * (face *) fastlookup(cavetetseglist, i); - suninfect(checkseg); + paryseg = (face *) fastlookup(cavetetseglist, i); + suninfect(*paryseg); + } + + if (ivf->rejflag & 1) { + // Reject this point if it encroaches upon any segment. + face *paryseg1; + for (i = 0; i < cavetetseglist->objects; i++) { + paryseg1 = (face *) fastlookup(cavetetseglist, i); + if (checkseg4encroach((point) paryseg1->sh[3], (point) paryseg1->sh[4], + insertpt)) { + encseglist->newindex((void **) &paryseg); + *paryseg = *paryseg1; + } + } // i + if (encseglist->objects > 0) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSEGMENT; + return 0; + } } } // if (checksubsegflag) if (checksubfaceflag) { // Collect all subfaces of C(p). + shellface *sptr; for (i = 0; i < caveoldtetlist->objects; i++) { cavetet = (triface *) fastlookup(caveoldtetlist, i); - oldtet = *cavetet; - for (oldtet.ver = 0; oldtet.ver < 4; oldtet.ver++) { - tspivot(oldtet, checksh); - if (checksh.sh != NULL) { - if (!sinfected(checksh)) { - sinfect(checksh); - cavetetshlist->newindex((void **) &parysh); - *parysh = checksh; + if ((sptr = (shellface*) cavetet->tet[9]) != NULL) { + for (j = 0; j < 4; j++) { + if (sptr[j]) { + sdecode(sptr[j], checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + cavetetshlist->newindex((void **) &parysh); + *parysh = checksh; + } } - } + } // j } - } + } // i // Uninfect collected subfaces. for (i = 0; i < cavetetshlist->objects; i++) { - checksh = * (face *) fastlookup(cavetetshlist, i); - suninfect(checksh); + parysh = (face *) fastlookup(cavetetshlist, i); + suninfect(*parysh); + } + + if (ivf->rejflag & 2) { + REAL rd, cent[3]; + badface *bface; + // Reject this point if it encroaches upon any subface. + for (i = 0; i < cavetetshlist->objects; i++) { + parysh = (face *) fastlookup(cavetetshlist, i); + if (checkfac4encroach((point) parysh->sh[3], (point) parysh->sh[4], + (point) parysh->sh[5], insertpt, cent, &rd)) { + encshlist->newindex((void **) &bface); + bface->ss = *parysh; + bface->forg = (point) parysh->sh[3]; // Not a dad one. + for (j = 0; j < 3; j++) bface->cent[j] = cent[j]; + bface->key = rd; + } + } + if (encshlist->objects > 0) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) ENCSUBFACE; + return 0; + } } } // if (checksubfaceflag) - if (ivf->rejflag & 1) { - // Reject insertion of this point if it encroaches upon any segment. - for (i = 0; i < cavetetseglist->objects; i++) { - checkseg = * (face *) fastlookup(cavetetseglist, i); - pa = sorg(checkseg); - pb = sdest(checkseg); - if (checkseg4encroach(pa, pb, insertpt)) { - if (b->verbose > 3) { - printf(" Found an encroached seg (%d, %d).\n", - pointmark(pa), pointmark(pb)); - } - encseglist->newindex((void **) &paryseg); - *paryseg = checkseg; - } - } // i - if (encseglist->objects > 0) { - if (b->verbose > 3) { - printf(" Found %ld encroached segments. Reject it.\n", - encseglist->objects); - } - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - // Clear working lists. - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetseglist->restart(); - cavetetshlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - return (int) ENCSEGMENT; - } - } // if (reject & 1) + if ((ivf->iloc == (int) OUTSIDE) && ivf->refineflag) { + // The vertex lies outside of the domain. And it does not encroach + // upon any boundary segment or subface. Do not insert it. + insertpoint_abort(splitseg, ivf); + return 0; + } - if (ivf->rejflag & 2) { - // Reject insertion of this point if it encroaches upon any subface. - for (i = 0; i < cavetetshlist->objects; i++) { - checksh = * (face *) fastlookup(cavetetshlist, i); - pa = sorg(checksh); - pb = sdest(checksh); - pc = sapex(checksh); - if (checkfac4encroach(pa, pb, pc, insertpt, cent, &rd)) { - if (b->verbose > 3) { - printf(" Found an encroached subface (%d, %d, %d).\n", - pointmark(pa), pointmark(pb), pointmark(pc)); - } - encshlist->newindex((void **) &bface); - bface->ss = checksh; - bface->forg = pa; // Not a dad one. - for (j = 0; j < 3; j++) bface->cent[j] = cent[j]; - bface->key = rd; - } - } // i - if (encshlist->objects > 0) { - if (b->verbose > 3) { - printf(" Found %ld encroached subfaces. Reject it.\n", - encshlist->objects); - } - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetseglist->restart(); - cavetetshlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - return (int) ENCSUBFACE; - } - } // if (reject & 2) - - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { - // Update the sC(p). + if (ivf->splitbdflag) { + // The new point locates in surface mesh. Update the sC(p). // We have already 'smarktested' the subfaces which directly intersect // with p in 'caveshlist'. From them, we 'smarktest' their neighboring // subfaces which are included in C(p). Do not across a segment. @@ -10331,8 +9414,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, assert(smarktested(*parysh)); checksh = *parysh; for (j = 0; j < 3; j++) { - sspivot(checksh, checkseg); - if (checkseg.sh == NULL) { + if (!isshsubseg(checksh)) { spivot(checksh, neighsh); assert(neighsh.sh != NULL); if (!smarktested(neighsh)) { @@ -10342,10 +9424,10 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (infected(neightet)) { // This subface is inside C(p). // Check if its diametrical circumsphere encloses 'p'. - pa = sorg(neighsh); - pb = sdest(neighsh); - pc = sapex(neighsh); - sign = incircle3d(pa, pb, pc, insertpt); + // The purpose of this check is to avoid forming invalid + // subcavity in surface mesh. + sign = incircle3d(sorg(neighsh), sdest(neighsh), + sapex(neighsh), insertpt); if (sign < 0) { smarktest(neighsh); caveshlist->newindex((void **) &parysh); @@ -10358,48 +9440,34 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, senextself(checksh); } // j } // i - if (b->verbose > 3) { - printf(" Initial subcavity size: %ld subfacess.\n", - caveshlist->objects); - } - } + } // if (ivf->splitbdflag) - cutcount = 0l; + if (ivf->validflag) { + // Validate C(p) and update it if it is not star-shaped. + int cutcount = 0; - if (ivf->validflag) { - //if (bowywat > 1) { // if (bowywat == 2 || bowywat == 3) { - // T is a CT. Validation is needed (fig/dump-cavity-case8). - cavetetlist->restart(); // Re-use it. - - //if (splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { if (ivf->respectbdflag) { // The initial cavity may include subfaces which are not on the facets - // being splitting. Find them and make them as boundary of C(p). - // Comment: We have already 'smarktested' the subfaces in sC(p). - // It is needed by 'splitbdflag'. + // being splitting. Find them and make them as boundary of C(p). + // Comment: We have already 'smarktested' the subfaces in sC(p). They + // are completely inside C(p). for (i = 0; i < cavetetshlist->objects; i++) { parysh = (face *) fastlookup(cavetetshlist, i); stpivot(*parysh, neightet); if (infected(neightet)) { fsymself(neightet); if (infected(neightet)) { + // Found a subface inside C(p). if (!smarktested(*parysh)) { - if (b->verbose > 3) { - printf(" Found a subface (%d, %d, %d) inside cavity.\n", - pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), - pointmark(sapex(*parysh))); - } // It is possible that this face is a boundary subface. // Check if it is a hull face. - assert(apex(neightet) != dummypoint); + //assert(apex(neightet) != dummypoint); if (oppo(neightet) != dummypoint) { fsymself(neightet); } if (oppo(neightet) != dummypoint) { - pa = org(neightet); - pb = dest(neightet); - pc = apex(neightet); - ori = orient3d(pa, pb, pc, insertpt); + ori = orient3d(org(neightet), dest(neightet), apex(neightet), + insertpt); if (ori < 0) { // A visible face, get its neighbor face. fsymself(neightet); @@ -10411,27 +9479,20 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } // Cut this tet if it is either invisible by or coplanar with p. if (ori >= 0) { - if (b->verbose > 3) { - printf(" Cut tet (%d, %d, %d, %d)\n", - pointmark(org(neightet)), pointmark(dest(neightet)), - pointmark(apex(neightet)), pointmark(oppo(neightet))); - } uninfect(neightet); unmarktest(neightet); cutcount++; - neightet.ver = epivot[neightet.ver & 3]; + neightet.ver = epivot[neightet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neightet; // Add three new faces to find new boundaries. for (j = 0; j < 3; j++) { esym(neightet, neineitet); - neineitet.ver = epivot[neineitet.ver & 3]; + neineitet.ver = epivot[neineitet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neineitet; enextself(neightet); } - // Update hullsize. - if (oppo(neightet) == dummypoint) hullsize++; } // if (ori >= 0) } } @@ -10453,10 +9514,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (spintet.tet == neightet.tet) break; } if (infected(spintet)) { - if (b->verbose > 3) { - printf(" Found an interior segment (%d, %d).\n", - pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg))); - } // Find an adjacent tet at this segment such that both faces // at this segment are not visible by p. pa = org(neightet); @@ -10481,7 +9538,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } } - } else { } fnextself(spintet); if (spintet.tet == neightet.tet) break; @@ -10499,42 +9555,34 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, uninfect(neightet); unmarktest(neightet); cutcount++; - neightet.ver = epivot[neightet.ver & 3]; + neightet.ver = epivot[neightet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neightet; // Add three new faces to find new boundaries. for (j = 0; j < 3; j++) { esym(neightet, neineitet); - neineitet.ver = epivot[neineitet.ver & 3]; + neineitet.ver = epivot[neineitet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neineitet; enextself(neightet); } - // Update hullsize. - //if (oppo(neightet) == dummypoint) hullsize++; - if ((point) (neightet.tet[7]) == dummypoint) hullsize++; } } } // i - } // if (bowywat > 2) + } // if (ivf->respectbdflag) // Update the cavity by removing invisible faces until it is star-shaped. for (i = 0; i < cavebdrylist->objects; i++) { cavetet = (triface *) fastlookup(cavebdrylist, i); - // 'cavetet' is an exterior tet adjacent to the cavity. - assert(cavetet->ver == epivot[cavetet->ver & 3]); // SELF_CHECK - // It must be not inside the cavity (since we only cut tets). - assert(!infected(*cavetet)); + // 'cavetet' is an exterior tet adjacent to the cavity. // Check if its neighbor is inside C(p). fsym(*cavetet, neightet); if (infected(neightet)) { if (apex(*cavetet) != dummypoint) { // It is a cavity boundary face. Check its visibility. if (oppo(neightet) != dummypoint) { - pa = org(*cavetet); - pb = dest(*cavetet); - pc = apex(*cavetet); - ori = orient3d(pa, pb, pc, insertpt); + ori = orient3d(org(*cavetet), dest(*cavetet), apex(*cavetet), + insertpt); enqflag = (ori > 0); // Comment: if ori == 0 (coplanar case), we also cut the tet. } else { @@ -10551,24 +9599,17 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, cavetetlist->newindex((void **) &parytet); *parytet = *cavetet; } else { - if (b->verbose > 3) { - printf(" Cut tet (%d, %d, %d, %d)\n", - pointmark(org(neightet)), pointmark(dest(neightet)), - pointmark(apex(neightet)), pointmark(oppo(neightet))); - } uninfect(neightet); unmarktest(neightet); cutcount++; // Add three new faces to find new boundaries. for (j = 0; j < 3; j++) { esym(neightet, neineitet); - neineitet.ver = epivot[neineitet.ver & 3]; + neineitet.ver = epivot[neineitet.ver]; cavebdrylist->newindex((void **) &parytet); *parytet = neineitet; enextself(neightet); } - // Update the hullsize. - if (oppo(neightet) == dummypoint) hullsize++; // 'cavetet' is not on the cavity boundary anymore. unmarktest(*cavetet); } @@ -10580,14 +9621,11 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (cutcount > 0) { // The cavity has been updated. - // Update the cavity boundary faces. cavebdrylist->restart(); for (i = 0; i < cavetetlist->objects; i++) { cavetet = (triface *) fastlookup(cavetetlist, i); // 'cavetet' was an exterior tet adjacent to the cavity. - assert(cavetet->ver == epivot[cavetet->ver & 3]); // SELF_CHECK - assert(!infected(*cavetet)); fsym(*cavetet, neightet); if (infected(neightet)) { // It is a cavity boundary face. @@ -10615,31 +9653,13 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // The cavity should contain at least one tet. if (caveoldtetlist->objects == 0l) { - assert(cavebdrylist->objects == 0l); - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetseglist->restart(); - cavetetshlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - return (int) BADELEMENT; + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; } - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { - cutshcount = 0; + if (ivf->splitbdflag) { + int cutshcount = 0; // Update the sub-cavity sC(p). for (i = 0; i < caveshlist->objects; i++) { parysh = (face *) fastlookup(caveshlist, i); @@ -10653,11 +9673,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } if (!enqflag) { - if (b->verbose > 3) { - printf(" Cut subface (%d, %d, %d).\n", - pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), - pointmark(sapex(*parysh))); - } sunmarktest(*parysh); // Use the last entry of this array to fill this entry. j = caveshlist->objects - 1; @@ -10674,16 +9689,16 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, i = 0; // Count the number of invalid subfaces/segments. // Valid the updated sub-cavity sC(p). if (loc == ONFACE) { - if (splitsh != NULL) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { // The to-be split subface should be in sC(p). if (!smarktested(*splitsh)) i++; } } else if (loc == ONEDGE) { - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { // The to-be split segment should be in sC(p). if (!smarktested(*splitseg)) i++; } - if (splitsh != NULL) { + if ((splitsh != NULL) && (splitsh->sh != NULL)) { // All subfaces at this edge should be in sC(p). pa = sorg(*splitsh); neighsh = *splitsh; @@ -10705,127 +9720,140 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (i > 0) { // The updated sC(p) is invalid. Do not insert this vertex. - if (b->verbose > 3) { - printf(" Found %d invalid items. Reject it.\n", i); - } - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetseglist->restart(); - cavetetshlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - return (int) BADELEMENT; + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; } } // if (cutshcount > 0) - } // if (bowywat > 2) - + } // if (ivf->splitbdflag) } // if (cutcount > 0) - } // if (validflag) // if (bowywat > 1) - - if (b->verbose > 3) { - printf(" Final cavity: %ld tets, %ld faces.", - caveoldtetlist->objects, cavebdrylist->objects); - if (cutcount > 0l) { - printf(" Updated %ld times.", cutcount); - } - printf("\n"); - } - + } // if (ivf->validflag) if (ivf->refineflag) { // The new point is inserted by Delaunay refinement, i.e., it is the // circumcenter of a tetrahedron, or a subface, or a segment. // Do not insert this point if the tetrahedron, or subface, or segment // is not inside the final cavity. - rejptflag = 0; - if (ivf->refineflag == 1) { - // The new point is the circumcenter of a tetrahedron. - assert(!isdeadtet(ivf->refinetet)); - if (!infected(ivf->refinetet)) { - rejrefinetetcount++; - rejptflag = 1; - } - } else if (ivf->refineflag == 2) { - // The new point is the circumcenter of a subface. - assert(ivf->refinesh.sh != NULL); - if (!smarktested(ivf->refinesh)) { - rejrefineshcount++; - rejptflag = 1; - } + if (((ivf->refineflag == 1) && !infected(ivf->refinetet)) || + ((ivf->refineflag == 2) && !smarktested(ivf->refinesh))) { + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) BADELEMENT; + return 0; } - if (rejptflag) { - if (b->verbose > 2) { - printf(" Point %d does not refine its element. Rejected.\n", - pointmark(insertpt)); - } - // Restore the original status. - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - // Clear working lists. - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetshlist->restart(); - cavetetseglist->restart(); - cavetetvertlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - - // Restore the hullsize. - hullsize = bakhullsize; - loc = BADELEMENT; - return (int) loc; - } // if (rejptflag) } // if (ivf->refineflag) - rejptflag = (ivf->rejflag & 4); - encptflag = 0; + if (b->plc && (loc != INSTAR)) { + // Reject the new point if it lies too close to an existing point (b->plc), + // or it lies inside a protecting ball of near vertex (ivf->rejflag & 4). + // Collect the list of vertices of the initial cavity. + if (loc == OUTSIDE) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 3; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } else if (loc == INTETRAHEDRON) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 4; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } else if (loc == ONFACE) { + pts = (point *) &(searchtet->tet[4]); + for (i = 0; i < 3; i++) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + if (pts[3] != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[3]; + } + fsym(*searchtet, spintet); + if (oppo(spintet) != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = oppo(spintet); + } + } else if (loc == ONEDGE) { + spintet = *searchtet; + cavetetvertlist->newindex((void **) &parypt); + *parypt = org(spintet); + cavetetvertlist->newindex((void **) &parypt); + *parypt = dest(spintet); + while (1) { + if (apex(spintet) != dummypoint) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = apex(spintet); + } + fnextself(spintet); + if (spintet.tet == searchtet->tet) break; + } + } - if (b->weighted || b->plc || rejptflag) { + int rejptflag = (ivf->rejflag & 4); + REAL rd; + pts = NULL; + + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + rd = distance(*parypt, insertpt); + // Is the point very close to an existing point? + if (rd < b->minedgelength) { + pts = parypt; + loc = NEARVERTEX; + break; + } + if (rejptflag) { + // Is the point encroaches upon an existing point? + if (rd < (0.5 * (*parypt)[pointmtrindex])) { + pts = parypt; + loc = ENCVERTEX; + break; + } + } + } + cavetetvertlist->restart(); // Clear the work list. + + if (pts != NULL) { + // The point is either too close to an existing vertex (NEARVERTEX) + // or encroaches upon (inside the protecting ball) of that vertex. + if (loc == NEARVERTEX) { + if (b->nomergevertex) { // -M0/1 option. + // In this case, we still insert this vertex. Although it is very + // close to an existing vertex. Give a warning, anyway. + if (!b->quiet) { + printf("Warning: Two points, %d and %d, are very close.\n", + pointmark(insertpt), pointmark(*pts)); + printf(" Creating a very short edge (len = %g) (< %g).\n", + rd, b->minedgelength); + printf(" You may try a smaller tolerance (-T) (current is %g)\n", + b->epsilon); + printf(" to avoid this warning.\n"); + } + } else { + insertpt[3] = rd; // Only for reporting. + setpoint2ppt(insertpt, *pts); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; + } + } else { // loc == ENCVERTEX + // The point lies inside the protection ball. + setpoint2ppt(insertpt, *pts); + insertpoint_abort(splitseg, ivf); + ivf->iloc = (int) loc; + return 0; + } + } + } // if (b->plc && (loc != INSTAR)) + + if (b->weighted || ivf->cdtflag || ivf->smlenflag + ) { + // There may be other vertices inside C(p). We need to find them. // Collect all vertices of C(p). for (i = 0; i < caveoldtetlist->objects; i++) { cavetet = (triface *) fastlookup(caveoldtetlist, i); - assert(infected(*cavetet)); + //assert(infected(*cavetet)); pts = (point *) &(cavetet->tet[4]); for (j = 0; j < 4; j++) { if (pts[j] != dummypoint) { @@ -10837,118 +9865,48 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // j } // i - if (b->verbose > 3) { - printf(" %ld cavity vertices.\n", cavetetvertlist->objects); - } // Uninfect all collected (cavity) vertices. for (i = 0; i < cavetetvertlist->objects; i++) { parypt = (point *) fastlookup(cavetetvertlist, i); puninfect(*parypt); } - if (b->plc || rejptflag) { - // Check if p is too close to an existing vertex. - pts = NULL; - for (i = 0; i < cavetetvertlist->objects; i++) { + if (ivf->smlenflag) { + REAL len; + // Get the length of the shortest edge connecting to 'newpt'. + parypt = (point *) fastlookup(cavetetvertlist, 0); + ivf->smlen = distance(*parypt, insertpt); + ivf->parentpt = *parypt; + for (i = 1; i < cavetetvertlist->objects; i++) { parypt = (point *) fastlookup(cavetetvertlist, i); - rd = distance(*parypt, insertpt); - // Is the point very close to an existing point? - if (rd < b->minedgelength) { - pts = parypt; - break; + len = distance(*parypt, insertpt); + if (len < ivf->smlen) { + ivf->smlen = len; + ivf->parentpt = *parypt; } - if (rejptflag) { - // Is the point encroaches upon an existing point? - if (rd < (*parypt)[pointmtrindex]) { - // The point lies inside the protection ball. - if (b->verbose > 2) { - printf(" Point %d lies in protball of %d. Rejected.\n", - pointmark(insertpt), pointmark(*parypt)); - } - pts = parypt; - encptflag = 1; - break; - } - } - } // i - if (pts != NULL) { - // p is too close to *pts. - if (ivf->iloc != (int) INSTAR) { - if (pointmark(insertpt) <= in->numberofpoints) { - // It's an input point. - if (!b->quiet) { - printf("Warning: Point %d is replaced by point %d.\n", - pointmark(insertpt), pointmark(*pts)); - } - // Count the number of duplicated points. - dupverts++; - } else { // It's a Steiner point. - if (b->verbose) { - if (!rejptflag) { - printf("Warning: Reject a Steiner point %d (close to %d).\n", - pointmark(insertpt), pointmark(*pts)); - } - } - } - // Remember it is a duplicated point. - setpointtype(insertpt, DUPLICATEDVERTEX); - // Set a pointer to the point it duplicates. - setpoint2ppt(insertpt, *pts); + } + } + } - // Restore the original status. - for (i = 0; i < caveoldtetlist->objects; i++) { - cavetet = (triface *) fastlookup(caveoldtetlist, i); - uninfect(*cavetet); - unmarktest(*cavetet); - } - for (i = 0; i < cavebdrylist->objects; i++) { - cavetet = (triface *) fastlookup(cavebdrylist, i); - unmarktest(*cavetet); // Unmark it. - } - // Clear working lists. - cavetetlist->restart(); - cavebdrylist->restart(); - caveoldtetlist->restart(); - cavetetshlist->restart(); - cavetetseglist->restart(); - cavetetvertlist->restart(); - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { - if (splitseg != NULL) { - sunmarktest(*splitseg); - } - for (i = 0; i < caveshlist->objects; i++) { - parysh = (face *) fastlookup(caveshlist, i); - assert(smarktested(*parysh)); - sunmarktest(*parysh); - } - caveshlist->restart(); - cavesegshlist->restart(); - } - // Restore the hullsize. - hullsize = bakhullsize; - if (!encptflag) { - loc = NEARVERTEX; - } else { - loc = ENCVERTEX; - } - return (int) loc; - } else { // (iloc == (int) INSTAR) - // The cavity is guaranteed to be valid by the caller of this - // function. We still insert this vertex. - if (b->verbose) { - printf("Warning: The Steiner point %d is very close to %d.\n", - pointmark(insertpt), pointmark(*pts)); - } - } - } // if (pts != NULL) - } - } - - // The new point will be inserted. - totaldeadtets += caveoldtetlist->objects; - totalbowatcavsize += cavebdrylist->objects; - if (maxbowatcavsize < cavebdrylist->objects) { - maxbowatcavsize = cavebdrylist->objects; + if (ivf->cdtflag) { + // Unmark tets. + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + unmarktest(*cavetet); + } + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); + } + // Clean up arrays which are not needed. + cavetetlist->restart(); + if (checksubsegflag) { + cavetetseglist->restart(); + } + if (checksubfaceflag) { + cavetetshlist->restart(); + } + return 1; } // Before re-mesh C(p). Process the segments and subfaces which are on the @@ -10970,7 +9928,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, while (1) { j++; if (!infected(spintet)) { - neineitet = spintet; // An outer tet. Remember it. + neineitet = spintet; // An outer tet. Remember it. } else { k++; // An in tet. } @@ -10991,12 +9949,8 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sstbond1(*paryseg, neineitet); } else { // k == j // The segment is inside C(p). - if (!ivf->splitbdflag) {//if (bowywat < 3) { // if (bowywat == 2) { + if (!ivf->splitbdflag) { checkseg = *paryseg; - if (b->verbose > 3) { - printf(" Queueing a missing seg (%d, %d)\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } sinfect(checkseg); // Flag it as an interior segment. caveencseglist->newindex((void **) &paryseg); *paryseg = checkseg; @@ -11011,10 +9965,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sinfect(*paryseg); } } // i - if (b->verbose > 3) { - printf(" %ld (%ld) cavity (interior) segments.\n", - cavetetseglist->objects, caveencseglist->objects); - } } // if (checksubsegflag) if (checksubfaceflag) { @@ -11027,7 +9977,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, for (j = 0; j < 2; j++) { stpivot(*parysh, neightet); if (!infected(neightet)) { - checksh = *parysh; // Remeber this side. + checksh = *parysh; // Remember this side. } else { k++; } @@ -11044,13 +9994,8 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // This side is the outer boundary of C(p). *parysh = checksh; } else { // k == 2 - if (!ivf->splitbdflag) { //if (bowywat < 3) { // if (bowywat == 2) { + if (!ivf->splitbdflag) { checksh = *parysh; - if (b->verbose > 3) { - printf(" Queueing a missing subface (%d, %d, %d)\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } sinfect(checksh); // Flag it. caveencshlist->newindex((void **) &parysh); *parysh = checksh; @@ -11065,31 +10010,26 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, sinfect(*parysh); } } // i - if (b->verbose > 3) { - printf(" %ld (%ld) cavity (interior) subfaces.\n", - cavetetshlist->objects, caveencshlist->objects); - } - } // if (checksubfaceflag) { + } // if (checksubfaceflag) // Create new tetrahedra to fill the cavity. for (i = 0; i < cavebdrylist->objects; i++) { cavetet = (triface *) fastlookup(cavebdrylist, i); neightet = *cavetet; - assert(!infected(neightet)); unmarktest(neightet); // Unmark it. // Get the oldtet (inside the cavity). fsym(neightet, oldtet); if (apex(neightet) != dummypoint) { - // Create a new tet in the cavity (see Fig. bowyerwatson 1 or 3). + // Create a new tet in the cavity. maketetrahedron(&newtet); setorg(newtet, dest(neightet)); setdest(newtet, org(neightet)); setapex(newtet, apex(neightet)); setoppo(newtet, insertpt); } else { - // Create a new hull tet (see Fig. bowyerwatson 2). - hullsize++; + // Create a new hull tet. + hullsize++; maketetrahedron(&newtet); setorg(newtet, org(neightet)); setdest(newtet, dest(neightet)); @@ -11118,10 +10058,8 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, //setpoint2tet(insertpt, encode(newtet)); setpoint2tet(insertpt, (tetrahedron) (newtet.tet)); - if (ivf->lawson > 1) { // if (lawson == 2 || lawson == 3) { - // Re-use this list to save new interior cavity faces. - cavetetlist->restart(); - } + // Re-use this list to save new interior cavity faces. + cavetetlist->restart(); // Connect adjacent new tetrahedra together. for (i = 0; i < cavebdrylist->objects; i++) { @@ -11143,11 +10081,9 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } fsym(spintet, newneitet); esymself(newneitet); - assert(newneitet.tet[newneitet.ver & 3] == NULL); // FOR DEBUG + assert(newneitet.tet[newneitet.ver & 3] == NULL); bond(neightet, newneitet); - if (ivf->lawson > 1) { - // We are updateing a CDT. Queue the internal face. - // See also fig/dump-cavity-case13, -case21. + if (ivf->lawson > 1) { cavetetlist->newindex((void **) &parytet); *parytet = neightet; } @@ -11191,13 +10127,14 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } - if (splitsh != NULL) { + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { // Split a subface or a segment. - sinsertvertex(insertpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat); + sinsertvertex(insertpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); } if (checksubfaceflag) { - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { + if (ivf->splitbdflag) { // Recover new subfaces in C(p). for (i = 0; i < caveshbdlist->objects; i++) { // Get an old subface at edge [a, b]. @@ -11242,8 +10179,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // There should be no missing interior subfaces in C(p). assert(caveencshlist->objects == 0l); } else { - // bowywat = 1 or bowywat = 2. - // The Boundary reocvery phase. + // The Boundary recovery phase. // Put all new subfaces into stack for recovery. for (i = 0; i < caveshbdlist->objects; i++) { // Get an old subface at edge [a, b]. @@ -11251,12 +10187,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, spivot(*parysh, checksh); // The new subface [a, b, p]. // Do not recover a deleted new face (degenerated). if (checksh.sh[3] != NULL) { - if (b->verbose > 3) { - printf(" Queue new subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - //sdissolve(checksh); // It has not been connected yet. subfacstack->newindex((void **) &parysh); *parysh = checksh; } @@ -11268,11 +10198,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // Some subfaces inside C(p) might be split in sinsertvertex(). // Only queue those faces which are not split. if (!smarktested(*parysh)) { - if (b->verbose > 3) { - printf(" Queue a missing subface (%d, %d, %d) x%lx.\n", - pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), - pointmark(sapex(*parysh)), (uintptr_t) parysh->sh); - } checksh = *parysh; suninfect(checksh); stdissolve(checksh); // Detach connections to old tets. @@ -11284,7 +10209,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } // if (checksubfaceflag) if (checksubsegflag) { - if (ivf->splitbdflag) { //if (bowywat > 2) { // if (bowywat == 3) { + if (ivf->splitbdflag) { if (splitseg != NULL) { // Recover the two new subsegments in C(p). for (i = 0; i < cavesegshlist->objects; i++) { @@ -11299,11 +10224,9 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, stpivot(checksh, neightet); } else { // It's a dangling segment. - pa = sorg(checkseg); - pb = sdest(checkseg); - point2tetorg(pa, neightet); - finddirection(&neightet, pb); - assert(dest(neightet) == pb); + point2tetorg(sorg(checkseg), neightet); + finddirection(&neightet, sdest(checkseg)); + assert(dest(neightet) == sdest(checkseg)); } assert(!infected(neightet)); sstbond1(checkseg, neightet); @@ -11318,17 +10241,12 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // There should be no interior segment in C(p). assert(caveencseglist->objects == 0l); } else { - // bowywat == 1 or bowywat == 2; // The Boundary Recovery Phase. // Queue missing segments in C(p) for recovery. if (splitseg != NULL) { // Queue two new subsegments in C(p) for recovery. for (i = 0; i < cavesegshlist->objects; i++) { paryseg = (face *) fastlookup(cavesegshlist, i); - if (b->verbose > 3) { - printf(" Queue new subseg (%d, %d)\n", - pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg))); - } checkseg = *paryseg; //sstdissolve1(checkseg); // It has not been connected yet. s = randomnation(subsegstack->objects + 1); @@ -11342,10 +10260,6 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, paryseg = (face *) fastlookup(caveencseglist, i); assert(sinfected(*paryseg)); if (!smarktested(*paryseg)) { // It may be split. - if (b->verbose > 3) { - printf(" Queue a missing segment (%d, %d).\n", - pointmark(sorg(*paryseg)), pointmark(sdest(*paryseg))); - } checkseg = *paryseg; suninfect(checkseg); sstdissolve1(checkseg); // Detach connections to old tets. @@ -11359,12 +10273,10 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } } // if (checksubsegflag) - if (b->plc || b->weighted) { + if (b->weighted + ) { // Some vertices may be completed inside the cavity. They must be // detected and added to recovering list. - if (b->plc) { - tetcount = subvertstack->objects; // Re-use tetcount; - } // Since every "live" vertex must contain a pointer to a non-dead // tetrahedron, we can check for each vertex this pointer. for (i = 0; i < cavetetvertlist->objects; i++) { @@ -11376,26 +10288,12 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (b->verbose > 1) { printf(" Point #%d is non-regular after the insertion of #%d.\n", pointmark(*pts), pointmark(insertpt)); - } + } setpointtype(*pts, NREGULARVERTEX); nonregularcount++; - } else { - if (b->verbose > 3) { - printf(" Queue a dangling vertex %d.\n", pointmark(*pts)); - } - subvertstack->newindex((void **) &parypt); - *parypt = *pts; } } } - if (b->plc) { - if (subvertstack->objects > tetcount) { - // There are missing vertices after inserting the new point. - printf("DBG: Insert %d. Found %ld interior vertices.\n", - pointmark(insertpt), subvertstack->objects); - assert(0); // NEED TO DEBUG. - } - } } if (ivf->chkencflag & 1) { @@ -11404,23 +10302,14 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, paryseg = (face *) fastlookup(cavetetseglist, i); // Skip if it is the split segment. if (!sinfected(*paryseg)) { - // Skip it if it has already queued. - if (!smarktest2ed(*paryseg)) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = *paryseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(*paryseg); // An alive badface. - } + enqueuesubface(badsubsegs, paryseg); } } if (splitseg != NULL) { // Queue the two new subsegments inside C(p). for (i = 0; i < cavesegshlist->objects; i++) { paryseg = (face *) fastlookup(cavesegshlist, i); - bface = (badface *) badsubsegs->alloc(); - bface->ss = *paryseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(*paryseg); // An alive badface. + enqueuesubface(badsubsegs, paryseg); } } } // if (chkencflag & 1) @@ -11431,15 +10320,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, parysh = (face *) fastlookup(cavetetshlist, i); // Skip if it is a split subface. if (!sinfected(*parysh)) { - // Skip it if it has already queued. - if (!smarktest2ed(*parysh)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = *parysh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(*parysh); // An alive badface. - //bface->fdest = sdest(*parysh); - //bface->fapex = sapex(*parysh); - } + enqueuesubface(badsubfacs, parysh); } } // Queue all new subfaces inside C(p). @@ -11449,11 +10330,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, spivot(*parysh, checksh); // checksh is a new subface [a, b, p]. // Do not recover a deleted new face (degenerated). if (checksh.sh[3] != NULL) { - //assert(!smarktest2ed(checksh)); - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface. + enqueuesubface(badsubfacs, &checksh); } } } // if (chkencflag & 2) @@ -11462,23 +10339,23 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, // Queue all new tetrahedra in C(p). for (i = 0; i < cavebdrylist->objects; i++) { cavetet = (triface *) fastlookup(cavebdrylist, i); - //assert(!marktest2ed(*cavetet)); - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = *cavetet; - marktest2(bface->tt); - bface->forg = org(*cavetet); + enqueuetetrahedron(cavetet); } } - // C(p) is re-meshed successfully. + // C(p) is re-meshed successfully. - // Deleted the old tets in C(p). + // Delete the old tets in C(p). for (i = 0; i < caveoldtetlist->objects; i++) { searchtet = (triface *) fastlookup(caveoldtetlist, i); + if (ishulltet(*searchtet)) { + hullsize--; + } tetrahedrondealloc(searchtet->tet); } - if (splitsh != NULL) { + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { // Delete the old subfaces in sC(p). for (i = 0; i < caveshlist->objects; i++) { parysh = (face *) fastlookup(caveshlist, i); @@ -11500,7 +10377,7 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, } shellfacedealloc(subfaces, parysh->sh); } - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { // Delete the old segment in sC(p). shellfacedealloc(subsegs, splitseg->sh); } @@ -11509,22 +10386,16 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, if (ivf->lawson) { for (i = 0; i < cavebdrylist->objects; i++) { searchtet = (triface *) fastlookup(cavebdrylist, i); - //flippush(flipstack, searchtet, insertpt); flippush(flipstack, searchtet); } if (ivf->lawson > 1) { for (i = 0; i < cavetetlist->objects; i++) { searchtet = (triface *) fastlookup(cavetetlist, i); - //flippush(flipstack, searchtet, oppo(*searchtet)); flippush(flipstack, searchtet); } } } - // The vertex should already have a type. - assert(pointtype(insertpt) != UNUSEDVERTEX); - - // Clean the working lists. @@ -11542,17 +10413,60 @@ int tetgenmesh::insertvertex(point insertpt, triface *searchtet, face *splitsh, caveencshlist->restart(); } - if (b->plc || b->weighted) { + if (b->weighted || ivf->validflag) { cavetetvertlist->restart(); } - if (splitsh != NULL) { + if (((splitsh != NULL) && (splitsh->sh != NULL)) || + ((splitseg != NULL) && (splitseg->sh != NULL))) { caveshlist->restart(); caveshbdlist->restart(); cavesegshlist->restart(); } - return (int) loc; + return 1; // Point is inserted. +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// insertpoint_abort() Abort the insertion of a new vertex. // +// // +// The cavity will be restored. All working lists are cleared. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::insertpoint_abort(face *splitseg, insertvertexflags *ivf) +{ + triface *cavetet; + face *parysh; + int i; + + for (i = 0; i < caveoldtetlist->objects; i++) { + cavetet = (triface *) fastlookup(caveoldtetlist, i); + uninfect(*cavetet); + unmarktest(*cavetet); + } + for (i = 0; i < cavebdrylist->objects; i++) { + cavetet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*cavetet); + } + cavetetlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + cavetetseglist->restart(); + cavetetshlist->restart(); + if (ivf->splitbdflag) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + sunmarktest(*splitseg); + } + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + assert(smarktested(*parysh)); + sunmarktest(*parysh); + } + caveshlist->restart(); + cavesegshlist->restart(); + } } //// //// @@ -11583,6 +10497,9 @@ void tetgenmesh::transfernodes() int mtrindex; int i, j; + if (b->psc) { + assert(in->pointparamlist != NULL); + } // Read the points. coordindex = 0; @@ -11620,7 +10537,7 @@ void tetgenmesh::transfernodes() pointloop[3] = w; // Regular tetrahedralization. } } - // Determine the smallest and largests x, y and z coordinates. + // Determine the smallest and largest x, y and z coordinates. if (i == 0) { xmin = xmax = x; ymin = ymax = y; @@ -11657,7 +10574,7 @@ void tetgenmesh::transfernodes() longest = sqrt(x * x + y * y + z * z); if (longest == 0.0) { printf("Error: The point set is trivial.\n"); - terminatetetgen(3); + terminatetetgen(this, 3); } // Two identical points are distinguished by 'lengthlimit'. @@ -11761,12 +10678,12 @@ int tetgenmesh::hilbert_split(point* vertexarray,int arraysize,int gc0,int gc1, d = ((gc0 & (1< 0) { do { for (; i < arraysize; i++) { @@ -11812,12 +10729,6 @@ void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, int p[9], w, e_w, d_w, k, ei, di; int n = 3, mask = 7; - - // Record the highest order of the curve. - if (depth + 1 > max_hcurve_depth_count) { - max_hcurve_depth_count = depth + 1; - } - p[0] = 0; p[8] = arraysize; @@ -11849,13 +10760,14 @@ void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, } } - // Recursivly sort the points in sub-boxes. + // Recursively sort the points in sub-boxes. for (w = 0; w < 8; w++) { // w is the local Hilbert index (NOT Gray code). // Sort into the sub-box either there are more than 2 points in it, or // the prescribed order of the curve is not reached yet. - if ((p[w+1] - p[w] > b->hilbert_limit) || (b->hilbert_order > 0)) { - // Calulcate the start point (ei) of the curve in this sub-box. + //if ((p[w+1] - p[w] > b->hilbert_limit) || (b->hilbert_order > 0)) { + if ((p[w+1] - p[w]) > b->hilbert_limit) { + // Calculcate the start point (ei) of the curve in this sub-box. // update e = e ^ (e(w) left_rotate (d+1)). if (w == 0) { e_w = 0; @@ -11903,6 +10815,28 @@ void tetgenmesh::hilbert_sort3(point* vertexarray, int arraysize, int e, int d, } // w } +/////////////////////////////////////////////////////////////////////////////// +// // +// brio_multiscale_sort() Sort the points using BRIO and Hilbert curve. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::brio_multiscale_sort(point* vertexarray, int arraysize, + int threshold, REAL ratio, int *depth) +{ + int middle; + + middle = 0; + if (arraysize >= threshold) { + (*depth)++; + middle = arraysize * ratio; + brio_multiscale_sort(vertexarray, middle, threshold, ratio, depth); + } + // Sort the right-array (rnd-th round) using the Hilbert curve. + hilbert_sort3(&(vertexarray[middle]), arraysize - middle, 0, 0, // e, d + xmin, xmax, ymin, ymax, zmin, zmax, 0); // depth. +} + /////////////////////////////////////////////////////////////////////////////// // // // randomnation() Generate a random number between 0 and 'choices' - 1. // @@ -11935,11 +10869,11 @@ unsigned long tetgenmesh::randomnation(unsigned int choices) // Searching begins from one of handles: the input 'searchtet', a recently // // encountered tetrahedron 'recenttet', or from one chosen from a random // // sample. The choice is made by determining which one's origin is closest // -// to the point we are searcing for. // +// to the point we are searching for. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::randomsample(point searchpt, triface *searchtet) +void tetgenmesh::randomsample(point searchpt,triface *searchtet) { tetrahedron *firsttet, *tetptr; point torg; @@ -11954,44 +10888,40 @@ void tetgenmesh::randomsample(point searchpt, triface *searchtet) pointmark(searchpt)); } - if (searchtet->tet == NULL) { - // A null tet. Choose the recenttet as the starting tet. - *searchtet = recenttet; - // Recenttet should not be dead. - assert(recenttet.tet[4] != NULL); - } - - // 'searchtet' should be a valid tetrahedron. Choose the base face - // whose vertices must not be 'dummypoint'. - searchtet->ver = 3; - // Record the distance from its origin to the searching point. - torg = org(*searchtet); - searchdist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + - (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + - (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); - if (b->verbose > 3) { - printf(" Dist %g from tet (%d, %d, %d, %d).\n", searchdist, - pointmark(torg), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); - } - - // If a recently encountered tetrahedron has been recorded and has not - // been deallocated, test it as a good starting point. - if (recenttet.tet != searchtet->tet) { - recenttet.ver = 3; - torg = org(recenttet); - dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + - (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + - (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); - if (dist < searchdist) { + if (!nonconvex) { + if (searchtet->tet == NULL) { + // A null tet. Choose the recenttet as the starting tet. *searchtet = recenttet; - searchdist = dist; - if (b->verbose > 3) { - printf(" Dist %g from recent tet (%d, %d, %d, %d).\n", - searchdist, pointmark(torg), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); + // Recenttet should not be dead. + assert(recenttet.tet[4] != NULL); + } + + // 'searchtet' should be a valid tetrahedron. Choose the base face + // whose vertices must not be 'dummypoint'. + searchtet->ver = 3; + // Record the distance from its origin to the searching point. + torg = org(*searchtet); + searchdist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + + // If a recently encountered tetrahedron has been recorded and has not + // been deallocated, test it as a good starting point. + if (recenttet.tet != searchtet->tet) { + recenttet.ver = 3; + torg = org(recenttet); + dist = (searchpt[0] - torg[0]) * (searchpt[0] - torg[0]) + + (searchpt[1] - torg[1]) * (searchpt[1] - torg[1]) + + (searchpt[2] - torg[2]) * (searchpt[2] - torg[2]); + if (dist < searchdist) { + *searchtet = recenttet; + searchdist = dist; } } + } else { + // The mesh is non-convex. Do not use 'recenttet'. + assert(samples >= 1l); // Make sure at least 1 sample. + searchdist = longest; } // Select "good" candidate using k random samples, taking the closest one. @@ -12031,11 +10961,6 @@ void tetgenmesh::randomsample(point searchpt, triface *searchtet) searchtet->tet = tetptr; searchtet->ver = 11; // torg = org(t); searchdist = dist; - if (b->verbose > 3) { - printf(" Dist %g from tet (%d, %d, %d, %d).\n", searchdist, - pointmark(torg), pointmark(dest(*searchtet)), - pointmark(apex(*searchtet)), pointmark(oppo(*searchtet))); - } } } else { // A dead tet. Re-sample it. @@ -12050,7 +10975,6 @@ void tetgenmesh::randomsample(point searchpt, triface *searchtet) // // // locate() Find a tetrahedron containing a given point. // // // -// This routine implements the simple Walk-through point location algorithm. // // Begins its search from 'searchtet', assume there is a line segment L from // // a vertex of 'searchtet' to the query point 'searchpt', and simply walk // // towards 'searchpt' by traversing all faces intersected by L. // @@ -12069,22 +10993,19 @@ void tetgenmesh::randomsample(point searchpt, triface *searchtet) // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::locateresult - tetgenmesh::locate(point searchpt, triface* searchtet, int chkencflag) +enum tetgenmesh::locateresult tetgenmesh::locate(point searchpt, + triface* searchtet) { - triface neightet; - face checksh; point torg, tdest, tapex, toppo; enum {ORGMOVE, DESTMOVE, APEXMOVE} nextmove; REAL ori, oriorg, oridest, oriapex; - enum locateresult loc; + enum locateresult loc = OUTSIDE; + int t1ver; int s; if (searchtet->tet == NULL) { // A null tet. Choose the recenttet as the starting tet. - *searchtet = recenttet; - // Recenttet should not be dead. - assert(recenttet.tet[4] != NULL); + searchtet->tet = recenttet.tet; } // Check if we are in the outside of the convex hull. @@ -12092,7 +11013,6 @@ enum tetgenmesh::locateresult // Get its adjacent tet (inside the hull). searchtet->ver = 3; fsymself(*searchtet); - assert(!ishulltet(*searchtet)); } // Let searchtet be the face such that 'searchpt' lies above to it. @@ -12103,17 +11023,11 @@ enum tetgenmesh::locateresult ori = orient3d(torg, tdest, tapex, searchpt); if (ori < 0.0) break; } - if (searchtet->ver == 4) { // SELF_CHECK - assert(0); - } - - loc = OUTSIDE; // Set a default return value. + assert(searchtet->ver != 4); // Walk through tetrahedra to locate the point. while (true) { - ptloc_count++; // Count the number of visited tets. - toppo = oppo(*searchtet); // Check if the vertex is we seek. @@ -12146,8 +11060,8 @@ enum tetgenmesh::locateresult } } else { // Two faces, opposite to origin and destination, are viable. - s = randomnation(2); // 's' is in {0,1}. - if (s == 0) { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { nextmove = ORGMOVE; } else { nextmove = DESTMOVE; @@ -12156,8 +11070,8 @@ enum tetgenmesh::locateresult } else { if (oriapex < 0) { // Two faces, opposite to origin and apex, are viable. - s = randomnation(2); // 's' is in {0,1}. - if (s == 0) { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { nextmove = ORGMOVE; } else { nextmove = APEXMOVE; @@ -12171,8 +11085,8 @@ enum tetgenmesh::locateresult if (oridest < 0) { if (oriapex < 0) { // Two faces, opposite to destination and apex, are viable. - s = randomnation(2); // 's' is in {0,1}. - if (s == 0) { + //s = randomnation(2); // 's' is in {0,1}. + if (randomnation(2)) { nextmove = DESTMOVE; } else { nextmove = APEXMOVE; @@ -12190,10 +11104,8 @@ enum tetgenmesh::locateresult // tetrahedron. Check for boundary cases. if (oriorg == 0) { // Go to the face opposite to origin. - //enextfnextself(*searchtet); enextesymself(*searchtet); if (oridest == 0) { - //enextself(*searchtet); // edge apex->oppo eprevself(*searchtet); // edge oppo->apex if (oriapex == 0) { // oppo is duplicated with p. @@ -12204,7 +11116,6 @@ enum tetgenmesh::locateresult break; } if (oriapex == 0) { - //enext2self(*searchtet); enextself(*searchtet); // edge dest->oppo loc = ONEDGE; // return ONEDGE; break; @@ -12214,10 +11125,8 @@ enum tetgenmesh::locateresult } if (oridest == 0) { // Go to the face opposite to destination. - //enext2fnextself(*searchtet); eprevesymself(*searchtet); if (oriapex == 0) { - //enextself(*searchtet); eprevself(*searchtet); // edge oppo->org loc = ONEDGE; // return ONEDGE; break; @@ -12227,7 +11136,6 @@ enum tetgenmesh::locateresult } if (oriapex == 0) { // Go to the face opposite to apex - //fnextself(*searchtet); esymself(*searchtet); loc = ONFACE; // return ONFACE; break; @@ -12246,14 +11154,6 @@ enum tetgenmesh::locateresult } else { esymself(*searchtet); } - if (chkencflag) { - // Check if we are walking across a subface. - tspivot(*searchtet, checksh); - if (checksh.sh != NULL) { - loc = ENCSUBFACE; - break; - } - } // Move to the adjacent tetrahedron (maybe a hull tetrahedron). fsymself(*searchtet); if (oppo(*searchtet) == dummypoint) { @@ -12271,6 +11171,275 @@ enum tetgenmesh::locateresult return loc; } +/////////////////////////////////////////////////////////////////////////////// +// // +// flippush() Push a face (possibly will be flipped) into flipstack. // +// // +// The face is marked. The flag is used to check the validity of the face on // +// its popup. Some other flips may change it already. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::flippush(badface*& fstack, triface* flipface) +{ + if (!facemarked(*flipface)) { + badface *newflipface = (badface *) flippool->alloc(); + newflipface->tt = *flipface; + markface(newflipface->tt); + // Push this face into stack. + newflipface->nextitem = fstack; + fstack = newflipface; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// incrementalflip() Incrementally flipping to construct DT. // +// // +// Faces need to be checked for flipping are already queued in 'flipstack'. // +// Return the total number of performed flips. // +// // +// Comment: This routine should be only used in the incremental Delaunay // +// construction. In other cases, lawsonflip3d() should be used. // +// // +// If the new point lies outside of the convex hull ('hullflag' is set). The // +// incremental flip algorithm still works as usual. However, we must ensure // +// that every flip (2-to-3 or 3-to-2) does not create a duplicated (existing)// +// edge or face. Otherwise, the underlying space of the triangulation becomes// +// non-manifold and it is not possible to flip further. // +// Thanks to Joerg Rambau and Frank Lutz for helping in this issue. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::incrementalflip(point newpt, int hullflag, flipconstraints *fc) +{ + badface *popface; + triface fliptets[5], *parytet; + point *pts, *parypt, pe; + REAL sign, ori; + int flipcount = 0; + int t1ver; + int i; + + if (b->verbose > 2) { + printf(" Lawson flip (%ld faces).\n", flippool->items); + } + + if (hullflag) { + // 'newpt' lies in the outside of the convex hull. + // Mark all hull vertices which are connecting to it. + popface = flipstack; + while (popface != NULL) { + pts = (point *) popface->tt.tet; + for (i = 4; i < 8; i++) { + if ((pts[i] != newpt) && (pts[i] != dummypoint)) { + if (!pinfected(pts[i])) { + pinfect(pts[i]); + cavetetvertlist->newindex((void **) &parypt); + *parypt = pts[i]; + } + } + } + popface = popface->nextitem; + } + } + + // Loop until the queue is empty. + while (flipstack != NULL) { + + // Pop a face from the stack. + popface = flipstack; + fliptets[0] = popface->tt; + flipstack = flipstack->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // Skip it if it is a dead tet (destroyed by previous flips). + if (isdeadtet(fliptets[0])) continue; + // Skip it if it is not the same tet as we saved. + if (!facemarked(fliptets[0])) continue; + + unmarkface(fliptets[0]); + + if ((point) fliptets[0].tet[7] == dummypoint) { + // It must be a hull edge. + fliptets[0].ver = epivot[fliptets[0].ver]; + // A hull edge. The current convex hull may be enlarged. + fsym(fliptets[0], fliptets[1]); + pts = (point *) fliptets[1].tet; + ori = orient3d(pts[4], pts[5], pts[6], newpt); + if (ori < 0) { + // Visible. The convex hull will be enlarged. + // Decide which flip (2-to-3, 3-to-2, or 4-to-1) to use. + // Check if the tet [a,c,e,d] or [c,b,e,d] exists. + enext(fliptets[1], fliptets[2]); + eprev(fliptets[1], fliptets[3]); + fnextself(fliptets[2]); // [a,c,e,*] + fnextself(fliptets[3]); // [c,b,e,*] + if (oppo(fliptets[2]) == newpt) { + if (oppo(fliptets[3]) == newpt) { + // Both tets exist! A 4-to-1 flip is found. + terminatetetgen(this, 2); // Report a bug. + } else { + esym(fliptets[2], fliptets[0]); + fnext(fliptets[0], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Perform a 3-to-2 flip. Replace edge [c,a] by face [d,e,b]. + // This corresponds to my standard labels, where edge [e,d] is + // repalced by face [a,b,c], and a is the new vertex. + // [0] [c,a,d,e] (d = newpt) + // [1] [c,a,e,b] (c = dummypoint) + // [2] [c,a,b,d] + flip32(fliptets, 1, fc); + } + } else { + if (oppo(fliptets[3]) == newpt) { + fnext(fliptets[3], fliptets[0]); + fnext(fliptets[0], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Perform a 3-to-2 flip. Replace edge [c,b] by face [d,a,e]. + // [0] [c,b,d,a] (d = newpt) + // [1] [c,b,a,e] (c = dummypoint) + // [2] [c,b,e,d] + flip32(fliptets, 1, fc); + } else { + if (hullflag) { + // Reject this flip if pe is already marked. + pe = oppo(fliptets[1]); + if (!pinfected(pe)) { + pinfect(pe); + cavetetvertlist->newindex((void **) &parypt); + *parypt = pe; + // Perform a 2-to-3 flip. + flip23(fliptets, 1, fc); + } else { + // Reject this flip. + flipcount--; + } + } else { + // Perform a 2-to-3 flip. Replace face [a,b,c] by edge [e,d]. + // [0] [a,b,c,d], d = newpt. + // [1] [b,a,c,e], c = dummypoint. + flip23(fliptets, 1, fc); + } + } + } + flipcount++; + } + continue; + } // if (dummypoint) + + fsym(fliptets[0], fliptets[1]); + if ((point) fliptets[1].tet[7] == dummypoint) { + // A hull face is locally Delaunay. + continue; + } + // Check if the adjacent tet has already been tested. + if (marktested(fliptets[1])) { + // It has been tested and it is Delaunay. + continue; + } + + // Test whether the face is locally Delaunay or not. + pts = (point *) fliptets[1].tet; + if (b->weighted) { + sign = orient4d_s(pts[4], pts[5], pts[6], pts[7], newpt, + pts[4][3], pts[5][3], pts[6][3], pts[7][3], + newpt[3]); + } else { + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], newpt); + } + + + if (sign < 0) { + point pd = newpt; + point pe = oppo(fliptets[1]); + // Check the convexity of its three edges. Stop checking either a + // locally non-convex edge (ori < 0) or a flat edge (ori = 0) is + // encountered, and 'fliptet' represents that edge. + for (i = 0; i < 3; i++) { + ori = orient3d(org(fliptets[0]), dest(fliptets[0]), pd, pe); + if (ori <= 0) break; + enextself(fliptets[0]); + } + if (ori > 0) { + // A 2-to-3 flip is found. + // [0] [a,b,c,d], + // [1] [b,a,c,e]. no dummypoint. + flip23(fliptets, 0, fc); + flipcount++; + } else { // ori <= 0 + // The edge ('fliptets[0]' = [a',b',c',d]) is non-convex or flat, + // where the edge [a',b'] is one of [a,b], [b,c], and [c,a]. + // Check if there are three or four tets sharing at this edge. + esymself(fliptets[0]); // [b,a,d,c] + for (i = 0; i < 3; i++) { + fnext(fliptets[i], fliptets[i+1]); + } + if (fliptets[3].tet == fliptets[0].tet) { + // A 3-to-2 flip is found. (No hull tet.) + flip32(fliptets, 0, fc); + flipcount++; + } else { + // There are more than 3 tets at this edge. + fnext(fliptets[3], fliptets[4]); + if (fliptets[4].tet == fliptets[0].tet) { + if (ori == 0) { + // A 4-to-4 flip is found. (Two hull tets may be involved.) + // Current tets in 'fliptets': + // [0] [b,a,d,c] (d may be newpt) + // [1] [b,a,c,e] + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + esymself(fliptets[0]); // [a,b,c,d] + // A 2-to-3 flip replaces face [a,b,c] by edge [e,d]. + // This creates a degenerate tet [e,d,a,b] (tmpfliptets[0]). + // It will be removed by the followed 3-to-2 flip. + flip23(fliptets, 0, fc); // No hull tet. + fnext(fliptets[3], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Current tets in 'fliptets': + // [0] [...] + // [1] [b,a,d,e] (degenerated, d may be new point). + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + // A 3-to-2 flip replaces edge [b,a] by face [d,e,f]. + // Hull tets may be involved (f may be dummypoint). + flip32(&(fliptets[1]), (apex(fliptets[3]) == dummypoint), fc); + flipcount++; + } + } + } + } // ori + } else { + // The adjacent tet is Delaunay. Mark it to avoid testing it again. + marktest(fliptets[1]); + // Save it for unmarking it later. + cavebdrylist->newindex((void **) &parytet); + *parytet = fliptets[1]; + } + + } // while (flipstack) + + // Unmark saved tetrahedra. + for (i = 0; i < cavebdrylist->objects; i++) { + parytet = (triface *) fastlookup(cavebdrylist, i); + unmarktest(*parytet); + } + cavebdrylist->restart(); + + if (hullflag) { + // Unmark infected vertices. + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + puninfect(*parypt); + } + cavetetvertlist->restart(); + } + + + return flipcount; +} + /////////////////////////////////////////////////////////////////////////////// // // // initialdelaunay() Create an initial Delaunay tetrahedralization. // @@ -12363,15 +11532,15 @@ void tetgenmesh::initialdelaunay(point pa, point pb, point pc, point pd) // // /////////////////////////////////////////////////////////////////////////////// + void tetgenmesh::incrementaldelaunay(clock_t& tv) { triface searchtet; point *permutarray, swapvertex; - insertvertexflags ivf; REAL v1[3], v2[3], n[3]; REAL bboxsize, bboxsize2, bboxsize3, ori; - int randindex, loc; - int ngroup, nstart, nend; + int randindex; + int ngroup = 0; int i, j; if (!b->quiet) { @@ -12382,82 +11551,81 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) permutarray = new point[in->numberofpoints]; points->traversalinit(); - if (b->verbose) { - printf(" Permuting vertices.\n"); - } - srand(in->numberofpoints); - for (i = 0; i < in->numberofpoints; i++) { - randindex = rand() % (i + 1); // randomnation(i + 1); - permutarray[i] = permutarray[randindex]; - permutarray[randindex] = (point) points->traverse(); - } - if (b->brio_hilbert) { // -b option + if (b->no_sort) { if (b->verbose) { - printf(" Sort the points using simple BRIO and Hilbert curve L(%d).\n", - b->hilbert_limit); + printf(" Using the input order.\n"); } - hilbert_init(in->mesh_dim); - max_hcurve_depth_count = 0; - - ngroup = (int) log((double) in->numberofpoints); - nstart = 0; - for (i = 0; i < ngroup; i++) { - nend = in->numberofpoints >> (ngroup - 1 - i); - hilbert_sort3(&(permutarray[nstart]), nend - nstart, 0, 0, // e, d - xmin, xmax, ymin, ymax, zmin, zmax, 0); - nstart = nend; + for (i = 0; i < in->numberofpoints; i++) { + permutarray[i] = (point) points->traverse(); } + } else { if (b->verbose) { - printf(" Number of sorted subsets: %d.\n", ngroup); - printf(" Maximum curve order: %d.\n", max_hcurve_depth_count); + printf(" Permuting vertices.\n"); + } + srand(in->numberofpoints); + for (i = 0; i < in->numberofpoints; i++) { + randindex = rand() % (i + 1); // randomnation(i + 1); + permutarray[i] = permutarray[randindex]; + permutarray[randindex] = (point) points->traverse(); + } + if (b->brio_hilbert) { // -b option + if (b->verbose) { + printf(" Sorting vertices.\n"); + } + hilbert_init(in->mesh_dim); + brio_multiscale_sort(permutarray, in->numberofpoints, b->brio_threshold, + b->brio_ratio, &ngroup); } } tv = clock(); // Remember the time for sorting points. // Calculate the diagonal size of its bounding box. - bboxsize = sqrt(NORM2(xmax - xmin, ymax - ymin, zmax - zmin)); + bboxsize = sqrt(norm2(xmax - xmin, ymax - ymin, zmax - zmin)); bboxsize2 = bboxsize * bboxsize; bboxsize3 = bboxsize2 * bboxsize; // Make sure the second vertex is not identical with the first one. i = 1; - while ((DIST(permutarray[0], permutarray[i]) / bboxsize) < b->epsilon) { + while ((distance(permutarray[0],permutarray[i])/bboxsize)epsilon) { i++; if (i == in->numberofpoints - 1) { printf("Exception: All vertices are (nearly) identical (Tol = %g).\n", b->epsilon); - terminatetetgen(10); + terminatetetgen(this, 10); } } if (i > 1) { - // Swap to move the non-indetical vertex from index i to index 1. + // Swap to move the non-identical vertex from index i to index 1. swapvertex = permutarray[i]; permutarray[i] = permutarray[1]; permutarray[1] = swapvertex; } // Make sure the third vertex is not collinear with the first two. + // Acknowledgement: Thanks Jan Pomplun for his correction by using + // epsilon^2 and epsilon^3 (instead of epsilon). 2013-08-15. i = 2; for (j = 0; j < 3; j++) { v1[j] = permutarray[1][j] - permutarray[0][j]; v2[j] = permutarray[i][j] - permutarray[0][j]; } - CROSS(v1, v2, n); - while ((sqrt(NORM2(n[0], n[1], n[2])) / bboxsize2) < b->epsilon) { + cross(v1, v2, n); + while ((sqrt(norm2(n[0], n[1], n[2])) / bboxsize2) < + (b->epsilon * b->epsilon)) { i++; if (i == in->numberofpoints - 1) { printf("Exception: All vertices are (nearly) collinear (Tol = %g).\n", b->epsilon); - terminatetetgen(10); + terminatetetgen(this, 10); } for (j = 0; j < 3; j++) { v2[j] = permutarray[i][j] - permutarray[0][j]; } - CROSS(v1, v2, n); + cross(v1, v2, n); } if (i > 2) { - // Swap to move the non-indetical vertex from index i to index 1. + // Swap to move the non-identical vertex from index i to index 1. swapvertex = permutarray[i]; permutarray[i] = permutarray[2]; permutarray[2] = swapvertex; @@ -12465,20 +11633,20 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) // Make sure the fourth vertex is not coplanar with the first three. i = 3; - ori = orient3d(permutarray[0], permutarray[1], permutarray[2], - permutarray[i]); - while ((fabs(ori) / bboxsize3) < b->epsilon) { + ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], + permutarray[i]); + while ((fabs(ori) / bboxsize3) < (b->epsilon * b->epsilon * b->epsilon)) { i++; if (i == in->numberofpoints) { printf("Exception: All vertices are coplanar (Tol = %g).\n", b->epsilon); - terminatetetgen(10); + terminatetetgen(this, 10); } - ori = orient3d(permutarray[0], permutarray[1], permutarray[2], - permutarray[i]); + ori = orient3dfast(permutarray[0], permutarray[1], permutarray[2], + permutarray[i]); } if (i > 3) { - // Swap to move the non-indetical vertex from index i to index 1. + // Swap to move the non-identical vertex from index i to index 1. swapvertex = permutarray[i]; permutarray[i] = permutarray[3]; permutarray[3] = swapvertex; @@ -12500,11 +11668,14 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) if (b->verbose) { printf(" Incrementally inserting vertices.\n"); } + insertvertexflags ivf; + flipconstraints fc; // Choose algorithm: Bowyer-Watson (default) or Incremental Flip if (b->incrflip) { ivf.bowywat = 0; ivf.lawson = 1; + fc.enqflag = 1; } else { ivf.bowywat = 1; ivf.lawson = 0; @@ -12512,45 +11683,58 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) for (i = 4; i < in->numberofpoints; i++) { - if (b->verbose > 2) printf(" #%d", i); if (pointtype(permutarray[i]) == UNUSEDVERTEX) { setpointtype(permutarray[i], VOLVERTEX); } - // Auto choose the starting tet for point location. - searchtet.tet = NULL; + if (b->brio_hilbert || b->no_sort) { // -b or -b/1 + // Start the last updated tet. + searchtet.tet = recenttet.tet; + } else { // -b0 + // Randomly choose the starting tet for point location. + searchtet.tet = NULL; + } ivf.iloc = (int) OUTSIDE; // Insert the vertex. - loc = insertvertex(permutarray[i], &searchtet, NULL, NULL, &ivf); - if (loc == (int) ONVERTEX) { - // The point already exists. Mark it and do nothing on it. - swapvertex = org(searchtet); - assert(swapvertex != permutarray[i]); // SELF_CHECK - if (b->object != tetgenbehavior::STL) { - if (!b->quiet) { - printf("Warning: Point #%d is coincident with #%d. Ignored!\n", - pointmark(permutarray[i]), pointmark(swapvertex)); - } + if (insertpoint(permutarray[i], &searchtet, NULL, NULL, &ivf)) { + if (flipstack != NULL) { + // Perform flip to recover Delaunayness. + incrementalflip(permutarray[i], (ivf.iloc == (int) OUTSIDE), &fc); + } + } else { + if (ivf.iloc == (int) ONVERTEX) { + // The point already exists. Mark it and do nothing on it. + swapvertex = org(searchtet); + assert(swapvertex != permutarray[i]); // SELF_CHECK + if (b->object != tetgenbehavior::STL) { + if (!b->quiet) { + printf("Warning: Point #%d is coincident with #%d. Ignored!\n", + pointmark(permutarray[i]), pointmark(swapvertex)); + } + } + setpoint2ppt(permutarray[i], swapvertex); + setpointtype(permutarray[i], DUPLICATEDVERTEX); + dupverts++; + } else if (ivf.iloc == (int) NEARVERTEX) { + swapvertex = point2ppt(permutarray[i]); + if (!b->quiet) { + printf("Warning: Point %d is replaced by point %d.\n", + pointmark(permutarray[i]), pointmark(swapvertex)); + printf(" Avoid creating a very short edge (len = %g) (< %g).\n", + permutarray[i][3], b->minedgelength); + printf(" You may try a smaller tolerance (-T) (current is %g)\n", + b->epsilon); + printf(" or use the option -M0/1 to avoid such replacement.\n"); + } + // Remember it is a duplicated point. + setpointtype(permutarray[i], DUPLICATEDVERTEX); + // Count the number of duplicated points. + dupverts++; } - setpoint2ppt(permutarray[i], swapvertex); - setpointtype(permutarray[i], DUPLICATEDVERTEX); - dupverts++; - continue; - } else if (loc == (int) NREGULARVERTEX) { - // The point is non-regular. Skipped. - continue; - } - if (ivf.lawson) { - // Perform flip to recover Delaunayness. - lawsonflip3d(permutarray[i], ivf.lawson, 0, 0, 0); } } - if (b->brio_hilbert) { - b->brio_hilbert = 0; // Disable it. - } - delete [] permutarray; } @@ -12562,123 +11746,6 @@ void tetgenmesh::incrementaldelaunay(clock_t& tv) //// //// //// //// -/////////////////////////////////////////////////////////////////////////////// -// // -// calculateabovepoint() Calculate a point above a facet in 'dummypoint'. // -// // -/////////////////////////////////////////////////////////////////////////////// - -bool tetgenmesh::calculateabovepoint(arraypool *facpoints, point *ppa, - point *ppb, point *ppc) -{ - point *ppt, pa, pb, pc; - REAL v1[3], v2[3], n[3]; - REAL lab, len, A, area; - REAL x, y, z; - int i; - - ppt = (point *) fastlookup(facpoints, 0); - pa = *ppt; // a is the first point. - pb = pc = NULL; // Avoid compiler warnings. - - // Get a point b s.t. the length of [a, b] is maximal. - lab = 0; - for (i = 1; i < facpoints->objects; i++) { - ppt = (point *) fastlookup(facpoints, i); - x = (*ppt)[0] - pa[0]; - y = (*ppt)[1] - pa[1]; - z = (*ppt)[2] - pa[2]; - len = x * x + y * y + z * z; - if (len > lab) { - lab = len; - pb = *ppt; - } - } - lab = sqrt(lab); - if (lab == 0) { - if (!b->quiet) { - printf("Warning: All points of a facet are coincident with %d.\n", - pointmark(pa)); - } - return false; - } - - // Get a point c s.t. the area of [a, b, c] is maximal. - v1[0] = pb[0] - pa[0]; - v1[1] = pb[1] - pa[1]; - v1[2] = pb[2] - pa[2]; - A = 0; - for (i = 1; i < facpoints->objects; i++) { - ppt = (point *) fastlookup(facpoints, i); - v2[0] = (*ppt)[0] - pa[0]; - v2[1] = (*ppt)[1] - pa[1]; - v2[2] = (*ppt)[2] - pa[2]; - CROSS(v1, v2, n); - area = DOT(n, n); - if (area > A) { - A = area; - pc = *ppt; - } - } - if (A == 0) { - // All points are collinear. No above point. - if (!b->quiet) { - printf("Warning: All points of a facet are collinaer with [%d, %d].\n", - pointmark(pa), pointmark(pb)); - } - return false; - } - - // Calculate an above point of this facet. - facenormal(pa, pb, pc, n, 1, NULL); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - lab /= 2.0; // Half the maximal length. - dummypoint[0] = pa[0] + lab * n[0]; - dummypoint[1] = pa[1] + lab * n[1]; - dummypoint[2] = pa[2] + lab * n[2]; - - if (ppa != NULL) { - // Return the three points. - *ppa = pa; - *ppb = pb; - *ppc = pc; - } - - return true; -} - -/////////////////////////////////////////////////////////////////////////////// -// // -// Calculate an above point. It lies above the plane containing the subface // -// [a,b,c], and save it in dummypoint. Moreover, the vector pa->dummypoint // -// is the normal of the plane. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::calculateabovepoint4(point pa, point pb, point pc, point pd) -{ - arraypool *ptarray; - point *parypt; - - ptarray = new arraypool(sizeof(point), 4); - - ptarray->newindex((void **) &parypt); - *parypt = pa; - ptarray->newindex((void **) &parypt); - *parypt = pb; - ptarray->newindex((void **) &parypt); - *parypt = pc; - ptarray->newindex((void **) &parypt); - *parypt = pd; - - calculateabovepoint(ptarray, NULL, NULL, NULL); - - delete ptarray; -} - /////////////////////////////////////////////////////////////////////////////// // // // flipshpush() Push a facet edge into flip stack. // @@ -12699,19 +11766,20 @@ void tetgenmesh::flipshpush(face* flipedge) /////////////////////////////////////////////////////////////////////////////// // // -// flip22() Remove an edge by transforming 2-to-2 subfaces. // +// flip22() Perform a 2-to-2 flip in surface mesh. // // // -// 'flipfaces' contains two faces: abc and bad. This routine removes these 2 // -// faces and replaces them by two new faces: cdb and dca. // +// 'flipfaces' is an array of two subfaces. On input, they are [a,b,c] and // +// [b,a,d]. On output, they are [c,d,b] and [d,c,a]. As a result, edge [a,b] // +// is replaced by edge [c,d]. // // // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) { - face bdedges[4], outfaces[4], infaces[4], bdsegs[4]; - face checkface, checkseg; + face bdedges[4], outfaces[4], infaces[4]; + face bdsegs[4]; + face checkface; point pa, pb, pc, pd; - badface *bface; int i; pa = sorg(flipfaces[0]); @@ -12723,10 +11791,6 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) sesymself(flipfaces[1]); } - if (b->verbose > 3) { - printf(" flip 2-to-2: (%d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd)); - } flip22count++; // Collect the four boundary edges. @@ -12741,8 +11805,7 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) infaces[i] = outfaces[i]; sspivot(bdedges[i], bdsegs[i]); if (outfaces[i].sh != NULL) { - sspivot(bdedges[i], checkseg); - if (checkseg.sh != NULL) { + if (isshsubseg(bdedges[i])) { spivot(infaces[i], checkface); while (checkface.sh != bdedges[i].sh) { infaces[i] = checkface; @@ -12756,9 +11819,9 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) // Shellmark does not change. // area constraint does not change. - // Transform abc -> cdb. + // Transform [a,b,c] -> [c,d,b]. setshvertices(flipfaces[0], pc, pd, pb); - // Transform bad -> dca. + // Transform [b,a,d] -> [d,c,a]. setshvertices(flipfaces[1], pd, pc, pa); // Update the point-to-subface map. @@ -12794,12 +11857,7 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) ssbond(bdedges[i], bdsegs[(3 + i) % 4]); if (chkencflag & 1) { // Queue this segment for encroaching check. - if (!smarktest2ed(bdsegs[(3 + i) % 4])) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = bdsegs[(3 + i) % 4]; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(bface->ss); // An alive badface. - } + enqueuesubface(badsubsegs, &(bdsegs[(3 + i) % 4])); } } else { ssdissolve(bdedges[i]); @@ -12809,12 +11867,7 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) if (chkencflag & 2) { // Queue the flipped subfaces for quality/encroaching checks. for (i = 0; i < 2; i++) { - if (!smarktest2ed(flipfaces[i])) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = flipfaces[i]; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(bface->ss); // An alive badface. - } + enqueuesubface(badsubfacs, &(flipfaces[i])); } } @@ -12845,22 +11898,17 @@ void tetgenmesh::flip22(face* flipfaces, int flipflag, int chkencflag) void tetgenmesh::flip31(face* flipfaces, int flipflag) { - face bdedges[3], outfaces[3], infaces[3], bdsegs[3]; - face checkface, checkseg; - point pa, pb, pc, delpt; - REAL area; + face bdedges[3], outfaces[3], infaces[3]; + face bdsegs[3]; + face checkface; + point pa, pb, pc; int i; - delpt = sorg(flipfaces[0]); pa = sdest(flipfaces[0]); pb = sdest(flipfaces[1]); pc = sdest(flipfaces[2]); - if (b->verbose > 3) { - printf(" flip 3-to-1: (%d, %d, %d) - %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(delpt)); - } - // flip31count++; + flip31count++; // Collect all infos at the three boundary edges. for (i = 0; i < 3; i++) { @@ -12869,8 +11917,7 @@ void tetgenmesh::flip31(face* flipfaces, int flipflag) infaces[i] = outfaces[i]; sspivot(bdedges[i], bdsegs[i]); if (outfaces[i].sh != NULL) { - sspivot(bdedges[i], checkseg); - if (checkseg.sh != NULL) { + if (isshsubseg(bdedges[i])) { spivot(infaces[i], checkface); while (checkface.sh != bdedges[i].sh) { infaces[i] = checkface; @@ -12885,8 +11932,11 @@ void tetgenmesh::flip31(face* flipfaces, int flipflag) setshvertices(flipfaces[3], pa, pb,pc); setshellmark(flipfaces[3], shellmark(flipfaces[0])); if (checkconstraints) { - area = areabound(flipfaces[0]); - setareabound(flipfaces[3], area); + //area = areabound(flipfaces[0]); + setareabound(flipfaces[3], areabound(flipfaces[0])); + } + if (useinsertradius) { + setfacetindex(flipfaces[3], getfacetindex(flipfaces[0])); } // Update the point-to-subface map. @@ -12943,15 +11993,13 @@ long tetgenmesh::lawsonflip() { badface *popface; face flipfaces[2]; - face checkseg; point pa, pb, pc, pd; REAL sign; - long flipcount; + long flipcount = 0; if (b->verbose > 2) { printf(" Lawson flip %ld edges.\n", flippool->items); } - flipcount = flip22count; while (flipstack != (badface *) NULL) { @@ -12968,8 +12016,7 @@ long tetgenmesh::lawsonflip() // Skip it if it is not the same edge as we saved. if ((sorg(flipfaces[0]) != pa) || (sdest(flipfaces[0]) != pb)) continue; // Skip it if it is a subsegment. - sspivot(flipfaces[0], checkseg); - if (checkseg.sh != NULL) continue; + if (isshsubseg(flipfaces[0])) continue; // Get the adjacent face. spivot(flipfaces[0], flipfaces[1]); @@ -12982,16 +12029,15 @@ long tetgenmesh::lawsonflip() if (sign < 0) { // It is non-locally Delaunay. Flip it. flip22(flipfaces, 1, 0); + flipcount++; } } if (b->verbose > 2) { - printf(" %ld edges stacked, %ld flips.\n", flippool->items, - flip22count - flipcount); + printf(" Performed %ld flips.\n", flipcount); } - assert(flippool->items == 0l); // SELF_CHECK - return flip22count - flipcount; + return flipcount; } /////////////////////////////////////////////////////////////////////////////// @@ -13003,75 +12049,72 @@ long tetgenmesh::lawsonflip() // 'caveshbdlist' contains new subfaces in C(p). If the new point lies on a // // segment, 'cavesegshlist' returns the two new subsegments. // // // -// NOTE: the old subfaces in C(p) are not deleted. Theyare needed in case we // -// want to remove the new point immedately. // +// 'iloc' suggests the location of the point. If it is OUTSIDE, this routine // +// will first locate the point. It starts searching from 'searchsh' or 'rec- // +// entsh' if 'searchsh' is NULL. // +// // +// If 'bowywat' is set (1), the Bowyer-Watson algorithm is used to insert // +// the vertex. Otherwise, only insert the vertex in the initial cavity. // +// // +// If 'iloc' is 'INSTAR', this means the cavity of this vertex was already // +// provided in the list 'caveshlist'. // +// // +// If 'splitseg' is not NULL, the new vertex lies on the segment and it will // +// be split. 'iloc' must be either 'ONEDGE' or 'INSTAR'. // +// // +// 'rflag' (rounding) is a parameter passed to slocate() function. If it is // +// set, after the location of the point is found, either ONEDGE or ONFACE, // +// round the result using an epsilon. // +// // +// NOTE: the old subfaces in C(p) are not deleted. They're needed in case we // +// want to remove the new point immediately. // // // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::sinsertvertex(point insertpt, face *searchsh, face *splitseg, - int iloc, int bowywat) + int iloc, int bowywat, int rflag) { - triface adjtet; face cavesh, neighsh, *parysh; face newsh, casout, casin; - face aseg, bseg, aoutseg, boutseg; face checkseg; - point pa, pb, pc; - enum locateresult loc; - REAL sign, ori, area; + point pa, pb; + enum locateresult loc = OUTSIDE; + REAL sign, ori; int i, j; if (b->verbose > 2) { printf(" Insert facet point %d.\n", pointmark(insertpt)); } - if (splitseg != NULL) { + if (bowywat == 3) { + loc = INSTAR; + } + + if ((splitseg != NULL) && (splitseg->sh != NULL)) { // A segment is going to be split, no point location. spivot(*splitseg, *searchsh); - loc = ONEDGE; + if (loc != INSTAR) loc = ONEDGE; } else { - loc = (enum locateresult) iloc; + if (loc != INSTAR) loc = (enum locateresult) iloc; if (loc == OUTSIDE) { // Do point location in surface mesh. if (searchsh->sh == NULL) { *searchsh = recentsh; } - // Start searching from 'searchsh'. - loc = slocate(insertpt, searchsh, 1, 1, 0); + // Search the vertex. An above point must be provided ('aflag' = 1). + loc = slocate(insertpt, searchsh, 1, 1, rflag); } } - if (b->verbose > 2) { - if (searchsh->sh != NULL) { - pa = sorg(*searchsh); - pb = sdest(*searchsh); - pc = sapex(*searchsh); - printf(" Located subface (%d, %d, %d).\n", pointmark(pa), - pointmark(pb), pointmark(pc)); - } else { - assert(splitseg != NULL); - pa = sorg(*splitseg); - pb = sdest(*splitseg); - printf(" Located segment (%d, %d).\n", pointmark(pa),pointmark(pb)); - } - } - -if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { // Form the initial sC(p). if (loc == ONFACE) { - if (b->verbose > 2) { - printf(" Inside face.\n"); - } // Add the face into list (in B-W cavity). smarktest(*searchsh); caveshlist->newindex((void **) &parysh); *parysh = *searchsh; } else if (loc == ONEDGE) { - if (b->verbose > 2) { - printf(" On edge.\n"); - } - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { splitseg->shver = 0; pa = sorg(*splitseg); } else { @@ -13082,9 +12125,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { neighsh = *searchsh; while (1) { // Adjust the origin of its edge to be 'pa'. - if (sorg(neighsh) != pa) { - sesymself(neighsh); - } + if (sorg(neighsh) != pa) sesymself(neighsh); // Add this face into list (in B-W cavity). smarktest(neighsh); caveshlist->newindex((void **) &parysh); @@ -13100,18 +12141,12 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } } // If (not a non-dangling segment). } else if (loc == ONVERTEX) { - if (b->verbose > 2) { - printf(" On vertex.\n"); - } return (int) loc; } else if (loc == OUTSIDE) { // Comment: This should only happen during the surface meshing step. // Enlarge the convex hull of the triangulation by including p. // An above point of the facet is set in 'dummypoint' to replace // orient2d tests by orient3d tests. - if (b->verbose > 2) { - printf(" Outside face.\n"); - } // Imagine that the current edge a->b (in 'searchsh') is horizontal in a // plane, and a->b is directed from left to right, p lies above a->b. // Find the right-most edge of the triangulation which is visible by p. @@ -13121,9 +12156,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { spivot(neighsh, casout); if (casout.sh == NULL) { // A convex hull edge. Is it visible by p. - pa = sorg(neighsh); - pb = sdest(neighsh); - ori = orient3d(pa, pb, dummypoint, insertpt); + ori = orient3d(sorg(neighsh), sdest(neighsh), dummypoint, insertpt); if (ori < 0) { *searchsh = neighsh; // Visible, update 'searchsh'. } else { @@ -13144,8 +12177,11 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { setshvertices(newsh, pb, pa, insertpt); setshellmark(newsh, shellmark(*searchsh)); if (checkconstraints) { - area = areabound(*searchsh); - setareabound(newsh, area); + //area = areabound(*searchsh); + setareabound(newsh, areabound(*searchsh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(*searchsh)); } // Connect the new subface to the bottom subfaces. sbond1(newsh, *searchsh); @@ -13181,47 +12217,30 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { // Finish the process if p is not visible by the hull edge. if (ori >= 0) break; } + } else if (loc == INSTAR) { + // Under this case, the sub-cavity sC(p) has already been formed in + // insertvertex(). } -} else { - - // Under this case, the sub-cavity sC(p) has already been formed in - // insertvertex(). Check it. - // FOR DEBUG ONLY. - for (i = 0; i < caveshlist->objects; i++) { - cavesh = * (face *) fastlookup(caveshlist, i); - assert(smarktested(cavesh)); - } - if (splitseg != NULL) { - assert(smarktested(*splitseg)); - } - - -}// if (bowywat < 3) - // Form the Bowyer-Watson cavity sC(p). for (i = 0; i < caveshlist->objects; i++) { cavesh = * (face *) fastlookup(caveshlist, i); for (j = 0; j < 3; j++) { - sspivot(cavesh, checkseg); - if (checkseg.sh == NULL) { + if (!isshsubseg(cavesh)) { spivot(cavesh, neighsh); if (neighsh.sh != NULL) { // The adjacent face exists. if (!smarktested(neighsh)) { if (bowywat) { - if (bowywat > 2) { + if (loc == INSTAR) { // if (bowywat > 2) { // It must be a boundary edge. sign = 1; } else { // Check if this subface is connected to adjacent tet(s). - stpivot(neighsh, adjtet); - if (adjtet.tet == NULL) { + if (!isshtet(neighsh)) { // Check if the subface is non-Delaunay wrt. the new pt. - pa = sorg(neighsh); - pb = sdest(neighsh); - pc = sapex(neighsh); - sign = incircle3d(pa, pb, pc, insertpt); + sign = incircle3d(sorg(neighsh), sdest(neighsh), + sapex(neighsh), insertpt); } else { // It is connected to an adjacent tet. A boundary edge. sign = 1; @@ -13265,10 +12284,6 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } // j } // i - if (b->verbose > 3) { - printf(" Size of cavity: %ld faces, %ld bdry edges.\n", - caveshlist->objects, caveshbdlist->objects); - } // Creating new subfaces. for (i = 0; i < caveshbdlist->objects; i++) { @@ -13281,10 +12296,12 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { makeshellface(subfaces, &newsh); setshvertices(newsh, pa, pb, insertpt); setshellmark(newsh, shellmark(*parysh)); - setshelltype(newsh, shelltype(*parysh)); if (checkconstraints) { - area = areabound(*parysh); - setareabound(newsh, area); + //area = areabound(*parysh); + setareabound(newsh, areabound(*parysh)); + } + if (useinsertradius) { + setfacetindex(newsh, getfacetindex(*parysh)); } // Update the point-to-subface map. if (pointtype(pa) == FREEFACETVERTEX) { @@ -13302,7 +12319,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { checkseg.shver = 0; if (sorg(newsh) != sorg(checkseg)) { sesymself(newsh); - sesymself(*parysh); // This side should also be inversed. + sesymself(*parysh); // This side should also be inverse. } spivot(casin, neighsh); while (neighsh.sh != parysh->sh) { @@ -13321,8 +12338,10 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { sbond1(*parysh, newsh); } - // Set a handle for searching. - recentsh = newsh; + if (newsh.sh != NULL) { + // Set a handle for searching. + recentsh = newsh; + } // Update the point-to-subface map. if (pointtype(insertpt) == FREEFACETVERTEX) { @@ -13350,8 +12369,6 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { if (neighsh.sh != NULL) { // Now 'neighsh' is a new subface at edge [b, #]. if (sorg(neighsh) != pb) sesymself(neighsh); - assert(sorg(neighsh) == pb); // SELF_CHECK - assert(sapex(neighsh) == insertpt); // SELF_CHECK senext2self(neighsh); // Go to the open edge [p, b]. sbond(newsh, neighsh); } else { @@ -13376,8 +12393,6 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { if (neighsh.sh != NULL) { // Now 'neighsh' is a new subface at edge [#, a]. if (sdest(neighsh) != pa) sesymself(neighsh); - assert(sdest(neighsh) == pa); // SELF_CHECK - assert(sapex(neighsh) == insertpt); // SELF_CHECK senextself(neighsh); // Go to the open edge [a, p]. sbond(newsh, neighsh); } else { @@ -13387,13 +12402,15 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } } - if (loc == ONEDGE) { - + if ((loc == ONEDGE) || ((splitseg != NULL) && (splitseg->sh != NULL)) + || (cavesegshlist->objects > 0l)) { // An edge is being split. We distinguish two cases: // (1) the edge is not on the boundary of the cavity; // (2) the edge is on the boundary of the cavity. // In case (2), the edge is either a segment or a hull edge. There are // degenerated new faces in the cavity. They must be removed. + face aseg, bseg, aoutseg, boutseg; + for (i = 0; i < cavesegshlist->objects; i++) { // Get the saved old subface. parysh = (face *) fastlookup(cavesegshlist, i); @@ -13413,7 +12430,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } assert(sapex(neighsh) == insertpt); // SELF_CHECK // Connect adjacent faces at two other edges of cavesh and neighsh. - // As a result, the two degenrated new faces are squessed from the + // As a result, the two degenerated new faces are squeezed from the // new triangulation of the cavity. Note that the squeezed faces // still hold the adjacent informations which will be used in // re-connecting subsegments (if they exist). @@ -13425,7 +12442,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { sbond1(newsh, casout); // newsh <- casout. } } else { - // There is only one subface containing this edge [a,b]. Squeese the + // There is only one subface containing this edge [a,b]. Squeeze the // degenerated new face [a,b,c] by disconnecting it from its two // adjacent subfaces at edges [b,c] and [c,a]. Note that the face // [a,b,c] still hold the connection to them. @@ -13435,7 +12452,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { sdissolve(newsh); } } - recentsh = newsh; + //recentsh = newsh; // Update the point-to-subface map. if (pointtype(insertpt) == FREEFACETVERTEX) { setpoint2sh(insertpt, sencode(newsh)); @@ -13443,18 +12460,14 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } } - if (splitseg != NULL) { - if (bowywat < 3) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { + if (loc != INSTAR) { // if (bowywat < 3) { smarktest(*splitseg); // Mark it as being processed. } aseg = *splitseg; pa = sorg(*splitseg); pb = sdest(*splitseg); - if (b->verbose > 2) { - printf(" Split seg (%d, %d) by %d.\n", pointmark(pa), - pointmark(pb), pointmark(insertpt)); - } // Insert the new point p. makeshellface(subsegs, &aseg); @@ -13464,12 +12477,14 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { setshvertices(bseg, insertpt, pb, NULL); setshellmark(aseg, shellmark(*splitseg)); setshellmark(bseg, shellmark(*splitseg)); - setshelltype(aseg, shelltype(*splitseg)); - setshelltype(bseg, shelltype(*splitseg)); if (checkconstraints) { setareabound(aseg, areabound(*splitseg)); setareabound(bseg, areabound(*splitseg)); } + if (useinsertradius) { + setfacetindex(aseg, getfacetindex(*splitseg)); + setfacetindex(bseg, getfacetindex(*splitseg)); + } // Connect [#, a]<->[a, p]. senext2(*splitseg, boutseg); // Temporarily use boutseg. @@ -13491,7 +12506,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { sbond(aoutseg, boutseg); // Connect subsegs [a, p] and [p, b] to adjacent new subfaces. - // Although the degenerated new faces have been squeesed. They still + // Although the degenerated new faces have been squeezed. They still // hold the connections to the actual new faces. for (i = 0; i < cavesegshlist->objects; i++) { parysh = (face *) fastlookup(cavesegshlist, i); @@ -13510,11 +12525,17 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { // Let the point remember the segment it lies on. - setpoint2sh(insertpt, sencode(aseg)); + if (pointtype(insertpt) == FREESEGVERTEX) { + setpoint2sh(insertpt, sencode(aseg)); + } // Update the point-to-seg map. - setpoint2sh(pa, sencode(aseg)); - setpoint2sh(pb, sencode(bseg)); - } // if (splitseg != NULL) + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(aseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(bseg)); + } + } // if ((splitseg != NULL) && (splitseg->sh != NULL)) // Delete all degenerated new faces. for (i = 0; i < cavesegshlist->objects; i++) { @@ -13526,7 +12547,7 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { } cavesegshlist->restart(); - if (splitseg != NULL) { + if ((splitseg != NULL) && (splitseg->sh != NULL)) { // Return the two new subsegments (for further process). // Re-use 'cavesegshlist'. cavesegshlist->newindex((void **) &parysh); @@ -13534,7 +12555,6 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { cavesegshlist->newindex((void **) &parysh); *parysh = bseg; } - } // if (loc == ONEDGE) @@ -13549,39 +12569,34 @@ if (bowywat < 3) { // if (bowywat == 1 || bowywat == 2) { // a segment vertex, and the origin of 'parentseg' is p. Otherwise, p is a // // facet vertex, and the origin of 'parentsh' is p. // // // -// If 'lawson' > 0, the Lawson flip algorithm is used to recover Delaunay- // -// ness after p is removed. // -// // // Within each facet, we first use a sequence of 2-to-2 flips to flip any // // edge at p, finally use a 3-to-1 flip to remove p. // // // // All new created subfaces are returned in the global array 'caveshbdlist'. // // The new segment (when p is on segment) is returned in 'parentseg'. // // // +// If 'lawson' > 0, the Lawson flip algorithm is used to recover Delaunay- // +// ness after p is removed. // +// // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, int lawson) { - face flipfaces[4], *parysh; - face spinsh, startsh, neighsh, nextsh, fakesh; - face abseg, prevseg, checkseg; - face adjseg1, adjseg2; + face flipfaces[4], spinsh, *parysh; point pa, pb, pc, pd; + REAL ori1, ori2; int it, i, j; - REAL *norm, n1[3], n2[3]; - REAL len, len1, len2; - REAL ori1, ori2; - if (parentseg != NULL) { - assert(sorg(*parentseg) == delpt); - assert(parentseg->shver == 0); // 'delpt' (p) should be a Steiner point inserted in a segment [a,b], // where 'parentseg' should be [p,b]. Find the segment [a,p]. + face startsh, neighsh, nextsh; + face abseg, prevseg, checkseg; + face adjseg1, adjseg2; + face fakesh; senext2(*parentseg, prevseg); spivotself(prevseg); - assert(prevseg.sh != NULL); prevseg.shver = 0; assert(sdest(prevseg) == delpt); // Restore the original segment [a,b]. @@ -13594,10 +12609,12 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, makeshellface(subsegs, &abseg); setshvertices(abseg, pa, pb, NULL); setshellmark(abseg, shellmark(*parentseg)); - setshelltype(abseg, shelltype(*parentseg)); if (checkconstraints) { setareabound(abseg, areabound(*parentseg)); } + if (useinsertradius) { + setfacetindex(abseg, getfacetindex(*parentseg)); + } // Connect [#, a]<->[a, b]. senext2(prevseg, adjseg1); spivotself(adjseg1); @@ -13625,16 +12642,17 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, // Get the faces in face ring at segment [p, b]. // Re-use array 'caveshlist'. spivot(*parentseg, *parentsh); - spinsh = *parentsh; - while (1) { - // Save this face in list. - caveshlist->newindex((void **) &parysh); - *parysh = spinsh; - // Go to the next face in the ring. - spivotself(spinsh); - if (spinsh.sh == NULL) break; - if (spinsh.sh == parentsh->sh) break; - } + if (parentsh->sh != NULL) { + spinsh = *parentsh; + while (1) { + // Save this face in list. + caveshlist->newindex((void **) &parysh); + *parysh = spinsh; + // Go to the next face in the ring. + spivotself(spinsh); + if (spinsh.sh == parentsh->sh) break; + } + } // Create the face ring of the new segment [a,b]. Each face in the ring // is [a,b,p] (degenerated!). It will be removed (automatically). @@ -13686,12 +12704,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, // Since we will re-connect the face ring using the faked subfaces. // We put the adjacent face of [a,b,p] to the list. spivot(neighsh, startsh); // The original adjacent subface. - if (sorg(startsh) != pa) { - sesymself(startsh); - } - assert(sorg(startsh) == pa); - assert(sdest(startsh) == pb); - assert(sapex(startsh) != delpt); + if (sorg(startsh) != pa) sesymself(startsh); sdissolve(startsh); // Connect fakesh to the segment [a,b]. ssbond(startsh, abseg); @@ -13742,9 +12755,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, parentsh = (face *) fastlookup(cavesegshlist, it); // [a,b,p] senextself(*parentsh); // [b,p,a]. spivotself(*parentsh); - if (sorg(*parentsh) != delpt) { - sesymself(*parentsh); - } + if (sorg(*parentsh) != delpt) sesymself(*parentsh); // now parentsh is [p,b,#]. if (sorg(*parentsh) != delpt) { // The vertex has already been removed in above special case. @@ -13762,10 +12773,8 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, spivotself(spinsh); assert(spinsh.sh != NULL); if (spinsh.sh == parentsh->sh) break; - if (sorg(spinsh) != delpt) { - sesymself(spinsh); - assert(sorg(spinsh) == delpt); - } + if (sorg(spinsh) != delpt) sesymself(spinsh); + assert(sorg(spinsh) == delpt); } // while (1) if (caveshlist->objects == 3) { @@ -13784,9 +12793,6 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, *parysh = flipfaces[3]; // The vertex is removed. break; - } else { - // There should be more than 3 subfaces in list. - assert(caveshlist->objects > 3); } // Search an edge to flip. @@ -13794,35 +12800,15 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, parysh = (face *) fastlookup(caveshlist, i); flipfaces[0] = *parysh; spivot(flipfaces[0], flipfaces[1]); - if (sorg(flipfaces[0]) != sdest(flipfaces[1])) { + if (sorg(flipfaces[0]) != sdest(flipfaces[1])) sesymself(flipfaces[1]); - } // Skip this edge if it belongs to a faked subface. if (!smarktested(flipfaces[0]) && !smarktested(flipfaces[1])) { pa = sorg(flipfaces[0]); pb = sdest(flipfaces[0]); pc = sapex(flipfaces[0]); pd = sapex(flipfaces[1]); - // Select a base. - facenormal(pa, pb, pc, n1, 1, NULL); - len1 = sqrt(DOT(n1, n1)); - facenormal(pa, pb, pd, n2, 1, NULL); - len2 = sqrt(DOT(n2, n2)); - if (len1 > len2) { - norm = n1; - len = len1; - } else { - norm = n2; - len = len2; - } - assert(len > 0); - norm[0] /= len; - norm[1] /= len; - norm[2] /= len; - len = DIST(pa, pb); - dummypoint[0] = pa[0] + len * norm[0]; - dummypoint[1] = pa[1] + len * norm[1]; - dummypoint[2] = pa[2] + len * norm[2]; + calculateabovepoint4(pa, pb, pc, pd); // Check if a 2-to-2 flip is possible. ori1 = orient3d(pc, pd, dummypoint, pa); ori2 = orient3d(pc, pd, dummypoint, pb); @@ -13839,6 +12825,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, } } // } // i + if (i == caveshlist->objects) { // This can happen only if there are 4 edges at p, and they are // orthogonal to each other, see Fig. 2010-11-01. @@ -13856,6 +12843,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, caveshbdlist->newindex((void **) &parysh); *parysh = flipfaces[0]; } + // The edge list at p are changed. caveshlist->restart(); } // while (1) @@ -13894,7 +12882,7 @@ int tetgenmesh::sremovevertex(point delpt, face* parentsh, face* parentseg, // If 'rflag' (rounding) is set, after the location of the point is found, // // either ONEDGE or ONFACE, round the result using an epsilon. // // // -// The returned value inducates the following cases: // +// The returned value indicates the following cases: // // - ONVERTEX, p is the origin of 'searchsh'. // // - ONEDGE, p lies on the edge of 'searchsh'. // // - ONFACE, p lies in the interior of 'searchsh'. // @@ -13907,35 +12895,19 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, face* searchsh, int aflag, int cflag, int rflag) { face neighsh; - face checkseg; - point pa, pb, pc, pd, *parypt; + point pa, pb, pc; enum locateresult loc; enum {MOVE_BC, MOVE_CA} nextmove; REAL ori, ori_bc, ori_ca; - REAL dist_bc, dist_ca; int i; - // For finding an approximate location. - //REAL n[3], len, len3; - REAL n[3], area_abc, area_abp, area_bcp, area_cap; - pa = sorg(*searchsh); pb = sdest(*searchsh); pc = sapex(*searchsh); if (!aflag) { // No above point is given. Calculate an above point for this facet. - // Re-use the 'cavetetvertlist'. - cavetetvertlist->newindex((void **) &parypt); - *parypt = pa; - cavetetvertlist->newindex((void **) &parypt); - *parypt = pb; - cavetetvertlist->newindex((void **) &parypt); - *parypt = pc; - cavetetvertlist->newindex((void **) &parypt); - *parypt = searchpt; - calculateabovepoint(cavetetvertlist, NULL, NULL, NULL); - cavetetvertlist->restart(); + calculateabovepoint4(pa, pb, pc, searchpt); } // 'dummypoint' is given. Make sure it is above [a,b,c] @@ -13970,25 +12942,7 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, if (ori_bc < 0) { if (ori_ca < 0) { // (--) // Any of the edges is a viable move. - senext(*searchsh, neighsh); // At edge [b, c]. - spivotself(neighsh); - if (neighsh.sh != NULL) { - pd = sapex(neighsh); - dist_bc = NORM2(searchpt[0] - pd[0], searchpt[1] - pd[1], - searchpt[2] - pd[2]); - } else { - dist_bc = NORM2(xmax - xmin, ymax - ymin, zmax - zmin); - } - senext2(*searchsh, neighsh); // At edge [c, a]. - spivotself(neighsh); - if (neighsh.sh != NULL) { - pd = sapex(neighsh); - dist_ca = NORM2(searchpt[0] - pd[0], searchpt[1] - pd[1], - searchpt[2] - pd[2]); - } else { - dist_ca = dist_bc; - } - if (dist_ca < dist_bc) { + if (randomnation(2)) { nextmove = MOVE_CA; } else { nextmove = MOVE_BC; @@ -14033,8 +12987,7 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, } if (!cflag) { // NON-convex case. Check if we will cross a boundary. - sspivot(*searchsh, checkseg); - if (checkseg.sh != NULL) { + if (isshsubseg(*searchsh)) { return ENCSEGMENT; } } @@ -14066,6 +13019,8 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, if (rflag) { // Round the locate result before return. + REAL n[3], area_abc, area_abp, area_bcp, area_cap; + pa = sorg(*searchsh); pb = sdest(*searchsh); pc = sapex(*searchsh); @@ -14133,23 +13088,21 @@ enum tetgenmesh::locateresult tetgenmesh::slocate(point searchpt, // The segment is given by the origin of 'searchsh' and 'endpt'. Assume the // // orientation of 'searchsh' is CCW w.r.t. the above point. // // // -// If an edge in T is found matching this segment, the segment is "locaked" // +// If an edge in T is found matching this segment, the segment is "locked" // // in T at the edge. Otherwise, flip the first edge in T that the segment // // crosses. Continue the search from the flipped face. // // // /////////////////////////////////////////////////////////////////////////////// -enum tetgenmesh::interresult - tetgenmesh::sscoutsegment(face *searchsh, point endpt) +enum tetgenmesh::interresult tetgenmesh::sscoutsegment(face *searchsh, + point endpt) { face flipshs[2], neighsh; - face newseg, checkseg; + face newseg; point startpt, pa, pb, pc, pd; enum interresult dir; enum {MOVE_AB, MOVE_CA} nextmove; - REAL ori_ab, ori_ca; - REAL dist_b, dist_c; - int shmark = 0; + REAL ori_ab, ori_ca, len; // The origin of 'searchsh' is fixed. startpt = sorg(*searchsh); // pa = startpt; @@ -14159,6 +13112,7 @@ enum tetgenmesh::interresult printf(" Scout segment (%d, %d).\n", pointmark(startpt), pointmark(endpt)); } + len = distance(startpt, endpt); // Search an edge in 'searchsh' on the path of this segment. while (1) { @@ -14177,22 +13131,22 @@ enum tetgenmesh::interresult break; } - ori_ab = orient3d(startpt, pb, dummypoint, endpt); - ori_ca = orient3d(pc, startpt, dummypoint, endpt); + // Round the results. + if ((sqrt(triarea(startpt, pb, endpt)) / len) < b->epsilon) { + ori_ab = 0.0; + } else { + ori_ab = orient3d(startpt, pb, dummypoint, endpt); + } + if ((sqrt(triarea(pc, startpt, endpt)) / len) < b->epsilon) { + ori_ca = 0.0; + } else { + ori_ca = orient3d(pc, startpt, dummypoint, endpt); + } if (ori_ab < 0) { if (ori_ca < 0) { // (--) // Both sides are viable moves. - spivot(*searchsh, neighsh); // At edge [a, b]. - assert(neighsh.sh != NULL); // SELF_CHECK - pd = sapex(neighsh); - dist_b = NORM2(endpt[0] - pd[0], endpt[1] - pd[1], endpt[2] - pd[2]); - senext2(*searchsh, neighsh); // At edge [c, a]. - spivotself(neighsh); - assert(neighsh.sh != NULL); // SELF_CHECK - pd = sapex(neighsh); - dist_c = NORM2(endpt[0] - pd[0], endpt[1] - pd[1], endpt[2] - pd[2]); - if (dist_c < dist_b) { + if (randomnation(2)) { nextmove = MOVE_CA; } else { nextmove = MOVE_AB; @@ -14232,13 +13186,32 @@ enum tetgenmesh::interresult // Move 'searchsh' to the next face, keep the origin unchanged. if (nextmove == MOVE_AB) { spivot(*searchsh, neighsh); - if (sorg(neighsh) != pb) sesymself(neighsh); - senext(neighsh, *searchsh); + if (neighsh.sh != NULL) { + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } else { + // This side (startpt->pb) is outside. It is caused by rounding error. + // Try the next side, i.e., (pc->startpt). + senext2(*searchsh, neighsh); + spivotself(neighsh); + assert(neighsh.sh != NULL); + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } } else { senext2(*searchsh, neighsh); spivotself(neighsh); - if (sdest(neighsh) != pc) sesymself(neighsh); - *searchsh = neighsh; + if (neighsh.sh != NULL) { + if (sdest(neighsh) != pc) sesymself(neighsh); + *searchsh = neighsh; + } else { + // The same reason as above. + // Try the next side, i.e., (startpt->pb). + spivot(*searchsh, neighsh); + assert(neighsh.sh != NULL); + if (sorg(neighsh) != pb) sesymself(neighsh); + senext(neighsh, *searchsh); + } } assert(sorg(*searchsh) == startpt); // SELF_CHECK @@ -14248,11 +13221,8 @@ enum tetgenmesh::interresult // Insert the segment into the triangulation. makeshellface(subsegs, &newseg); setshvertices(newseg, startpt, endpt, NULL); - // Set the actual segment marker. - if (in->facetmarkerlist != NULL) { - shmark = shellmark(*searchsh); - setshellmark(newseg, in->facetmarkerlist[shmark - 1]); - } + // Set the default segment marker. + setshellmark(newseg, 1); ssbond(*searchsh, newseg); spivot(*searchsh, neighsh); if (neighsh.sh != NULL) { @@ -14269,21 +13239,20 @@ enum tetgenmesh::interresult if (dir == ACROSSEDGE) { // Edge [b, c] intersects with the segment. senext(*searchsh, flipshs[0]); - sspivot(flipshs[0], checkseg); - if (checkseg.sh != NULL) { + if (isshsubseg(flipshs[0])) { printf("Error: Invalid PLC.\n"); pb = sorg(flipshs[0]); pc = sdest(flipshs[0]); printf(" Two segments (%d, %d) and (%d, %d) intersect.\n", pointmark(startpt), pointmark(endpt), pointmark(pb), pointmark(pc)); - terminatetetgen(3); + terminatetetgen(this, 3); } // Flip edge [b, c], queue unflipped edges (for Delaunay checks). spivot(flipshs[0], flipshs[1]); assert(flipshs[1].sh != NULL); // SELF_CHECK if (sorg(flipshs[1]) != sdest(flipshs[0])) sesymself(flipshs[1]); flip22(flipshs, 1, 0); - // The flip may create an invered triangle, check it. + // The flip may create an inverted triangle, check it. pa = sapex(flipshs[1]); pb = sapex(flipshs[0]); pc = sorg(flipshs[0]); @@ -14294,16 +13263,8 @@ enum tetgenmesh::interresult ori_ca = orient3d(pd, pc, dummypoint, pa); //assert(ori_ab * ori_ca != 0); // SELF_CHECK if (ori_ab < 0) { - if (b->verbose > 2) { - printf(" Queue an inversed triangle (%d, %d, %d) %d\n", - pointmark(pc), pointmark(pd), pointmark(pb), pointmark(pa)); - } flipshpush(&(flipshs[0])); // push it to 'flipstack' } else if (ori_ca < 0) { - if (b->verbose > 2) { - printf(" Queue an inversed triangle (%d, %d, %d) %d\n", - pointmark(pd), pointmark(pc), pointmark(pa), pointmark(pb)); - } flipshpush(&(flipshs[1])); // // push it to 'flipstack' } // Set 'searchsh' s.t. its origin is 'startpt'. @@ -14325,7 +13286,6 @@ enum tetgenmesh::interresult void tetgenmesh::scarveholes(int holes, REAL* holelist) { face *parysh, searchsh, neighsh; - face checkseg; enum locateresult loc; int i, j; @@ -14348,8 +13308,7 @@ void tetgenmesh::scarveholes(int holes, REAL* holelist) } } else { // A hull side. Check if it is protected by a segment. - sspivot(searchsh, checkseg); - if (checkseg.sh == NULL) { + if (!isshsubseg(searchsh)) { // Not protected. Save this face. if (!sinfected(searchsh)) { sinfect(searchsh); @@ -14381,8 +13340,7 @@ void tetgenmesh::scarveholes(int holes, REAL* holelist) for (j = 0; j < 3; j++) { spivot(searchsh, neighsh); if (neighsh.sh != NULL) { - sspivot(searchsh, checkseg); - if (checkseg.sh == NULL) { + if (!isshsubseg(searchsh)) { if (!sinfected(neighsh)) { sinfect(neighsh); caveshbdlist->newindex((void **) &parysh); @@ -14414,6 +13372,9 @@ void tetgenmesh::scarveholes(int holes, REAL* holelist) // // // triangulate() Create a CDT for the facet. // // // +// All vertices of the triangulation have type FACETVERTEX. The actual type // +// of boundary vertices are set by the routine unifysements(). // +// // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, @@ -14422,13 +13383,9 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, face searchsh, newsh, *parysh; face newseg; point pa, pb, pc, *ppt, *cons; - enum locateresult loc; int iloc; int i, j; - int idx, fmarker; - REAL area; - if (b->verbose > 2) { printf(" f%d: %ld vertices, %ld segments", shmark, ptlist->objects, conlist->objects); @@ -14441,61 +13398,37 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, if (ptlist->objects < 2l) { // Not a segment or a facet. return; - } if (ptlist->objects == 2l) { + } + + if (ptlist->objects == 2l) { pa = * (point *) fastlookup(ptlist, 0); pb = * (point *) fastlookup(ptlist, 1); if (distance(pa, pb) > 0) { // It is a single segment. makeshellface(subsegs, &newseg); setshvertices(newseg, pa, pb, NULL); - // Set the actual segment marker. - if (in->facetmarkerlist != NULL) { - setshellmark(newseg, in->facetmarkerlist[shmark - 1]); - } + // Set the default segment marker '1'. + setshellmark(newseg, 1); } if (pointtype(pa) == VOLVERTEX) { - setpointtype(pa, RIDGEVERTEX); + setpointtype(pa, FACETVERTEX); } if (pointtype(pb) == VOLVERTEX) { - setpointtype(pb, RIDGEVERTEX); + setpointtype(pb, FACETVERTEX); } return; - } if (ptlist->objects == 3l) { - // The facet has only one triangle. + } + + + if (ptlist->objects == 3) { pa = * (point *) fastlookup(ptlist, 0); pb = * (point *) fastlookup(ptlist, 1); pc = * (point *) fastlookup(ptlist, 2); - if (triarea(pa, pb, pc) > 0) { - makeshellface(subfaces, &newsh); - setshvertices(newsh, pa, pb, pc); - setshellmark(newsh, shmark); - // Create three new segments. - for (i = 0; i < 3; i++) { - makeshellface(subsegs, &newseg); - setshvertices(newseg, sorg(newsh), sdest(newsh), NULL); - // Set the actual segment marker. - if (in->facetmarkerlist != NULL) { - setshellmark(newseg, in->facetmarkerlist[shmark - 1]); - } - ssbond(newsh, newseg); - senextself(newsh); - } - if (pointtype(pa) == VOLVERTEX) { - setpointtype(pa, FACETVERTEX); - } - if (pointtype(pb) == VOLVERTEX) { - setpointtype(pb, FACETVERTEX); - } - if (pointtype(pc) == VOLVERTEX) { - setpointtype(pc, FACETVERTEX); - } + } else { + // Calculate an above point of this facet. + if (!calculateabovepoint(ptlist, &pa, &pb, &pc)) { + return; // The point set is degenerate. } - return; - } - - // Calulcate an above point of this facet. - if (!calculateabovepoint(ptlist, &pa, &pb, &pc)) { - return; // The point set is degenerate. } // Create an initial triangulation. @@ -14516,6 +13449,8 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, // Are there area constraints? if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { + int idx, fmarker; + REAL area; idx = in->facetmarkerlist[shmark - 1]; // The actual facet marker. for (i = 0; i < in->numberoffacetconstraints; i++) { fmarker = (int) in->facetconstraintlist[i * 2]; @@ -14527,6 +13462,19 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, } } + if (ptlist->objects == 3) { + // The triangulation only has one element. + for (i = 0; i < 3; i++) { + makeshellface(subsegs, &newseg); + setshvertices(newseg, sorg(newsh), sdest(newsh), NULL); + // Set the default segment marker '1'. + setshellmark(newseg, 1); + ssbond(newsh, newseg); + senextself(newsh); + } + return; + } + // Incrementally build the triangulation. pinfect(pa); pinfect(pb); @@ -14536,9 +13484,8 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, if (!pinfected(*ppt)) { searchsh = recentsh; // Start from 'recentsh'. iloc = (int) OUTSIDE; - if (b->verbose > 2) printf(" # %d", i); - loc = (enum locateresult) sinsertvertex(*ppt, &searchsh, NULL, iloc, 1); - assert(loc != ONVERTEX); // SELF_CHECK + // Insert the vertex. Use Bowyer-Watson algo. Round the location. + iloc = sinsertvertex(*ppt, &searchsh, NULL, iloc, 1, 1); if (pointtype(*ppt) == VOLVERTEX) { setpointtype(*ppt, FACETVERTEX); } @@ -14560,8 +13507,26 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, for (i = 0; i < conlist->objects; i++) { cons = (point *) fastlookup(conlist, i); searchsh = recentsh; - loc = slocate(cons[0], &searchsh, 1, 1, 0); - assert(loc == ONVERTEX); // SELF_CHECK + iloc = (int) slocate(cons[0], &searchsh, 1, 1, 0); + if (iloc != (enum locateresult) ONVERTEX) { + // Not found due to roundoff errors. Do a brute-force search. + subfaces->traversalinit(); + searchsh.sh = shellfacetraverse(subfaces); + while (searchsh.sh != NULL) { + // Only search the subface in the same facet. + if (shellmark(searchsh) == shmark) { + if ((point) searchsh.sh[3] == cons[0]) { + searchsh.shver = 0; break; + } else if ((point) searchsh.sh[4] == cons[0]) { + searchsh.shver = 2; break; + } else if ((point) searchsh.sh[5] == cons[0]) { + searchsh.shver = 4; break; + } + } + searchsh.sh = shellfacetraverse(subfaces); + } + assert(searchsh.sh != NULL); + } // Recover the segment. Some edges may be flipped. sscoutsegment(&searchsh, cons[1]); if (flipstack != NULL) { @@ -14582,17 +13547,20 @@ void tetgenmesh::triangulate(int shmark, arraypool* ptlist, arraypool* conlist, // If c = d, then f1 and f2 are identical. Otherwise, these two subfaces // // intersect, and the mesher is stopped. // // // -// If the two subfaces are indentical, we try to replace f2 by f1, i.e, all // +// If the two subfaces are identical, we try to replace f2 by f1, i.e, all // // neighbors of f2 are re-connected to f1. // // // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::unifysubfaces(face *f1, face *f2) { - face casout, casin, neighsh; - face sseg, checkseg; + if (b->psc) { + // In this case, it is possible that two subfaces are identical. + // While they must belong to two different surfaces. + return; + } + point pa, pb, pc, pd; - int i; pa = sorg(*f1); pb = sdest(*f1); @@ -14602,86 +13570,28 @@ void tetgenmesh::unifysubfaces(face *f1, face *f2) if (pc != pd) { printf("Found two facets intersect each other.\n"); printf(" 1st: [%d, %d, %d] #%d\n", - pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); + pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); printf(" 2nd: [%d, %d, %d] #%d\n", - pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); - terminatetetgen(3); + pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); + terminatetetgen(this, 3); } else { printf("Found two duplicated facets.\n"); printf(" 1st: [%d, %d, %d] #%d\n", - pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); + pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*f1)); printf(" 2nd: [%d, %d, %d] #%d\n", - pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); - terminatetetgen(3); + pointmark(pa), pointmark(pb), pointmark(pd), shellmark(*f2)); + terminatetetgen(this, 3); } - // f1 and f2 are identical, replace f2 by f1. - if (!b->quiet) { - printf("Warning: Facet #%d is duplicated with Facet #%d. Removed!\n", - shellmark(*f2), shellmark(*f1)); - } - - // Make possible disconnections/reconnections at neighbors of f2. - for (i = 0; i < 3; i++) { - spivot(*f1, casout); - if (casout.sh == NULL) { - // f1 has no adjacent subfaces yet. - spivot(*f2, casout); - if (casout.sh != NULL) { - // Re-direct the adjacent connections of f2 to f1. - casin = casout; - spivot(casin, neighsh); - while (neighsh.sh != f2->sh) { - casin = neighsh; - spivot(casin, neighsh); - } - // Connect casout <= f1 <= casin. - sbond1(*f1, casout); - sbond1(casin, *f1); - } - } - sspivot(*f2, sseg); - if (sseg.sh != NULL) { - // f2 has a segment. It must be different to f1's. - // Disconnect bonds of subfaces to this segment. - spivot(*f2, casout); - if (casout.sh != NULL) { - casin = casout; - ssdissolve(casin); - spivot(casin, neighsh); - while (neighsh.sh != f2->sh) { - casin = neighsh; - ssdissolve(casin); - spivot(casin, neighsh); - } - } - // Delete the segment. - shellfacedealloc(subsegs, sseg.sh); - } - spivot(*f2, casout); - if (casout.sh != NULL) { - // Find the subface (casin) pointing to f2. - casin = casout; - spivot(casin, neighsh); - while (neighsh.sh != f2->sh) { - casin = neighsh; - spivot(casin, neighsh); - } - // Disconnect f2 <= casin. - sdissolve(casin); - } - senextself(*f1); - senextself(*f2); - } // i - - // Delete f2. - shellfacedealloc(subfaces, f2->sh); } /////////////////////////////////////////////////////////////////////////////// // // // unifysegments() Remove redundant segments and create face links. // // // +// After this routine, although segments are unique, but some of them may be // +// removed later by mergefacet(). All vertices still have type FACETVERTEX. // +// // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::unifysegments() @@ -14695,9 +13605,6 @@ void tetgenmesh::unifysegments() int *idx2faclist; int idx, k, m; - int e1, e2; - REAL len; - if (b->verbose > 1) { printf(" Unifying segments.\n"); } @@ -14705,6 +13612,79 @@ void tetgenmesh::unifysegments() // Create a mapping from vertices to subfaces. makepoint2submap(subfaces, idx2faclist, facperverlist); + if (b->psc) { + face sface1; + face seg, seg1; + int fmarker, fmarker1; + // First only connect subfaces which belong to the same surfaces. + subsegloop.shver = 0; + subsegs->traversalinit(); + subsegloop.sh = shellfacetraverse(subsegs); + while (subsegloop.sh != (shellface *) NULL) { + torg = sorg(subsegloop); + tdest = sdest(subsegloop); + + idx = pointmark(torg) - in->firstnumber; + for (k = idx2faclist[idx]; k < idx2faclist[idx + 1]; k++) { + sface = facperverlist[k]; + // The face may be deleted if it is a duplicated face. + if (sface.sh[3] == NULL) continue; + // Search the edge torg->tdest. + assert(sorg(sface) == torg); // SELF_CHECK + if (sdest(sface) != tdest) { + senext2self(sface); + sesymself(sface); + } + if (sdest(sface) != tdest) continue; + + sspivot(sface, seg); + if (seg.sh == NULL) continue; + // assert(seg.sh != NULL); It may or may not be subsegloop. + + // Find the adjacent subface on the same facet. + fmarker = in->facetmarkerlist[shellmark(sface) - 1]; + sface1.sh = NULL; + k++; + for (; k < idx2faclist[idx + 1]; k++) { + sface1 = facperverlist[k]; + // The face may be deleted if it is a duplicated face. + if (sface1.sh[3] == NULL) continue; + // Search the edge torg->tdest. + assert(sorg(sface1) == torg); // SELF_CHECK + if (sdest(sface1) != tdest) { + senext2self(sface1); + sesymself(sface1); + } + if (sdest(sface1) != tdest) continue; + // Found a subface sharing at the same edge. + fmarker1 = in->facetmarkerlist[shellmark(sface1) - 1]; + if (fmarker1 == fmarker) { + // Found a pair of adjacent subfaces. Connect them. + // Delete a redundent segment. + sspivot(sface1, seg1); + assert(seg1.sh != NULL); // SELF_CHECK + shellfacedealloc(subsegs, seg.sh); + shellfacedealloc(subsegs, seg1.sh); + ssdissolve(sface); + ssdissolve(sface1); + // Connect them. + sbond(sface, sface1); + // Set Steiner point -to- subface map. + if (pointtype(torg) == FREEFACETVERTEX) { + setpoint2sh(torg, sencode(sface)); + } + if (pointtype(tdest) == FREEFACETVERTEX) { + setpoint2sh(tdest, sencode(sface)); + } + break; + } + } + break; + } + subsegloop.sh = shellfacetraverse(subsegs); + } + } // if (b->psc) + subsegloop.shver = 0; subsegs->traversalinit(); subsegloop.sh = shellfacetraverse(subsegs); @@ -14807,7 +13787,7 @@ void tetgenmesh::unifysegments() // f is either codirection with f1 or is codirection with f2. facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); facenormal(torg, tdest, sapex(sface), n2, 1, NULL); - if (DOT(n1, n2) > 0) { + if (dot(n1, n2) > 0) { unifysubfaces(&(f1->ss), &sface); } else { unifysubfaces(&(f2->ss), &sface); @@ -14833,7 +13813,7 @@ void tetgenmesh::unifysegments() // f is coplanar with f1 (see Fig. 8). facenormal(torg, tdest, sapex(f1->ss), n1, 1, NULL); facenormal(torg, tdest, sapex(sface), n2, 1, NULL); - if (DOT(n1, n2) > 0) { + if (dot(n1, n2) > 0) { // The two faces are codirectional as well. unifysubfaces(&(f1->ss), &sface); } @@ -14855,17 +13835,16 @@ void tetgenmesh::unifysegments() } } // for (k = idx2faclist[idx]; ...) - if (b->verbose > 2) { - printf(" Found %ld segments at (%d %d).\n", flippool->items, - pointmark(torg), pointmark(tdest)); + if (b->psc) { + // Set Steiner point -to- segment map. + if (pointtype(torg) == FREESEGVERTEX) { + setpoint2sh(torg, sencode(subsegloop)); + } + if (pointtype(tdest) == FREESEGVERTEX) { + setpoint2sh(tdest, sencode(subsegloop)); + } } - //if (b->nobisect || b->nomerge) { // -Y or -M - // Set the vertex types of the endpoints of the segment. - setpointtype(torg, RIDGEVERTEX); - setpointtype(tdest, RIDGEVERTEX); - //} - // Set the connection between this segment and faces containing it, // at the same time, remove redundant segments. f1 = facelink; @@ -14885,11 +13864,6 @@ void tetgenmesh::unifysegments() f1 = facelink; for (k = 1; k <= flippool->items; k++) { k < flippool->items ? f2 = f1->nextitem : f2 = facelink; - if (b->verbose > 3) { - printf(" Bond subfaces (%d, %d, %d) and (%d, %d, %d).\n", - pointmark(torg), pointmark(tdest), pointmark(sapex(f1->ss)), - pointmark(torg), pointmark(tdest), pointmark(sapex(f2->ss))); - } sbond1(f1->ss, f2->ss); f1 = f2; } @@ -14900,6 +13874,8 @@ void tetgenmesh::unifysegments() // Are there length constraints? if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + int e1, e2; + REAL len; for (k = 0; k < in->numberofsegmentconstraints; k++) { e1 = (int) in->segmentconstraintlist[k * 3]; e2 = (int) in->segmentconstraintlist[k * 3 + 1]; @@ -14976,11 +13952,6 @@ void tetgenmesh::mergefacets() ang = facedihedral(pa, pb, pc, pd); if (ang > PI) ang = (2 * PI - ang); if (ang > ang_tol) { - if (b->verbose > 2) { - printf(" Merge at segment (%d, %d)-(%d, %d) ang = %g\n", - pointmark(pa), pointmark(pb), pointmark(pc), - pointmark(pd), ang / PI * 180.0); - } remsegcount++; ssdissolve(parentsh); ssdissolve(neighsh); @@ -15000,7 +13971,6 @@ void tetgenmesh::mergefacets() lawsonflip(); // Recover Delaunayness. } - if (b->verbose > 1) { printf(" %d segments are removed.\n", remsegcount); } @@ -15025,7 +13995,7 @@ void tetgenmesh::identifypscedges(point *idx2verlist) int* idx2shlist; face searchsh, neighsh; face segloop, checkseg, newseg; - point checkpt, pa, pb; + point checkpt, pa = NULL, pb = NULL; int *endpts; int edgemarker; int idx, i, j; @@ -15037,8 +14007,18 @@ void tetgenmesh::identifypscedges(point *idx2verlist) printf("Inserting edges ...\n"); } - // All identified segments have the initial marker '0'. - // All segments inserted here should have a non-zero marker. + // All identified segments have the initial marker '1'. + // All segments inserted here should have a marker 'k >= 0'. + + if (b->psc) { + // First mark all segments of the mesh with a marker '-1'. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + setshellmark(segloop, -1); + segloop.sh = shellfacetraverse(subsegs); + } + } // Construct a map from points to subfaces. makepoint2submap(subfaces, idx2shlist, shperverlist); @@ -15046,6 +14026,8 @@ void tetgenmesh::identifypscedges(point *idx2verlist) // Process the set of PSC edges. for (i = 0; i < in->numberofedges; i++) { endpts = &(in->edgelist[(i << 1)]); + edgemarker = in->edgemarkerlist ? in->edgemarkerlist[i] : 0; + // Find a face contains the edge. newseg.sh = NULL; searchsh.sh = NULL; @@ -15064,43 +14046,31 @@ void tetgenmesh::identifypscedges(point *idx2verlist) } } } // j - edgemarker = 0; - if (in->edgemarkerlist) { - edgemarker = in->edgemarkerlist[i]; - } - if (edgemarker == 0) { - edgemarker = 1; - } - // We should find a subface having this edge. + if (searchsh.sh != NULL) { // Check if this edge is already a segment of the mesh. sspivot(searchsh, checkseg); if (checkseg.sh != NULL) { - // There should be no duplicated edges. - assert(shellmark(checkseg) == 0); - setshellmark(checkseg, edgemarker); + // This segment already exist. + newseg = checkseg; } else { // Create a new segment at this edge. pa = sorg(searchsh); pb = sdest(searchsh); - if (b->verbose > 2) { - printf(" Create a new segment (%d, %d).\n", - pointmark(pa), pointmark(pb)); - } makeshellface(subsegs, &newseg); setshvertices(newseg, pa, pb, NULL); - setshellmark(newseg, edgemarker); ssbond(searchsh, newseg); spivot(searchsh, neighsh); if (neighsh.sh != NULL) { ssbond(neighsh, newseg); - // There should be only two subfaces at this segment. - spivotself(neighsh); // SELF_CHECK - assert(neighsh.sh == searchsh.sh); } - if (!b->psc) { - setpointtype(pa, RIDGEVERTEX); - setpointtype(pb, RIDGEVERTEX); + if (b->psc) { + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(newseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(newseg)); + } } } } else { @@ -15108,64 +14078,91 @@ void tetgenmesh::identifypscedges(point *idx2verlist) // Get the two endpoints of this segment. pa = idx2verlist[endpts[0]]; pb = idx2verlist[endpts[1]]; - if (b->verbose > 2) { - printf(" Create a new segment (%d, %d) - dangling.\n", - pointmark(pa), pointmark(pb)); + // Check if segment [a,b] already exists. + // TODO: Change the brute-force search. Slow! + point *ppt; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + ppt = (point *) &(segloop.sh[3]); + if (((ppt[0] == pa) && (ppt[1] == pb)) || + ((ppt[0] == pb) && (ppt[1] == pa))) { + // Found! + newseg = segloop; + break; + } + segloop.sh = shellfacetraverse(subsegs); + } + if (newseg.sh == NULL) { + makeshellface(subsegs, &newseg); + setshvertices(newseg, pa, pb, NULL); + if (b->psc) { + if (pointtype(pa) == FREESEGVERTEX) { + setpoint2sh(pa, sencode(newseg)); + } + if (pointtype(pb) == FREESEGVERTEX) { + setpoint2sh(pb, sencode(newseg)); + } + } } - makeshellface(subsegs, &newseg); - setshvertices(newseg, pa, pb, NULL); - setshellmark(newseg, edgemarker); - //if (!b->psc) { - setpointtype(pa, RIDGEVERTEX); - setpointtype(pb, RIDGEVERTEX); - //} } - if (newseg.sh != NULL) { - if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { - for (i = 0; i < in->numberofsegmentconstraints; i++) { - e1 = (int) in->segmentconstraintlist[i * 3]; - e2 = (int) in->segmentconstraintlist[i * 3 + 1]; - if (((pointmark(pa) == e1) && (pointmark(pb) == e2)) || - ((pointmark(pa) == e2) && (pointmark(pb) == e1))) { - len = in->segmentconstraintlist[i * 3 + 2]; - setareabound(newseg, len); - break; - } + setshellmark(newseg, edgemarker); + + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + for (i = 0; i < in->numberofsegmentconstraints; i++) { + e1 = (int) in->segmentconstraintlist[i * 3]; + e2 = (int) in->segmentconstraintlist[i * 3 + 1]; + if (((pointmark(pa) == e1) && (pointmark(pb) == e2)) || + ((pointmark(pa) == e2) && (pointmark(pb) == e1))) { + len = in->segmentconstraintlist[i * 3 + 2]; + setareabound(newseg, len); + break; } } } } // i + + delete [] shperverlist; + delete [] idx2shlist; + if (b->psc) { - // Delete all segments of the mesh with a marker '0'. + // Removing all segments with a marker '-1'. subsegs->traversalinit(); segloop.sh = shellfacetraverse(subsegs); while (segloop.sh != NULL) { - if (shellmark(segloop) == 0) { - if (b->verbose > 2) { - printf(" Remove a segment (%d, %d).\n", - pointmark(sorg(segloop)), pointmark(sdest(segloop))); - } - spivot(segloop, searchsh); - if (searchsh.sh != NULL) { - ssdissolve(searchsh); - spivot(searchsh, neighsh); - if (neighsh.sh != NULL) { - ssdissolve(neighsh); - // There should be only two subfaces at this segment. - spivotself(neighsh); // SELF_CHECK - assert(neighsh.sh == searchsh.sh); - } - } + if (shellmark(segloop) == -1) { shellfacedealloc(subsegs, segloop.sh); } segloop.sh = shellfacetraverse(subsegs); } - } + + // Connecting subsegments at Steiner points. + face seg1, seg2; + // Re-use 'idx2shlist' and 'shperverlist'. + makepoint2submap(subsegs, idx2shlist, shperverlist); - delete [] shperverlist; - delete [] idx2shlist; + points->traversalinit(); + pa = pointtraverse(); + while (pa != NULL) { + if (pointtype(pa) == FREESEGVERTEX) { + idx = pointmark(pa) - in->firstnumber; + // There must be only two segments containing this vertex. + assert((idx2shlist[idx + 1] - idx2shlist[idx]) == 2); + i = idx2shlist[idx]; + seg1 = shperverlist[i]; + seg2 = shperverlist[i+1]; + senextself(seg1); + senextself(seg2); + sbond(seg1, seg2); + } + pa = pointtraverse(); + } + + delete [] shperverlist; + delete [] idx2shlist; + } } /////////////////////////////////////////////////////////////////////////////// @@ -15308,16 +14305,27 @@ void tetgenmesh::meshsurface() if (!b->diagnose) { // Remove redundant segments and build the face links. unifysegments(); - } - - if (!b->nomerge && !b->nobisect && !b->diagnose) { - // Merge adjacent coplanar facets. - mergefacets(); - } - - if (in->numberofedges > 0) { // if (b->psc) - // There are segments specified by the user. Read and create them. - identifypscedges(idx2verlist); + if (!b->psc && !b->nomergefacet && !b->nobisect) { + // Merge adjacent coplanar facets. + mergefacets(); + } + if (in->numberofedges > 0) { // if (b->psc) + // There are segments specified by the user. Read and create them. + identifypscedges(idx2verlist); + } + if (!b->psc) { + // Mark all segment vertices to be RIDGEVERTEX. + face segloop; + point *ppt; + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + while (segloop.sh != NULL) { + ppt = (point *) &(segloop.sh[3]); + setpointtype(ppt[0], RIDGEVERTEX); + setpointtype(ppt[1], RIDGEVERTEX); + segloop.sh = shellfacetraverse(subsegs); + } + } } if (b->object == tetgenbehavior::STL) { @@ -15343,7 +14351,7 @@ void tetgenmesh::meshsurface() // interecursive() Recursively do intersection test on a set of triangles.// // // // Recursively split the set 'subfacearray' of subfaces into two sets using // -// a cut plane parallel to x-, or, y-, or z-axies. The split criteria are // +// a cut plane parallel to x-, or, y-, or z-axis. The split criteria are // // follows. Assume the cut plane is H, and H+ denotes the left halfspace of // // H, and H- denotes the right halfspace of H; and s be a subface: // // // @@ -15385,11 +14393,11 @@ void tetgenmesh::interecursive(shellface** subfacearray, int arraysize, leftarray = new shellface*[arraysize]; if (leftarray == NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } rightarray = new shellface*[arraysize]; if (rightarray == NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } leftsize = rightsize = 0; @@ -15556,7 +14564,7 @@ void tetgenmesh::detectinterfaces() internum = 0; // Recursively split the set of triangles into two sets using a cut plane - // parallel to x-, or, y-, or z-axies. Stop splitting when the number + // parallel to x-, or, y-, or z-axis. Stop splitting when the number // of subfaces is not decreasing anymore. Do tests on the current set. interecursive(subfacearray, subfaces->items, 0, xmin, xmax, ymin, ymax, zmin, zmax, &internum); @@ -15599,138 +14607,77 @@ void tetgenmesh::detectinterfaces() /////////////////////////////////////////////////////////////////////////////// // // -// markacutevertices() Classify vertices as ACUTEVERTEXs or RIDGEVERTEXs. // +// makesegmentendpointsmap() Create a map from a segment to its endpoints.// // // -// Initially all segment vertices have type RIDGEVERTEX. A segment is acute // -// if there are at least two segments incident at it form an angle less than // -// theta (= 60 degree). // -// // -// The minimum segment-segment angle (minfaceang) is calculated. // +// The map is saved in the array 'segmentendpointslist'. The length of this // +// array is twice the number of segments. Each segment is assigned a unique // +// index (starting from 0). // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::markacutevertices() +void tetgenmesh::makesegmentendpointsmap() { - face* segperverlist; - int* idx2seglist; - point pa, pb, pc; - REAL anglimit, ang; - bool acuteflag; - int acutecount; - int idx, i, j; + arraypool *segptlist; + face segloop, prevseg, nextseg; + point eorg, edest, *parypt; + int segindex = 0, idx = 0; + int i; - REAL sharpanglimit; - int sharpsegcount; - - if (b->verbose) { - printf(" Marking acute vertices.\n"); + if (b->verbose > 0) { + printf(" Creating the segment-endpoints map.\n"); } - anglimit = PI / 3.0; // 60 degree. - sharpanglimit = 5.0 / 180.0 * PI; // 5 degree. - minfaceang = PI; // 180 degree. - acutecount = sharpsegcount = 0; - // Construct a map from points to segments. - makepoint2submap(subsegs, idx2seglist, segperverlist); + segptlist = new arraypool(2 * sizeof(point), 10); - // Loop over the set of vertices. - points->traversalinit(); - pa = pointtraverse(); - while (pa != NULL) { - idx = pointmark(pa) - in->firstnumber; - // Mark it if it is an endpoint of some segments. - if (idx2seglist[idx + 1] > idx2seglist[idx]) { - if (b->psc) { - // Only test it if it is an input vertex. - if (pointtype(pa) == FREESEGVERTEX) { - pa = pointtraverse(); - continue; - } - } - acuteflag = false; - // Do a brute-force pair-pair check. - for (i=idx2seglist[idx]; i 4) { - // There are at least 5 segments shared at this vertices. - acuteflag = true; - } - } - if (acuteflag) { - if (b->verbose > 2) { - printf(" Mark %d as ACUTEVERTEX.\n", pointmark(pa)); - } - setpointtype(pa, ACUTEVERTEX); - acutecount++; + // A segment s may have been split into many subsegments. Operate the one + // which contains the origin of s. Then mark the rest of subsegments. + subsegs->traversalinit(); + segloop.sh = shellfacetraverse(subsegs); + segloop.shver = 0; + while (segloop.sh != NULL) { + senext2(segloop, prevseg); + spivotself(prevseg); + if (prevseg.sh == NULL) { + eorg = sorg(segloop); + edest = sdest(segloop); + setfacetindex(segloop, segindex); + senext(segloop, nextseg); + spivotself(nextseg); + while (nextseg.sh != NULL) { + setfacetindex(nextseg, segindex); + nextseg.shver = 0; + if (sorg(nextseg) != edest) sesymself(nextseg); + assert(sorg(nextseg) == edest); + edest = sdest(nextseg); + // Go the next connected subsegment at edest. + senextself(nextseg); + spivotself(nextseg); } + segptlist->newindex((void **) &parypt); + parypt[0] = eorg; + parypt[1] = edest; + segindex++; } - pa = pointtraverse(); + segloop.sh = shellfacetraverse(subsegs); } if (b->verbose) { - if (acutecount > 0) { - printf(" Found %d acute vertices.\n", acutecount); - } - if (sharpsegcount > 0) { - printf(" Found %d sharp segments.\n", sharpsegcount); - } - printf(" Minimum seg-seg angle = %g.\n", minfaceang / PI * 180.0); + printf(" Found %ld segments.\n", segptlist->objects); } - delete [] idx2seglist; - delete [] segperverlist; + segmentendpointslist = new point[segptlist->objects * 2]; + + totalworkmemory += (segptlist->objects * 2) * sizeof(point *); + + for (i = 0; i < segptlist->objects; i++) { + parypt = (point *) fastlookup(segptlist, i); + segmentendpointslist[idx++] = parypt[0]; + segmentendpointslist[idx++] = parypt[1]; + } + + delete segptlist; } -/////////////////////////////////////////////////////////////////////////////// -// // -// reportselfintersect() Report a self-intersection. // -// // -/////////////////////////////////////////////////////////////////////////////// - -void tetgenmesh::reportselfintersect(face *checkseg, face *checksh) -{ - face parentsh; - point pa, pb, pc, pd, pe; - point fa, fb; - - pa = sorg(*checkseg); - pb = sdest(*checkseg); - fa = farsorg(*checkseg); - fb = farsdest(*checkseg); - - pc = sorg(*checksh); - pd = sdest(*checksh); - pe = sapex(*checksh); - - printf(" !! Detected a self-intersection between:\n"); - printf(" A segment [%d,%d] < [%d,%d], \n", pointmark(pa), pointmark(pb), - pointmark(fa), pointmark(fb)); - printf(" a subface [%d,%d,%d] in facet #%d.\n", pointmark(pc), - pointmark(pd), pointmark(pe), shellmark(*checksh)); - -} /////////////////////////////////////////////////////////////////////////////// // // @@ -15757,14 +14704,14 @@ enum tetgenmesh::interresult point pa, pb, pc, pd; enum {HMOVE, RMOVE, LMOVE} nextmove; REAL hori, rori, lori; + int t1ver; int s; // The origin is fixed. pa = org(*searchtet); if ((point) searchtet->tet[7] == dummypoint) { // A hull tet. Choose the neighbor of its base face. - searchtet->ver = 11; - fsymself(*searchtet); + decode(searchtet->tet[3], *searchtet); // Reset the origin to be pa. if ((point) searchtet->tet[4] == pa) { searchtet->ver = 11; @@ -15788,8 +14735,7 @@ enum tetgenmesh::interresult pc = apex(*searchtet); if (pc == endpt) { // pa->pc is the search edge. - eprevself(*searchtet); - esymself(*searchtet); + eprevesymself(*searchtet); return ACROSSVERT; } @@ -15797,12 +14743,6 @@ enum tetgenmesh::interresult while (1) { pd = oppo(*searchtet); - - if (b->verbose > 3) { - printf(" From tet (%d, %d, %d, %d) to %d.\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(endpt)); - } - // Check whether the opposite vertex is 'endpt'. if (pd == endpt) { // pa->pd is the search edge. @@ -15842,8 +14782,8 @@ enum tetgenmesh::interresult } } else { // Two tets, below horizon and below right, are viable. - s = randomnation(2); - if (s == 0) { + //s = randomnation(2); + if (randomnation(2)) { nextmove = HMOVE; } else { nextmove = RMOVE; @@ -15852,8 +14792,8 @@ enum tetgenmesh::interresult } else { if (lori > 0) { // Two tets, below horizon and below left, are viable. - s = randomnation(2); - if (s == 0) { + //s = randomnation(2); + if (randomnation(2)) { nextmove = HMOVE; } else { nextmove = LMOVE; @@ -15867,8 +14807,8 @@ enum tetgenmesh::interresult if (rori > 0) { if (lori > 0) { // Two tets, below right and below left, are viable. - s = randomnation(2); - if (s == 0) { + //s = randomnation(2); + if (randomnation(2)) { nextmove = RMOVE; } else { nextmove = LMOVE; @@ -15890,8 +14830,7 @@ enum tetgenmesh::interresult } if (lori == 0) { // pa->'endpt' is COLLINEAR with pa->pc. - eprevself(*searchtet); - esymself(*searchtet); // [a,c,d] + eprevesymself(*searchtet); // // [a,c,d] return ACROSSVERT; } // pa->'endpt' crosses the edge pb->pc. @@ -15911,8 +14850,7 @@ enum tetgenmesh::interresult } if (lori == 0) { // pa->'endpt' crosses the edge pc->pd. - eprevself(*searchtet); - esymself(*searchtet); // face acd + eprevesymself(*searchtet); // [a,c,d] return ACROSSEDGE; } // pa->'endpt' crosses the face bcd. @@ -15942,25 +14880,22 @@ enum tetgenmesh::interresult /////////////////////////////////////////////////////////////////////////////// // // -// scoutsegment() Look for a given segment in the tetrahedralization T. // +// scoutsegment() Search an edge in the tetrahedralization. // // // -// Search an edge in the tetrahedralization that matches the given segmment. // -// If such an edge exists, the segment is 'locked' at the edge. 'searchtet' // -// returns this (constrained) edge. Otherwise, the segment is missing. // +// If the edge is found, it returns SHAREEDGE, and 'searchtet' returns the // +// edge from startpt to endpt. // // // -// The returned value indicates one of the following cases: // -// - SHAREEDGE, the segment exists and is inserted in T; // -// - ACROSSEDGE, the segment intersects an edge (in 'searchtet'). // -// - ACROSSFACE, the segment crosses a face (in 'searchtet'). // +// If the edge is missing, it returns either ACROSSEDGE or ACROSSFACE, which // +// indicates that the edge intersects an edge or a face. If 'refpt' is NULL,// +// 'searchtet' returns the edge or face. If 'refpt' is not NULL, it returns // +// a vertex which encroaches upon this edge, and 'searchtet' returns a tet // +// which containing 'refpt'. // // // // The following cases can happen when the input PLC is not valid. // -// - ACROSSVERT, the segment intersects a vertex ('refpt'). // -// - ACROSSSEG, the segment intersects a segment(returned by 'searchtet'). // -// - ACROSSSUB, the segment intersects a subface(returned by 'searchtet'). // -// // -// If the returned value is ACROSSEDGE or ACROSSFACE, i.e., the segment is // -// missing, 'refpt' returns the reference point for splitting thus segment, // -// 'searchtet' returns a tet containing the 'refpt'. // +// - ACROSSVERT, the edge intersects a vertex return by the origin of // +// 'searchtet'. // +// - ACROSSSEG, the edge intersects a segment returned by 'searchtet'. // +// - ACROSSSUB, the edge intersects a subface returned by 'searchtet'. // // // /////////////////////////////////////////////////////////////////////////////// @@ -15968,18 +14903,12 @@ enum tetgenmesh::interresult tetgenmesh::scoutsegment(point startpt, point endpt, triface* searchtet, point* refpt, arraypool* intfacelist) { - triface neightet, reftet; - face checkseg, checksh; - point pa, pb, pc, pd; - badface *bface; + point pd; enum interresult dir; - REAL angmax, ang; - long facecount; - int types[2], poss[4]; - int pos, i, j; + int t1ver; if (b->verbose > 2) { - printf(" Scout seg (%d, %d).\n", pointmark(startpt), pointmark(endpt)); + printf(" Scout seg (%d, %d).\n",pointmark(startpt),pointmark(endpt)); } point2tetorg(startpt, *searchtet); @@ -15992,14 +14921,13 @@ enum tetgenmesh::interresult return SHAREEDGE; } else { // A point is on the path. - *refpt = pd; + // Let the origin of the searchtet be the vertex. + enextself(*searchtet); + if (refpt) *refpt = pd; return ACROSSVERT; } } // if (dir == ACROSSVERT) - if (b->verbose > 2) { - printf(" Seg is missing.\n"); - } // dir is either ACROSSEDGE or ACROSSFACE. enextesymself(*searchtet); // Go to the opposite face. @@ -16007,29 +14935,28 @@ enum tetgenmesh::interresult if (dir == ACROSSEDGE) { // Check whether two segments are intersecting. - tsspivot1(*searchtet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(*searchtet)) { return ACROSSSEG; } - across_edge_count++; } else if (dir == ACROSSFACE) { if (checksubfaceflag) { // Check whether a segment and a subface are intersecting. - tspivot(*searchtet, checksh); - if (checksh.sh != NULL) { + if (issubface(*searchtet)) { return ACROSSSUB; } } } if (refpt == NULL) { + // Do not need a reference point. Return. return dir; } - if (b->verbose > 2) { - printf(" Scout a ref-point for it.\n"); - } - facecount = across_face_count; + triface neightet, reftet; + point pa, pb, pc; + REAL angmax, ang; + int types[2], poss[4]; + int pos = 0, i, j; pa = org(*searchtet); angmax = interiorangle(pa, startpt, endpt, NULL); @@ -16051,53 +14978,10 @@ enum tetgenmesh::interresult // Search intersecting faces along the segment. while (1) { - if (intfacelist != NULL) { - if (dir == ACROSSFACE) { - // Save the intersecting face. - intfacelist->newindex((void **) &bface); - bface->tt = *searchtet; - bface->forg = org(*searchtet); - bface->fdest = dest(*searchtet); - bface->fapex = apex(*searchtet); - // Save the intersection type (ACROSSFACE or ACROSSEDGE). - bface->key = (REAL) dir; - } else { // dir == ACROSSEDGE - i = 0; - if (intfacelist->objects > 0l) { - // Get the last saved one. - bface = (badface *) fastlookup(intfacelist, intfacelist->objects - 1); - if (((enum interresult) (int) bface->key) == ACROSSEDGE) { - // Skip this edge if it is the same as the last saved one. - if (((bface->forg == org(*searchtet)) && - (bface->fdest == dest(*searchtet))) || - ((bface->forg == dest(*searchtet)) && - (bface->fdest == org(*searchtet)))) { - i = 1; // Skip this edge. - } - } - } - if (i == 0) { - // Save this crossing edge. - intfacelist->newindex((void **) &bface); - bface->tt = *searchtet; - bface->forg = org(*searchtet); - bface->fdest = dest(*searchtet); - // bface->fapex = apex(*searchtet); - // Save the intersection type (ACROSSFACE or ACROSSEDGE). - bface->key = (REAL) dir; - } - } - } pd = oppo(*searchtet); assert(pd != dummypoint); // SELF_CHECK - if (b->verbose > 3) { - printf(" Passing face (%d, %d, %d, %d), dir(%d).\n", - pointmark(pa), pointmark(pb), pointmark(pc), pointmark(pd), - (int) dir); - } - across_face_count++; // Stop if we meet 'endpt'. if (pd == endpt) break; @@ -16165,9 +15049,6 @@ enum tetgenmesh::interresult enextself(neightet); } pd = org(neightet); - if (b->verbose > 2) { - angmax = interiorangle(pd, startpt, endpt, NULL); - } *refpt = pd; // break; return ACROSSVERT; @@ -16182,16 +15063,13 @@ enum tetgenmesh::interresult if (dir == ACROSSEDGE) { // Check whether two segments are intersecting. - tsspivot1(*searchtet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(*searchtet)) { return ACROSSSEG; } - across_edge_count++; } else if (dir == ACROSSFACE) { if (checksubfaceflag) { // Check whether a segment and a subface are intersecting. - tspivot(*searchtet, checksh); - if (checksh.sh != NULL) { + if (issubface(*searchtet)) { return ACROSSSUB; } } @@ -16205,19 +15083,6 @@ enum tetgenmesh::interresult *refpt = NULL; } - // dir is either ACROSSVERT, or ACROSSEDGE, or ACROSSFACE. - if (b->verbose > 2) { - if (*refpt != NULL) { - printf(" Refpt %d (%g), visited %ld faces.\n", pointmark(*refpt), - angmax / PI * 180.0, across_face_count - facecount); - } else { - printf(" No refpt (%g) is found, visited %ld faces.\n", - angmax / PI * 180.0, across_face_count - facecount); - } - } - if (across_face_count - facecount > across_max_count) { - across_max_count = across_face_count - facecount; - } *searchtet = reftet; return dir; @@ -16227,180 +15092,80 @@ enum tetgenmesh::interresult // // // getsteinerpointonsegment() Get a Steiner point on a segment. // // // +// Return '1' if 'refpt' lies on an adjacent segment of this segment. Other- // +// wise, return '0'. // +// // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) +int tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) { - point ei, ej; - REAL Li, Lj, L, dj, dr; - REAL ti = 0.0, tj = 0.0, t; - int type, eid = 0, i; - - REAL diff, stept = 0.0, L1; - int iter; - - ei = sorg(*seg); - ej = sdest(*seg); - - - if (b->verbose > 2) { - printf(" Get Steiner point on seg [%d (%c), %d (%c)].\n", - pointmark(ei), pointtype(ei) == ACUTEVERTEX ? 'A' : 'N', - pointmark(ej), pointtype(ej) == ACUTEVERTEX ? 'A' : 'N'); - } - - if (b->psc) { - eid = shellmark(*seg); - if (pointtype(ei) != FREESEGVERTEX) { - ti = in->getvertexparamonedge(in->geomhandle, pointmark(ei), eid); - } else { - ti = pointgeomuv(ei, 0); - } - if (pointtype(ej) != FREESEGVERTEX) { - tj = in->getvertexparamonedge(in->geomhandle, pointmark(ej), eid); - } else { - tj = pointgeomuv(ej, 0); - } - } + point ei = sorg(*seg); + point ej = sdest(*seg); + int adjflag = 0, i; if (refpt != NULL) { - if (pointtype(ei) == ACUTEVERTEX) { - if (pointtype(ej) == ACUTEVERTEX) { - // Choose the vertex which is closer to refpt. - Li = distance(ei, refpt); - Lj = distance(ej, refpt); - if (Li > Lj) { - // Swap ei and ej; - sesymself(*seg); - ei = sorg(*seg); - ej = sdest(*seg); - t = ti; - ti = tj; - tj = t; - } - type = 1; - } else { - type = 1; - } - } else { - if (pointtype(ej) == ACUTEVERTEX) { - type = 1; - // Swap ei and ej; - sesymself(*seg); - ei = sorg(*seg); - ej = sdest(*seg); - t = ti; - ti = tj; - tj = t; - } else { - type = 0; - } - } - } else { - type = 0; - } - - if (type == 1) { - L = distance(ei, ej); - Li = distance(ei, refpt); - // Cut the segment by a sphere centered at ei with radius Li. - if (b->psc) { - stept = (tj - ti) / 100.0; - iter = 0; - t = ti + (Li / L) * (tj - ti); - while (1) { - in->getsteineronedge(in->geomhandle, eid, t, steinpt); - L1 = distance(steinpt, ei); - diff = L1 - Li; - if ((fabs(diff) / L) < 1e-3) { - break; - } - if (diff > 0) { - t -= stept; // Move it towards ei. - } else { - t += stept; // Move it towards ej. - } - iter++; - if (iter > 10) { - printf("Warning: Get the right Steiner point failed.\n"); - break; - } - } // while (1) - } else { - t = Li / L; - for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); - } - } - // Avoid creating a too short edge. - dj = distance(steinpt, ej); - dr = distance(steinpt, refpt); - if (dj < dr) { - // Cut the segment by the radius equal to Li / 2. - if (b->psc) { - iter = 0; - t = ti + ((Li / 2.0) / L) * (tj - ti); - while (1) { - in->getsteineronedge(in->geomhandle, eid, t, steinpt); - L1 = distance(steinpt, ei); - diff = L1 - (Li / 2.0); - if ((fabs(diff) / L) < 1e-3) { - break; - } - if (diff > 0) { - t -= stept; // Move it towards ei. - } else { - t += stept; // Move it towards ej. - } - iter++; - if (iter > 10) { - printf("Warning: Get the right Steiner point failed.\n"); - break; - } - } // while (1) - } else { - t = (Li / 2.0) / L; + REAL L, L1, t; + + if (pointtype(refpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(refpt), parentseg); + int sidx1 = getfacetindex(parentseg); + point far_pi = segmentendpointslist[sidx1 * 2]; + point far_pj = segmentendpointslist[sidx1 * 2 + 1]; + int sidx2 = getfacetindex(*seg); + point far_ei = segmentendpointslist[sidx2 * 2]; + point far_ej = segmentendpointslist[sidx2 * 2 + 1]; + if ((far_pi == far_ei) || (far_pj == far_ei)) { + // Create a Steiner point at the intersection of the segment + // [far_ei, far_ej] and the sphere centered at far_ei with + // radius |far_ei - refpt|. + L = distance(far_ei, far_ej); + L1 = distance(far_ei, refpt); + t = L1 / L; for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); + steinpt[i] = far_ei[i] + t * (far_ej[i] - far_ei[i]); } + adjflag = 1; + } else if ((far_pi == far_ej) || (far_pj == far_ej)) { + L = distance(far_ei, far_ej); + L1 = distance(far_ej, refpt); + t = L1 / L; + for (i = 0; i < 3; i++) { + steinpt[i] = far_ej[i] + t * (far_ei[i] - far_ej[i]); + } + adjflag = 1; + } else { + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); } - r3count++; } else { - r2count++; + // Cut the segment by the projection point of refpt. + projpt2edge(refpt, ei, ej, steinpt); + } + + // Make sure that steinpt is not too close to ei and ej. + L = distance(ei, ej); + L1 = distance(steinpt, ei); + t = L1 / L; + if ((t < 0.2) || (t > 0.8)) { + // Split the point at the middle. + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); + } } } else { // Split the point at the middle. - if (b->psc) { - t = 0.5 * (ti + tj); - in->getsteineronedge(in->geomhandle, eid, t, steinpt); - } else { - t = 0.5; - for (i = 0; i < 3; i++) { - steinpt[i] = ei[i] + t * (ej[i] - ei[i]); - } + for (i = 0; i < 3; i++) { + steinpt[i] = ei[i] + 0.5 * (ej[i] - ei[i]); } - r1count++; } - if (b->psc) { - setpointgeomuv(steinpt, 0, t); - setpointgeomtag(steinpt, eid); - } - if (pointtype(steinpt) == UNUSEDVERTEX) { - setpointtype(steinpt, FREESEGVERTEX); - } - - if (b->verbose > 2) { - printf(" Split at t(%g)", t); - if (b->psc) { - printf(", ti(%g), tj(%g)", ti, tj); - } - printf(".\n"); - } + return adjflag; } + /////////////////////////////////////////////////////////////////////////////// // // // delaunizesegments() Recover segments in a DT. // @@ -16418,29 +15183,18 @@ void tetgenmesh::getsteinerptonsegment(face* seg, point refpt, point steinpt) void tetgenmesh::delaunizesegments() { triface searchtet, spintet; - face searchsh, checksh; - face sseg, checkseg, *psseg; + face searchsh; + face sseg, *psseg; point refpt, newpt; enum interresult dir; insertvertexflags ivf; - int loc; + int t1ver; - // For reporting PLC problems. - point forg1, fdest1; // The 1st segment. - point forg2, fdest2, fapex2; // The 2nd segment. - // Does this mesh containing subfaces? - if (checksubfaceflag) { - ivf.bowywat = 2; // The mesh is a CDT. - ivf.lawson = 2; // Do flip to recover Delaunayness. - ivf.validflag = 1; // Validation is needed. - } else { - ivf.bowywat = 1; // The mesh is a DT. - ivf.lawson = 0; // No need to do flip. - ivf.validflag = 0; // No need to valid the B-W cavity. - } - - searchsh.sh = NULL; + ivf.bowywat = 1; // Use Bowyer-Watson insertion. + ivf.assignmeshsize = b->metric; + ivf.sloc = (int) ONEDGE; // on 'sseg'. + ivf.sbowywat = 1; // Use Bowyer-Watson insertion. // Loop until 'subsegstack' is empty. while (subsegstack->objects > 0l) { @@ -16449,13 +15203,9 @@ void tetgenmesh::delaunizesegments() psseg = (face *) fastlookup(subsegstack, subsegstack->objects); sseg = *psseg; - assert(!sinfected(sseg)); // Check if this segment has been recovered. sstpivot1(sseg, searchtet); if (searchtet.tet != NULL) { - // Check if the tet contains the same segment. - tsspivot1(searchtet, checkseg); // SELF_CHECK - assert(checkseg.sh == sseg.sh); continue; // Not a missing segment. } @@ -16464,8 +15214,7 @@ void tetgenmesh::delaunizesegments() if (dir == SHAREEDGE) { // Found this segment, insert it. - tsspivot1(searchtet, checkseg); // SELF_CHECK - if (checkseg.sh == NULL) { + if (!issubseg(searchtet)) { // Let the segment remember an adjacent tet. sstbond1(sseg, searchtet); // Bond the segment to all tets containing it. @@ -16475,7 +15224,7 @@ void tetgenmesh::delaunizesegments() fnextself(spintet); } while (spintet.tet != searchtet.tet); } else { - // Collision! Should not happen. + // Collision! Maybe a bug. assert(0); } } else { @@ -16486,145 +15235,30 @@ void tetgenmesh::delaunizesegments() //setpointtype(newpt, FREESEGVERTEX); getsteinerptonsegment(&sseg, refpt, newpt); - // Start searching from the 'searchtet'. + // Start searching from 'searchtet'. ivf.iloc = (int) OUTSIDE; - //ivf.bowywat; - //ivf.lawson; - ivf.rejflag = 0; - ivf.chkencflag = 0; - ivf.sloc = ivf.iloc; - ivf.sbowywat = ivf.bowywat; - ivf.splitbdflag = 0; - // ivf.validflag - ivf.respectbdflag = 0; - ivf.assignmeshsize = b->metric; // Insert the new point into the tetrahedralization T. // Missing segments and subfaces are queued for recovery. // Note that T is convex (nonconvex = 0). - loc = insertvertex(newpt, &searchtet, &searchsh, &sseg, &ivf); - - assert(loc != (int) ONVERTEX); - if (loc != (int) NEARVERTEX) { + if (insertpoint(newpt, &searchtet, &searchsh, &sseg, &ivf)) { // The new point has been inserted. - if (ivf.lawson > 0) { - // For CDT, use flips to reocver Delaunayness. - lawsonflip3d(newpt, ivf.lawson, 0, 0, 0); - } st_segref_count++; if (steinerleft > 0) steinerleft--; } else { - // The new point is either ON or VERY CLOSE to an existing point. - refpt = point2ppt(newpt); - printf(" !! Avoid to create a short edge (length = %g)\n", - distance(newpt, refpt)); - - // It is probably an input problem. Two possible cases are: - // (1) An input vertex is very close an input segment; or - // (2) Two input segments are nearly intersect each other. - forg1 = farsorg(sseg); - fdest1 = farsdest(sseg); - - if ((pointtype(refpt) == RIDGEVERTEX) || - (pointtype(refpt) == ACUTEVERTEX) || - (pointtype(refpt) == VOLVERTEX)) { - // Case (1) - printf(" !! Point %d is very close to segment (%d, %d).\n", - pointmark(refpt), pointmark(forg1), pointmark(fdest1)); - } else if (pointtype(refpt) == FREESEGVERTEX) { - // Case (2). Find a subsegment contain 'refpt'. - subsegs->traversalinit(); - checkseg.sh = shellfacetraverse(subsegs); - while (checkseg.sh != NULL) { - if (((point) checkseg.sh[3] == refpt) || - ((point) checkseg.sh[4] == refpt)) break; - checkseg.sh = shellfacetraverse(subsegs); - } - assert(checkseg.sh != NULL); - checkseg.shver = 0; - forg2 = farsorg(checkseg); - fdest2 = farsdest(checkseg); - printf(" !! Two segments are very close to each other.\n"); - printf(" 1st: (%d, %d), 2nd: (%d, %d)\n", pointmark(forg1), - pointmark(fdest1), pointmark(forg2), pointmark(fdest2)); - } else { - // Unknown case - assert(0); - } - // Indicate it may be an input problem. - printf(" Short edge length bound is: %g. Tolerance is %g.\n", - b->minedgelength, b->epsilon); - terminatetetgen(4); + assert (ivf.iloc == (enum locateresult) NEARVERTEX); + terminatetetgen(this, 4); } } else { - // The input PLC contains self-intersections. - if (dir == ACROSSVERT) { - // refpt is the vertex intersecting the segment. - forg1 = farsorg(sseg); - fdest1 = farsdest(sseg); - if ((pointtype(refpt) == RIDGEVERTEX) || - (pointtype(refpt) == ACUTEVERTEX) || - (pointtype(refpt) == FACETVERTEX) || - (pointtype(refpt) == VOLVERTEX)) { - printf("Point %d is on segment (%d, %d).\n", - pointmark(refpt), pointmark(forg1), pointmark(fdest1)); - } else if (pointtype(refpt) == FREESEGVERTEX) { - // Case (2). Find a subsegment contain 'refpt'. - subsegs->traversalinit(); - checkseg.sh = shellfacetraverse(subsegs); - while (checkseg.sh != NULL) { - if (((point) checkseg.sh[3] == refpt) || - ((point) checkseg.sh[4] == refpt)) break; - checkseg.sh = shellfacetraverse(subsegs); - } - assert(checkseg.sh != NULL); - checkseg.shver = 0; - forg2 = farsorg(checkseg); - fdest2 = farsdest(checkseg); - printf("Two segments intersect.\n"); - printf(" 1st: (%d, %d), 2nd: (%d, %d)", pointmark(forg1), - pointmark(fdest1), pointmark(forg2), pointmark(fdest2)); - } else if (pointtype(refpt) == FREEFACETVERTEX) { - assert(0); // Report this case. - } - } else if (dir == ACROSSSEG) { - tsspivot1(searchtet, checkseg); - if (!b->quiet) { - printf("Two segments intersect.\n"); - forg1 = farsorg(sseg); - fdest1 = farsdest(sseg); - forg2 = farsorg(checkseg); - fdest2 = farsdest(checkseg); - printf(" 1st: (%d, %d), 2nd: (%d, %d).\n", pointmark(forg1), - pointmark(fdest1), pointmark(forg2), pointmark(fdest2)); - } - } else if (dir == ACROSSSUB) { - tspivot(searchtet, checksh); - if (!b->quiet) { - printf("A segment and a subface intersect.\n"); - forg1 = farsorg(sseg); - fdest1 = farsdest(sseg); - forg2 = sorg(checksh); - fdest2 = sdest(checksh); - fapex2 = sapex(checksh); - printf(" Seg: (%d, %d), Sub: (%d, %d, %d).\n", - pointmark(forg1), pointmark(fdest1), - pointmark(forg2), pointmark(fdest2), pointmark(fapex2)); - } - } else { - // Unknown cases. - assert(0); - } // Indicate it is an input problem. - terminatetetgen(3); + terminatetetgen(this, 3); } } } // while - } /////////////////////////////////////////////////////////////////////////////// // // -// scoutsubface() Look for a given subface in the tetrahedralization T. // +// scoutsubface() Search subface in the tetrahedralization. // // // // 'searchsh' is searched in T. If it exists, it is 'locked' at the face in // // T. 'searchtet' refers to the face. Otherwise, it is missing. // @@ -16640,17 +15274,13 @@ enum tetgenmesh::interresult tetgenmesh::scoutsubface(face* searchsh, triface* searchtet) { triface spintet; - face checksh; point pa, pb, pc; enum interresult dir; + int t1ver; pa = sorg(*searchsh); pb = sdest(*searchsh); - if (b->verbose > 2) { - printf(" Scout subface (%d, %d, %d).\n", pointmark(pa), pointmark(pb), - pointmark(sapex(*searchsh))); - } // Get a tet whose origin is a. point2tetorg(pa, *searchtet); @@ -16659,8 +15289,10 @@ enum tetgenmesh::interresult if (dir == ACROSSVERT) { // Check validity of a PLC. if (dest(*searchtet) != pb) { - // A vertex lies on the search edge. Return it. + // A vertex lies on the search edge. enextself(*searchtet); + // It is possible a PLC self-intersection problem. + terminatetetgen(this, 3); return TOUCHEDGE; } // The edge exists. Check if the face exists. @@ -16670,8 +15302,7 @@ enum tetgenmesh::interresult while (1) { if (apex(spintet) == pc) { // Found a face matching to 'searchsh'! - tspivot(spintet, checksh); - if (checksh.sh == NULL) { + if (!issubface(spintet)) { // Insert 'searchsh'. tsbond(spintet, *searchsh); fsymself(spintet); @@ -16681,11 +15312,13 @@ enum tetgenmesh::interresult return SHAREFACE; } else { // Another subface is already inserted. + face checksh; + tspivot(spintet, checksh); assert(checksh.sh != searchsh->sh); // SELF_CHECK // This is possibly an input problem, i.e., two facets overlap. // Report this problem and exit. printf("Warning: Found two facets nearly overlap.\n"); - terminatetetgen(5); + terminatetetgen(this, 5); // unifysubfaces(&checksh, searchsh); *searchtet = spintet; return COLLISIONFACE; @@ -16697,47 +15330,39 @@ enum tetgenmesh::interresult } // dir is either ACROSSEDGE or ACROSSFACE. - return dir; //ACROSSTET; + return dir; } /////////////////////////////////////////////////////////////////////////////// // // -// formmissingregion() Form the missing region of a missing subface. // +// formregion() Form the missing region of a missing subface. // // // // 'missh' is a missing subface. From it we form a missing region R which is // -// a collection of missing subfaces connected through adjacent edges. // +// a connected region formed by a set of missing subfaces of a facet. // +// Comment: There should be no segment inside R. // // // -// The missing region R is returned in the array 'missingshs'. All subfaces // -// in R are oriented as 'missh'. The array 'missingshverts' returns all ver- // -// tices of R. All subfaces and vertices of R are marktested. // -// // -// 'adjtets' returns a list of tetrahedra adjacent to R. They are used to // -// search a crossing tetrahedron of R. // -// // -// Many ways are possible to form the missing region. The method used here // -// is to search missing edges in R. Starting from 'missh', its three edges // -// are checked. If one of the edges is missing, then the adjacent subface at // -// this edge is also missing. It is added to the array. By an incrementally // -// broad-first searching, we can find all subfaces of R. // +// 'missingshs' returns the list of subfaces in R. All subfaces in this list // +// are oriented as the 'missh'. 'missingshbds' returns the list of boundary // +// edges (tetrahedral handles) of R. 'missingshverts' returns all vertices // +// of R. They are all pmarktested. // // // +// Except the first one (which is 'missh') in 'missingshs', each subface in // +// this list represents an internal edge of R, i.e., it is missing in the // +// tetrahedralization. Since R may contain interior vertices, not all miss- // +// ing edges can be found by this way. // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::formmissingregion(face* missh, arraypool* missingshs, - arraypool* missingshbds, - arraypool* missingshverts, - arraypool* adjtets) +void tetgenmesh::formregion(face* missh, arraypool* missingshs, + arraypool* missingshbds, arraypool* missingshverts) { - triface searchtet, *parytet; + triface searchtet, spintet; face neighsh, *parysh; + face neighseg, fakeseg; point pa, pb, *parypt; enum interresult dir; + int t1ver; int i, j; - if (b->verbose > 2) { - printf(" Form missing region from subface (%d, %d, %d)\n", - pointmark(sorg(*missh)), pointmark(sdest(*missh)), - pointmark(sapex(*missh))); - } smarktest(*missh); missingshs->newindex((void **) &parysh); *parysh = *missh; @@ -16748,43 +15373,22 @@ void tetgenmesh::formmissingregion(face* missh, arraypool* missingshs, for (j = 0; j < 3; j++) { pa = sorg(*missh); pb = sdest(*missh); - // Get a tet whose origin is a. point2tetorg(pa, searchtet); - // Search the edge [a,b]. dir = finddirection(&searchtet, pb); if (dir != ACROSSVERT) { // This edge is missing. Its neighbor is a missing subface. spivot(*missh, neighsh); - assert(neighsh.sh != NULL); if (!smarktested(neighsh)) { // Adjust the face orientation. - if (sorg(neighsh) != pb) { - sesymself(neighsh); - } - if (b->verbose > 3) { - printf(" Add a missing subface (%d, %d, %d)\n", - pointmark(pb), pointmark(pa), pointmark(sapex(neighsh))); - } + if (sorg(neighsh) != pb) sesymself(neighsh); smarktest(neighsh); missingshs->newindex((void **) &parysh); *parysh = neighsh; } } else { - if (dest(searchtet) == pb) { - // Remember an existing edge for searching the first crossing tet. - adjtets->newindex((void **) &parytet); - *parytet = searchtet; - // Found an existing edge, it must be a boundary edge of R. - if (b->verbose > 3) { - printf(" -- A boundary edge (%d, %d)\n", pointmark(pa), - pointmark(pb)); - } - missingshbds->newindex((void **) &parysh); - *parysh = *missh; // It is only queued once. - } else { - // The input PLC has problem. - //assert(0); - terminatetetgen(3); + if (dest(searchtet) != pb) { + // This might be a self-intersection problem. + terminatetetgen(this, 3); } } // Collect the vertices of R. @@ -16797,56 +15401,86 @@ void tetgenmesh::formmissingregion(face* missh, arraypool* missingshs, } // j } // i - if (b->verbose > 2) { - printf(" Region has: %ld subfaces, %ld vertices\n", - missingshs->objects, missingshverts->objects); - } + // Get the boundary edges of R. + for (i = 0; i < missingshs->objects; i++) { + missh = (face *) fastlookup(missingshs, i); + for (j = 0; j < 3; j++) { + spivot(*missh, neighsh); + if ((neighsh.sh == NULL) || !smarktested(neighsh)) { + // A boundary edge of R. + // Let the segment point to the adjacent tet. + point2tetorg(sorg(*missh), searchtet); + finddirection(&searchtet, sdest(*missh)); + missingshbds->newindex((void **) &parysh); + *parysh = *missh; + // Check if this edge is a segment. + sspivot(*missh, neighseg); + if (neighseg.sh == NULL) { + // Temporarily create a segment at this edge. + makeshellface(subsegs, &fakeseg); + setsorg(fakeseg, sorg(*missh)); + setsdest(fakeseg, sdest(*missh)); + sinfect(fakeseg); // Mark it as faked. + // Connect it to all tets at this edge. + spintet = searchtet; + while (1) { + tssbond1(spintet, fakeseg); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + neighseg = fakeseg; + } + // Let the segment and the boundary edge point to each other. + ssbond(*missh, neighseg); + sstbond1(neighseg, searchtet); + } + senextself(*missh); + } // j + } // i - if (missingshs->objects > maxregionsize) { - maxregionsize = missingshs->objects; - } // Unmarktest collected missing subfaces. for (i = 0; i < missingshs->objects; i++) { - missh = (face *) fastlookup(missingshs, i); - sunmarktest(*missh); + parysh = (face *) fastlookup(missingshs, i); + sunmarktest(*parysh); } - - // Comment: All vertices in R are pmarktested. } - /////////////////////////////////////////////////////////////////////////////// // // // scoutcrossedge() Search an edge that crosses the missing region. // // // +// Return 1 if a crossing edge is found. It is returned by 'crosstet'. More- // +// over, the edge is oriented such that its origin lies below R. Return 0 // +// if no such edge is found. // +// // // Assumption: All vertices of the missing region are marktested. // // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, +int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* missingshbds, arraypool* missingshs) { - triface *searchtet, spintet; + triface searchtet, spintet; face *parysh; - face checkseg; + face neighseg; point pa, pb, pc, pd, pe; enum interresult dir; REAL ori; int types[2], poss[4]; int searchflag, interflag; + int t1ver; int i, j; - if (b->verbose > 2) { - printf(" Search a crossing edge.\n"); - } searchflag = 0; - for (j = 0; j < adjtets->objects && !searchflag; j++) { - searchtet = (triface *) fastlookup(adjtets, j); + for (j = 0; j < missingshbds->objects && !searchflag; j++) { + parysh = (face *) fastlookup(missingshbds, j); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); interflag = 0; // Let 'spintet' be [#,#,d,e] where [#,#] is the boundary edge of R. - spintet = *searchtet; + spintet = searchtet; while (1) { pd = apex(spintet); pe = oppo(spintet); @@ -16860,7 +15494,7 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, pa = sorg(*parysh); pb = sdest(*parysh); pc = sapex(*parysh); - interflag = tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); + interflag=tri_edge_test(pa, pb, pc, pd, pe, NULL, 1, types, poss); if (interflag > 0) { if (interflag == 2) { // They intersect at a single point. @@ -16868,14 +15502,13 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { //pos = poss[0]; // Go to the crossing edge [d,e,#,#]. - eprev(spintet, crosstet); - esymself(crosstet); - enextself(crosstet); // [d,e,#,#]. + edestoppo(spintet, crosstet); // // [d,e,#,#]. // Check if it is a segment. - tsspivot1(crosstet, checkseg); - if (checkseg.sh != NULL) { - reportselfintersect(&checkseg, parysh); - terminatetetgen(3); + if (issubseg(crosstet)) { + //face checkseg; + //tsspivot1(crosstet, checkseg); + //reportselfintersect(&checkseg, parysh); + terminatetetgen(this, 3); } // Adjust the edge such that d lies below [a,b,c]. ori = orient3d(pa, pb, pc, pd); @@ -16883,21 +15516,7 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, if (ori < 0) { esymself(crosstet); } - if (b->verbose > 2) { - printf(" Found edge (%d, %d) intersect", pointmark(pd), - pointmark(pe)); - printf(" face (%d, %d, %d)\n", pointmark(pa), pointmark(pb), - pointmark(pc)); - } - // Save the corners of this subface. - plane_pa = pa; - plane_pb = pb; - plane_pc = pc; searchflag = 1; - } else { - // An improper intersection type. - // Maybe it is a PLC problem. - // At the moment, just ignore it. } } break; @@ -16909,11 +15528,10 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, if (interflag > 0) break; // Go to the next tetrahedron. fnextself(spintet); - if (spintet.tet == searchtet->tet) break; + if (spintet.tet == searchtet.tet) break; } // while (1) } // j - adjtets->restart(); return searchflag; } @@ -16927,7 +15545,6 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, // #] which intersects R in its interior, where the edge [d,e] intersects R, // // and d lies below R. // // // -// // // 'crosstets' returns the set of crossing tets. Every tet in it has the // // form [d,e,#,#] where [d,e] is a crossing edge, and d lies below R. The // // set of tets form the cavity C, which is divided into two parts by R, one // @@ -16936,13 +15553,6 @@ int tetgenmesh::scoutcrossedge(triface& crosstet, arraypool* adjtets, // in the top part of C, and so does 'botpoints'. Both 'toppoints' and // // 'botpoints' contain vertices of R. // // // -// NOTE: 'toppoints' may contain points which are not vertices of any top // -// faces, and so may 'botpoints'. Such points may belong to other facets and // -// need to be present after the recovery of this cavity (P1029.poly). // -// // -// A pair of boundary faces: 'firsttopface' and 'firstbotface', are saved. // -// They share the same edge in the boundary of the missing region. // -// // // Important: This routine assumes all vertices of the facet containing this // // subface are marked, i.e., pmarktested(p) returns true. // // // @@ -16953,25 +15563,22 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, arraypool* botfaces, arraypool* toppoints, arraypool* botpoints) { - arraypool *crossedges, *testededges; + arraypool *crossedges; triface spintet, neightet, *parytet; - face checksh, *parysh = NULL; - face checkseg; // *paryseg; + face *parysh = NULL; point pa, pd, pe, *parypt; enum interresult dir; bool testflag, invalidflag; int types[2], poss[4]; + int t1ver; int i, j, k; // Temporarily re-use 'topfaces' for all crossing edges. crossedges = topfaces; - // Temporarily re-use 'botfaces' for all tested edges. - testededges = botfaces; // Only used by 'b->psc'. if (b->verbose > 2) { - printf(" Form the cavity of missing region.\n"); + printf(" Form the cavity of a missing region.\n"); } - missingsubfacecount += missingshs->objects; // Mark this edge to avoid testing it later. markedge(*searchtet); crossedges->newindex((void **) &parytet); @@ -16980,7 +15587,7 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, invalidflag = 0; // Collect all crossing tets. Each cross tet is saved in the standard - // form [d,e,#,#], where [d,e] is a corossing edge, d lies below R. + // form [d,e,#,#], where [d,e] is a crossing edge, d lies below R. // NEITHER d NOR e is a vertex of R (!pmarktested). for (i = 0; i < crossedges->objects; i++) { // Get a crossing edge [d,e,#,#]. @@ -16988,14 +15595,12 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, // Sort vertices into the bottom and top arrays. pd = org(*searchtet); - assert(!pmarktested(pd)); // pd is not on R. if (!pinfected(pd)) { pinfect(pd); botpoints->newindex((void **) &parypt); *parypt = pd; } pe = dest(*searchtet); - assert(!pmarktested(pe)); // pe is not on R. if (!pinfected(pe)) { pinfect(pe); toppoints->newindex((void **) &parypt); @@ -17006,11 +15611,6 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, spintet = *searchtet; while (1) { if (!infected(spintet)) { - if (b->verbose > 3) { - printf(" Add a crossing tet (%d, %d, %d, %d)\n", - pointmark(org(spintet)), pointmark(dest(spintet)), - pointmark(apex(spintet)), pointmark(oppo(spintet))); - } infect(spintet); crosstets->newindex((void **) &parytet); *parytet = spintet; @@ -17026,10 +15626,10 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, // spintet is [d,e,a,#], where d lies below R, and e lies above R. pa = apex(spintet); if (pa != dummypoint) { - if (!pmarktested(pa) || b->psc) { - // There exists a crossing edge, either [e,a] or [a,d]. First check - // if the crossing edge has already be added. This is to check if - // a tetrahedron at this edge is marked. + if (!pmarktested(pa)) { + // There exists a crossing edge, either [e,a] or [a,d]. First check + // if the crossing edge has already be added, i.e., check if a + // tetrahedron at this edge is marked. testflag = true; for (j = 0; j < 2 && testflag; j++) { if (j == 0) { @@ -17055,12 +15655,8 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, pe = dest(spintet); for (k = 0; k < missingshs->objects; k++) { parysh = (face *) fastlookup(missingshs, k); - plane_pa = sorg(*parysh); - plane_pb = sdest(*parysh); - plane_pc = sapex(*parysh); - // Test if this face intersects [e,a]. - if (tri_edge_test(plane_pa, plane_pb, plane_pc, pe, pa, - NULL, 1, types, poss)) { + if (tri_edge_test(sorg(*parysh), sdest(*parysh), sapex(*parysh), + pe, pa, NULL, 1, types, poss)) { // Found intersection. 'a' lies below R. enext(spintet, neightet); dir = (enum interresult) types[0]; @@ -17072,9 +15668,8 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, } break; } - // Test if this face intersects [a,d]. - if (tri_edge_test(plane_pa, plane_pb, plane_pc, pa, pd, - NULL, 1, types, poss)) { + if (tri_edge_test(sorg(*parysh), sdest(*parysh), sapex(*parysh), + pa, pd, NULL, 1, types, poss)) { // Found intersection. 'a' lies above R. eprev(spintet, neightet); dir = (enum interresult) types[0]; @@ -17088,47 +15683,28 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, } } // k if (k < missingshs->objects) { - // Found a pair of triangle - edge interseciton. + // Found a pair of triangle - edge intersection. if (invalidflag) { - if (b->verbose > 2) { - printf(" A non-valid subface - edge intersection\n"); + if (!b->quiet) { + printf("Warning: A non-valid facet - edge intersection\n"); printf(" subface: (%d, %d, %d) edge: (%d, %d)\n", - pointmark(plane_pa), pointmark(plane_pb), - pointmark(plane_pc), pointmark(org(neightet)), + pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), + pointmark(sapex(*parysh)), pointmark(org(neightet)), pointmark(dest(neightet))); } // It may be a PLC problem. - terminatetetgen(3); - } else if (b->psc) { - if (pmarktested(pa)) { - // The intersection is invalid. - if (b->verbose > 2) { - printf(" A non-valid subface - edge intersection\n"); - printf(" subface: (%d, %d, %d) edge: (%d, %d)\n", - pointmark(plane_pa), pointmark(plane_pb), - pointmark(plane_pc), pointmark(org(neightet)), - pointmark(dest(neightet))); - } - // Split the subface intersecting this edge. - recentsh = *parysh; - recenttet = neightet; // For point location. - invalidflag = 1; - break; - } // if (pmarktested(pa)) - } // if (b->psc) + terminatetetgen(this, 3); + } // Adjust the edge direction, so that its origin lies below R, // and its destination lies above R. esymself(neightet); // Check if this edge is a segment. - tsspivot1(neightet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(neightet)) { // Invalid PLC! - reportselfintersect(&checkseg, parysh); - terminatetetgen(3); - } - if (b->verbose > 3) { - printf(" Add a crossing edge (%d, %d)\n", - pointmark(org(neightet)), pointmark(dest(neightet))); + //face checkseg; + //tsspivot1(neightet, checkseg); + //reportselfintersect(&checkseg, parysh); + terminatetetgen(this, 3); } // Mark this edge to avoid testing it again. markedge(neightet); @@ -17136,32 +15712,29 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, *parytet = neightet; } else { // No intersection is found. It may be a PLC problem. - //assert(b->psc); - // Mark this edge to avoid testing it again. - //markedge(neightet); - //testededges->newindex((void **) &parytet); - //*parytet = neightet; invalidflag = 1; // Split the subface intersecting [d,e]. for (k = 0; k < missingshs->objects; k++) { parysh = (face *) fastlookup(missingshs, k); - plane_pa = sorg(*parysh); - plane_pb = sdest(*parysh); - plane_pc = sapex(*parysh); // Test if this face intersects [e,a]. - if (tri_edge_test(plane_pa, plane_pb, plane_pc, pd, pe, - NULL, 1, types, poss)) { + if (tri_edge_test(sorg(*parysh),sdest(*parysh),sapex(*parysh), + pd, pe, NULL, 1, types, poss)) { break; } } // k - assert(k < missingshs->objects); + if (k == missingshs->objects) { + // Not found such an edge. + // Arbitrarily choose an edge (except the first) to split. + k = randomnation(missingshs->objects - 1); + parysh = (face *) fastlookup(missingshs, k + 1); + } recentsh = *parysh; recenttet = spintet; // For point location. break; // the while (1) loop } // if (k == missingshs->objects) } // if (testflag) - } // if (!pmarktested(pa) || b->psc) - } + } // if (!pmarktested(pa) || b->psc) + } // if (pa != dummypoint) // Go to the next crossing tet. fnextself(spintet); if (spintet.tet == searchtet->tet) break; @@ -17176,7 +15749,6 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, printf(" Formed cavity: %ld (%ld) cross tets (edges).\n", crosstets->objects, crossedges->objects); } - crossingtetcount += crosstets->objects; // Unmark all marked edges. for (i = 0; i < crossedges->objects; i++) { @@ -17186,17 +15758,6 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, } crossedges->restart(); - if (b->psc) { - // Unmark all marked edges. - for (i = 0; i < testededges->objects; i++) { - searchtet = (triface *) fastlookup(testededges, i); - assert(edgemarked(*searchtet)); // SELF_CHECK - unmarkedge(*searchtet); - } - testededges->restart(); - } else { // only p->plc - assert(testededges->objects == 0l); - } if (invalidflag) { // Unmark all collected tets. @@ -17216,44 +15777,21 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, crosstets->restart(); botpoints->restart(); toppoints->restart(); + + // Randomly split an interior edge of R. + i = randomnation(missingshs->objects - 1); + recentsh = * (face *) fastlookup(missingshs, i); return false; } - // Find a pair of cavity boundary faces from the top and bottom sides of - // the facet each, and they share the same edge. Save them in the - // global variables: firsttopface, firstbotface. They will be used in - // fillcavity() for gluing top and bottom new tets. - for (i = 0; i < crosstets->objects; i++) { - searchtet = (triface *) fastlookup(crosstets, i); - // Crosstet is [d,e,a,b]. - enextesym(*searchtet, spintet); - eprevself(spintet); // spintet is [b,a,e,d] - fsym(spintet, neightet); // neightet is [a,b,e,#] - if (!infected(neightet)) { - // A top face. - firsttopface = neightet; - } else { - continue; // Go to the next cross tet. - } - eprevesym(*searchtet, spintet); - enextself(spintet); // spintet is [a,b,d,e] - fsym(spintet, neightet); // neightet is [b,a,d,#] - if (!infected(neightet)) { - // A bottom face. - firstbotface = neightet; - } else { - continue; - } - break; - } // i - assert(i < crosstets->objects); // SELF_CHECK // Collect the top and bottom faces and the middle vertices. Since all top // and bottom vertices have been infected. Uninfected vertices must be // middle vertices (i.e., the vertices of R). // NOTE 1: Hull tets may be collected. Process them as a normal one. // NOTE 2: Some previously recovered subfaces may be completely inside the - // cavity. In such case, we remove these subfaces from the cavity and put // them into 'subfacstack'. They will be recovered later. + // cavity. In such case, we remove these subfaces from the cavity and put + // them into 'subfacstack'. They will be recovered later. // NOTE 3: Some segments may be completely inside the cavity, e.g., they // attached to a subface which is inside the cavity. Such segments are // put in 'subsegstack'. They will be recovered later. @@ -17263,16 +15801,14 @@ bool tetgenmesh::formcavity(triface* searchtet, arraypool* missingshs, for (i = 0; i < crosstets->objects; i++) { searchtet = (triface *) fastlookup(crosstets, i); // searchtet is [d,e,a,b]. - enextesym(*searchtet, spintet); - eprevself(spintet); // spintet is [b,a,e,d] + eorgoppo(*searchtet, spintet); fsym(spintet, neightet); // neightet is [a,b,e,#] if (!infected(neightet)) { // A top face. topfaces->newindex((void **) &parytet); *parytet = neightet; - } - eprevesym(*searchtet, spintet); - enextself(spintet); // spintet is [a,b,d,e] + } + edestoppo(*searchtet, spintet); fsym(spintet, neightet); // neightet is [b,a,d,#] if (!infected(neightet)) { // A bottom face. @@ -17337,19 +15873,17 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, arraypool *cavshells, arraypool *newtets, arraypool *crosstets, arraypool *misfaces) { - triface searchtet, neightet, spintet, *parytet, *parytet1; - face checksh, tmpsh, *parysh; - face checkseg; + triface searchtet, neightet, *parytet, *parytet1; + face tmpsh, *parysh; point pa, pb, pc, pd, pt[3], *parypt; enum interresult dir; insertvertexflags ivf; - REAL ori; //, ang, len; + REAL ori; long baknum, bakhullsize; int bakchecksubsegflag, bakchecksubfaceflag; - //int iloc; + int t1ver; int i, j; - if (b->verbose > 2) { printf(" Delaunizing cavity: %ld points, %ld faces.\n", cavpoints->objects, cavfaces->objects); @@ -17363,31 +15897,38 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, checksubsegflag = 0; checksubfaceflag = 0; b->verbose--; // Suppress informations for creating Delaunay tetra. - b->plc = 0; // Do not do unifypoint(); + b->plc = 0; // Do not check near vertices. + + ivf.bowywat = 1; // Use Bowyer-Watson algorithm. // Get four non-coplanar points (no dummypoint). - parytet = (triface *) fastlookup(cavfaces, 0); - pa = org(*parytet); - pb = dest(*parytet); - pc = apex(*parytet); + pa = pb = pc = NULL; + for (i = 0; i < cavfaces->objects; i++) { + parytet = (triface *) fastlookup(cavfaces, i); + parytet->ver = epivot[parytet->ver]; + if (apex(*parytet) != dummypoint) { + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + break; + } + } pd = NULL; - for (i = 1; i < cavfaces->objects; i++) { + for (; i < cavfaces->objects; i++) { parytet = (triface *) fastlookup(cavfaces, i); pt[0] = org(*parytet); pt[1] = dest(*parytet); pt[2] = apex(*parytet); for (j = 0; j < 3; j++) { if (pt[j] != dummypoint) { // Do not include a hull point. - // if (!pinfected(pt[j])) { - ori = orient3d(pa, pb, pc, pt[j]); - if (ori != 0) { - pd = pt[j]; - if (ori > 0) { // Swap pa and pb. - pt[j] = pa; pa = pb; pb = pt[j]; - } - break; + ori = orient3d(pa, pb, pc, pt[j]); + if (ori != 0) { + pd = pt[j]; + if (ori > 0) { // Swap pa and pb. + pt[j] = pa; pa = pb; pb = pt[j]; } - // } + break; + } } } if (pd != NULL) break; @@ -17400,15 +15941,13 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, // Incrementally insert the vertices (duplicated vertices are ignored). for (i = 0; i < cavpoints->objects; i++) { pt[0] = * (point *) fastlookup(cavpoints, i); - assert(pt[0] != dummypoint); // SELF_CHECK searchtet = recenttet; ivf.iloc = (int) OUTSIDE; - ivf.bowywat = 1; - insertvertex(pt[0], &searchtet, NULL, NULL, &ivf); + insertpoint(pt[0], &searchtet, NULL, NULL, &ivf); } if (b->verbose > 2) { - printf(" Identfying %ld boundary faces of the cavity.\n", + printf(" Identifying %ld boundary faces of the cavity.\n", cavfaces->objects); } @@ -17419,10 +15958,7 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, parytet = (triface *) fastlookup(cavfaces, i); // Skip an interior face (due to the enlargement of the cavity). if (infected(*parytet)) continue; - // This face may contain dummypoint (See fig/dum-cavity-case2). - // If so, dummypoint must be its apex. - j = (parytet->ver & 3); // j is the face number. - parytet->ver = epivot[j]; // [4,5,2,11] + parytet->ver = epivot[parytet->ver]; pt[0] = org(*parytet); pt[1] = dest(*parytet); pt[2] = apex(*parytet); @@ -17433,38 +15969,21 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, searchtet.tet = NULL; dir = scoutsubface(&tmpsh, &searchtet); if (dir == SHAREFACE) { - // Inserted. Make sure that tmpsh connects an interior tet of C. - stpivot(tmpsh, neightet); - // neightet and tmpsh refer to the same edge [pt[0], pt[1]]. - // If the origin of neightet is pt[1], it is inside. - if (org(neightet) != pt[1]) { - fsymself(neightet); - assert(org(neightet) == pt[1]); // SELF_CHECK - // Make sure that tmpsh is connected with an interior tet. - sesymself(tmpsh); - tsbond(neightet, tmpsh); - } - assert(dest(neightet) == pt[0]); // SELF_CHECK - } else if (dir == COLLISIONFACE) { - // This case is not possible anymore. 2010-02-01 - assert(0); - } else { - if (b->verbose > 2) { - printf(" bdry face (%d, %d, %d) -- %d is missing\n", - pointmark(pt[0]), pointmark(pt[1]), pointmark(pt[2]), i); - } + // Inserted! 'tmpsh' must face toward the inside of the cavity. + // Remember the boundary tet (outside the cavity) in tmpsh + // (use the adjacent tet slot). + tmpsh.sh[0] = (shellface) encode(*parytet); + // Save this subface. + cavshells->newindex((void **) &parysh); + *parysh = tmpsh; + } + else { + // This boundary face is missing. shellfacedealloc(subfaces, tmpsh.sh); // Save this face in list. misfaces->newindex((void **) &parytet1); *parytet1 = *parytet; - continue; } - // Remember the boundary tet (outside the cavity) in tmpsh - // (use the adjacent tet slot). - tmpsh.sh[0] = (shellface) encode(*parytet); - // Save this subface. - cavshells->newindex((void **) &parysh); - *parysh = tmpsh; } // i if (misfaces->objects > 0) { @@ -17473,7 +15992,7 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, misfaces->objects); } - // Removing all tempoaray subfaces. + // Removing all temporary subfaces. for (i = 0; i < cavshells->objects; i++) { parysh = (face *) fastlookup(cavshells, i); stpivot(*parysh, neightet); @@ -17504,11 +16023,7 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, if (!pinfected(pd)) { searchtet = recenttet; ivf.iloc = (int) OUTSIDE; - ivf.bowywat = 1; - insertvertex(pd, &searchtet, NULL, NULL, &ivf); - if (b->verbose > 2) { - printf(" Add point %d into list.\n", pointmark(pd)); - } + insertpoint(pd, &searchtet, NULL, NULL, &ivf); pinfect(pd); cavpoints->newindex((void **) &parypt); *parypt = pd; @@ -17518,15 +16033,9 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, esym(*parytet, neightet); fsymself(neightet); if (!infected(neightet)) { - if (b->verbose > 2) { - printf(" Add a cavface (%d, %d, %d).\n", - pointmark(org(neightet)), pointmark(dest(neightet)), - pointmark(apex(neightet))); - } cavfaces->newindex((void **) &parytet1); *parytet1 = neightet; - } else { - } + } enextself(*parytet); } // j } // if (!infected(parytet)) @@ -17552,8 +16061,8 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, *parytet = recenttet; for (i = 0; i < newtets->objects; i++) { searchtet = * (triface *) fastlookup(newtets, i); - for (searchtet.ver = 0; searchtet.ver < 4; searchtet.ver++) { - fsym(searchtet, neightet); + for (j = 0; j < 4; j++) { + decode(searchtet.tet[j], neightet); if (!marktested(neightet)) { marktest(neightet); newtets->newindex((void **) &parytet); @@ -17565,9 +16074,6 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, cavpoints->restart(); cavfaces->restart(); - if (cavshells->objects > maxcavsize) { - maxcavsize = cavshells->objects; - } if (crosstets->objects > baknum) { // The cavity has been enlarged. cavityexpcount++; @@ -17595,112 +16101,131 @@ void tetgenmesh::delaunizecavity(arraypool *cavpoints, arraypool *cavfaces, /////////////////////////////////////////////////////////////////////////////// bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, - arraypool* midfaces, arraypool* missingshs) + arraypool* midfaces, arraypool* missingshs, + arraypool* topnewtets, arraypool* botnewtets, + triface* crossedge) { arraypool *cavshells; - triface *parytet, bdrytet, toptet, bottet, midface; - triface neightet, spintet; - face checksh, *parysh; + triface bdrytet, neightet, *parytet; + triface searchtet, spintet; + face *parysh; face checkseg; - point pa, pb, pc, pf, pg; //, *pts; - int types[2], poss[4]; - //REAL elen[3]; //ori, len, n[3]; - bool mflag, bflag; - int i, j, k; + point pa, pb, pc; + bool mflag; + int t1ver; + int i, j; // Connect newtets to tets outside the cavity. These connections are needed // for identifying the middle faces (which belong to R). - for (k = 0; k < 2; k++) { - cavshells = (k == 0 ? topshells : botshells); + for (j = 0; j < 2; j++) { + cavshells = (j == 0 ? topshells : botshells); if (cavshells != NULL) { for (i = 0; i < cavshells->objects; i++) { // Get a temp subface. parysh = (face *) fastlookup(cavshells, i); - // Get the boundary tet outside the cavity. + // Get the boundary tet outside the cavity (saved in sh[0]). decode(parysh->sh[0], bdrytet); pa = org(bdrytet); pb = dest(bdrytet); pc = apex(bdrytet); - // Get the adjacent new tet. + // Get the adjacent new tet inside the cavity. stpivot(*parysh, neightet); - assert(org(neightet) == pb); // SELF_CHECK - assert(dest(neightet) == pa); // SELF_CHECK - // Mark neightet as an interior tet of this cavity, 2009-04-24. - // Comment: We know neightet is an interior tet. - if (!infected(neightet)) { - infect(neightet); - } - assert(oppo(bdrytet) != NULL); // No faked tet. - // if (oppo(bdrytet) != NULL) { - // Bond the two tets. - bond(bdrytet, neightet); // Also cleared the pointer to tmpsh. - // } + // Mark neightet as an interior tet of this cavity. + infect(neightet); + // Connect the two tets (the old connections are replaced). + bond(bdrytet, neightet); tsdissolve(neightet); // Clear the pointer to tmpsh. // Update the point-to-tets map. - setpoint2tet(pa, encode(neightet)); - setpoint2tet(pb, encode(neightet)); - setpoint2tet(pc, encode(neightet)); - // Delete the temp subface. - // shellfacedealloc(subfacepool, parysh->sh); + setpoint2tet(pa, (tetrahedron) neightet.tet); + setpoint2tet(pb, (tetrahedron) neightet.tet); + setpoint2tet(pc, (tetrahedron) neightet.tet); } // i } // if (cavshells != NULL) - } // k + } // j - mflag = true; // Initialize it. + if (crossedge != NULL) { + // Glue top and bottom tets at their common facet. + triface toptet, bottet, spintet, *midface; + point pd, pe; + REAL ori; + int types[2], poss[4]; + int interflag; + int bflag; - if (midfaces != NULL) { + mflag = false; + pd = org(*crossedge); + pe = dest(*crossedge); + + // Search the first (middle) face in R. + // Since R may be non-convex, we must make sure that the face is in the + // interior of R. We search a face in 'topnewtets' whose three vertices + // are on R and it intersects 'crossedge' in its interior. Then search + // a matching face in 'botnewtets'. + for (i = 0; i < topnewtets->objects && !mflag; i++) { + searchtet = * (triface *) fastlookup(topnewtets, i); + for (searchtet.ver = 0; searchtet.ver < 4 && !mflag; searchtet.ver++) { + pa = org(searchtet); + if (pmarktested(pa)) { + pb = dest(searchtet); + if (pmarktested(pb)) { + pc = apex(searchtet); + if (pmarktested(pc)) { + // Check if this face intersects [d,e]. + interflag = tri_edge_test(pa,pb,pc,pd,pe,NULL,1,types,poss); + if (interflag == 2) { + // They intersect at a single point. Found. + toptet = searchtet; + // The face lies in the interior of R. + // Get the tet (in topnewtets) which lies above R. + ori = orient3d(pa, pb, pc, pd); + assert(ori != 0); + if (ori < 0) { + fsymself(toptet); + pa = org(toptet); + pb = dest(toptet); + } + // Search the face [b,a,c] in 'botnewtets'. + for (j = 0; j < botnewtets->objects; j++) { + neightet = * (triface *) fastlookup(botnewtets, j); + // Is neightet contains 'b'. + if ((point) neightet.tet[4] == pb) { + neightet.ver = 11; + } else if ((point) neightet.tet[5] == pb) { + neightet.ver = 3; + } else if ((point) neightet.tet[6] == pb) { + neightet.ver = 7; + } else if ((point) neightet.tet[7] == pb) { + neightet.ver = 0; + } else { + continue; + } + // Is the 'neightet' contains edge [b,a]. + if (dest(neightet) == pa) { + // 'neightet' is just the edge. + } else if (apex(neightet) == pa) { + eprevesymself(neightet); + } else if (oppo(neightet) == pa) { + esymself(neightet); + enextself(neightet); + } else { + continue; + } + // Is 'neightet' the face [b,a,c]. + if (apex(neightet) == pc) { + bottet = neightet; + mflag = true; + break; + } + } // j + } // if (interflag == 2) + } // pc + } // pb + } // pa + } // toptet.ver + } // i - // The first pair of top and bottom tets share the same edge [a, b]. - // toptet = * (triface *) fastlookup(topfaces, 0); - if (infected(firsttopface)) { - // This is due to he enlargement of the cavity. Find the updated top - // boundary face at edge [a,b]. - // Comment: An uninfected tet at [a,b] should be found since [a,b] is a - // boundary edge of the missing region R. It should not be enclosed - // by the enlarged cavity. - pa = apex(firsttopface); // SELF_CHECK - while (1) { - fnextself(firsttopface); - if (!infected(firsttopface)) break; - assert(apex(firsttopface) != pa); // SELF_CHECK - } - } - toptet = firsttopface; - pa = apex(toptet); - fsymself(toptet); - // Search a subface from the top mesh. - while (1) { - esymself(toptet); // The next face in the same tet. - pc = apex(toptet); - assert(pc != pa); // We should not return to the starting point. - if (pmarktested(pc)) break; // [a,b,c] is a subface. - fsymself(toptet); // Go to the adjacent tet. - } - // Search the subface [a,b,c] in the bottom mesh. - // bottet = * (triface *) fastlookup(botfaces, 0); - if (infected(firstbotface)) { - pa = apex(firstbotface); // SELF_CHECK - while (1) { - fnextself(firstbotface); - if (!infected(firstbotface)) break; - assert(apex(firstbotface) != pa); // SELF_CHECK - } - } - bottet = firstbotface; - pa = apex(bottet); - fsymself(bottet); - while (1) { - esymself(bottet); // The next face in the same tet. - pf = apex(bottet); - assert(pf != pa); // We should not return to the starting point. - if (pf == pc) break; // Face matched. - if (pmarktested(pf)) { - mflag = false; break; // Not matched. - } - fsymself(bottet); - } if (mflag) { - // Connect the two tets together. + // Found a pair of matched faces in 'toptet' and 'bottet'. bond(toptet, bottet); // Both are interior tets. infect(toptet); @@ -17709,20 +16234,23 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, markface(toptet); midfaces->newindex((void **) &parytet); *parytet = toptet; + } else { + // No pair of 'toptet' and 'bottet'. + toptet.tet = NULL; + // Randomly split an interior edge of R. + i = randomnation(missingshs->objects - 1); + recentsh = * (face *) fastlookup(missingshs, i); } - // Match pairs of subfaces (middle faces), connect top and bottom tets. + // Find other middle faces, connect top and bottom tets. for (i = 0; i < midfaces->objects && mflag; i++) { // Get a matched middle face [a, b, c] - midface = * (triface *) fastlookup(midfaces, i); + midface = (triface *) fastlookup(midfaces, i); // The tet must be a new created tet (marktested). - assert(marktested(midface)); // SELF_CHECK - - // Check the neighbors at edges [b, c] and [c, a]. - for (j = 0; j < 2 && mflag; j++) { - enextself(midface); // [b, c] or [c, a]. - pg = apex(midface); - toptet = midface; + assert(marktested(*midface)); // SELF_CHECK + // Check the neighbors at the edges of this face. + for (j = 0; j < 3 && mflag; j++) { + toptet = *midface; bflag = false; while (1) { // Go to the next face in the same tet. @@ -17732,6 +16260,7 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, break; // Find a subface. } if (pc == dummypoint) { + assert(0); // Check this case. break; // Find a subface. } // Go to the adjacent tet. @@ -17745,16 +16274,19 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, if (!bflag) { // assert(marktested(toptet)); // SELF_CHECK if (!facemarked(toptet)) { - fsym(midface, bottet); + fsym(*midface, bottet); + spintet = bottet; while (1) { esymself(bottet); - pf = apex(bottet); - if (pf == pc) break; // Face matched. - if (pmarktested(pf)) { - mflag = false; break; // Not matched - } + pd = apex(bottet); + if (pd == pc) break; // Face matched. fsymself(bottet); - } + if (bottet.tet == spintet.tet) { + // Not found a matched bottom face. + mflag = false; + break; + } + } // while (1) if (mflag) { if (marktested(bottet)) { // Connect two tets together. @@ -17766,79 +16298,185 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, markface(toptet); midfaces->newindex((void **) &parytet); *parytet = toptet; - } else { - // The 'bottet' is not inside the cavity! - // This case can happen when the cavity was enlarged, and the - // 'toptet' is a co-facet (sub)face adjacent to the missing - // region, and it is a boundary face of the top cavity. - // So the toptet and bottet should be bonded already through - // a temp subface. See fig/dump-cavity-case18. Check it. - fsym(toptet, neightet); - assert(neightet.tet == bottet.tet); // SELF_CHECK - assert(neightet.ver == bottet.ver); // SELF_CHECK - // Do not add this face into 'midfaces'. } - } + } else { // mflag == false + // Adjust 'toptet' and 'bottet' to be the crossing edges. + fsym(*midface, bottet); + spintet = bottet; + while (1) { + esymself(bottet); + pd = apex(bottet); + if (pmarktested(pd)) { + // assert(pd != pc); + // Let 'toptet' be [a,b,c,#], and 'bottet' be [b,a,d,*]. + // Adjust 'toptet' and 'bottet' to be the crossing edges. + // Test orient3d(b,c,#,d). + ori = orient3d(dest(toptet), pc, oppo(toptet), pd); + if (ori < 0) { + // Edges [a,d] and [b,c] cross each other. + enextself(toptet); // [b,c] + enextself(bottet); // [a,d] + } else if (ori > 0) { + // Edges [a,c] and [b,d] cross each other. + eprevself(toptet); // [c,a] + eprevself(bottet); // [d,b] + } else { + // b,c,#,and d are coplanar!. + assert(0); + } + break; // Not matched + } + fsymself(bottet); + assert (bottet.tet != spintet.tet); + } + } // if (!mflag) } // if (!facemarked(toptet)) - } + } // if (!bflag) + enextself(*midface); } // j } // i - } // if (midfaces != NULL) - - if (mflag) { - if (midfaces != NULL) { + if (mflag) { if (b->verbose > 2) { printf(" Found %ld middle subfaces.\n", midfaces->objects); } - if (midfaces->objects > maxregionsize) { - maxregionsize = midfaces->objects; - } - // Unmark middle faces. + face oldsh, newsh, casout, casin, neighsh; + + oldsh = * (face *) fastlookup(missingshs, 0); + + // Create new subfaces to fill the region R. for (i = 0; i < midfaces->objects; i++) { // Get a matched middle face [a, b, c] - midface = * (triface *) fastlookup(midfaces, i); - assert(facemarked(midface)); // SELF_CHECK - unmarkface(midface); + midface = (triface *) fastlookup(midfaces, i); + unmarkface(*midface); + makeshellface(subfaces, &newsh); + setsorg(newsh, org(*midface)); + setsdest(newsh, dest(*midface)); + setsapex(newsh, apex(*midface)); + // The new subface gets its markers from the old one. + setshellmark(newsh, shellmark(oldsh)); + if (checkconstraints) { + setareabound(newsh, areabound(oldsh)); + } + // Connect the new subface to adjacent tets. + tsbond(*midface, newsh); + fsym(*midface, neightet); + sesymself(newsh); + tsbond(neightet, newsh); } - } - } else { - // Faces at top and bottom are not matched. There exists non-Delaunay - // subedges. See fig/dump-cavity-case5.lua. - pa = org(toptet); - pb = dest(toptet); - pc = apex(toptet); - pf = apex(bottet); - pf = oppo(toptet); - pg = oppo(bottet); - // Find a subface in R which intersects the edge [f,g]. - for (i = 0; i < missingshs->objects; i++) { - parysh = (face *) fastlookup(missingshs, i); - pa = sorg(*parysh); - pb = sdest(*parysh); - pc = sapex(*parysh); - if (tri_edge_test(pa, pb, pc, pf, pg, NULL, 1, types, poss)) { - // Found a subface. - break; + // Connect new subfaces together and to the bdry of R. + // Delete faked segments. + for (i = 0; i < midfaces->objects; i++) { + // Get a matched middle face [a, b, c] + midface = (triface *) fastlookup(midfaces, i); + for (j = 0; j < 3; j++) { + tspivot(*midface, newsh); + spivot(newsh, casout); + if (casout.sh == NULL) { + // Search its neighbor. + fnext(*midface, searchtet); + while (1) { + // (1) First check if this side is a bdry edge of R. + tsspivot1(searchtet, checkseg); + if (checkseg.sh != NULL) { + // It's a bdry edge of R. + assert(!infected(searchtet)); // It must not be a cavity tet. + // Get the old subface. + checkseg.shver = 0; + spivot(checkseg, oldsh); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + checkseg.sh = NULL; + } + spivot(oldsh, casout); + if (casout.sh != NULL) { + casin = casout; + if (checkseg.sh != NULL) { + // Make sure that the subface has the right ori at the + // segment. + checkseg.shver = 0; + if (sorg(newsh) != sorg(checkseg)) { + sesymself(newsh); + } + spivot(casin, neighsh); + while (neighsh.sh != oldsh.sh) { + casin = neighsh; + spivot(casin, neighsh); + } + } + sbond1(newsh, casout); + sbond1(casin, newsh); + } + if (checkseg.sh != NULL) { + ssbond(newsh, checkseg); + } + break; + } // if (checkseg.sh != NULL) + // (2) Second check if this side is an interior edge of R. + tspivot(searchtet, neighsh); + if (neighsh.sh != NULL) { + // Found an adjacent subface of newsh (an interior edge). + sbond(newsh, neighsh); + break; + } + fnextself(searchtet); + assert(searchtet.tet != midface->tet); + } // while (1) + } // if (casout.sh == NULL) + enextself(*midface); + } // j + } // i + + // Delete old subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + shellfacedealloc(subfaces, parysh->sh); } - } - - if (i < missingshs->objects) { - // Such subface exist. - recentsh = *parysh; } else { - assert(0); // Debug this case. + if (toptet.tet != NULL) { + // Faces at top and bottom are not matched. + // Choose a Steiner point in R. + // Split one of the crossing edges. + pa = org(toptet); + pb = dest(toptet); + pc = org(bottet); + pd = dest(bottet); + // Search an edge in R which is either [a,b] or [c,d]. + // Reminder: Subfaces in this list 'missingshs', except the first + // one, represents an interior edge of R. + for (i = 1; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + if (((sorg(*parysh) == pa) && (sdest(*parysh) == pb)) || + ((sorg(*parysh) == pb) && (sdest(*parysh) == pa))) break; + if (((sorg(*parysh) == pc) && (sdest(*parysh) == pd)) || + ((sorg(*parysh) == pd) && (sdest(*parysh) == pc))) break; + } + if (i < missingshs->objects) { + // Found. Return it. + recentsh = *parysh; + } else { + assert(0); + } + } } - - // Set a tet for searching the new point. - recenttet = firsttopface; - } + midfaces->restart(); + } else { + mflag = true; + } // Delete the temp subfaces. - for (k = 0; k < 2; k++) { - cavshells = (k == 0 ? topshells : botshells); + for (j = 0; j < 2; j++) { + cavshells = (j == 0 ? topshells : botshells); if (cavshells != NULL) { for (i = 0; i < cavshells->objects; i++) { parysh = (face *) fastlookup(cavshells, i); @@ -17851,9 +16489,6 @@ bool tetgenmesh::fillcavity(arraypool* topshells, arraypool* botshells, if (botshells != NULL) { botshells->restart(); } - if (midfaces != NULL) { - midfaces->restart(); - } return mflag; } @@ -17868,10 +16503,12 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, arraypool *botnewtets) { arraypool *newtets; + shellface *sptr, *ssptr; triface *parytet, *pnewtet, newtet, neightet, spintet; face checksh, *parysh; face checkseg, *paryseg; - int i, j, k; + int t1ver; + int i, j; if (b->verbose > 2) { printf(" Carve cavity: %ld old tets.\n", crosstets->objects); @@ -17887,38 +16524,44 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // Collect all subfaces and segments which attached to the old tets. for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); - assert(infected(*parytet)); // SELF_CHECK - for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { - tspivot(*parytet, checksh); - if (checksh.sh != NULL) { - if (!sinfected(checksh)) { - sinfect(checksh); - cavetetshlist->newindex((void **) &parysh); - *parysh = checksh; + if ((sptr = (shellface*) parytet->tet[9]) != NULL) { + for (j = 0; j < 4; j++) { + if (sptr[j]) { + sdecode(sptr[j], checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + cavetetshlist->newindex((void **) &parysh); + *parysh = checksh; + } } - } + } // j } - for (j = 0; j < 6; j++) { - parytet->ver = edge2ver[j]; - tsspivot1(*parytet, checkseg); - if (checkseg.sh != NULL) { - if (!sinfected(checkseg)) { - sinfect(checkseg); - cavetetseglist->newindex((void **) &paryseg); - *paryseg = checkseg; + if ((ssptr = (shellface*) parytet->tet[8]) != NULL) { + for (j = 0; j < 6; j++) { + if (ssptr[j]) { + sdecode(ssptr[j], checkseg); + // Skip a deleted segment (was a faked segment) + if (checkseg.sh[3] != NULL) { + if (!sinfected(checkseg)) { + sinfect(checkseg); + cavetetseglist->newindex((void **) &paryseg); + *paryseg = checkseg; + } + } } - } + } // j } } // i + // Uninfect collected subfaces. for (i = 0; i < cavetetshlist->objects; i++) { - checksh = * (face *) fastlookup(cavetetshlist, i); - suninfect(checksh); + parysh = (face *) fastlookup(cavetetshlist, i); + suninfect(*parysh); } // Uninfect collected segments. for (i = 0; i < cavetetseglist->objects; i++) { - checkseg = * (face *) fastlookup(cavetetseglist, i); - suninfect(checkseg); + paryseg = (face *) fastlookup(cavetetseglist, i); + suninfect(*paryseg); } // Connect subfaces to new tets. @@ -17934,11 +16577,6 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // Does this tet lie inside the cavity. if (infected(neightet)) { checksh = *parysh; - if (b->verbose > 2) { - printf(" Found an interior subface (%d, %d, %d)\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } stdissolve(checksh); caveencshlist->newindex((void **) &parysh); *parysh = checksh; @@ -17952,10 +16590,7 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, tsbond(newtet, *parysh); } } // i - if (b->verbose > 2) { - printf(" %ld (%ld) cavity (interior) subfaces.\n", - cavetetshlist->objects, caveencshlist->objects); - } + for (i = 0; i < cavetetseglist->objects; i++) { checkseg = * (face *) fastlookup(cavetetseglist, i); @@ -17969,10 +16604,6 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, } fnextself(spintet); if (spintet.tet == neightet.tet) { - if (b->verbose > 2) { - printf(" Found an interior seg (%d, %d)\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } sstdissolve1(checkseg); caveencseglist->newindex((void **) &paryseg); *paryseg = checkseg; @@ -17990,10 +16621,7 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, } } } // i - if (b->verbose > 2) { - printf(" %ld (%ld) cavity (interior) segments.\n", - cavetetseglist->objects, caveencseglist->objects); - } + cavetetshlist->restart(); cavetetseglist->restart(); @@ -18001,6 +16629,9 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // Delete the old tets in cavity. for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); + if (ishulltet(*parytet)) { + hullsize--; + } tetrahedrondealloc(parytet->tet); } @@ -18008,8 +16639,8 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // Collect new tets in cavity. Some new tets have already been found // (and infected) in the fillcavity(). We first collect them. - for (k = 0; k < 2; k++) { - newtets = (k == 0 ? topnewtets : botnewtets); + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); if (newtets != NULL) { for (i = 0; i < newtets->objects; i++) { parytet = (triface *) fastlookup(newtets, i); @@ -18019,20 +16650,17 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, } } // i } - } // k + } // j // Now we collect all new tets in cavity. for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); - if (i == 0) { - recenttet = *parytet; // Remember a live handle. - } for (j = 0; j < 4; j++) { decode(parytet->tet[j], neightet); if (marktested(neightet)) { // Is it a new tet? if (!infected(neightet)) { // Find an interior tet. - assert((point) neightet.tet[7] != dummypoint); // SELF_CHECK + //assert((point) neightet.tet[7] != dummypoint); // SELF_CHECK infect(neightet); crosstets->newindex((void **) &pnewtet); *pnewtet = neightet; @@ -18041,9 +16669,12 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, } // j } // i + parytet = (triface *) fastlookup(crosstets, 0); + recenttet = *parytet; // Remember a live handle. + // Delete outer new tets. - for (k = 0; k < 2; k++) { - newtets = (k == 0 ? topnewtets : botnewtets); + for (j = 0; j < 2; j++) { + newtets = (j == 0 ? topnewtets : botnewtets); if (newtets != NULL) { for (i = 0; i < newtets->objects; i++) { parytet = (triface *) fastlookup(newtets, i); @@ -18051,6 +16682,9 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, // This is an interior tet. uninfect(*parytet); unmarktest(*parytet); + if (ishulltet(*parytet)) { + hullsize++; + } } else { // An outer tet. Delete it. tetrahedrondealloc(parytet->tet); @@ -18073,21 +16707,19 @@ void tetgenmesh::carvecavity(arraypool *crosstets, arraypool *topnewtets, /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, - arraypool *botnewtets) + arraypool *botnewtets, arraypool *missingshbds) { - triface *parytet, neightet; - face checksh; + triface *parytet, neightet, spintet; + face *parysh; face checkseg; point *ppt; + int t1ver; int i, j; // Reconnect crossing tets to cavity boundary. for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); assert(infected(*parytet)); // SELF_CHECK - if (i == 0) { - recenttet = *parytet; // Remember a live handle. - } parytet->ver = 0; for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { fsym(*parytet, neightet); @@ -18110,6 +16742,31 @@ void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, uninfect(*parytet); } + // Remember a live handle. + recenttet = * (triface *) fastlookup(crosstets, 0); + + // Delete faked segments. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, checkseg); + assert(checkseg.sh != NULL); + if (checkseg.sh[3] != NULL) { + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + sstpivot1(checkseg, neightet); + spintet = neightet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == neightet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(*parysh); + //checkseg.sh = NULL; + } + } + } // i + // Delete new tets. for (i = 0; i < topnewtets->objects; i++) { parytet = (triface *) fastlookup(topnewtets, i); @@ -18139,7 +16796,8 @@ void tetgenmesh::restorecavity(arraypool *crosstets, arraypool *topnewtets, // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::flipcertify(triface *chkface, badface **pqueue) +void tetgenmesh::flipcertify(triface *chkface,badface **pqueue,point plane_pa, + point plane_pb, point plane_pc) { badface *parybf, *prevbf, *nextbf; triface neightet; @@ -18216,7 +16874,7 @@ void tetgenmesh::flipcertify(triface *chkface, badface **pqueue) for (i = 0; i < 5; i++) { if (pmarktest2ed(p[i])) { // A top point has a positive weight. - w[i] = orient3d(plane_pa, plane_pb, plane_pc, p[i]); + w[i] = orient3dfast(plane_pa, plane_pb, plane_pc, p[i]); if (w[i] < 0) w[i] = -w[i]; assert(w[i] != 0); } else { @@ -18307,19 +16965,20 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, arraypool *botpoints, arraypool *midpoints) { arraypool *crossfaces, *bfacearray; - triface fliptets[5], baktets[2], fliptet, newface; + triface fliptets[6], baktets[2], fliptet, newface; triface neightet, *parytet; face checksh; face checkseg; badface *pqueue; badface *popbf, bface; + point plane_pa, plane_pb, plane_pc; point p1, p2, pd, pe; point *parypt; + flipconstraints fc; REAL ori[3]; int convcount, copcount; int flipflag, fcount; int n, i; - long f23count, f32count, f44count; long totalfcount; @@ -18367,9 +17026,6 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, if (b->verbose > 1) { printf(" Found %ld crossing faces.\n", crossfaces->objects); } - if (crossfaces->objects > maxcrossfacecount) { - maxcrossfacecount = crossfaces->objects; - } for (i = 0; i < crosstets->objects; i++) { parytet = (triface *) fastlookup(crosstets, i); @@ -18382,7 +17038,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, for (i = 0; i < crossfaces->objects; i++) { parytet = (triface *) fastlookup(crossfaces, i); - flipcertify(parytet, &pqueue); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); } crossfaces->restart(); @@ -18395,7 +17051,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, // Flip insert the facet. while (pqueue != NULL) { - // Pop a face from the priotity queue. + // Pop a face from the priority queue. popbf = pqueue; bface = *popbf; @@ -18450,7 +17106,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, fliptets[0] = fliptet; // abcd, d may be the new vertex. fliptets[1] = neightet; // bace. - flip23(fliptets, 1, 0, 0); + flip23(fliptets, 1, &fc); // Put the link faces into check list. for (i = 0; i < 3; i++) { eprevesym(fliptets[i], newface); @@ -18496,7 +17152,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, if (n == 3) { // Found a 3-to-2 flip. - flip32(fliptets, 1, 0, 0); + flip32(fliptets, 1, &fc); // Put the link faces into check list. for (i = 0; i < 3; i++) { esym(fliptets[0], newface); @@ -18530,7 +17186,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, baktets[0] = fliptets[2]; // = [b,a,e,f] baktets[1] = fliptets[3]; // = [b,a,f,d] // The flip may involve hull tets. - flip23(fliptets, 1, 0, 0); + flip23(fliptets, 1, &fc); // Put the "outer" link faces into check list. // fliptets[0] = [e,d,a,b] => will be flipped, so // [a,b,d] and [a,b,e] are not "outer" link faces. @@ -18549,7 +17205,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, eprevself(fliptets[0]); // = [b,a,d,c], d is the new vertex. fliptets[1] = baktets[0]; // = [b,a,e,f] fliptets[2] = baktets[1]; // = [b,a,f,d] - flip32(fliptets, 1, 0, 0); + flip32(fliptets, 1, &fc); // Put the "outer" link faces into check list. // fliptets[0] = [d,e,f,a] // fliptets[1] = [e,d,f,b] @@ -18587,14 +17243,13 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, pointmark(bface.fapex), pointmark(bface.foppo), pointmark(bface.noppo), bface.key); } - dbg_ignore_facecount++; } // if (convcount == 1) if (flipflag == 1) { // Update the priority queue. for (i = 0; i < crossfaces->objects; i++) { parytet = (triface *) fastlookup(crossfaces, i); - flipcertify(parytet, &pqueue); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); } crossfaces->restart(); if (1) { // if (!b->flipinsert_random) { @@ -18603,7 +17258,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, parytet = (triface *) fastlookup(bfacearray, i); // This face may be changed. if (!isdeadtet(*parytet)) { - flipcertify(parytet, &pqueue); + flipcertify(parytet, &pqueue, plane_pa, plane_pb, plane_pc); } } bfacearray->restart(); @@ -18628,7 +17283,7 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, } // 'bfacearray' may be not empty (for what reason ??). - dbg_unflip_facecount += bfacearray->objects; + //dbg_unflip_facecount += bfacearray->objects; assert(flippool->items == 0l); delete bfacearray; @@ -18647,11 +17302,6 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, f32count = flip32count - f32count; f44count = flip44count - f44count; totalfcount = f23count + f32count + f44count; - - if (totalfcount > maxflipsequence) { - maxflipsequence = totalfcount; - } - if (b->verbose > 2) { printf(" Total %ld flips. f23(%ld), f32(%ld), f44(%ld).\n", totalfcount, f23count, f32count, f44count); @@ -18664,156 +17314,85 @@ void tetgenmesh::flipinsertfacet(arraypool *crosstets, arraypool *toppoints, // // // 'missingshs' contains the list of subfaces in R. Moreover, each subface // // (except the first one) in this list represents an interior edge of R. // -// Note: All subfaces in R are smarktested. // // // // Note: We assume that all vertices of R are marktested so we can detect // // new subface by checking the flag in apexes. // // // /////////////////////////////////////////////////////////////////////////////// -bool tetgenmesh::fillregion(arraypool* missingshs, arraypool* missingshbds, +bool tetgenmesh::fillregion(arraypool* missingshs, arraypool* missingshbds, arraypool* newshs) { badface *newflipface, *popface; - triface searchtet, spintet; + triface searchtet, spintet, neightet; face oldsh, newsh, opensh, *parysh; face casout, casin, neighsh, checksh; - face checkseg, fakeseg; - point pc, pd, pe, pf, ppt[2]; - enum interresult dir; - REAL n[3], len; // elen[3]; - bool insideflag; - int types[2], poss[4]; - int i, j, k; + face neighseg, checkseg; + point pc; + int success; + int t1ver; + int i, j; - if (b->verbose > 2) { - printf(" Fill region: %ld old subfaces (%ld).\n", missingshs->objects, - fillregioncount); - } - // Search the first constrained face of R. It is found from the set of - // faces sharing at a boundary edge [a,b]. Such face must be found. - // The search takes the following two steps: - // - First, finds a candidate face [a,b,c] where c is also a vertex of R; - // Note that [a,b,c] may not be the right face to fill R. For instance, - // when R is concave at b. - // - Second, check if [a,b,c] can fill R. This can be checked if an - // adjacent tet of [a,b,c] intersects R. This is a tetrahedron-triangle - // intersection test. It can be reduced to two triangle-edge intersect - // tests, i.e., intersect the two faces not containing the edge [a,b] in - // this tet with all interior edges of R. - - // We start from the first boundary edge of R. - oldsh = * (face *) fastlookup(missingshbds, 0); - ppt[0] = sorg(oldsh); - ppt[1] = sdest(oldsh); - point2tetorg(ppt[0], searchtet); - dir = finddirection(&searchtet, ppt[1]); - assert(dir == ACROSSVERT); // SELF_CHECK - - insideflag = false; - - // Each face has two adjacent tets. - for (k = 0; k < 2; k++) { - if (b->verbose > 2) { - printf(" Search an interior face from edge (%d, %d).\n", - pointmark(ppt[0]), pointmark(ppt[1])); - } + // Search the first new subface to fill the region. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + sspivot(*parysh, neighseg); + sstpivot1(neighseg, searchtet); + j = 0; // Count the number of passes of R. spintet = searchtet; while (1) { pc = apex(spintet); if (pmarktested(pc)) { - // Found a candidate face. Check if it is inside R. - if (missingshs->objects > 2l) { - // pd = oppo(spintet); - // if (pd == dummypoint) { - // Calculate an above point for this subface. - facenormal(ppt[0], ppt[1], pc, n, 1, NULL); - len = sqrt(DOT(n, n)); - n[0] /= len; - n[1] /= len; - n[2] /= len; - len = DIST(ppt[0], ppt[1]); - len += DIST(ppt[1], pc); - len += DIST(pc, ppt[0]); - len /= 3.0; - dummypoint[0] = ppt[0][0] + len * n[0]; - dummypoint[1] = ppt[0][1] + len * n[1]; - dummypoint[2] = ppt[0][2] + len * n[2]; - pd = dummypoint; - // } - //if (pd != dummypoint) { - for (j = 0; j < 2 && !insideflag; j++) { - for (i = 1; i < missingshs->objects && !insideflag; i++) { - parysh = (face *) fastlookup(missingshs, i); - // Get an interior edge of R. - pe = sorg(*parysh); - pf = sdest(*parysh); - if (tri_edge_test(ppt[j],pc,pd,pe,pf,NULL,1,types,poss)) { - dir = (enum interresult) types[0]; - if (dir == ACROSSFACE) { - searchtet = spintet; - insideflag = true; - } else if (dir == ACROSSEDGE) { - searchtet = spintet; - insideflag = true; - } - } - } // i - } // j - // } - // if (pd == dummypoint) { - dummypoint[0] = 0; - dummypoint[1] = 0; - dummypoint[2] = 0; - // } - } else { - // It is a simple 2-to-2 flip. - searchtet = spintet; - insideflag = true; - } - } // if (pmarktested(pc)) - if (insideflag) break; + neightet = spintet; + j++; + } fnextself(spintet); if (spintet.tet == searchtet.tet) break; - } // while (1) - if (insideflag) break; - esymself(searchtet); - ppt[0] = org(searchtet); - ppt[1] = dest(searchtet); - } // k + } + assert(j >= 1); + if (j == 1) { + // Found an interior new subface. + searchtet = neightet; + oldsh = *parysh; + break; + } + } // i - if (!insideflag) { - // Something strange is happening. - // Refine the missing region by adding a Steiner point. - recentsh = oldsh; - recenttet = searchtet; // For point location. + if (i == missingshbds->objects) { + // Failed to find any interior subface. + // Need Steiner points. return false; } - // Create a new subface at the boundary edge. - if (b->verbose > 2) { - printf(" Create a new subface (%d, %d, %d)\n", pointmark(ppt[0]), - pointmark(ppt[1]), pointmark(pc)); - } makeshellface(subfaces, &newsh); - setsorg(newsh, ppt[0]); - setsdest(newsh, ppt[1]); - setsapex(newsh, pc); + setsorg(newsh, org(searchtet)); + setsdest(newsh, dest(searchtet)); + setsapex(newsh, apex(searchtet)); // The new subface gets its markers from the old one. setshellmark(newsh, shellmark(oldsh)); if (checkconstraints) { setareabound(newsh, areabound(oldsh)); } // Connect the new subface to adjacent tets. - tspivot(searchtet, checksh); // SELF_CHECK - assert(checksh.sh == NULL); // SELF_CHECK tsbond(searchtet, newsh); fsymself(searchtet); sesymself(newsh); tsbond(searchtet, newsh); // Connect newsh to outer subfaces. sspivot(oldsh, checkseg); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + checkseg.sh = NULL; + } spivot(oldsh, casout); if (casout.sh != NULL) { casin = casout; @@ -18849,367 +17428,474 @@ bool tetgenmesh::fillregion(arraypool* missingshs, arraypool* missingshbds, flipstack = newflipface; } - // Every other boundary edge of R is identified as a segment. Insert a faked - // segments at the place if it is not a segment. - for (i = 1; i < missingshbds->objects; i++) { - parysh = (face *) fastlookup(missingshbds, i); - ppt[0] = sorg(*parysh); - ppt[1] = sdest(*parysh); - point2tetorg(ppt[0], searchtet); - dir = finddirection(&searchtet, ppt[1]); - assert(dir == ACROSSVERT); // SELF_CHECK - tsspivot1(searchtet, checkseg); - if (checkseg.sh == NULL) { - // Insert a fake segment at this tet. - if (b->verbose > 2) { - printf(" Insert a fake segment (%d, %d)\n", pointmark(ppt[0]), - pointmark(ppt[1])); - } - makeshellface(subsegs, &fakeseg); - setsorg(fakeseg, ppt[0]); - setsdest(fakeseg, ppt[1]); - sinfect(fakeseg); // Mark it as faked. - // Connect it to all tets at this edge. - spintet = searchtet; - while (1) { - tssbond1(spintet, fakeseg); - fnextself(spintet); - if (spintet.tet == searchtet.tet) break; - } - checkseg = fakeseg; - } - // Let the segment hold the old subface. - checkseg.shver = 0; - sbond1(checkseg, *parysh); - // Remember it to free it later. - *parysh = checkseg; - } + success = 1; // Loop until 'flipstack' is empty. - while (flipstack != NULL) { - + while ((flipstack != NULL) && success) { // Pop an "open" side from the stack. popface = flipstack; opensh = popface->ss; flipstack = popface->nextitem; // The next top item in stack. flippool->dealloc((void *) popface); - // Process it if it is still open. - spivot(opensh, casout); - if (casout.sh == NULL) { - if (b->verbose > 2) { - printf(" Get an open side (%d, %d) - %d.\n", - pointmark(sorg(opensh)), pointmark(sdest(opensh)), - pointmark(sapex(opensh))); - } - // Search a neighbor to close this side. - stpivot(opensh, searchtet); - tsspivot1(searchtet, checkseg); - if (checkseg.sh == NULL) { - // No segment. It is inside R. Search for a new face to fill in R. - // Note that the face may not be found (see fig 2010-05-25-c). - spintet = searchtet; - fnextself(spintet); // Skip the current face. - while (1) { - pc = apex(spintet); - if (pmarktested(pc)) { - // Found a place for a new subface inside R -- Case (i). + // opensh is either (1) an interior edge or (2) a bdry edge. + stpivot(opensh, searchtet); + tsspivot1(searchtet, checkseg); + if (checkseg.sh == NULL) { + // No segment. It is an interior edge of R. + // Search for a new face in R. + spintet = searchtet; + fnextself(spintet); // Skip the current face. + while (1) { + pc = apex(spintet); + if (pmarktested(pc)) { + // 'opensh' is an interior edge. + if (!issubface(spintet)) { + // Create a new subface. + makeshellface(subfaces, &newsh); + setsorg(newsh, org(spintet)); + setsdest(newsh, dest(spintet)); + setsapex(newsh, pc); + // The new subface gets its markers from its neighbor. + setshellmark(newsh, shellmark(opensh)); + if (checkconstraints) { + setareabound(newsh, areabound(opensh)); + } + // Connect the new subface to adjacent tets. + tsbond(spintet, newsh); + fsymself(spintet); + sesymself(newsh); + tsbond(spintet, newsh); + // Connect newsh to its adjacent subface. + sbond(newsh, opensh); + // Add this new subface into list. + sinfect(newsh); + newshs->newindex((void **) &parysh); + *parysh = newsh; + // Push two "open" side of the new subface into stack. + for (i = 0; i < 2; i++) { + senextself(newsh); + newflipface = (badface *) flippool->alloc(); + newflipface->ss = newsh; + newflipface->nextitem = flipstack; + flipstack = newflipface; + } + } else { + // Connect to another open edge. tspivot(spintet, checksh); - if (checksh.sh == NULL) { - // Create a new subface. - if (b->verbose > 2) { - printf(" Create a new subface (%d, %d, %d)\n", - pointmark(org(spintet)), pointmark(dest(spintet)), - pointmark(pc)); - } - makeshellface(subfaces, &newsh); - setsorg(newsh, org(spintet)); - setsdest(newsh, dest(spintet)); - setsapex(newsh, pc); - // The new subface gets its markers from its neighbor. - setshellmark(newsh, shellmark(opensh)); - if (checkconstraints) { - setareabound(newsh, areabound(opensh)); - } - // Connect the new subface to adjacent tets. - tsbond(spintet, newsh); - fsymself(spintet); - sesymself(newsh); - tsbond(spintet, newsh); - // Connect newsh to its adjacent subface. - sbond(newsh, opensh); - // Add this new subface into list. - sinfect(newsh); - newshs->newindex((void **) &parysh); - *parysh = newsh; - // Push two "open" side of the new subface into stack. - for (i = 0; i < 2; i++) { - senextself(newsh); - newflipface = (badface *) flippool->alloc(); - newflipface->ss = newsh; - newflipface->nextitem = flipstack; - flipstack = newflipface; - } - } else { - // A new subface has already been created. - assert(sinfected(checksh)); // It must be in stack. - spivot(checksh, neighsh); // SELF_CHECK - assert(neighsh.sh == NULL); // Its side must be open. - if (b->verbose > 2) { - printf(" Connect to another open side (%d, %d, %d)\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - sbond(opensh, checksh); // Simply connect them. - } - break; // -- Case (i) + sbond(opensh, checksh); } + break; + } // if (pmarktested(pc)) + fnextself(spintet); + if (spintet.tet == searchtet.tet) { + // Not find any face to fill in R at this side. + // Suggest a point to split the edge. + success = 0; + break; + } + } // while (1) + } else { + // This side coincident with a boundary edge of R. + checkseg.shver = 0; + spivot(checkseg, oldsh); + if (sinfected(checkseg)) { + // It's a faked segment. Delete it. + spintet = searchtet; + while (1) { + tssdissolve1(spintet); fnextself(spintet); - if (spintet.tet == searchtet.tet) { - // Not find any face to fill in R at this side. - // TO DO: suggest a point to split the edge. - assert(0); - } - } // while (1) - } else { - // This side coincident with a boundary edge of R. - checkseg.shver = 0; - spivot(checkseg, oldsh); - if (sinfected(checkseg)) { - // It's a faked segment. Delete it. - if (b->verbose > 2) { - printf(" Delete a fake segment (%d, %d)\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - spintet = searchtet; - while (1) { - tssdissolve1(spintet); - fnextself(spintet); - if (spintet.tet == searchtet.tet) break; - } - shellfacedealloc(subsegs, checkseg.sh); - } - if (b->verbose > 2) { - printf(" Connect to a boundary edge (%d, %d, %d)\n", - pointmark(sorg(oldsh)), pointmark(sdest(oldsh)), - pointmark(sapex(oldsh))); - } - sspivot(oldsh, checkseg); - spivot(oldsh, casout); - if (casout.sh != NULL) { - casin = casout; - if (checkseg.sh != NULL) { - // Make sure that the subface has the right ori at the segment. - checkseg.shver = 0; - if (sorg(opensh) != sorg(checkseg)) { - sesymself(opensh); - } - spivot(casin, neighsh); - while (neighsh.sh != oldsh.sh) { - casin = neighsh; - spivot(casin, neighsh); - } - } - sbond1(opensh, casout); - sbond1(casin, opensh); + if (spintet.tet == searchtet.tet) break; } + shellfacedealloc(subsegs, checkseg.sh); + ssdissolve(oldsh); + checkseg.sh = NULL; + } + spivot(oldsh, casout); + if (casout.sh != NULL) { + casin = casout; if (checkseg.sh != NULL) { - ssbond(opensh, checkseg); + // Make sure that the subface has the right ori at the segment. + checkseg.shver = 0; + if (sorg(opensh) != sorg(checkseg)) { + sesymself(opensh); + } + spivot(casin, neighsh); + while (neighsh.sh != oldsh.sh) { + casin = neighsh; + spivot(casin, neighsh); + } + } + sbond1(opensh, casout); + sbond1(casin, opensh); + } + if (checkseg.sh != NULL) { + ssbond(opensh, checkseg); + } + } // if (checkseg.sh != NULL) + } // while ((flipstack != NULL) && success) + + if (success) { + // Uninfect all new subfaces. + for (i = 0; i < newshs->objects; i++) { + parysh = (face *) fastlookup(newshs, i); + suninfect(*parysh); + } + // Delete old subfaces. + for (i = 0; i < missingshs->objects; i++) { + parysh = (face *) fastlookup(missingshs, i); + shellfacedealloc(subfaces, parysh->sh); + } + fillregioncount++; + } else { + // Failed to fill the region. + // Re-connect old subfaces at boundaries of R. + // Also delete fake segments. + for (i = 0; i < missingshbds->objects; i++) { + parysh = (face *) fastlookup(missingshbds, i); + // It still connect to 'casout'. + // Re-connect 'casin' to it. + spivot(*parysh, casout); + casin = casout; + spivot(casin, neighsh); + while (1) { + if (sinfected(neighsh)) break; + if (neighsh.sh == parysh->sh) break; + casin = neighsh; + spivot(casin, neighsh); + } + if (sinfected(neighsh)) { + sbond1(casin, *parysh); + } + sspivot(*parysh, checkseg); + if (checkseg.sh != NULL) { + if (checkseg.sh[3] != NULL) { + if (sinfected(checkseg)) { + sstpivot1(checkseg, searchtet); + spintet = searchtet; + while (1) { + tssdissolve1(spintet); + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + ssdissolve(*parysh); + shellfacedealloc(subsegs, checkseg.sh); + } } } + } + // Delete all new subfaces. + for (i = 0; i < newshs->objects; i++) { + parysh = (face *) fastlookup(newshs, i); + shellfacedealloc(subfaces, parysh->sh); + } + // Clear the flip pool. + flippool->restart(); + flipstack = NULL; - } // if (casout.sh == NULL) - - } // while (flipstack != NULL) - - // Uninfect all new subfaces. - for (i = 0; i < newshs->objects; i++) { - parysh = (face *) fastlookup(newshs, i); - suninfect(*parysh); + // Choose an interior edge of R to split. + assert(missingshs->objects > 1); + // Skip the first subface in 'missingshs'. + i = randomnation(missingshs->objects - 1) + 1; + parysh = (face *) fastlookup(missingshs, i); + recentsh = *parysh; } + newshs->restart(); + + return success; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// insertpoint_cdt() Insert a new point into a CDT. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::insertpoint_cdt(point newpt, triface *searchtet, face *splitsh, + face *splitseg, insertvertexflags *ivf, + arraypool *cavpoints, arraypool *cavfaces, + arraypool *cavshells, arraypool *newtets, + arraypool *crosstets, arraypool *misfaces) +{ + triface neightet, *parytet; + face checksh, *parysh, *parysh1; + face *paryseg, *paryseg1; + point *parypt; + int t1ver; + int i; + if (b->verbose > 2) { - printf(" Created %ld new subfaces.\n", newshs->objects); + printf(" Insert point %d into CDT\n", pointmark(newpt)); } - fillregioncount++; - return true; + if (!insertpoint(newpt, searchtet, NULL, NULL, ivf)) { + // Point is not inserted. Check ivf->iloc for reason. + return 0; + } + + + for (i = 0; i < cavetetvertlist->objects; i++) { + cavpoints->newindex((void **) &parypt); + *parypt = * (point *) fastlookup(cavetetvertlist, i); + } + // Add the new point into the point list. + cavpoints->newindex((void **) &parypt); + *parypt = newpt; + + for (i = 0; i < cavebdrylist->objects; i++) { + cavfaces->newindex((void **) &parytet); + *parytet = * (triface *) fastlookup(cavebdrylist, i); + } + + for (i = 0; i < caveoldtetlist->objects; i++) { + crosstets->newindex((void **) &parytet); + *parytet = * (triface *) fastlookup(caveoldtetlist, i); + } + + cavetetvertlist->restart(); + cavebdrylist->restart(); + caveoldtetlist->restart(); + + // Insert the point using the cavity algorithm. + delaunizecavity(cavpoints, cavfaces, cavshells, newtets, crosstets, + misfaces); + fillcavity(cavshells, NULL, NULL, NULL, NULL, NULL, NULL); + carvecavity(crosstets, newtets, NULL); + + if ((splitsh != NULL) || (splitseg != NULL)) { + // Insert the point into the surface mesh. + sinsertvertex(newpt, splitsh, splitseg, ivf->sloc, ivf->sbowywat, 0); + + // Put all new subfaces into stack. + for (i = 0; i < caveshbdlist->objects; i++) { + // Get an old subface at edge [a, b]. + parysh = (face *) fastlookup(caveshbdlist, i); + spivot(*parysh, checksh); // The new subface [a, b, p]. + // Do not recover a deleted new face (degenerated). + if (checksh.sh[3] != NULL) { + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + + if (splitseg != NULL) { + // Queue two new subsegments in C(p) for recovery. + for (i = 0; i < cavesegshlist->objects; i++) { + paryseg = (face *) fastlookup(cavesegshlist, i); + subsegstack->newindex((void **) &paryseg1); + *paryseg1 = *paryseg; + } + } // if (splitseg != NULL) + + // Delete the old subfaces in sC(p). + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + if (checksubfaceflag) { + // It is possible that this subface still connects to adjacent + // tets which are not in C(p). If so, clear connections in the + // adjacent tets at this subface. + stpivot(*parysh, neightet); + if (neightet.tet != NULL) { + if (neightet.tet[4] != NULL) { + // Found an adjacent tet. It must be not in C(p). + assert(!infected(neightet)); + tsdissolve(neightet); + fsymself(neightet); + assert(!infected(neightet)); + tsdissolve(neightet); + } + } + } + shellfacedealloc(subfaces, parysh->sh); + } + if (splitseg != NULL) { + // Delete the old segment in sC(p). + shellfacedealloc(subsegs, splitseg->sh); + } + + // Clear working lists. + caveshlist->restart(); + caveshbdlist->restart(); + cavesegshlist->restart(); + } // if ((splitsh != NULL) || (splitseg != NULL)) + + // Put all interior subfaces into stack for recovery. + // They were collected in carvecavity(). + // Note: Some collected subfaces may be deleted by sinsertvertex(). + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + if (parysh->sh[3] != NULL) { + subfacstack->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + } + + // Put all interior segments into stack for recovery. + // They were collected in carvecavity(). + // Note: Some collected segments may be deleted by sinsertvertex(). + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + if (paryseg->sh[3] != NULL) { + subsegstack->newindex((void **) &paryseg1); + *paryseg1 = *paryseg; + } + } + + caveencshlist->restart(); + caveencseglist->restart(); + + return 1; } /////////////////////////////////////////////////////////////////////////////// // // // refineregion() Refine a missing region by inserting points. // // // +// 'splitsh' represents an edge of the facet to be split. It must be not a // +// segment. +// // +// Assumption: The current mesh is a CDT and is convex. // +// // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::refineregion() +void tetgenmesh::refineregion(face &splitsh, arraypool *cavpoints, + arraypool *cavfaces, arraypool *cavshells, + arraypool *newtets, arraypool *crosstets, + arraypool *misfaces) { - triface searchtet; - face splitsh; - face *paryseg, sseg; - point steinpt, pa, pb, pc; + triface searchtet, spintet; + face splitseg, *paryseg; + point steinpt, pa, pb, refpt; insertvertexflags ivf; - REAL auv[2], buv[2], newuv[2], t; - int fmark, fid, eid; - int loc; // iloc, sloc; - int s, i; + enum interresult dir; + long baknum = points->items; + int t1ver; + int i; - // The mesh is a CDT. - assert(subsegstack->objects == 0l); // SELF_CHECK + if (b->verbose > 2) { + printf(" Refining region at edge (%d, %d, %d).\n", + pointmark(sorg(splitsh)), pointmark(sdest(splitsh)), + pointmark(sapex(splitsh))); + } - // Create a new point. - makepoint(&steinpt, FREEFACETVERTEX); - - // The 'recentsh' saved an edge to be split. - splitsh = recentsh; // Add the Steiner point at the barycenter of the face. pa = sorg(splitsh); pb = sdest(splitsh); - pc = sapex(splitsh); - - if (b->psc) { - assert(in->facetmarkerlist != NULL); - fmark = shellmark(splitsh) - 1; - fid = in->facetmarkerlist[fmark]; - if (pointtype(pa) == RIDGEVERTEX) { - in->getvertexparamonface(in->geomhandle, pointmark(pa), fid, auv); - } else if (pointtype(pa) == FREESEGVERTEX) { - eid = pointgeomtag(pa); // The Edge containing this Steiner point. - t = pointgeomuv(pa, 0); // The Steiner point's parameter on Edge. - in->getedgesteinerparamonface(in->geomhandle, eid, t, fid, auv); - } else if (pointtype(pa) == FREEFACETVERTEX) { - auv[0] = pointgeomuv(pa, 0); - auv[1] = pointgeomuv(pa, 1); - } else { - assert(0); - } - if (pointtype(pb) == RIDGEVERTEX) { - in->getvertexparamonface(in->geomhandle, pointmark(pb), fid, buv); - } else if (pointtype(pb) == FREESEGVERTEX) { - eid = pointgeomtag(pb); // The Edge containing this Steiner point. - t = pointgeomuv(pb, 0); // The Steiner point's parameter on Edge. - in->getedgesteinerparamonface(in->geomhandle, eid, t, fid, buv); - } else if (pointtype(pb) == FREEFACETVERTEX) { - buv[0] = pointgeomuv(pb, 0); - buv[1] = pointgeomuv(pb, 1); - } else { - assert(0); - } - newuv[0] = 0.5 * (auv[0] + buv[0]); - newuv[1] = 0.5 * (auv[1] + buv[1]); - in->getsteineronface(in->geomhandle, fid, newuv, steinpt); - setpointgeomuv(steinpt, 0, newuv[0]); - setpointgeomuv(steinpt, 1, newuv[1]); - setpointgeomtag(steinpt, fid); - } else { - for (i = 0; i < 3; i++) { - steinpt[i] = (pa[i] + pb[i] + pc[i]) / 3.0; - } + // Create a new point. + makepoint(&steinpt, FREEFACETVERTEX); + for (i = 0; i < 3; i++) { + steinpt[i] = 0.5 * (pa[i] + pb[i]); } - // Start searching it from 'recentet'. - searchtet = recenttet; - // Now insert the point p. The flags are chosen as follows: - // - boywat = 2, the current T is a CDT, - // - lawson = 2, do flip after inserting p, some existing segments - // and subfaces may be flipped, they are queued and - // and will be recovered. - // - rejflag = 1, reject p if it encroaches upon at least one segment, - // queue encroached segments. - ivf.iloc = (int) OUTSIDE; - ivf.bowywat = 2; - ivf.lawson = 2; - ivf.rejflag = 1; - ivf.chkencflag = 0; - ivf.sloc = (int) ONFACE; - ivf.sbowywat = 2; - ivf.splitbdflag = 0; - ivf.validflag = 1; - ivf.respectbdflag = 0; + ivf.bowywat = 1; // Use the Bowyer-Watson algorrithm. + ivf.cdtflag = 1; // Only create the initial cavity. + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; ivf.assignmeshsize = b->metric; - loc = insertvertex(steinpt, &searchtet, &splitsh, NULL, &ivf); - assert((loc != OUTSIDE) && (loc != ONVERTEX)); - if (loc == NEARVERTEX) { - // The new point is either ON or VERY CLOSE to an existing point. - pa = point2ppt(steinpt); - printf(" !! Avoid to create a short edge (length = %g)\n", - distance(steinpt, pa)); - // Indicate it may be an input problem. - printf(" Short edge length bound is: %g. Tolerance is %g.\n", - b->minedgelength, b->epsilon); - terminatetetgen(4); - } + point2tetorg(pa, searchtet); // Start location from it. + ivf.iloc = (int) OUTSIDE; - if (loc == ENCSEGMENT) { - // Some segments are encroached and queued. - assert(encseglist->objects > 0l); - // Randomly pick one encroached segment to split. - s = randomnation(encseglist->objects); - paryseg = (face *) fastlookup(encseglist, s); - sseg = *paryseg; - // The new point p is the midpoint of this segment. - getsteinerptonsegment(&sseg, NULL, steinpt); - setpointtype(steinpt, FREESEGVERTEX); - encseglist->restart(); // Clear the queue. + ivf.rejflag = 1; // Reject it if it encroaches upon any segment. + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, NULL, &ivf, cavpoints, + cavfaces, cavshells, newtets, crosstets, misfaces)) { + if (ivf.iloc == (int) ENCSEGMENT) { + pointdealloc(steinpt); + // Split an encroached segment. + assert(encseglist->objects > 0); + i = randomnation(encseglist->objects); + paryseg = (face *) fastlookup(encseglist, i); + splitseg = *paryseg; + encseglist->restart(); - // Start searching from an adjacent tetrahedron (containing the segment). - sstpivot1(sseg, searchtet); - spivot(sseg, splitsh); - // Insert the point p. The flags are chosen as follows: - // - boywat = 2, the current T is a CDT, - // - lawson = 2, do flip after inserting p, some existing segments - // and subfaces may be flipped, they are queued and - // and will be recovered. - // - rejflag = 0, always insert p, even it will cause some segments - // or subfaces missing, queue missing boundaries. - ivf.iloc = (int) ONEDGE; - ivf.bowywat = 2; - ivf.lawson = 2; - ivf.rejflag = 0; - ivf.chkencflag = 0; - ivf.sloc = (int) ONEDGE; - ivf.sbowywat = 2; - ivf.splitbdflag = 0; - ivf.validflag = 1; - ivf.respectbdflag = 0; - ivf.assignmeshsize = b->metric; - loc = insertvertex(steinpt, &searchtet, &splitsh, &sseg, &ivf); - - if (loc == NEARVERTEX) { - // The new point is either ON or VERY CLOSE to an existing point. - pa = point2ppt(steinpt); - printf(" !! Avoid to create a short edge (length = %g)\n", - distance(steinpt, pa)); - // Indicate it may be an input problem. - printf(" Short edge length bound is: %g. Tolerance is %g.\n", - b->minedgelength, b->epsilon); - terminatetetgen(4); + // Split the segment. + pa = sorg(splitseg); + pb = sdest(splitseg); + // Create a new point. + makepoint(&steinpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinpt[i] = 0.5 * (pa[i] + pb[i]); + } + point2tetorg(pa, searchtet); + ivf.iloc = (int) OUTSIDE; + ivf.rejflag = 0; + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, + cavpoints, cavfaces, cavshells, newtets, + crosstets, misfaces)) { + assert(0); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + assert(0); } - - st_segref_count++; } else { st_facref_count++; - } - if (steinerleft > 0) steinerleft--; - - // Do flip to recover Delaunayniess. - lawsonflip3d(steinpt, 2, 0, 0, 0); - - // Some vertices may be queued, recover them. - if (subvertstack->objects > 0l) { - assert(0); //delaunizevertices(); + if (steinerleft > 0) steinerleft--; } - // Some subsegments may be queued, recover them. - if (subsegstack->objects > 0l) { - delaunizesegments(); + while (subsegstack->objects > 0l) { + // seglist is used as a stack. + subsegstack->objects--; + paryseg = (face *) fastlookup(subsegstack, subsegstack->objects); + splitseg = *paryseg; + + // Check if this segment has been recovered. + sstpivot1(splitseg, searchtet); + if (searchtet.tet != NULL) continue; + + // Search the segment. + dir = scoutsegment(sorg(splitseg), sdest(splitseg), &searchtet, &refpt, + NULL); + if (dir == SHAREEDGE) { + // Found this segment, insert it. + if (!issubseg(searchtet)) { + // Let the segment remember an adjacent tet. + sstbond1(splitseg, searchtet); + // Bond the segment to all tets containing it. + spintet = searchtet; + do { + tssbond1(spintet, splitseg); + fnextself(spintet); + } while (spintet.tet != searchtet.tet); + } else { + // Collision! Should not happen. + assert(0); + } + } else { + if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { + // Split the segment. + // Create a new point. + makepoint(&steinpt, FREESEGVERTEX); + //setpointtype(newpt, FREESEGVERTEX); + getsteinerptonsegment(&splitseg, refpt, steinpt); + ivf.iloc = (int) OUTSIDE; + ivf.rejflag = 0; + if (!insertpoint_cdt(steinpt, &searchtet, &splitsh, &splitseg, &ivf, + cavpoints, cavfaces, cavshells, newtets, + crosstets, misfaces)) { + assert(0); + } + st_segref_count++; + if (steinerleft > 0) steinerleft--; + } else { + // Maybe a PLC problem. + assert(0); + } + } + } // while + + if (b->verbose > 2) { + printf(" Added %ld Steiner points.\n", points->items - baknum); } } /////////////////////////////////////////////////////////////////////////////// // // -// constrainedfacets() Recover subfaces saved in 'subfacestack'. // +// constrainedfacets() Recover constrained facets in a CDT. // +// // +// All unrecovered subfaces are queued in 'subfacestack'. // // // /////////////////////////////////////////////////////////////////////////////// @@ -19220,16 +17906,14 @@ void tetgenmesh::constrainedfacets() arraypool *tg_topshells, *tg_botshells, *tg_facfaces; arraypool *tg_toppoints, *tg_botpoints; arraypool *tg_missingshs, *tg_missingshbds, *tg_missingshverts; - - triface searchtet, neightet; - face searchsh, neighsh, *parysh; - face checkseg, *paryseg; - point refpt, *parypt; + triface searchtet, neightet, crossedge; + face searchsh, *parysh, *parysh1; + face *paryseg; + point *parypt; enum interresult dir; - bool success; int facetcount; - //int bakhullsize; - int crossflag; + int success; + int t1ver; int i, j; // Initialize arrays. @@ -19247,245 +17931,184 @@ void tetgenmesh::constrainedfacets() tg_missingshs = new arraypool(sizeof(face), 10); tg_missingshbds = new arraypool(sizeof(face), 10); tg_missingshverts = new arraypool(sizeof(point), 8); - // This is a global array used by refineregion(). - encseglist = new arraypool(sizeof(face), 4); + encseglist = new arraypool(sizeof(face), 4); facetcount = 0; - // Loop until 'subfacstack' is empty. while (subfacstack->objects > 0l) { + subfacstack->objects--; parysh = (face *) fastlookup(subfacstack, subfacstack->objects); searchsh = *parysh; - if (searchsh.sh[3] == NULL) continue; // Skip a dead subface. + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. - stpivot(searchsh, neightet); - if (neightet.tet == NULL) { - // Find an unrecovered subface. - smarktest(searchsh); - tg_facfaces->newindex((void **) &parysh); - *parysh = searchsh; - // Collect all non-recovered subfaces of the same facet. - for (i = 0; i < tg_facfaces->objects; i++) { - searchsh = * (face *) fastlookup(tg_facfaces, i); - for (j = 0; j < 3; j++) { - sspivot(searchsh, checkseg); - if (checkseg.sh == NULL) { - spivot(searchsh, neighsh); - assert(neighsh.sh != NULL); // SELF_CHECK - if (!smarktested(neighsh)) { - // It may be already recovered. - stpivot(neighsh, neightet); - if (neightet.tet == NULL) { - smarktest(neighsh); - tg_facfaces->newindex((void **) &parysh); - *parysh = neighsh; - } + // Collect all unrecovered subfaces which are co-facet. + smarktest(searchsh); + tg_facfaces->newindex((void **) &parysh); + *parysh = searchsh; + for (i = 0; i < tg_facfaces->objects; i++) { + parysh = (face *) fastlookup(tg_facfaces, i); + for (j = 0; j < 3; j++) { + if (!isshsubseg(*parysh)) { + spivot(*parysh, searchsh); + assert(searchsh.sh != NULL); // SELF_CHECK + if (!smarktested(searchsh)) { + if (!isshtet(searchsh)) { + smarktest(searchsh); + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = searchsh; } } - senextself(searchsh); - } // j - } // i - // Have found all facet subfaces (vertices). Uninfect them. - for (i = 0; i < tg_facfaces->objects; i++) { - parysh = (face *) fastlookup(tg_facfaces, i); - sunmarktest(*parysh); - } + } + senextself(*parysh); + } // j + } // i + // Have found all facet subfaces. Unmark them. + for (i = 0; i < tg_facfaces->objects; i++) { + parysh = (face *) fastlookup(tg_facfaces, i); + sunmarktest(*parysh); + } - if (b->verbose > 2) { - printf(" Recover facet #%d: %ld subfaces.\n", facetcount + 1, - tg_facfaces->objects); - } - facetcount++; + if (b->verbose > 2) { + printf(" Recovering facet #%d: %ld subfaces.\n", facetcount + 1, + tg_facfaces->objects); + } + facetcount++; - // Loop until 'tg_facfaces' is empty. - while (tg_facfaces->objects > 0l) { - // Get the last subface of this array. - tg_facfaces->objects--; - parysh = (face *) fastlookup(tg_facfaces, tg_facfaces->objects); - searchsh = *parysh; + while (tg_facfaces->objects > 0l) { - if (searchsh.sh[3] == NULL) continue; // Skip a dead subface. + tg_facfaces->objects--; + parysh = (face *) fastlookup(tg_facfaces, tg_facfaces->objects); + searchsh = *parysh; - stpivot(searchsh, neightet); - if (neightet.tet != NULL) continue; // Not a missing subface. + if (searchsh.sh[3] == NULL) continue; // It is dead. + if (isshtet(searchsh)) continue; // It is recovered. - // Insert the subface. - searchtet.tet = NULL; - dir = scoutsubface(&searchsh, &searchtet); - if (dir == SHAREFACE) continue; // The subface is inserted. - if (dir == COLLISIONFACE) continue; // The subface is removed. + searchtet.tet = NULL; + dir = scoutsubface(&searchsh, &searchtet); + if (dir == SHAREFACE) continue; // The subface is inserted. - // The subface is missing. Form the missing region. - // Re-use 'tg_crosstets' for 'adjtets'. - formmissingregion(&searchsh, tg_missingshs, tg_missingshbds, - tg_missingshverts, tg_crosstets); + // The subface is missing. Form the missing region. + // Re-use 'tg_crosstets' for 'adjtets'. + formregion(&searchsh, tg_missingshs, tg_missingshbds, tg_missingshverts); - // Search for a crossing edge (tg_crosstets is cleared). - crossflag = scoutcrossedge(searchtet, tg_crosstets, tg_missingshs); - - if (crossflag == 1) { - // Recover subfaces by local retetrahedralization. - // Form a cavity of crossing tets. - if (formcavity(&searchtet, tg_missingshs, tg_crosstets, tg_topfaces, - tg_botfaces, tg_toppoints, tg_botpoints)) { - if (!b->flipinsert) { - // Tetrahedralize the top part. Re-use 'tg_midfaces'. - delaunizecavity(tg_toppoints, tg_topfaces, tg_topshells, - tg_topnewtets, tg_crosstets, tg_midfaces); - // Tetrahedralize the bottom part. Re-use 'tg_midfaces'. - delaunizecavity(tg_botpoints, tg_botfaces, tg_botshells, - tg_botnewtets, tg_crosstets, tg_midfaces); - // Fill the cavity with new tets. - success = fillcavity(tg_topshells, tg_botshells, tg_midfaces, - tg_missingshs); - if (success) { - // Cavity is remeshed. Delete old tets and outer new tets. - carvecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); - // Insert the missing region into cavity. - j = 0; // FOR DEBUG! Count the number of non-recovered faces. - for (i = 0; i < tg_missingshs->objects; i++) { - searchsh = * (face *) fastlookup(tg_missingshs, i); - searchtet.tet = NULL; - dir = scoutsubface(&searchsh, &searchtet); - assert(dir != COLLISIONFACE); // SELF_CHECK - if (dir != SHAREFACE) { - // A subface is missing. This is possible that the subface - // is not actually a constrained Delaunay face in T. - // Add this face at the end of the list, so it will be - // processed immediately. This is necessary because we - // have created some non-locally Delaunay face (by the - // remesh of the cavity). We have to insert the subfaces - // to make these face constrained Delaunay. - tg_facfaces->newindex((void **) &parysh); - *parysh = searchsh; - j++; // FOR DEBUG! - } - } // i - // Recover interior subfaces. - for (i = 0; i < caveencshlist->objects; i++) { - searchsh = * (face *) fastlookup(caveencshlist, i); - searchtet.tet = NULL; - dir = scoutsubface(&searchsh, &searchtet); - assert(dir != COLLISIONFACE); // SELF_CHECK - if (dir != SHAREFACE) { - // The subface is missing. This is possible that the subface - // is removed by the enlargement of the cavity. It has to - // be recovered. - // Add this face at the end of the list, so it will be - // processed immediately. We have to insert the subfaces - // to make these face constrained Delaunay. - tg_facfaces->newindex((void **) &parysh); - *parysh = searchsh; - j++; // FOR DEBUG! - } - } // i - // Recover interior segments. This should always be recovered. - for (i = 0; i < caveencseglist->objects; i++) { - paryseg = (face *) fastlookup(caveencseglist, i); - searchtet.tet = NULL; - refpt = NULL; - dir = scoutsegment(sorg(*paryseg),sdest(*paryseg),&searchtet, - &refpt, NULL); - assert(dir == SHAREEDGE); - // Insert this segment. - tsspivot1(searchtet, checkseg); // SELF_CHECK - if (checkseg.sh == NULL) { - // Let the segment remember an adjacent tet. - sstbond1(*paryseg, searchtet); - // Bond the segment to all tets containing it. - neightet = searchtet; - do { - tssbond1(neightet, *paryseg); - fnextself(neightet); - } while (neightet.tet != searchtet.tet); - } else { - // Collision! Should not happen. - assert(0); - } - } // i - caveencshlist->restart(); - caveencseglist->restart(); - } else { - // Restore old tets and delete new tets. - restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); - // Set a handle for searching subface. - //recentsh = searchsh; - } + if (scoutcrossedge(searchtet, tg_missingshbds, tg_missingshs)) { + // Save this crossing edge, will be used by fillcavity(). + crossedge = searchtet; + // Form a cavity of crossing tets. + success = formcavity(&searchtet, tg_missingshs, tg_crosstets, + tg_topfaces, tg_botfaces, tg_toppoints, + tg_botpoints); + if (success) { + if (!b->flipinsert) { + // Tetrahedralize the top part. Re-use 'tg_midfaces'. + delaunizecavity(tg_toppoints, tg_topfaces, tg_topshells, + tg_topnewtets, tg_crosstets, tg_midfaces); + // Tetrahedralize the bottom part. Re-use 'tg_midfaces'. + delaunizecavity(tg_botpoints, tg_botfaces, tg_botshells, + tg_botnewtets, tg_crosstets, tg_midfaces); + // Fill the cavity with new tets. + success = fillcavity(tg_topshells, tg_botshells, tg_midfaces, + tg_missingshs, tg_topnewtets, tg_botnewtets, + &crossedge); + if (success) { + // Cavity is remeshed. Delete old tets and outer new tets. + carvecavity(tg_crosstets, tg_topnewtets, tg_botnewtets); } else { - // Use the flip algorithm of Shewchuk to recover the subfaces. - flipinsertfacet(tg_crosstets, tg_toppoints, tg_botpoints, - tg_missingshverts); - // Check the missing subfaces again. - j = 0; // FOR DEBUG! Count the number of non-recovered faces. - for (i = 0; i < tg_missingshs->objects; i++) { - searchsh = * (face *) fastlookup(tg_missingshs, i); - searchtet.tet = NULL; - dir = scoutsubface(&searchsh, &searchtet); - assert(dir != COLLISIONFACE); // SELF_CHECK - if (dir != SHAREFACE) { - // A subface is missing. This is possible that the subface - // is not actually a constrained Delaunay face in T. - // Add this face at the end of the list, so it will be - // processed immediately. This is necessary because we - // have created some non-locally Delaunay face (by the - // remesh of the cavity). We have to insert the subfaces - // to make these face constrained Delaunay. - tg_facfaces->newindex((void **) &parysh); - *parysh = searchsh; - j++; // FOR DEBUG! - } - } // i - // Clear working lists. - tg_crosstets->restart(); - tg_topfaces->restart(); - tg_botfaces->restart(); - tg_toppoints->restart(); - tg_botpoints->restart(); - success = true; - } // if (b->flipinsert) - } else { - // Formcavity failed. - success = false; - } - } else { //if (crossflag == 0) { - // Recover subfaces by retriangulate the surface mesh. - // Re-use tg_topshells for newshs. - success = fillregion(tg_missingshs, tg_missingshbds, tg_topshells); - if (success) { - // Region is remeshed. Delete old subfaces (in tg_missingshs). - for (i = 0; i < tg_missingshs->objects; i++) { - parysh = (face *) fastlookup(tg_missingshs, i); - shellfacedealloc(subfaces, parysh->sh); + restorecavity(tg_crosstets, tg_topnewtets, tg_botnewtets, + tg_missingshbds); } - tg_topshells->restart(); } else { - // Search a handle for searching tetrahedron. - recenttet = searchtet; - } - } + // Use the flip algorithm of Shewchuk to recover the subfaces. + flipinsertfacet(tg_crosstets, tg_toppoints, tg_botpoints, + tg_missingshverts); + // Recover the missing region. + success = fillregion(tg_missingshs, tg_missingshbds, tg_topshells); + assert(success); + // Clear working lists. + tg_crosstets->restart(); + tg_topfaces->restart(); + tg_botfaces->restart(); + tg_toppoints->restart(); + tg_botpoints->restart(); + } // b->flipinsert - // Unmarktest all points of the missing region. - for (i = 0; i < tg_missingshverts->objects; i++) { - parypt = (point *) fastlookup(tg_missingshverts, i); - punmarktest(*parypt); - } - tg_missingshverts->restart(); - tg_missingshbds->restart(); - tg_missingshs->restart(); + if (success) { + // Recover interior subfaces. + for (i = 0; i < caveencshlist->objects; i++) { + parysh = (face *) fastlookup(caveencshlist, i); + dir = scoutsubface(parysh, &searchtet); + if (dir != SHAREFACE) { + // Add this face at the end of the list, so it will be + // processed immediately. + tg_facfaces->newindex((void **) &parysh1); + *parysh1 = *parysh; + } + } + caveencshlist->restart(); + // Recover interior segments. This should always be recovered. + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + dir = scoutsegment(sorg(*paryseg),sdest(*paryseg),&searchtet, + NULL, NULL); + assert(dir == SHAREEDGE); + // Insert this segment. + if (!issubseg(searchtet)) { + // Let the segment remember an adjacent tet. + sstbond1(*paryseg, searchtet); + // Bond the segment to all tets containing it. + neightet = searchtet; + do { + tssbond1(neightet, *paryseg); + fnextself(neightet); + } while (neightet.tet != searchtet.tet); + } else { + // Collision! Should not happen. + assert(0); + } + } + caveencseglist->restart(); + } // success - remesh cavity + } // success - form cavity + } else { + // Recover subfaces by retriangulate the surface mesh. + // Re-use tg_topshells for newshs. + success = fillregion(tg_missingshs, tg_missingshbds, tg_topshells); + } - if (!success) { - // The missing region can not be recovered. Refine it. - refineregion(); - // Clean the current list of facet subfaces. - //tg_facfaces->restart(); - } - } // while (tg_facfaces->objects > 0l) + // Unmarktest all points of the missing region. + for (i = 0; i < tg_missingshverts->objects; i++) { + parypt = (point *) fastlookup(tg_missingshverts, i); + punmarktest(*parypt); + } + tg_missingshverts->restart(); + tg_missingshbds->restart(); + tg_missingshs->restart(); - } // if (neightet.tet == NULL) - } // while (subfacstack->objects > 0l) + if (!success) { + // The missing region can not be recovered. Refine it. + refineregion(recentsh, tg_toppoints, tg_topfaces, tg_topshells, + tg_topnewtets, tg_crosstets, tg_midfaces); + // Clean the current list of facet subfaces. + // tg_facfaces->restart(); + } + } // while (tg_facfaces->objects) + + } // while ((subfacstack->objects) + + // Accumulate the dynamic memory. + totalworkmemory += (tg_crosstets->totalmemory + tg_topnewtets->totalmemory + + tg_botnewtets->totalmemory + tg_topfaces->totalmemory + + tg_botfaces->totalmemory + tg_midfaces->totalmemory + + tg_toppoints->totalmemory + tg_botpoints->totalmemory + + tg_facfaces->totalmemory + tg_topshells->totalmemory + + tg_botshells->totalmemory + tg_missingshs->totalmemory + + tg_missingshbds->totalmemory + + tg_missingshverts->totalmemory + + encseglist->totalmemory); // Delete arrays. delete tg_crosstets; @@ -19520,13 +18143,13 @@ void tetgenmesh::constraineddelaunay(clock_t& tv) // Statistics. long bakfillregioncount; long bakcavitycount, bakcavityexpcount; + long bakseg_ref_count; if (!b->quiet) { printf("Constrained Delaunay...\n"); } - // Identify acute vertex for PLC inputs. - markacutevertices(); + makesegmentendpointsmap(); if (b->verbose) { printf(" Delaunizing segments.\n"); @@ -19534,26 +18157,25 @@ void tetgenmesh::constraineddelaunay(clock_t& tv) checksubsegflag = 1; - // Put all segments into the list. - // In random order. - subsegs->traversalinit(); - for (i = 0; i < subsegs->items; i++) { - s = randomnation(i + 1); - // Move the s-th seg to the i-th. - subsegstack->newindex((void **) &paryseg); - *paryseg = * (face *) fastlookup(subsegstack, s); - // Put i-th seg to be the s-th. - searchseg.sh = shellfacetraverse(subsegs); - //sinfect(searchseg); // Only save it once. - paryseg = (face *) fastlookup(subsegstack, s); - *paryseg = searchseg; - } + // Put all segments into the list (in random order). + subsegs->traversalinit(); + for (i = 0; i < subsegs->items; i++) { + s = randomnation(i + 1); + // Move the s-th seg to the i-th. + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + // Put i-th seg to be the s-th. + searchseg.sh = shellfacetraverse(subsegs); + //sinfect(searchseg); // Only save it once. + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = searchseg; + } // Recover non-Delaunay segments. delaunizesegments(); if (b->verbose) { - printf(" %ld Steiner points.\n", st_segref_count); + printf(" Inserted %ld Steiner points.\n", st_segref_count); } tv = clock(); @@ -19562,17 +18184,13 @@ void tetgenmesh::constraineddelaunay(clock_t& tv) printf(" Constraining facets.\n"); } - if (b->flipinsert) { - // Clear the counters. - flip23count = flip32count = flip44count = 0l; - } - // Subfaces will be introduced. checksubfaceflag = 1; bakfillregioncount = fillregioncount; bakcavitycount = cavitycount; bakcavityexpcount = cavityexpcount; + bakseg_ref_count = st_segref_count; // Randomly order the subfaces. subfaces->traversalinit(); @@ -19601,10 +18219,10 @@ void tetgenmesh::constraineddelaunay(clock_t& tv) } printf(".\n"); } - if (st_segref_count + st_facref_count > 0) { + if (st_segref_count + st_facref_count - bakseg_ref_count > 0) { printf(" Inserted %ld (%ld, %ld) refine points.\n", - st_segref_count + st_facref_count, st_segref_count, - st_facref_count); + st_segref_count + st_facref_count - bakseg_ref_count, + st_segref_count - bakseg_ref_count, st_facref_count); } } } @@ -19635,31 +18253,23 @@ int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, int level, int edgepivot, flipconstraints* fc) { - int rejflag; - int i; - point tmppts[3]; - REAL normal[3], area, len; - REAL ori1, ori2; - REAL abovept[3]; - enum interresult dir; int types[2], poss[4]; int intflag; - - rejflag = 0; + int rejflag = 0; + int i; if (fc->seg[0] != NULL) { // A constraining edge is given (e.g., for edge recovery). if (fliptype == 1) { // A 2-to-3 flip: [a,b,c] => [e,d,a], [e,d,b], [e,d,c]. - if (pc != dummypoint) { - // Do not flip if the newly created faces intersect this edge in - // their interiors. - tmppts[0] = pa; - tmppts[1] = pb; - tmppts[2] = pc; - for (i = 0; i < 3 && !rejflag; i++) { + tmppts[0] = pa; + tmppts[1] = pb; + tmppts[2] = pc; + for (i = 0; i < 3 && !rejflag; i++) { + if (tmppts[i] != dummypoint) { + // Test if the face [e,d,#] intersects the edge. intflag = tri_edge_test(pe, pd, tmppts[i], fc->seg[0], fc->seg[1], NULL, 1, types, poss); if (intflag == 2) { @@ -19685,134 +18295,31 @@ int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, rejflag = 1; } } - } // if (intflag == 4) - } // i - } else { // pc == dummypoint - // Do not flip if the new hull edge [e,d] will intersect this edge - // in its interior. - // Comment: Here we actually need a 3D edge-edge test. - // We only do test if the edge in 'fc' is coplanar with the plane - // containing a,b,e,and d. - // Choose a better triangle [a,b,e] or [a,b,d]. - facenormal(pa, pb, pe, normal, 1, &len); - area = sqrt(DOT(normal, normal)); - facenormal(pa, pb, pd, normal, 1, &len); - len = sqrt(DOT(normal, normal)); // Re-use len as area. - if (area > len) { - // Choose [a,b,e] - ori1 = orient3d(pa, pb, pe, fc->seg[0]); - ori2 = orient3d(pa, pb, pe, fc->seg[1]); - } else { - // Choose [a,b,d] - ori1 = orient3d(pa, pb, pd, fc->seg[0]); - ori2 = orient3d(pa, pb, pd, fc->seg[1]); - } - if ((ori1 == 0) && (ori2 == 0)) { - calculateabovepoint4(pa, pb, pe, pd); - for (i = 0; i < 3; i++) { - abovept[i] = dummypoint[i]; } - intflag = tri_edge_test(pe, pd, abovept, fc->seg[0], fc->seg[1], - NULL, 1, types, poss); - if (intflag == 2) { - dir = (enum interresult) types[0]; - assert(dir != ACROSSFACE); - if (dir == ACROSSEDGE) { - if (poss[0] == 0) { - // The interior of [e,d] intersect the segment. - // Since [e,d] is the newly created edge. Reject this flip. - rejflag = 1; - } - } - } else if (intflag == 4) { - // [e,d,abovept] is coplanar with the constraining edge 'fc'. - // This is poissible if the edge in 'fc' is just the edge [e,d] - // (SHAREEDGE) or they share a common vertex (SHAREVEER) - dir = (enum interresult) types[0]; - if (dir == ACROSSEDGE) { - // This case can only happen if [e,d] is coplanar with 'fc'. - assert(0); // Not possible. - } - } - } - } // if (pc == dummypoint) + } // if (tmppts[0] != dummypoint) + } // i } else if (fliptype == 2) { // A 3-to-2 flip: [e,d,a], [e,d,b], [e,d,c] => [a,b,c] if (pc != dummypoint) { - if (!rejflag) { - // Check if the new face [a,b,c] intersect the edge in its interior. - intflag = tri_edge_test(pa, pb, pc, fc->seg[0], fc->seg[1], NULL, - 1, types, poss); - if (intflag == 2) { - // They intersect at a single point. - dir = (enum interresult) types[0]; - if (dir == ACROSSFACE) { - // The interior of [a,b,c] intersect the segment. - rejflag = 1; // Do not flip. - } else if (dir == ACROSSEDGE) { - // This case is possible since we allow a previous 2-to-3 flip - // even it will create a degenerate tet at edge [a,b]. - } - } else if (intflag == 4) { - // [a,b,c] is coplanar with the edge. - dir = (enum interresult) types[0]; - if (dir == ACROSSEDGE) { - // The boundary of [a,b,c] intersect the segment. - // An example is found in case 'camila.poly', during the recovery - // of segment [151, 161] (at linklevel = 2). See: 2011-06-10-a. - rejflag = 1; // Do not flip. - } + // Check if the new face [a,b,c] intersect the edge in its interior. + intflag = tri_edge_test(pa, pb, pc, fc->seg[0], fc->seg[1], NULL, + 1, types, poss); + if (intflag == 2) { + // They intersect at a single point. + dir = (enum interresult) types[0]; + if (dir == ACROSSFACE) { + // The interior of [a,b,c] intersect the segment. + rejflag = 1; // Do not flip. + } + } else if (intflag == 4) { + // [a,b,c] is coplanar with the edge. + dir = (enum interresult) types[0]; + if (dir == ACROSSEDGE) { + // The boundary of [a,b,c] intersect the segment. + rejflag = 1; // Do not flip. } - } // if (!relflag) - } else { // pc == dummypoint - // The flip 3-to-2 will replace [e,d] with a new hull edge [a,b]. - // Only do flip if [a,b] does not intersect the edge of 'fc'. - // Comment: Here we acutually need a 3D edge-edge intersection test. - // We only do test if the edge in 'fc' is coplanar with the plane - // containing a,b,e, and d. - // Choose a better triangle [a,b,e] or [a,b,d]. - facenormal(pa, pb, pe, normal, 1, &len); - area = sqrt(DOT(normal, normal)); - facenormal(pa, pb, pd, normal, 1, &len); - len = sqrt(DOT(normal, normal)); // Re-use len as area. - if (area > len) { - // Choose [a,b,e] - ori1 = orient3d(pa, pb, pe, fc->seg[0]); - ori2 = orient3d(pa, pb, pe, fc->seg[1]); - } else { - // Choose [a,b,d] - ori1 = orient3d(pa, pb, pd, fc->seg[0]); - ori2 = orient3d(pa, pb, pd, fc->seg[1]); } - if ((ori1 == 0) && (ori2 == 0)) { - // The edge in 'fc' is coplanar with the plane containing [a,b,e,d]. - calculateabovepoint4(pa, pb, pe, pd); - for (i = 0; i < 3; i++) { - abovept[i] = dummypoint[i]; - } - intflag = tri_edge_test(pa, pb, abovept, fc->seg[0], fc->seg[1], - NULL, 1, types, poss); - if (intflag == 2) { - dir = (enum interresult) types[0]; - assert(dir != ACROSSFACE); - if (dir == ACROSSEDGE) { - assert(0); // Check this case. - rejflag = 1; // Do not flip. - } - } else if (intflag == 4) { - // The edge 'fc' is coplanar with [a,b,abovept]. - // This is poissible if the edge in 'fc' is just the edge [a,b] - // (SHAREEDGE) or they share a common vertex (SHAREVEER) - dir = (enum interresult) types[0]; - if (dir == ACROSSEDGE) { - // This case can only happen if [a,b] is coplanar with 'fc'. - assert(0); // Not possible. - } - } - } // if (ori1 == 0 && ori2 == 0) - } - } else { - assert(0); // An unknown flip type. + } // if (pc != dummypoint) } } // if (fc->seg[0] != NULL) @@ -19974,7 +18481,7 @@ int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, // 'flipedge' is a non-convex or flat edge [a,b,#,#] to be removed. // // // // The return value is a positive integer, it indicates whether the edge is // -// removed or not. A value "2" means the edge is removed, othereise, the // +// removed or not. A value "2" means the edge is removed, otherwise, the // // edge is not removed and the value (must >= 3) is the current number of // // tets in the edge star. // // // @@ -19983,27 +18490,16 @@ int tetgenmesh::checkflipeligibility(int fliptype, point pa, point pb, int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) { triface *abtets, spintet; - face checkseg, *paryseg; + int t1ver; int n, nn, i; - if (b->verbose > 2) { - printf(" Removing edge (%d, %d)\n", pointmark(org(*flipedge)), - pointmark(dest(*flipedge))); - } - - fc->clearcounters(); - if (checksubsegflag) { // Do not flip a segment. - tsspivot1(*flipedge, checkseg); - if (checkseg.sh != NULL) { - if (b->verbose > 2) { - printf(" Can't flip a segment (%d, %d).\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - fc->encsegcount++; + if (issubseg(*flipedge)) { if (fc->collectencsegflag) { + face checkseg, *paryseg; + tsspivot1(*flipedge, checkseg); if (!sinfected(checkseg)) { // Queue this segment in list. sinfect(checkseg); @@ -20017,33 +18513,16 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) // Count the number of tets at edge [a,b]. n = 0; - int counter = 0; // Sum of star counters. // SELF_CHECK. spintet = *flipedge; - i = 0; while (1) { - counter += elemcounter(spintet); - i++; + n++; fnextself(spintet); if (spintet.tet == flipedge->tet) break; } - //assert(i >= 3); - if (i < 3) { - // It is only possible when the mesh contains inverted tetrahedra. - assert(checkinverttetflag); - // Since "return 2" means success, we return 0. - return 0; - } - assert(counter == 0); // SELF_CHECK - n = i; + assert(n >= 3); - flipstarcount++; - // Record the maximum star size. - if (n > maxflipstarsize) { - maxflipstarsize = n; - } if ((b->flipstarsize > 0) && (n > b->flipstarsize)) { // The star size exceeds the limit. - skpflipstarcount++; return 0; // Do not flip it. } @@ -20054,7 +18533,7 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) i = 0; while (1) { abtets[i] = spintet; - setelemcounter(abtets[i], 1); // Marktest it (in Star(ab)). + setelemcounter(abtets[i], 1); i++; fnextself(spintet); if (spintet.tet == flipedge->tet) break; @@ -20065,18 +18544,9 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) nn = flipnm(abtets, n, 0, 0, fc); - if (nn == 2) { - // Edge is flipped. - if (b->verbose > 2) { - printf(" Edge is removed.\n"); - } - } else { - if (b->verbose > 2) { - printf(" Edge is not removed. n(%d), nn(%d).\n", n, nn); - } + if (nn > 2) { // Edge is not flipped. Unmarktest the remaining tets in Star(ab). for (i = 0; i < nn; i++) { - assert(elemcounter(abtets[i]) == 1); setelemcounter(abtets[i], 0); } // Restore the input edge (needed by Lawson's flip). @@ -20087,20 +18557,20 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) // NOTE: fc->unflip must be 0. int bakunflip = fc->unflip; fc->unflip = 0; - flipnm_post(abtets, n, nn, 0, fc); - fc->unflip = bakunflip; delete [] abtets; - return nn; //return nn == 2; + return nn; } /////////////////////////////////////////////////////////////////////////////// // // // removefacebyflips() Remove a face by flips. // // // +// Return 1 if the face is removed. Otherwise, return 0. // +// // // ASSUMPTIONS: // // - 'flipface' must not be a hull face. // // // @@ -20108,22 +18578,17 @@ int tetgenmesh::removeedgebyflips(triface *flipedge, flipconstraints* fc) int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) { - triface fliptets[3], flipedge; - face checksh; - point pa, pb, pc, pd, pe; - REAL ori; - int reducflag, rejflag; - if (checksubfaceflag) { - tspivot(*flipface, checksh); - if (checksh.sh != NULL) { - if (b->verbose > 2) { - printf(" Can't flip a subface.\n"); - } + if (issubface(*flipface)) { return 0; } } + triface fliptets[3], flipedge; + point pa, pb, pc, pd, pe; + REAL ori; + int reducflag = 0; + fliptets[0] = *flipface; fsym(*flipface, fliptets[1]); pa = org(fliptets[0]); @@ -20132,13 +18597,6 @@ int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) pd = oppo(fliptets[0]); pe = oppo(fliptets[1]); - if (b->verbose > 2) { - printf(" Removing face (%d, %d, %d) -- %d, %d\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd), pointmark(pe)); - } - - reducflag = 0; - ori = orient3d(pa, pb, pd, pe); if (ori > 0) { ori = orient3d(pb, pc, pd, pe); @@ -20159,23 +18617,11 @@ int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) if (reducflag) { // A 2-to-3 flip is found. - rejflag = 0; - if (fc != NULL) { - //rejflag = checkflipeligibility(1, pa, pb, pc, pd, pe, fc); - } - if (!rejflag) { - flip23(fliptets, 0, 0, 0); - if (b->verbose > 2) { - printf(" Face is removed by a 2-to-3 flip.\n"); - } - return 1; - } + flip23(fliptets, 0, fc); + return 1; } else { // Try to flip the selected edge of this face. if (removeedgebyflips(&flipedge, fc) == 2) { - if (b->verbose > 2) { - printf(" Face is removed by removing an edge.\n"); - } return 1; } } @@ -20191,7 +18637,7 @@ int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) // If the edge is recovered, 'searchtet' returns a tet containing the edge. // // // // This edge may intersect a set of faces and edges in the mesh. All these // -// faces or edges are needed to be flipped. // +// faces or edges are needed to be removed. // // // // If the parameter 'fullsearch' is set, it tries to flip any face or edge // // that intersects the recovering edge. Otherwise, only the face or edge // @@ -20202,21 +18648,12 @@ int tetgenmesh::removefacebyflips(triface *flipface, flipconstraints* fc) int tetgenmesh::recoveredgebyflips(point startpt, point endpt, triface* searchtet, int fullsearch) { - triface neightet, spintet; - point pa, pb, pc, pd; - badface bakface; - enum interresult dir, dir1; flipconstraints fc; - int types[2], poss[4], pos = 0; - int success; - int i, j; + enum interresult dir; - if (b->verbose > 2) { - printf(" Recovering edge (%d, %d)\n", pointmark(startpt), - pointmark(endpt)); - } fc.seg[0] = startpt; fc.seg[1] = endpt; + fc.checkflipeligibility = 1; // The mainloop of the edge reocvery. while (1) { // Loop I @@ -20228,8 +18665,7 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, if (dest(*searchtet) == endpt) { return 1; // Edge is recovered. } else { - // A PLC problem, or there is a Steiner point. - terminatetetgen(3); + terminatetetgen(this, 3); // // It may be a PLC problem. } } @@ -20248,15 +18684,21 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, continue; } } else { - terminatetetgen(3); //assert(0); // A PLC problem. + terminatetetgen(this, 3); // It may be a PLC problem. } // The edge is missing. if (fullsearch) { - // Try to flip one of the faces/edges which intersects the edge. - success = 0; + triface neightet, spintet; + point pa, pb, pc, pd; + badface bakface; + enum interresult dir1; + int types[2], poss[4], pos = 0; + int success = 0; + int t1ver; + int i, j; // Loop through the sequence of intersecting faces/edges from // 'startpt' to 'endpt'. @@ -20389,7 +18831,7 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, searchtet->tet = NULL; break; // Not find. } - } // while (1) + } // while (1) if (searchtet->tet != NULL) { if (oppo(*searchtet) != bakface.foppo) { fsymself(*searchtet); @@ -20404,7 +18846,7 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, searchtet->tet = NULL; // Not find. } if (searchtet->tet == NULL) { - success = 0; // This face/edge has been destroed. + success = 0; // This face/edge has been destroyed. break; // Loop I-I } } @@ -20422,7 +18864,6 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, } // while (1) // Loop I - // The edge is not recovered. return 0; } @@ -20438,11 +18879,6 @@ int tetgenmesh::recoveredgebyflips(point startpt, point endpt, // Such set of tets arises when we want to recover an edge from 'p0' to 'p_ // // (n-1)', and the number of tets at [a,b] can not be reduced by any flip. // // // -// The union of these tets is a polyhedron P. Obviously that P is a star- // -// shaped polyhedron. The midpoint of [a,b] is visible by all boundary faces // -// of P, push it slightly inside P does not change the visibilty. Indeed // -// every interior point of [a,b] is visible by the boundary faces of P. // -// // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, @@ -20450,55 +18886,46 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, { triface worktet, *parytet; triface faketet1, faketet2; - point pa, pb, pc, pd; - point p1, p2, p3; - point steinerpt; + point pc, pd, steinerpt; insertvertexflags ivf; optparameters opm; REAL vcd[3], sampt[3], smtpt[3]; REAL maxminvol = 0.0, minvol = 0.0, ori; int success, maxidx = 0; - int loc; int it, i; - if (b->verbose > 2) { - printf(" Find a Steiner in Schoenhardt polyhedron (n=%d).\n", n); - } - pa = org(abtets[0]); - pb = dest(abtets[0]); pc = apex(abtets[0]); // pc = p0 pd = oppo(abtets[n-1]); // pd = p_(n-1) + // Find an optimial point in edge [c,d]. It is visible by all outer faces // of 'abtets', and it maxmizes the min volume. // initialize the list of 2n boundary faces. for (i = 0; i < n; i++) { - eprev(abtets[i], worktet); - esymself(worktet); // [a,p_i,p_i+1]. + edestoppo(abtets[i], worktet); // [p_i,p_i+1,a] cavetetlist->newindex((void **) &parytet); *parytet = worktet; - enext(abtets[i], worktet); - esymself(worktet); // [p_i,b,p_i+1]. + eorgoppo(abtets[i], worktet); // [p_i+1,p_i,b] cavetetlist->newindex((void **) &parytet); *parytet = worktet; } + int N = 100; + REAL stepi = 0.01; + // Search the point along the edge [c,d]. for (i = 0; i < 3; i++) vcd[i] = pd[i] - pc[i]; - // Sample 100 points in edge [c,d]. - for (it = 1; it < 100; it++) { + // Sample N points in edge [c,d]. + for (it = 1; it < N; it++) { for (i = 0; i < 3; i++) { - sampt[i] = pc[i] + (0.01 * (double) it) * vcd[i]; + sampt[i] = pc[i] + (stepi * (double) it) * vcd[i]; } for (i = 0; i < cavetetlist->objects; i++) { parytet = (triface *) fastlookup(cavetetlist, i); - p1 = org(*parytet); - p2 = dest(*parytet); - p3 = apex(*parytet); - ori = orient3d(p2, p1, p3, sampt); + ori = orient3d(dest(*parytet), org(*parytet), apex(*parytet), sampt); if (i == 0) { minvol = ori; } else { @@ -20517,26 +18944,22 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, } // it if (maxminvol <= 0) { - if (b->verbose > 2) { - printf(" Unable to find a initial point: maxminvol = %g\n", - maxminvol); - } cavetetlist->restart(); return 0; } for (i = 0; i < 3; i++) { - smtpt[i] = pc[i] + (0.01 * (double) maxidx) * vcd[i]; + smtpt[i] = pc[i] + (stepi * (double) maxidx) * vcd[i]; } // Create two faked tets to hold the two non-existing boundary faces: // [d,c,a] and [c,d,b]. maketetrahedron(&faketet1); - setvertices(faketet1, pd, pc, pa, dummypoint); + setvertices(faketet1, pd, pc, org(abtets[0]), dummypoint); cavetetlist->newindex((void **) &parytet); *parytet = faketet1; maketetrahedron(&faketet2); - setvertices(faketet2, pc, pd, pb, dummypoint); + setvertices(faketet2, pc, pd, dest(abtets[0]), dummypoint); cavetetlist->newindex((void **) &parytet); *parytet = faketet2; @@ -20569,9 +18992,6 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, cavetetlist->restart(); if (!success) { - if (b->verbose > 2) { - printf(" Unable to relocate the initial point.\n"); - } return 0; } @@ -20588,28 +19008,23 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, } worktet = abtets[0]; // No need point location. ivf.iloc = (int) INSTAR; - ivf.bowywat = 0; // Do not use Bowyer-Watson algorithm. - ivf.lawson = 0; // Do not flip. - ivf.rejflag = 0; ivf.chkencflag = chkencflag; - ivf.sloc = 0; - ivf.sbowywat = 0; - ivf.splitbdflag = 0; - ivf.validflag = 0; - ivf.respectbdflag = 0; ivf.assignmeshsize = b->metric; + if (ivf.assignmeshsize) { + // Search the tet containing 'steinerpt' for size interpolation. + locate(steinerpt, &(abtets[0])); + worktet = abtets[0]; + } // Insert the new point into the tetrahedralization T. // Note that T is convex (nonconvex = 0). - loc = insertvertex(steinerpt, &worktet, NULL, NULL, &ivf); - - if (loc == (int) INSTAR) { + if (insertpoint(steinerpt, &worktet, NULL, NULL, &ivf)) { // The vertex has been inserted. st_volref_count++; if (steinerleft > 0) steinerleft--; return 1; } else { - // The Steiner point is too close to an existing vertex. Reject it. + // Not inserted. pointdealloc(steinerpt); return 0; } @@ -20617,7 +19032,181 @@ int tetgenmesh::add_steinerpt_in_schoenhardtpoly(triface *abtets, int n, /////////////////////////////////////////////////////////////////////////////// // // -// addsteiner4recoversegment() Add a Steiner point for recoveing a seg. // +// add_steinerpt_in_segment() Add a Steiner point inside a segment. // +// // +/////////////////////////////////////////////////////////////////////////////// + +int tetgenmesh::add_steinerpt_in_segment(face* misseg, int searchlevel) +{ + triface searchtet; + face *paryseg, candseg; + point startpt, endpt, pc, pd; + flipconstraints fc; + enum interresult dir; + REAL P[3], Q[3], tp, tq; + REAL len, smlen = 0, split = 0, split_q = 0; + int success; + int i; + + startpt = sorg(*misseg); + endpt = sdest(*misseg); + + fc.seg[0] = startpt; + fc.seg[1] = endpt; + fc.checkflipeligibility = 1; + fc.collectencsegflag = 1; + + point2tetorg(startpt, searchtet); + dir = finddirection(&searchtet, endpt); + //assert(dir != ACROSSVERT); + + // Try to flip the first intersecting face/edge. + enextesymself(searchtet); // Go to the opposite face. + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = searchlevel; + + if (dir == ACROSSFACE) { + // A face is intersected with the segment. Try to flip it. + success = removefacebyflips(&searchtet, &fc); + assert(success == 0); + } else if (dir == ACROSSEDGE) { + // An edge is intersected with the segment. Try to flip it. + success = removeedgebyflips(&searchtet, &fc); + assert(success != 2); + } else { + terminatetetgen(this, 3); // It may be a PLC problem. + } + + split = 0; + for (i = 0; i < caveencseglist->objects; i++) { + paryseg = (face *) fastlookup(caveencseglist, i); + suninfect(*paryseg); + // Calculate the shortest edge between the two lines. + pc = sorg(*paryseg); + pd = sdest(*paryseg); + tp = tq = 0; + if (linelineint(startpt, endpt, pc, pd, P, Q, &tp, &tq)) { + // Does the shortest edge lie between the two segments? + // Round tp and tq. + if ((tp > 0) && (tq < 1)) { + if (tp < 0.5) { + if (tp < (b->epsilon * 1e+3)) tp = 0.0; + } else { + if ((1.0 - tp) < (b->epsilon * 1e+3)) tp = 1.0; + } + } + if ((tp <= 0) || (tp >= 1)) continue; + if ((tq > 0) && (tq < 1)) { + if (tq < 0.5) { + if (tq < (b->epsilon * 1e+3)) tq = 0.0; + } else { + if ((1.0 - tq) < (b->epsilon * 1e+3)) tq = 1.0; + } + } + if ((tq <= 0) || (tq >= 1)) continue; + // It is a valid shortest edge. Calculate its length. + len = distance(P, Q); + if (split == 0) { + smlen = len; + split = tp; + split_q = tq; + candseg = *paryseg; + } else { + if (len < smlen) { + smlen = len; + split = tp; + split_q = tq; + candseg = *paryseg; + } + } + } + } + + caveencseglist->restart(); + b->fliplinklevel = bak_fliplinklevel; + + if (split == 0) { + // Found no crossing segment. + return 0; + } + + face splitsh; + face splitseg; + point steinerpt, *parypt; + insertvertexflags ivf; + + if (b->addsteiner_algo == 1) { + // Split the segment at the closest point to a near segment. + makepoint(&steinerpt, FREESEGVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = startpt[i] + split * (endpt[i] - startpt[i]); + } + } else { // b->addsteiner_algo == 2 + for (i = 0; i < 3; i++) { + P[i] = startpt[i] + split * (endpt[i] - startpt[i]); + } + pc = sorg(candseg); + pd = sdest(candseg); + for (i = 0; i < 3; i++) { + Q[i] = pc[i] + split_q * (pd[i] - pc[i]); + } + makepoint(&steinerpt, FREEVOLVERTEX); + for (i = 0; i < 3; i++) { + steinerpt[i] = 0.5 * (P[i] + Q[i]); + } + } + + // We need to locate the point. Start searching from 'searchtet'. + if (split < 0.5) { + point2tetorg(startpt, searchtet); + } else { + point2tetorg(endpt, searchtet); + } + if (b->addsteiner_algo == 1) { + splitseg = *misseg; + spivot(*misseg, splitsh); + } else { + splitsh.sh = NULL; + splitseg.sh = NULL; + } + ivf.iloc = (int) OUTSIDE; + ivf.bowywat = 1; + ivf.lawson = 0; + ivf.rejflag = 0; + ivf.chkencflag = 0; + ivf.sloc = (int) ONEDGE; + ivf.sbowywat = 1; + ivf.splitbdflag = 0; + ivf.validflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + if (!insertpoint(steinerpt, &searchtet, &splitsh, &splitseg, &ivf)) { + pointdealloc(steinerpt); + return 0; + } + + if (b->addsteiner_algo == 1) { + // Save this Steiner point (for removal). + // Re-use the array 'subvertstack'. + subvertstack->newindex((void **) &parypt); + *parypt = steinerpt; + st_segref_count++; + } else { // b->addsteiner_algo == 2 + // Queue the segment for recovery. + subsegstack->newindex((void **) &paryseg); + *paryseg = *misseg; + st_volref_count++; + } + if (steinerleft > 0) steinerleft--; + + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// addsteiner4recoversegment() Add a Steiner point for recovering a seg. // // // /////////////////////////////////////////////////////////////////////////////// @@ -20625,16 +19214,14 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) { triface *abtets, searchtet, spintet; face splitsh; - face checkseg; face *paryseg; point startpt, endpt; point pa, pb, pd, steinerpt, *parypt; enum interresult dir; insertvertexflags ivf; int types[2], poss[4]; - REAL ip[3], u; int n, endi, success; - int loc; + int t1ver; int i; startpt = sorg(*misseg); @@ -20647,10 +19234,6 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) // Try to recover the edge by adding Steiner points. point2tetorg(startpt, searchtet); dir = finddirection(&searchtet, endpt); - assert(dir != ACROSSVERT); - - // Get the first intersecting face/edge. - assert(!ishulltet(searchtet)); enextself(searchtet); //assert(apex(searchtet) == startpt); @@ -20658,13 +19241,8 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) // The segment is crossing at least 3 faces. Find the common edge of // the first 3 crossing faces. esymself(searchtet); - assert(oppo(searchtet) == startpt); fsym(searchtet, spintet); pd = oppo(spintet); - if (pd == endpt) { - // This should be possible. - assert(0); // Debug this case. - } for (i = 0; i < 3; i++) { pa = org(spintet); pb = dest(spintet); @@ -20680,8 +19258,9 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) } else { assert(dir == ACROSSEDGE); // PLC check. - tsspivot1(searchtet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(searchtet)) { + face checkseg; + tsspivot1(searchtet, checkseg); printf("Found two segments intersect each other.\n"); pa = farsorg(*misseg); pb = farsdest(*misseg); @@ -20691,7 +19270,7 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) pb = farsdest(checkseg); printf(" 2nd: [%d,%d] %d.\n", pointmark(pa), pointmark(pb), shellmark(checkseg)); - terminatetetgen(3); + terminatetetgen(this, 3); } } assert(apex(searchtet) == startpt); @@ -20718,8 +19297,6 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) abtets[i] = spintet; fnextself(spintet); } - assert(apex(abtets[0]) == startpt); - assert(apex(abtets[endi]) == endpt); success = 0; @@ -20752,10 +19329,10 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) // However, there will be invalid tets (either zero or negtive // volume). Otherwise, [c,d] should already be recovered by the // recoveredge() function. - assert(0); // DEBUG IT + terminatetetgen(this, 2); // Report a bug. } } else { - assert(0); // A PLC problem. + terminatetetgen(this, 10); // A PLC problem. } delete [] abtets; @@ -20776,99 +19353,22 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) printf(" Splitting segment (%d, %d)\n", pointmark(startpt), pointmark(endpt)); } + steinerpt = NULL; - if (endi == -1) { - // Let the missing segment be [a,b]. Let the edge [c,d] whose star contains - // a and intersects [a,b]. We choose the Steiner point at the intersection - // of the edge star of [c,d] and [a,b] (not a). - if (dir == ACROSSFACE) { - pa = org(searchtet); - pb = dest(searchtet); - - spintet = searchtet; - n = 0; endi = -1; - while (1) { - n++; // Count a tet in the star. - fnextself(spintet); - if (spintet.tet == searchtet.tet) break; - // Check if the segment leaves the edge star. - pd = apex(spintet); - assert(pd != endpt); - if (!tri_edge_test(pa, pb, pd, startpt, endpt, NULL, 1, types, poss)) { - if (endi == -1) endi = (n - 1); - } - } - assert(n >= 3); - assert(endi != -1); - - // 'abtets' is only for debug purpose. - abtets = new triface[endi]; - spintet = searchtet; - for (i = 0; i < endi; i++) { - abtets[i] = spintet; - fnextself(spintet); - } - searchtet = abtets[endi - 1]; - esymself(searchtet); // The exit face of [startpt, endpt]. - delete [] abtets; - } else { - assert(dir == ACROSSEDGE); - assert(apex(searchtet) == startpt); - esymself(searchtet); // The exit face of [startpt, endpt]. - //assert(oppo(searchtet) == startpt); - pa = org(searchtet); - pb = dest(searchtet); + if (b->addsteiner_algo > 0) { // -Y/1 or -Y/2 + if (add_steinerpt_in_segment(misseg, 3)) { + return 1; } - - pd = apex(searchtet); - // Get the intersection type (ACROSSFACE or ACROSSEDGE). - if (tri_edge_test(pa, pb, pd, startpt, endpt, NULL, 1, types, poss)) { - dir = (enum interresult) types[0]; - assert((dir == ACROSSFACE) || (dir == ACROSSEDGE)); - } else { - assert(0); // not possible. + sesymself(*misseg); + if (add_steinerpt_in_segment(misseg, 3)) { + return 1; } - - // Calculate the intersection of the face [a,b,d] and the segment. - planelineint(pa, pb, pd, startpt, endpt, ip, &u); - assert((u > 0) && (u < 1)); - - // Create a Steiner point. - makepoint(&steinerpt, FREESEGVERTEX); - for (i = 0; i < 3; i++) steinerpt[i] = ip[i]; - - - spivot(*misseg, splitsh); - if (dir == ACROSSFACE) { - ivf.iloc = (int) ONFACE; - } else { - ivf.iloc = (int) ONEDGE; - } - ivf.bowywat = 1; - ivf.lawson = 0; - ivf.rejflag = 0; - ivf.chkencflag = 0; - ivf.sloc = (int) ONEDGE; - ivf.sbowywat = 1; - ivf.splitbdflag = 0; - ivf.validflag = 1; - ivf.respectbdflag = 1; - ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, &splitsh, misseg, &ivf); - - if (loc != ivf.iloc) { - if (loc == (int) NEARVERTEX) { - // The vertex is rejected. Too close to an existing vertex. - pointdealloc(steinerpt); - steinerpt = NULL; - } else { - assert(0); // Unknown case. - } - } - } else { // if (endi > 0) - steinerpt = NULL; + sesymself(*misseg); } + + + if (steinerpt == NULL) { // Split the segment at its midpoint. makepoint(&steinerpt, FREESEGVERTEX); @@ -20890,10 +19390,9 @@ int tetgenmesh::addsteiner4recoversegment(face* misseg, int splitsegflag) ivf.validflag = 1; ivf.respectbdflag = 1; ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, &splitsh, misseg, &ivf); - - assert(loc != (int) ONVERTEX); - assert(loc != (int) NEARVERTEX); + if (!insertpoint(steinerpt, &searchtet, &splitsh, misseg, &ivf)) { + assert(0); + } } // if (endi > 0) // Save this Steiner point (for removal). @@ -20923,11 +19422,12 @@ int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, int steinerflag) { triface searchtet, spintet; - face sseg, checkseg, *paryseg; + face sseg, *paryseg; point startpt, endpt; int success; - + int t1ver; long bak_inpoly_count = st_volref_count; + long bak_segref_count = st_segref_count; if (b->verbose > 1) { printf(" Recover segments [%s level = %2d] #: %ld.\n", @@ -20976,8 +19476,6 @@ int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, if (success) { // Segment is recovered. Insert it. - tsspivot1(searchtet, checkseg); // SELF_CHECK - assert(checkseg.sh == NULL); // Let the segment remember an adjacent tet. sstbond1(sseg, searchtet); // Bond the segment to all tets containing it. @@ -21016,6 +19514,10 @@ int tetgenmesh::recoversegments(arraypool *misseglist, int fullsearch, printf(" Add %ld Steiner points in volume.\n", st_volref_count - bak_inpoly_count); } + if (st_segref_count > bak_segref_count) { + printf(" Add %ld Steiner points in segments.\n", + st_segref_count - bak_segref_count); + } } } @@ -21035,24 +19537,19 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, face *searchsh, triface* searchtet) { triface spintet, flipedge; - face checkseg; point pd, pe; enum interresult dir; flipconstraints fc; + int types[2], poss[4], intflag; int success, success1; + int t1ver; int i, j; - int intflag; - int types[2], poss[4]; - - if (b->verbose > 2) { - printf(" Recovering face (%d, %d, %d) by flips\n", pointmark(pa), - pointmark(pb), pointmark(pc)); - } fc.fac[0] = pa; fc.fac[1] = pb; fc.fac[2] = pc; + fc.checkflipeligibility = 1; success = 0; for (i = 0; i < 3 && !success; i++) { @@ -21096,14 +19593,13 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, dir = (enum interresult) types[0]; if ((dir == ACROSSFACE) || (dir == ACROSSEDGE)) { // Go to the edge [d,e]. - eprev(spintet, flipedge); - esymself(flipedge); - enextself(flipedge); // [d,e,a,b]. + edestoppo(spintet, flipedge); // [d,e,a,b] if (searchsh != NULL) { // Check if [e,d] is a segment. - tsspivot1(flipedge, checkseg); - if (checkseg.sh != NULL) { - if (!b->quiet) { + if (issubseg(flipedge)) { + if (!b->quiet) { + face checkseg; + tsspivot1(flipedge, checkseg); printf("Found a segment and a subface intersect.\n"); pd = farsorg(checkseg); pe = farsdest(checkseg); @@ -21111,16 +19607,16 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, pointmark(pe), shellmark(checkseg)); printf(" 2nd: [%d,%d,%d] %d\n", pointmark(pa), pointmark(pb), pointmark(pc), shellmark(*searchsh)); - } - terminatetetgen(3); - } + } + terminatetetgen(this, 3); + } } // Try to flip the edge [d,e]. success1 = (removeedgebyflips(&flipedge, &fc) == 2); } else { if (dir == TOUCHFACE) { point touchpt, *parypt; - if (poss[0] == 0) { + if (poss[1] == 0) { touchpt = pd; // pd is a coplanar vertex. } else { touchpt = pe; // pe is a coplanar vertex. @@ -21128,17 +19624,11 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, if (pointtype(touchpt) == FREEVOLVERTEX) { // A volume Steiner point was added in this subface. // Split this subface by this point. - if (b->verbose > 2) { - printf(" Shift volume Steiner point %d to facet.\n", - pointmark(touchpt)); - } face checksh, *parysh; int siloc = (int) ONFACE; int sbowat = 0; // Only split this subface. - - sinsertvertex(touchpt, searchsh, NULL, siloc, sbowat); - setpointtype(touchpt, FREEFACETVERTEX); + sinsertvertex(touchpt, searchsh, NULL, siloc, sbowat, 0); st_volref_count--; st_facref_count++; // Queue this vertex for removal. @@ -21152,12 +19642,6 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, spivot(*parysh, checksh); // The new subface [a, b, p]. // Do not recover a deleted new face (degenerated). if (checksh.sh[3] != NULL) { - if (b->verbose > 3) { - printf(" Queue new subface (%d, %d, %d).\n", - pointmark(sorg(checksh)), pointmark(sdest(checksh)), - pointmark(sapex(checksh))); - } - //sdissolve(checksh); // It has not been connected yet. subfacstack->newindex((void **) &parysh); *parysh = checksh; } @@ -21183,7 +19667,7 @@ int tetgenmesh::recoverfacebyflips(point pa, point pb, point pc, } else if (pointtype(touchpt) == FREEFACETVERTEX) { // Two facets self-intersect. } - terminatetetgen(3); + terminatetetgen(this, 3); } } else { assert(0); // Unknown cases. Debug. @@ -21216,13 +19700,13 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) { triface searchtet, neightet, spintet; face searchsh, neighsh, neineish, *parysh; - face bdsegs[3], checkseg; + face bdsegs[3]; point startpt, endpt, apexpt, *parypt; point steinerpt; enum interresult dir; insertvertexflags ivf; int success; - int loc; + int t1ver; int i, j; if (b->verbose > 1) { @@ -21246,7 +19730,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) if (b->verbose > 2) { - printf(" Recover subface (%d, %d, %d).\n", pointmark(sorg(searchsh)), + printf(" Recover subface (%d, %d, %d).\n",pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), pointmark(sapex(searchsh))); } @@ -21266,14 +19750,13 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) startpt = sorg(searchsh); endpt = sdest(searchsh); point2tetorg(startpt, searchtet); - assert(org(searchtet) == startpt); // SELF_CHECK dir = finddirection(&searchtet, endpt); if (dir == ACROSSVERT) { if (dest(searchtet) == endpt) { success = 1; } else { //assert(0); // A PLC problem. - terminatetetgen(3); + terminatetetgen(this, 3); } } else { // The edge is missing. Try to recover it. @@ -21287,13 +19770,8 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) } if (success) { // Insert a temporary segment to protect this edge. - if (b->verbose > 2) { - printf(" Insert a temp segment to protect edge [%d, %d].\n", - pointmark(startpt), pointmark(endpt)); - } makeshellface(subsegs, &(bdsegs[i])); setshvertices(bdsegs[i], startpt, endpt, NULL); - //setshellmark(bdsegs[i], -2); // It's a temporary segment. smarktest2(bdsegs[i]); // It's a temporary segment. // Insert this segment into surface mesh. ssbond(searchsh, bdsegs[i]); @@ -21302,8 +19780,6 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) ssbond(neighsh, bdsegs[i]); } // Insert this segment into tetrahedralization. - tsspivot1(searchtet, checkseg); // SELF_CHECK - assert(checkseg.sh == NULL); sstbond1(bdsegs[i], searchtet); // Bond the segment to all tets containing it. spintet = searchtet; @@ -21315,11 +19791,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) // An edge of this subface is missing. Can't recover this subface. // Delete any temporary segment that has been created. for (j = (i - 1); j >= 0; j--) { - if (smarktest2ed(bdsegs[j])) { // if (shellmark(bdsegs[j]) == -2) { - if (b->verbose > 2) { - printf(" Remove a temp segment (%d, %d).\n", - pointmark(sorg(bdsegs[j])), pointmark(sdest(bdsegs[j]))); - } + if (smarktest2ed(bdsegs[j])) { spivot(bdsegs[j], neineish); assert(neineish.sh != NULL); //if (neineish.sh != NULL) { @@ -21331,7 +19803,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) spivotself(neighsh); // SELF_CHECK assert(neighsh.sh == neineish.sh); } - //} + //} sstpivot1(bdsegs[j], searchtet); assert(searchtet.tet != NULL); //if (searchtet.tet != NULL) { @@ -21341,7 +19813,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) fnextself(spintet); if (spintet.tet == searchtet.tet) break; } - //} + //} shellfacedealloc(subsegs, bdsegs[j].sh); } } // j @@ -21368,9 +19840,9 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) ivf.validflag = 1; ivf.respectbdflag = 1; ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, &searchsh, NULL, &ivf); - assert(loc != (int) OUTSIDE); - + if (!insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { + assert(0); + } // Save this Steiner point (for removal). // Re-use the array 'subvertstack'. subvertstack->newindex((void **) &parypt); @@ -21395,11 +19867,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) // Delete any temporary segment that has been created. for (j = 0; j < 3; j++) { - if (smarktest2ed(bdsegs[j])) { //if (shellmark(bdsegs[j]) == -2) { - if (b->verbose > 2) { - printf(" Remove a temp segment (%d, %d).\n", - pointmark(sorg(bdsegs[j])), pointmark(sdest(bdsegs[j]))); - } + if (smarktest2ed(bdsegs[j])) { spivot(bdsegs[j], neineish); assert(neineish.sh != NULL); //if (neineish.sh != NULL) { @@ -21411,7 +19879,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) spivotself(neighsh); // SELF_CHECK assert(neighsh.sh == neineish.sh); } - //} + //} sstpivot1(bdsegs[j], neightet); assert(neightet.tet != NULL); //if (neightet.tet != NULL) { @@ -21421,7 +19889,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) fnextself(spintet); if (spintet.tet == neightet.tet) break; } - //} + //} shellfacedealloc(subsegs, bdsegs[j].sh); } } // j @@ -21458,9 +19926,9 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) ivf.validflag = 1; ivf.respectbdflag = 1; ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, &searchsh, NULL, &ivf); - assert(loc != (int) OUTSIDE); - + if (!insertpoint(steinerpt, &searchtet, &searchsh, NULL, &ivf)) { + assert(0); + } // Save this Steiner point (for removal). // Re-use the array 'subvertstack'. subvertstack->newindex((void **) &parypt); @@ -21476,11 +19944,6 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) if (!success) { if (misshlist != NULL) { - if (b->verbose > 2) { - printf(" Subface (%d, %d, %d) is missing.\n", - pointmark(sorg(searchsh)), pointmark(sdest(searchsh)), - pointmark(sapex(searchsh))); - } // Save this subface. misshlist->newindex((void **) &parysh); *parysh = searchsh; @@ -21500,7 +19963,7 @@ int tetgenmesh::recoversubfaces(arraypool *misshlist, int steinerflag) // Otherwise, only a part of the star which is bounded by facets is returned.// // // // 'tetlist' returns the list of tets in the star of the vertex 'searchpt'. // -// Every tet in 'tetlist' is at the face oppsiting to 'searchpt'. // +// Every tet in 'tetlist' is at the face opposing to 'searchpt'. // // // // 'vertlist' returns the list of vertices in the star (exclude 'searchpt'). // // // @@ -21514,54 +19977,48 @@ int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, { triface searchtet, neightet, *parytet; face checksh, *parysh; - //face checkseg; point pt, *parypt; int collectflag; + int t1ver; int i, j; - if (b->verbose > 2) { - printf(" Form the star of vertex %d.\n", pointmark(searchpt)); - } - point2tetorg(searchpt, searchtet); // Go to the opposite face (the link face) of the vertex. - enextself(searchtet); - esymself(searchtet); + enextesymself(searchtet); //assert(oppo(searchtet) == searchpt); infect(searchtet); // Collect this tet (link face). tetlist->newindex((void **) &parytet); *parytet = searchtet; if (vertlist != NULL) { // Collect three (link) vertices. - for (i = 0; i < 3; i++) { - pt = org(searchtet); + j = (searchtet.ver & 3); // The current vertex index. + for (i = 1; i < 4; i++) { + pt = (point) searchtet.tet[4 + ((j + i) % 4)]; pinfect(pt); vertlist->newindex((void **) &parypt); *parypt = pt; - enextself(searchtet); } } collectflag = 1; esym(searchtet, neightet); - tspivot(neightet, checksh); - if (checksh.sh != NULL) { + if (issubface(neightet)) { if (shlist != NULL) { + tspivot(neightet, checksh); if (!sinfected(checksh)) { // Collect this subface (link edge). sinfected(checksh); shlist->newindex((void **) &parysh); *parysh = checksh; } - } // if (checksh.sh != NULL) + } if (!fullstar) { collectflag = 0; } } if (collectflag) { fsymself(neightet); // Goto the adj tet of this face. - assert(neightet.tet != NULL); esymself(neightet); // Goto the oppo face of this vertex. // assert(oppo(neightet) == searchpt); infect(neightet); // Collect this tet (link face). @@ -21581,15 +20038,14 @@ int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, searchtet = * (triface *) fastlookup(tetlist, i); // Note that 'searchtet' is a face opposite to 'searchpt', and the neighbor // tet at the current edge is already collected. - // Check the neighors at the other two edges of this face. + // Check the neighbors at the other two edges of this face. for (j = 0; j < 2; j++) { collectflag = 1; enextself(searchtet); - //fnext(searchtet, neightet); esym(searchtet, neightet); - tspivot(neightet, checksh); - if (checksh.sh != NULL) { + if (issubface(neightet)) { if (shlist != NULL) { + tspivot(neightet, checksh); if (!sinfected(checksh)) { // Collect this subface (link edge). sinfected(checksh); @@ -21603,7 +20059,6 @@ int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, } if (collectflag) { fsymself(neightet); - assert(neightet.tet != NULL); if (!infected(neightet)) { esymself(neightet); // Go to the face opposite to 'searchpt'. infect(neightet); @@ -21623,16 +20078,6 @@ int tetgenmesh::getvertexstar(int fullstar, point searchpt, arraypool* tetlist, } // j } // i - if (b->verbose > 2) { - printf(" Collected %ld tets", tetlist->objects); - if (vertlist != NULL) { - printf(", %ld vertices", vertlist->objects); - } - if (shlist != NULL) { - printf(", %ld subfaces", shlist->objects); - } - printf(".\n"); - } // Uninfect the list of tets and vertices. for (i = 0; i < tetlist->objects; i++) { @@ -21712,20 +20157,18 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) // Go to the link face of e1. point2tetorg(e1, searchtet); - enextself(searchtet); - esymself(searchtet); + enextesymself(searchtet); //assert(oppo(searchtet) == e1); - assert(cavetetlist->objects == 0l); // It will re-use this list. + assert(cavebdrylist->objects == 0l); // It will re-use this list. + arraypool *tetlist = cavebdrylist; // Search e2. for (i = 0; i < 3; i++) { pt = apex(searchtet); if (pt == e2) { // Found. 'searchtet' is [#,#,e2,e1]. - enext(searchtet, *tedge); - esymself(*tedge); - eprevself(*tedge); // [e1,e2,#,#]. + eorgoppo(searchtet, *tedge); // [e1,e2,#,#]. return 1; } enextself(searchtet); @@ -21738,24 +20181,22 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) pt = apex(neightet); if (pt == e2) { // Found. 'neightet' is [#,#,e2,e1]. - enext(neightet, *tedge); - esymself(*tedge); - eprevself(*tedge); // [e1,e2,#,#]. + eorgoppo(neightet, *tedge); // [e1,e2,#,#]. return 1; } // Continue searching in the link face of e1. infect(searchtet); - cavetetlist->newindex((void **) &parytet); + tetlist->newindex((void **) &parytet); *parytet = searchtet; infect(neightet); - cavetetlist->newindex((void **) &parytet); + tetlist->newindex((void **) &parytet); *parytet = neightet; done = 0; - for (i = 0; (i < cavetetlist->objects) && !done; i++) { - parytet = (triface *) fastlookup(cavetetlist, i); + for (i = 0; (i < tetlist->objects) && !done; i++) { + parytet = (triface *) fastlookup(tetlist, i); searchtet = *parytet; for (j = 0; (j < 2) && !done; j++) { enextself(searchtet); @@ -21765,13 +20206,11 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) pt = apex(neightet); if (pt == e2) { // Found. 'neightet' is [#,#,e2,e1]. - enext(neightet, *tedge); - esymself(*tedge); - eprevself(*tedge); // [e1,e2,#,#]. + eorgoppo(neightet, *tedge); done = 1; } else { infect(neightet); - cavetetlist->newindex((void **) &parytet); + tetlist->newindex((void **) &parytet); *parytet = neightet; } } @@ -21779,11 +20218,11 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) } // i // Uninfect the list of visited tets. - for (i = 0; i < cavetetlist->objects; i++) { - parytet = (triface *) fastlookup(cavetetlist, i); + for (i = 0; i < tetlist->objects; i++) { + parytet = (triface *) fastlookup(tetlist, i); uninfect(*parytet); } - cavetetlist->restart(); + tetlist->restart(); return done; } @@ -21799,7 +20238,6 @@ int tetgenmesh::getedge(point e1, point e2, triface *tedge) int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) { triface searchtet; - face checkseg; point *pendpt, *parypt; enum interresult dir; flipconstraints fc; @@ -21807,13 +20245,9 @@ int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) int count; int n, i, j; - if (b->verbose > 2) { - printf(" Initial edge degree = %ld.\n", endptlist->objects); - } - assert(endptlist->objects >= 4l); - // Reduce the number of edges. fc.remvert = startpt; + fc.checkflipeligibility = 1; while (1) { @@ -21840,8 +20274,7 @@ int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) if (dir == ACROSSVERT) { if (dest(searchtet) == *pendpt) { // Do not flip a segment. - tsspivot1(searchtet, checkseg); - if (checkseg.sh == NULL) { + if (!issubseg(searchtet)) { n = removeedgebyflips(&searchtet, &fc); if (n == 2) { reduceflag = 1; @@ -21872,10 +20305,6 @@ int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) } // while (1) - if (b->verbose > 2) { - printf(" Final edge degree = %ld.\n", endptlist->objects); - } - return (int) endptlist->objects; } @@ -21883,8 +20312,8 @@ int tetgenmesh::reduceedgesatvertex(point startpt, arraypool* endptlist) // // // removevertexbyflips() Remove a vertex by flips. // // // -// This routine attempts to remove the given vertex 'rempt' (p) from the cur-// -// rent tetrahedralization (T) by a sequence of elementary flips. // +// This routine attempts to remove the given vertex 'rempt' (p) from the // +// tetrahedralization (T) by a sequence of flips. // // // // The algorithm used here is a simple edge reduce method. Suppose there are // // n edges connected at p. We try to reduce the number of edges by flipping // @@ -21901,11 +20330,13 @@ int tetgenmesh::removevertexbyflips(point steinerpt) triface searchtet, spintet, neightet; face parentsh, spinsh, checksh; face leftseg, rightseg, checkseg; - point lpt = NULL, rpt = NULL, apexpt, *parypt; + point lpt = NULL, rpt = NULL, apexpt; //, *parypt; + flipconstraints fc; enum verttype vt; enum locateresult loc; int valence, removeflag; int slawson; + int t1ver; int n, i; vt = pointtype(steinerpt); @@ -21946,6 +20377,11 @@ int tetgenmesh::removevertexbyflips(point steinerpt) printf(" Removing Steiner point %d in volume.\n", pointmark(steinerpt)); } + } else if (vt == VOLVERTEX) { + if (b->verbose > 2) { + printf(" Removing a point %d in volume.\n", + pointmark(steinerpt)); + } } else { // It is not a Steiner point. return 0; @@ -21964,24 +20400,7 @@ int tetgenmesh::removevertexbyflips(point steinerpt) removeflag = 0; - if (valence < 3) { - assert(0); // Unknown cases. - } - - if (valence == 3) { - // Only three edges at this vertex. This is only possible when there are - // Inverted elements. - getvertexstar(1, steinerpt, cavetetlist, NULL, NULL); - if (cavetetlist->objects == 2) { - printf("to be continued...\n"); - assert(0); - } else { - assert(0); // Unknown cases. - } - cavetetlist->restart(); - loc = OUTSIDE; - removeflag = 1; - } else if (valence == 4) { + if (valence == 4) { // Only 4 vertices (4 tets) left! 'p' is inside the convex hull of the 4 // vertices. This case is due to that 'p' is not exactly on the segment. point2tetorg(steinerpt, searchtet); @@ -22014,19 +20433,24 @@ int tetgenmesh::removevertexbyflips(point steinerpt) // There are 4 tets sharing at [p,lpt]. There must be 4 tets sharing // at [p,rpt]. There must be a face [p, lpt, rpt]. if (apex(neightet) == rpt) { - // The edge (segment) has been already recovered! At first, this is - // due to the same reason as the case 'valence == 4'. Second, - // there are 4 vertices (including p, lpt, rpt) exactly coplanar. - // We can do a 6-to-2 flip to remove p and recover a face - // [lpt, rpt, c] = [a,b,c]. + // The edge (segment) has been already recovered! + // Check if a 6-to-2 flip is possible (to remove 'p'). // Let 'searchtet' be [p,d,a,b] esym(neightet, searchtet); enextself(searchtet); - loc = ONFACE; - removeflag = 1; + // Check if there are exactly three tets at edge [p,d]. + wrktets[0] = searchtet; // [p,d,a,b] + for (i = 0; i < 2; i++) { + fnext(wrktets[i], wrktets[i+1]); // [p,d,b,c], [p,d,c,a] + } + if (apex(wrktets[0]) == oppo(wrktets[2])) { + loc = ONFACE; + removeflag = 1; + } } } } else if (vt == FREEFACETVERTEX) { + // It is possible to do a 6-to-2 flip to remove the vertex. point2tetorg(steinerpt, searchtet); // Get the three faces of 'searchtet' which share at p. // All faces has p as origin. @@ -22037,29 +20461,35 @@ int tetgenmesh::removevertexbyflips(point steinerpt) wrktets[2] = searchtet; eprevself(wrktets[2]); esymself(wrktets[2]); - // Get the one which has a subface (should be only 1). - n = -1; - valence = 0; // Re-use it as a counter. + // All internal edges of the six tets have valance either 3 or 4. + // Get one edge which has valance 3. + searchtet.tet = NULL; for (i = 0; i < 3; i++) { - tspivot(wrktets[i], checksh); - if (checksh.sh != NULL) { - n = i; - valence++; + spintet = wrktets[i]; + valence = 0; + while (1) { + valence++; + fnextself(spintet); + if (spintet.tet == wrktets[i].tet) break; + } + if (valence == 3) { + // Found the edge. + searchtet = wrktets[i]; + break; + } else { + assert(valence == 4); } } - assert(valence == 1); - searchtet = wrktets[n]; - esymself(searchtet); - enextself(searchtet); + assert(searchtet.tet != NULL); + // Note, we do not detach the three subfaces at p. + // They will be removed within a 4-to-1 flip. loc = ONFACE; removeflag = 1; } else { // assert(0); DEBUG IT } //removeflag = 1; - } else { // valence > 5. - - } // if (valence > 5) + } if (!removeflag) { if (vt == FREESEGVERTEX) { @@ -22104,7 +20534,7 @@ int tetgenmesh::removevertexbyflips(point steinerpt) // Remove the vertex from the surface mesh. // This will re-create the segment [lpt, rpt] and re-triangulate // all the facets at the segment. - // Detach the subsegments from their surronding tets. + // Detach the subsegments from their surrounding tets. for (i = 0; i < 2; i++) { checkseg = (i == 0) ? leftseg : rightseg; sstpivot1(checkseg, neightet); @@ -22137,9 +20567,6 @@ int tetgenmesh::removevertexbyflips(point steinerpt) setpointtype(steinerpt, FREEVOLVERTEX); st_segref_count--; st_volref_count++; - // Save this Steiner points in (global) list. - suppsteinerptlist->newindex((void **) &parypt); - *parypt = steinerpt; return 1; } // if (!checksubfaceflag) } // if (getedge(...)) @@ -22147,10 +20574,6 @@ int tetgenmesh::removevertexbyflips(point steinerpt) } // if (!removeflag) if (!removeflag) { - if (b->verbose > 2) { - printf(" Unable to remove Steiner point %d val(%d).\n", - pointmark(steinerpt), valence); - } return 0; } @@ -22207,7 +20630,8 @@ int tetgenmesh::removevertexbyflips(point steinerpt) eprevself(fliptets[3]); esymself(fliptets[3]); // [a,b,c,p]. // Remove p by a 4-to-1 flip. - flip41(fliptets, 1, 0, 0); + //flip41(fliptets, 1, 0, 0); + flip41(fliptets, 1, &fc); //recenttet = fliptets[0]; } else if (loc == ONFACE) { // Let the original two tets be [a,b,c,d] and [b,a,c,e]. And p is in @@ -22225,6 +20649,32 @@ int tetgenmesh::removevertexbyflips(point steinerpt) for (i = 3; i < 5; i++) { fnext(fliptets[i], fliptets[i+1]); // [e,p,b,c], [e,p,c,a] } + if (vt == FREEFACETVERTEX) { + // We need to determine the location of three subfaces at p. + valence = 0; // Re-use it. + // Check if subfaces are all located in the lower three tets. + // i.e., [e,p,a,b], [e,p,b,c], and [e,p,c,a]. + for (i = 3; i < 6; i++) { + if (issubface(fliptets[i])) valence++; + } + if (valence > 0) { + assert(valence == 2); + // We must do 3-to-2 flip in the upper part. We simply re-arrange + // the six tets. + for (i = 0; i < 3; i++) { + esym(fliptets[i+3], wrktets[i]); + esym(fliptets[i], fliptets[i+3]); + fliptets[i] = wrktets[i]; + } + // Swap the last two pairs, i.e., [1]<->[[2], and [4]<->[5] + wrktets[1] = fliptets[1]; + fliptets[1] = fliptets[2]; + fliptets[2] = wrktets[1]; + wrktets[1] = fliptets[4]; + fliptets[4] = fliptets[5]; + fliptets[5] = wrktets[1]; + } + } // Remove p by a 6-to-2 flip, which is a combination of two flips: // a 3-to-2 (deletes the edge [e,p]), and // a 4-to-1 (deletes the vertex p). @@ -22232,10 +20682,12 @@ int tetgenmesh::removevertexbyflips(point steinerpt) // two new tets: [a,b,c,p] and [b,a,c,e]. The new tet [a,b,c,p] is // degenerate (has zero volume). It will be deleted in the followed // 4-to-1 flip. - flip32(&(fliptets[3]), 1, 0, 0); + //flip32(&(fliptets[3]), 1, 0, 0); + flip32(&(fliptets[3]), 1, &fc); // Second do a 4-to-1 flip on [p,d,a,b],[p,d,b,c],[p,d,c,a],[a,b,c,p]. // This creates a new tet [a,b,c,d]. - flip41(fliptets, 1, 0, 0); + //flip41(fliptets, 1, 0, 0); + flip41(fliptets, 1, &fc); //recenttet = fliptets[0]; } else if (loc == ONEDGE) { // Let the original edge be [e,d] and p is in [e,d]. Assume there are n @@ -22279,7 +20731,8 @@ int tetgenmesh::removevertexbyflips(point steinerpt) enextself(wrktets[1]); // [p,p_0,e,p_1] esymself(wrktets[1]); // [p_0,p,p_1,e] eprevself(wrktets[1]); // [p_1,p_0,p,e] [1] - flip23(wrktets, 1, 0, 0); + //flip23(wrktets, 1, 0, 0); + flip23(wrktets, 1, &fc); // Save the new tet [e,d,p,p_0] (degenerated). fliptets[n] = wrktets[2]; // Save the new tet [e,d,p_0,p_1]. @@ -22302,7 +20755,8 @@ int tetgenmesh::removevertexbyflips(point steinerpt) wrktets[2] = fliptets[i]; // [p,d,p_i,p_i+1] eprevself(wrktets[2]); // [p_i,p,d,p_i+1] esymself(wrktets[2]); // [p,p_i,p_i+1,d] [2] - flip32(wrktets, 1, 0, 0); + //flip32(wrktets, 1, 0, 0); + flip32(wrktets, 1, &fc); // Save the new tet [e,d,p_i,p_i+1]. // FOR DEBUG ONLY fliptets[i] = wrktets[0]; // [d,e,p_i+1,p_i] // FOR DEBUG ONLY esymself(fliptets[i]); // [e,d,p_i,p_i+1] // FOR DEBUG ONLY @@ -22327,7 +20781,8 @@ int tetgenmesh::removevertexbyflips(point steinerpt) enextself(wrktets[2]); // [p_p_n-1,e,p_0] esymself(wrktets[2]); // [p_n-1,p,p_0,e] enextself(wrktets[2]); // [p,p_0,p_n-1,e] [2] - flip41(wrktets, 1, 0, 0); + //flip41(wrktets, 1, 0, 0); + flip41(wrktets, 1, &fc); // Save the new tet [e,d,p_n-1,p_0] // FOR DEBUG ONLY fliptets[n-1] = wrktets[0]; // [e,d,p_n-1,p_0] // FOR DEBUG ONLY //recenttet = fliptets[0]; @@ -22403,51 +20858,42 @@ int tetgenmesh::removevertexbyflips(point steinerpt) } // if (vt == FREESEGVERTEX) // The point has been removed. - setpointtype(steinerpt, UNUSEDVERTEX); - unuverts++; - // Update the correspinding counters. - if (vt == FREESEGVERTEX) { - st_segref_count--; - } else if (vt == FREEFACETVERTEX) { - st_facref_count--; - } else if (vt == FREEVOLVERTEX) { - st_volref_count--; + if (pointtype(steinerpt) != UNUSEDVERTEX) { + setpointtype(steinerpt, UNUSEDVERTEX); + unuverts++; + } + if (vt != VOLVERTEX) { + // Update the correspinding counters. + if (vt == FREESEGVERTEX) { + st_segref_count--; + } else if (vt == FREEFACETVERTEX) { + st_facref_count--; + } else if (vt == FREEVOLVERTEX) { + st_volref_count--; + } + if (steinerleft > 0) steinerleft++; } - if (steinerleft > 0) steinerleft++; return 1; } /////////////////////////////////////////////////////////////////////////////// // // -// suppresssteinerpoint() Suppress a Steiner point. // -// // -// Remove a Steiner point 'p' from the segment it lies on. It is replaced by // -// a set of volume Steiner points in each sector at the segment. // -// // -// The list of volume Steiner points is returned in 'suppsteinerptlist'. // +// suppressbdrysteinerpoint() Suppress a boundary Steiner point // // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::suppressssteinerpoint(point steinerpt) +int tetgenmesh::suppressbdrysteinerpoint(point steinerpt) { - triface searchtet, neightet, spintet, *parytet; - triface newtet, newface; face parentsh, spinsh, *parysh; - face newsh, neighsh; - face leftseg, rightseg, checkseg, *splitseg; - point lpt = NULL, rpt = NULL, newpt, *parypt; - point pa, pb, pc; - verttype vt; - long bak_supp_steiners; - int slawson; - int i, j, k; + face leftseg, rightseg; + point lpt = NULL, rpt = NULL; + int i; - vt = pointtype(steinerpt); + verttype vt = pointtype(steinerpt); if (vt == FREESEGVERTEX) { sdecode(point2sh(steinerpt), leftseg); - assert(leftseg.sh != NULL); leftseg.shver = 0; if (sdest(leftseg) == steinerpt) { senext(leftseg, rightseg); @@ -22467,46 +20913,17 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) lpt = sorg(leftseg); rpt = sdest(rightseg); if (b->verbose > 2) { - printf(" Suppressing point %d from segment (%d, %d).\n", + printf(" Suppressing Steiner point %d in segment (%d, %d).\n", pointmark(steinerpt), pointmark(lpt), pointmark(rpt)); } - } else if (vt == FREEFACETVERTEX) { - if (b->verbose > 2) { - printf(" Suppressing point %d from facet.\n", - pointmark(steinerpt)); - } - //point2shorg(steinerpt, parentsh); - getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); - parysh = (face *) fastlookup(caveshlist, 0); - parentsh = *parysh; - //assert(sapex(parentsh) == steinerpt); - senext2self(parentsh); - assert(sorg(parentsh) == steinerpt); - cavetetlist->restart(); - caveshlist->restart(); - } else { - // Do nothing. - return 0; - } - - if (vt == FREESEGVERTEX) { - // Check if this edge [lpt, rpt] already exists. - if (getedge(lpt, rpt, &searchtet)) { - tsspivot1(searchtet, checkseg); // SELF_CHECK - assert(checkseg.sh == NULL); - return 0; - } - } - - bak_supp_steiners = suppsteinerptlist->objects; - - if (vt == FREESEGVERTEX) { // Get all subfaces at the left segment [lpt, steinerpt]. spivot(leftseg, parentsh); spinsh = parentsh; while (1) { cavesegshlist->newindex((void **) &parysh); *parysh = spinsh; + // Orient the face consistently. + if (sorg(*parysh)!= sorg(parentsh)) sesymself(*parysh); spivotself(spinsh); if (spinsh.sh == NULL) break; if (spinsh.sh == parentsh.sh) break; @@ -22516,6 +20933,198 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) cavesegshlist->restart(); return 0; } + } else if (vt == FREEFACETVERTEX) { + if (b->verbose > 2) { + printf(" Suppressing Steiner point %d from facet.\n", + pointmark(steinerpt)); + } + sdecode(point2sh(steinerpt), parentsh); + // A facet Steiner point. There are exactly two sectors. + for (i = 0; i < 2; i++) { + cavesegshlist->newindex((void **) &parysh); + *parysh = parentsh; + sesymself(parentsh); + } + } else { + return 0; + } + + triface searchtet, neightet, *parytet; + point pa, pb, pc, pd; + REAL v1[3], v2[3], len, u; + + REAL startpt[3] = {0,}, samplept[3] = {0,}, candpt[3] = {0,}; + REAL ori, minvol, smallvol; + int samplesize; + int it, j, k; + + int n = (int) cavesegshlist->objects; + point *newsteiners = new point[n]; + for (i = 0; i < n; i++) newsteiners[i] = NULL; + + // Search for each sector an interior vertex. + for (i = 0; i < cavesegshlist->objects; i++) { + parysh = (face *) fastlookup(cavesegshlist, i); + stpivot(*parysh, searchtet); + // Skip it if it is outside. + if (ishulltet(searchtet)) continue; + // Get the "half-ball". Tets in 'cavetetlist' all contain 'steinerpt' as + // opposite. Subfaces in 'caveshlist' all contain 'steinerpt' as apex. + // Moreover, subfaces are oriented towards the interior of the ball. + setpoint2tet(steinerpt, encode(searchtet)); + getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); + // Calculate the searching vector. + pa = sorg(*parysh); + pb = sdest(*parysh); + pc = sapex(*parysh); + facenormal(pa, pb, pc, v1, 1, NULL); + len = sqrt(dot(v1, v1)); + assert(len > 0.0); + v1[0] /= len; + v1[1] /= len; + v1[2] /= len; + if (vt == FREESEGVERTEX) { + parysh = (face *) fastlookup(cavesegshlist, (i + 1) % n); + pd = sapex(*parysh); + facenormal(pb, pa, pd, v2, 1, NULL); + len = sqrt(dot(v2, v2)); + assert(len > 0.0); + v2[0] /= len; + v2[1] /= len; + v2[2] /= len; + // Average the two vectors. + v1[0] = 0.5 * (v1[0] + v2[0]); + v1[1] = 0.5 * (v1[1] + v2[1]); + v1[2] = 0.5 * (v1[2] + v2[2]); + } + // Search the intersection of the ray starting from 'steinerpt' to + // the search direction 'v1' and the shell of the half-ball. + // - Construct an endpoint. + len = distance(pa, pb); + v2[0] = steinerpt[0] + len * v1[0]; + v2[1] = steinerpt[1] + len * v1[1]; + v2[2] = steinerpt[2] + len * v1[2]; + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + // Test if the ray startpt->v2 lies in the cone: where 'steinerpt' + // is the apex, and three sides are defined by the triangle + // [pa, pb, pc]. + ori = orient3d(steinerpt, pa, pb, v2); + if (ori >= 0) { + ori = orient3d(steinerpt, pb, pc, v2); + if (ori >= 0) { + ori = orient3d(steinerpt, pc, pa, v2); + if (ori >= 0) { + // Found! Calculate the intersection. + planelineint(pa, pb, pc, steinerpt, v2, startpt, &u); + assert(u != 0.0); + break; + } + } + } + } // j + assert(j < cavetetlist->objects); // There must be an intersection. + // Close the ball by adding the subfaces. + for (j = 0; j < caveshlist->objects; j++) { + parysh = (face *) fastlookup(caveshlist, j); + stpivot(*parysh, neightet); + cavetetlist->newindex((void **) &parytet); + *parytet = neightet; + } + // Search a best point inside the segment [startpt, steinerpt]. + it = 0; + samplesize = 100; + v1[0] = steinerpt[0] - startpt[0]; + v1[1] = steinerpt[1] - startpt[1]; + v1[2] = steinerpt[2] - startpt[2]; + minvol = -1.0; + while (it < 3) { + for (j = 1; j < samplesize - 1; j++) { + samplept[0] = startpt[0] + ((REAL) j / (REAL) samplesize) * v1[0]; + samplept[1] = startpt[1] + ((REAL) j / (REAL) samplesize) * v1[1]; + samplept[2] = startpt[2] + ((REAL) j / (REAL) samplesize) * v1[2]; + // Find the minimum volume for 'samplept'. + smallvol = -1; + for (k = 0; k < cavetetlist->objects; k++) { + parytet = (triface *) fastlookup(cavetetlist, k); + pa = org(*parytet); + pb = dest(*parytet); + pc = apex(*parytet); + ori = orient3d(pb, pa, pc, samplept); + if (ori <= 0) { + break; // An invalid tet. + } + if (smallvol == -1) { + smallvol = ori; + } else { + if (ori < smallvol) smallvol = ori; + } + } // k + if (k == cavetetlist->objects) { + // Found a valid point. Remember it. + if (minvol == -1.0) { + candpt[0] = samplept[0]; + candpt[1] = samplept[1]; + candpt[2] = samplept[2]; + minvol = smallvol; + } else { + if (minvol < smallvol) { + // It is a better location. Remember it. + candpt[0] = samplept[0]; + candpt[1] = samplept[1]; + candpt[2] = samplept[2]; + minvol = smallvol; + } else { + // No improvement of smallest volume. + // Since we are searching along the line [startpt, steinerpy], + // The smallest volume can only be decreased later. + break; + } + } + } + } // j + if (minvol > 0) break; + samplesize *= 10; + it++; + } // while (it < 3) + if (minvol == -1.0) { + // Failed to find a valid point. + cavetetlist->restart(); + caveshlist->restart(); + break; + } + // Create a new Steiner point inside this section. + makepoint(&(newsteiners[i]), FREEVOLVERTEX); + newsteiners[i][0] = candpt[0]; + newsteiners[i][1] = candpt[1]; + newsteiners[i][2] = candpt[2]; + cavetetlist->restart(); + caveshlist->restart(); + } // i + + if (i < cavesegshlist->objects) { + // Failed to suppress the vertex. + for (; i > 0; i--) { + if (newsteiners[i - 1] != NULL) { + pointdealloc(newsteiners[i - 1]); + } + } + delete [] newsteiners; + cavesegshlist->restart(); + return 0; + } + + // Remove p from the segment or the facet. + triface newtet, newface, spintet; + face newsh, neighsh; + face *splitseg, checkseg; + int slawson = 0; // Do not do flip afterword. + int t1ver; + + if (vt == FREESEGVERTEX) { // Detach 'leftseg' and 'rightseg' from their adjacent tets. // These two subsegments will be deleted. sstpivot1(leftseg, neightet); @@ -22532,13 +21141,6 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) fnextself(spintet); if (spintet.tet == neightet.tet) break; } - } else { // vt == FREEFACETVERTEX - // A facet Steiner point. There are exactly two sectors. - for (i = 0; i < 2; i++) { - cavesegshlist->newindex((void **) &parysh); - *parysh = parentsh; - sesymself(parentsh); - } } // Loop through all sectors bounded by facets at this segment. @@ -22551,27 +21153,25 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) // Get all tets in this sector. setpoint2tet(steinerpt, encode(neightet)); getvertexstar(0, steinerpt, cavetetlist, NULL, caveshlist); - assert(caveshlist->objects > 0); - // Create a new vertex 'np'. - makepoint(&newpt, FREEVOLVERTEX); - st_volref_count++; - // Init 'np' at the same location of 'p'. - for (j = 0; j < 3; j++) newpt[j] = steinerpt[j]; - // Within the tet, replace 'p' by 'np'. - for (j = 0; j < cavetetlist->objects; j++) { - parytet = (triface *) fastlookup(cavetetlist, j); - setoppo(*parytet, newpt); - } // j - // Save the new Steiner point in list. - suppsteinerptlist->newindex((void **) &parypt); - *parypt = newpt; + if (!ishulltet(neightet)) { + // Within each tet in the ball, replace 'p' by 'np'. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + setoppo(*parytet, newsteiners[i]); + } // j + // Point to a parent tet. + parytet = (triface *) fastlookup(cavetetlist, 0); + setpoint2tet(newsteiners[i], (tetrahedron) (parytet->tet)); + st_volref_count++; + if (steinerleft > 0) steinerleft--; + } // Disconnect the set of boundary faces. They're temporarily open faces. // They will be connected to the new tets after 'p' is removed. for (j = 0; j < caveshlist->objects; j++) { // Get a boundary face. parysh = (face *) fastlookup(caveshlist, j); stpivot(*parysh, neightet); - assert(apex(neightet) == newpt); + //assert(apex(neightet) == newpt); // Clear the connection at this face. dissolve(neightet); tsdissolve(neightet); @@ -22582,12 +21182,15 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) } // i cavesegshlist->restart(); - // Remove p from the segment. - slawson = 0; // Do not do flip afterword. if (vt == FREESEGVERTEX) { spivot(rightseg, parentsh); // 'rightseg' has p as its origin. splitseg = &rightseg; } else { + if (sdest(parentsh) == steinerpt) { + senextself(parentsh); + } else if (sapex(parentsh) == steinerpt) { + senext2self(parentsh); + } assert(sorg(parentsh) == steinerpt); splitseg = NULL; } @@ -22596,12 +21199,7 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) if (vt == FREESEGVERTEX) { // The original segment is returned in 'rightseg'. rightseg.shver = 0; - assert(sorg(rightseg) == lpt); - assert(sdest(rightseg) == rpt); } - // The set of new subfaces are found in 'caveshbdlist'. - assert(caveshbdlist->objects > 0); - // For each new subface, create two new tets at each side of it. // Both of the two new tets have its opposite be dummypoint. @@ -22621,6 +21219,8 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) sesymself(newsh); tsbond(neightet, newsh); } + // Temporarily increase the hullsize. + hullsize += (caveshbdlist->objects * 2l); if (vt == FREESEGVERTEX) { // Connecting new tets at the recovered segment. @@ -22628,30 +21228,20 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) assert(parentsh.sh != NULL); spinsh = parentsh; while (1) { - assert(sinfected(spinsh)); if (sorg(spinsh) != lpt) sesymself(spinsh); - assert(sorg(spinsh) == lpt); - assert(sdest(spinsh) == rpt); // Get the new tet at this subface. stpivot(spinsh, newtet); - assert(oppo(newtet) == dummypoint); tssbond1(newtet, rightseg); // Go to the other face at this segment. - esymself(newtet); - assert(org(newtet) == rpt); - assert(newtet.tet[newtet.ver & 3] == NULL); - // Get the adjacent tet at this segment. spivot(spinsh, neighsh); if (sorg(neighsh) != lpt) sesymself(neighsh); sesymself(neighsh); stpivot(neighsh, neightet); - assert(oppo(neightet) == dummypoint); tssbond1(neightet, rightseg); sstbond1(rightseg, neightet); - // Go to the other face at this segment. + // Connecting two adjacent tets at this segment. + esymself(newtet); esymself(neightet); - assert(org(neightet) == lpt); - assert(neightet.tet[neightet.ver & 3] == NULL); // Connect the two tets (at rightseg) together. bond(newtet, neightet); // Go to the next subface. @@ -22676,9 +21266,8 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) sspivot(newsh, checkseg); if (checkseg.sh != NULL) { // A segment. It must not be the recovered segment. - assert(checkseg.sh != rightseg.sh); tssbond1(newtet, checkseg); - //sstbond1(checkseg, newtet); + sstbond1(checkseg, newtet); } spivot(newsh, neighsh); if (neighsh.sh != NULL) { @@ -22721,10 +21310,23 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) neightet = searchtet; } pc = apex(newface); - if (pc == dummypoint) { - setapex(newface, apex(neightet)); + if (apex(neightet) == steinerpt) { + // Exterior case. The 'neightet' is a hull tet which contain + // 'steinerpt'. It will be deleted after 'steinerpt' is removed. + assert(pc == dummypoint); + caveoldtetlist->newindex((void **) &parytet); + *parytet = neightet; + // Connect newface to the adjacent hull tet of 'neightet', which + // has the same edge as 'newface', and does not has 'steinerpt'. + fnextself(neightet); } else { - assert(pc == apex(neightet)); + if (pc == dummypoint) { + if (apex(neightet) != dummypoint) { + setapex(newface, apex(neightet)); + // A hull tet has turned into an interior tet. + hullsize--; // Must update the hullsize. + } + } } bond(newface, neightet); } // if (newface.tet[newface.ver & 3] == NULL) @@ -22742,6 +21344,16 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) } caveshbdlist->restart(); + if (caveoldtetlist->objects > 0l) { + // Delete hull tets which contain 'steinerpt'. + for (i = 0; i < caveoldtetlist->objects; i++) { + parytet = (triface *) fastlookup(caveoldtetlist, i); + tetrahedrondealloc(parytet->tet); + } + // Must update the hullsize. + hullsize -= caveoldtetlist->objects; + caveoldtetlist->restart(); + } setpointtype(steinerpt, UNUSEDVERTEX); unuverts++; @@ -22750,163 +21362,201 @@ int tetgenmesh::suppressssteinerpoint(point steinerpt) } else { // vt == FREEFACETVERTEX st_facref_count--; } - if (steinerleft > 0) steinerleft++; + if (steinerleft > 0) steinerleft++; // We've removed a Steiner points. - if (b->verbose > 2) { - printf(" Duplicated %ld Steiner points.\n", - suppsteinerptlist->objects - bak_supp_steiners); + + point *parypt; + int steinercount = 0; + + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + + // Try to remove newly added Steiner points. + for (i = 0; i < n; i++) { + if (newsteiners[i] != NULL) { + if (!removevertexbyflips(newsteiners[i])) { + if (b->nobisect_param > 0) { // Not -Y0 + // Save it in subvertstack for removal. + subvertstack->newindex((void **) &parypt); + *parypt = newsteiners[i]; + } + steinercount++; + } + } } + b->fliplinklevel = bak_fliplinklevel; + + if (steinercount > 0) { + if (b->verbose > 2) { + printf(" Added %d interior Steiner points.\n", steinercount); + } + } + + delete [] newsteiners; + return 1; } + /////////////////////////////////////////////////////////////////////////////// // // // suppresssteinerpoints() Suppress Steiner points. // // // +// All Steiner points have been saved in 'subvertstack' in the routines // +// carveholes() and suppresssteinerpoint(). // // Each Steiner point is either removed or shifted into the interior. // // // /////////////////////////////////////////////////////////////////////////////// int tetgenmesh::suppresssteinerpoints() { - triface *parytet; - point rempt, *parypt, *plastpt, *ppt; - optparameters opm; - REAL ori; - int bak_fliplinklevel; - int remcount, smtcount; - int count, nt; - int i, j; if (!b->quiet) { printf("Suppressing Steiner points ...\n"); } - bak_fliplinklevel = b->fliplinklevel; - b->fliplinklevel = 100000; // Unlimited flip level. - remcount = 0; + point rempt, *parypt; - if (b->nobisect_param > 1) { // -Y2 - // Try to remove all the Steiner points. + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = 100000; // Unlimited flip level. + int suppcount = 0, remcount = 0; + int i; + + // Try to suppress boundary Steiner points. + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) != UNUSEDVERTEX) { + if ((pointtype(rempt) == FREESEGVERTEX) || + (pointtype(rempt) == FREEFACETVERTEX)) { + if (suppressbdrysteinerpoint(rempt)) { + suppcount++; + } + } + } + } // i + + if (suppcount > 0) { + if (b->verbose) { + printf(" Suppressed %d boundary Steiner points.\n", suppcount); + } + } + + if (b->nobisect_param > 0) { // -Y1 for (i = 0; i < subvertstack->objects; i++) { parypt = (point *) fastlookup(subvertstack, i); rempt = *parypt; if (pointtype(rempt) != UNUSEDVERTEX) { - if (removevertexbyflips(rempt)) { - remcount++; - } - } - } - if (b->verbose) { - if (remcount > 0) { - printf(" Removed %d Steiner points.\n", remcount); - } - } - subvertstack->restart(); - } - - remcount = smtcount = 0; - - // Try to remove the suppressed Steiner points. - for (i = 0; i < suppsteinerptlist->objects; i++) { - // Get the Steiner point. - parypt = (point *) fastlookup(suppsteinerptlist, i); - rempt = *parypt; - if (pointtype(rempt) != UNUSEDVERTEX) { - assert((pointtype(rempt) == FREESEGVERTEX) || - (pointtype(rempt) == FREEFACETVERTEX) || - (pointtype(rempt) == FREEVOLVERTEX)); - if (removevertexbyflips(rempt)) { - // Move the last entry to fill the current one. - j = (int) (suppsteinerptlist->objects - 1); - plastpt = (point *) fastlookup(suppsteinerptlist, j); - *parypt = *plastpt; - suppsteinerptlist->objects--; - i--; - remcount++; - } - } else { - // The point has been removed. - // Move the last entry to fill the current one. - j = (int) (suppsteinerptlist->objects - 1); - plastpt = (point *) fastlookup(suppsteinerptlist, j); - *parypt = *plastpt; - suppsteinerptlist->objects--; - i--; - } - } // i - - if (b->verbose) { - if (remcount > 0) { - printf(" Removed %d suppressed Steiner points.\n", remcount); - } - } - - if (suppsteinerptlist->objects == 0l) { - b->fliplinklevel = bak_fliplinklevel; - return remcount; - } - - // Point smooth options. - opm.max_min_volume = 1; - opm.numofsearchdirs = 20; - opm.searchstep = 0.001; - - nt = 0; - - while (1) { - // Try to smooth volume Steiner points. - count = 0; - - for (i = 0; i < suppsteinerptlist->objects; i++) { - parypt = (point *) fastlookup(suppsteinerptlist, i); - rempt = *parypt; - if (pointtype(rempt) == FREEVOLVERTEX) { - getvertexstar(1, rempt, cavetetlist, NULL, NULL); - // Calculate the initial smallest volume (maybe zero or negative). - for (j = 0; j < cavetetlist->objects; j++) { - parytet = (triface *) fastlookup(cavetetlist, j); - ppt = (point *) &(parytet->tet[4]); - ori = orient3d(ppt[1], ppt[0], ppt[2], ppt[3]); - if (j == 0) { - opm.initval = ori; - } else { - if (opm.initval > ori) opm.initval = ori; + if (pointtype(rempt) == FREEVOLVERTEX) { + if (removevertexbyflips(rempt)) { + remcount++; } } - if (smoothpoint(rempt, cavetetlist, 1, &opm)) { - count++; - } - cavetetlist->restart(); } - } // i - - smtcount += count; - - if (count == 0) { - // No point has been smoothed. - break; } + } - nt++; - if (nt > 2) { - break; // Already three iterations. - } - } // while - - // The mesh should not contain inverted (or degenrrated) tets now. - checkinverttetflag = 0; - - if (b->verbose) { - if (smtcount > 0) { - printf(" Smoothed %d Steiner points.\n", smtcount); + if (remcount > 0) { + if (b->verbose) { + printf(" Removed %d interior Steiner points.\n", remcount); } } b->fliplinklevel = bak_fliplinklevel; - return smtcount; + if (b->nobisect_param > 1) { // -Y2 + // Smooth interior Steiner points. + optparameters opm; + triface *parytet; + point *ppt; + REAL ori; + int smtcount, count, ivcount; + int nt, j; + + // Point smooth options. + opm.max_min_volume = 1; + opm.numofsearchdirs = 20; + opm.searchstep = 0.001; + opm.maxiter = 30; // Limit the maximum iterations. + + smtcount = 0; + + do { + + nt = 0; + + while (1) { + count = 0; + ivcount = 0; // Clear the inverted count. + + for (i = 0; i < subvertstack->objects; i++) { + parypt = (point *) fastlookup(subvertstack, i); + rempt = *parypt; + if (pointtype(rempt) == FREEVOLVERTEX) { + getvertexstar(1, rempt, cavetetlist, NULL, NULL); + // Calculate the initial smallest volume (maybe zero or negative). + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + ppt = (point *) &(parytet->tet[4]); + ori = orient3dfast(ppt[1], ppt[0], ppt[2], ppt[3]); + if (j == 0) { + opm.initval = ori; + } else { + if (opm.initval > ori) opm.initval = ori; + } + } + if (smoothpoint(rempt, cavetetlist, 1, &opm)) { + count++; + } + if (opm.imprval <= 0.0) { + ivcount++; // The mesh contains inverted elements. + } + cavetetlist->restart(); + } + } // i + + smtcount += count; + + if (count == 0) { + // No point has been smoothed. + break; + } + + nt++; + if (nt > 2) { + break; // Already three iterations. + } + } // while + + if (ivcount > 0) { + // There are inverted elements! + if (opm.maxiter > 0) { + // Set unlimited smoothing steps. Try again. + opm.numofsearchdirs = 30; + opm.searchstep = 0.0001; + opm.maxiter = -1; + continue; + } + } + + break; + } while (1); // Additional loop for (ivcount > 0) + + if (ivcount > 0) { + printf("BUG Report! The mesh contain inverted elements.\n"); + } + + if (b->verbose) { + if (smtcount > 0) { + printf(" Smoothed %d Steiner points.\n", smtcount); + } + } + } // -Y2 + + subvertstack->restart(); + + return 1; } /////////////////////////////////////////////////////////////////////////////// @@ -22928,16 +21578,11 @@ void tetgenmesh::recoverboundary(clock_t& tv) // Counters. long bak_segref_count, bak_facref_count, bak_volref_count; - long bak_supp_count; if (!b->quiet) { printf("Recovering boundaries...\n"); } - if (b->verbose) { - printf(" Flip link level = %d\n", b->fliplinklevel); - } - if (b->verbose) { printf(" Recovering segments.\n"); @@ -22949,19 +21594,18 @@ void tetgenmesh::recoverboundary(clock_t& tv) misseglist = new arraypool(sizeof(face), 8); bdrysteinerptlist = new arraypool(sizeof(point), 8); - // In random order. - subsegs->traversalinit(); - for (i = 0; i < subsegs->items; i++) { - s = randomnation(i + 1); - // Move the s-th seg to the i-th. - subsegstack->newindex((void **) &paryseg); - *paryseg = * (face *) fastlookup(subsegstack, s); - // Put i-th seg to be the s-th. - searchseg.sh = shellfacetraverse(subsegs); - //sinfect(searchseg); // Only save it once. - paryseg = (face *) fastlookup(subsegstack, s); - *paryseg = searchseg; - } + // In random order. + subsegs->traversalinit(); + for (i = 0; i < subsegs->items; i++) { + s = randomnation(i + 1); + // Move the s-th seg to the i-th. + subsegstack->newindex((void **) &paryseg); + *paryseg = * (face *) fastlookup(subsegstack, s); + // Put i-th seg to be the s-th. + searchseg.sh = shellfacetraverse(subsegs); + paryseg = (face *) fastlookup(subsegstack, s); + *paryseg = searchseg; + } // The init number of missing segments. ms = subsegs->items; @@ -22970,6 +21614,7 @@ void tetgenmesh::recoverboundary(clock_t& tv) autofliplinklevel = 1; // Init value. } + // First, trying to recover segments by only doing flips. while (1) { recoversegments(misseglist, 0, 0); @@ -23009,8 +21654,7 @@ void tetgenmesh::recoverboundary(clock_t& tv) } if (misseglist->objects > 0) { - // There are missing segments. Increase the fliplevel. - nit = 0; + // Second, trying to recover segments by doing more flips (fullsearch). while (misseglist->objects > 0) { ms = misseglist->objects; for (i = 0; i < misseglist->objects; i++) { @@ -23019,17 +21663,13 @@ void tetgenmesh::recoverboundary(clock_t& tv) } misseglist->restart(); - // Recover the missing segments by doing more flips. recoversegments(misseglist, 1, 0); if (misseglist->objects < ms) { // The number of missing segments is reduced. continue; } else { - nit++; - if (nit >= 3) { - break; - } + break; } } if (b->verbose) { @@ -23039,8 +21679,8 @@ void tetgenmesh::recoverboundary(clock_t& tv) } if (misseglist->objects > 0) { - // There are missing segments. Add Steiner points in volume. - nit = 0; + // Third, trying to recover segments by doing more flips (fullsearch) + // and adding Steiner points in the volume. while (misseglist->objects > 0) { ms = misseglist->objects; for (i = 0; i < misseglist->objects; i++) { @@ -23049,17 +21689,13 @@ void tetgenmesh::recoverboundary(clock_t& tv) } misseglist->restart(); - // Recover the missing segments (with Steiner points). recoversegments(misseglist, 1, 1); if (misseglist->objects < ms) { // The number of missing segments is reduced. continue; } else { - nit++; - if (nit >= 3) { - break; - } + break; } } if (b->verbose) { @@ -23068,7 +21704,8 @@ void tetgenmesh::recoverboundary(clock_t& tv) } if (misseglist->objects > 0) { - // There are missing segments. Add Steiner points to split them. + // Last, trying to recover segments by doing more flips (fullsearch), + // and adding Steiner points in the volume, and splitting segments. long bak_inpoly_count = st_volref_count; //st_inpoly_count; for (i = 0; i < misseglist->objects; i++) { subsegstack->newindex((void **) &paryseg); @@ -23076,7 +21713,6 @@ void tetgenmesh::recoverboundary(clock_t& tv) } misseglist->restart(); - // Recover the missing segments (with Steiner points). recoversegments(misseglist, 1, 2); if (b->verbose) { @@ -23155,6 +21791,7 @@ void tetgenmesh::recoverboundary(clock_t& tv) while (1) { recoversubfaces(misshlist, 0); + if (misshlist->objects > 0) { if (b->fliplinklevel >= 0) { break; @@ -23229,32 +21866,18 @@ void tetgenmesh::recoverboundary(clock_t& tv) } - if ((bdrysteinerptlist->objects > 0) && (b->nobisect_param > 0)) { // -Y1 - bak_supp_count = 0; - b->fliplinklevel = 100000; // Unlimited flip levels. - do { - // Suppress boundary Steiner points. - for (i = 0; i < bdrysteinerptlist->objects; i++) { - parypt = (point *) fastlookup(bdrysteinerptlist, i); - rempt = *parypt; - suppressssteinerpoint(rempt); - bak_supp_count++; - } - bdrysteinerptlist->restart(); - // There may be subfaces need to be recover. - if (subfacstack->objects > 0l) { - recoversubfaces(NULL, 1); - } - } while (bdrysteinerptlist->objects > 0); + if (bdrysteinerptlist->objects > 0) { if (b->verbose) { - printf(" Suppressed %ld Steiner points from boundary.\n", - bak_supp_count); + printf(" %ld Steiner points remained in boundary.\n", + bdrysteinerptlist->objects); } - // The mesh contains inverted (or degenrrated) tets now. - checkinverttetflag = 1; } // if + // Accumulate the dynamic memory. + totalworkmemory += (misseglist->totalmemory + misshlist->totalmemory + + bdrysteinerptlist->totalmemory); + delete bdrysteinerptlist; delete misseglist; delete misshlist; @@ -23279,54 +21902,38 @@ void tetgenmesh::recoverboundary(clock_t& tv) void tetgenmesh::carveholes() { arraypool *tetarray, *hullarray; - triface tetloop, neightet, hulltet, *parytet, *parytet1; - triface openface, casface; - triface *regiontets; - face checksh, casingout, casingin, *parysh; + triface tetloop, neightet, *parytet, *parytet1; + triface *regiontets = NULL; + face checksh, *parysh; face checkseg; - point *ppt, pa, pb, pc, *parypt; - enum locateresult loc; - REAL volume; - long delsegcount, delvertcount, delsteinercount; - int regioncount; - int attrnum, attr, maxattr; - int remflag; + point ptloop, *parypt; + int t1ver; int i, j, k; - tetrahedron ptr; - shellface sptr; - if (!b->quiet) { - printf("Removing exterior tetrahedra ...\n"); + if (b->convex) { + printf("Marking exterior tetrahedra ...\n"); + } else { + printf("Removing exterior tetrahedra ...\n"); + } } - // Initialize the pool of exterior tets. tetarray = new arraypool(sizeof(triface), 10); hullarray = new arraypool(sizeof(triface), 10); - regiontets = NULL; - regioncount = 0; - maxattr = 0; // Choose a small number here. - //attrnum = in->numberoftetrahedronattributes; - attrnum = numelemattrib - (b->regionattrib > 0); - // Comment: The element region marker is at the end of the list of - // the element attributes. - - // Mark as infected any unprotected hull tets. + // Collect unprotected tets and hull tets. tetrahedrons->traversalinit(); tetloop.ver = 11; // The face opposite to dummypoint. tetloop.tet = alltetrahedrontraverse(); while (tetloop.tet != (tetrahedron *) NULL) { - if ((point) tetloop.tet[7] == dummypoint) { + if (ishulltet(tetloop)) { // Is this side protected by a subface? - tspivot(tetloop, checksh); - if (checksh.sh == NULL) { + if (!issubface(tetloop)) { + // Collect an unprotected hull tet and tet. infect(tetloop); - tetarray->newindex((void **) &parytet); + hullarray->newindex((void **) &parytet); *parytet = tetloop; - hullsize--; - // Add the adjacent tet (not a hull tet) as well. // tetloop's face number is 11 & 3 = 3. decode(tetloop.tet[3], neightet); if (!infected(neightet)) { @@ -23345,50 +21952,43 @@ void tetgenmesh::carveholes() // Search a tet containing the i-th hole point. neightet.tet = NULL; randomsample(&(in->holelist[i]), &neightet); - loc = locate(&(in->holelist[i]), &neightet, 0); - if (loc != OUTSIDE) { + if (locate(&(in->holelist[i]), &neightet) != OUTSIDE) { // The tet 'neightet' contain this point. if (!infected(neightet)) { infect(neightet); tetarray->newindex((void **) &parytet); *parytet = neightet; // Add its adjacent tet if it is not protected. - tspivot(neightet, checksh); - if (checksh.sh == NULL) { + if (!issubface(neightet)) { decode(neightet.tet[neightet.ver & 3], tetloop); if (!infected(tetloop)) { infect(tetloop); - tetarray->newindex((void **) &parytet); + if (ishulltet(tetloop)) { + hullarray->newindex((void **) &parytet); + } else { + tetarray->newindex((void **) &parytet); + } *parytet = tetloop; } - } else { + } + else { // It is protected. Check if its adjacent tet is a hull tet. decode(neightet.tet[neightet.ver & 3], tetloop); - if (!infected(tetloop)) { - if (ishulltet(tetloop)) { - // It is hull tet, add it into the list. Moreover, the subface - // is dead, i.e., both sides are in exterior. + if (ishulltet(tetloop)) { + // It is hull tet, add it into the list. Moreover, the subface + // is dead, i.e., both sides are in exterior. + if (!infected(tetloop)) { infect(tetloop); - tetarray->newindex((void **) &parytet); + hullarray->newindex((void **) &parytet); *parytet = tetloop; - stdissolve(checksh); - assert(!sinfected(checksh)); - //if (!sinfected(checksh)) { - sinfect(checksh); // Only queue it once. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - //} - hullsize--; } - } else { + } + if (infected(tetloop)) { // Both sides of this subface are in exterior. - stdissolve(checksh); - assert(!sinfected(checksh)); - //if (!sinfected(checksh)) { - sinfect(checksh); // Only queue it once. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - //} + tspivot(neightet, checksh); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; } } } // if (!infected(neightet)) @@ -23400,9 +22000,9 @@ void tetgenmesh::carveholes() } } } // i - } + } // if (in->numberofholes > 0) - if (b->regionattrib && (in->numberofregions > 0)) { // If has -A option. + if (b->regionattrib && (in->numberofregions > 0)) { // -A option. // Record the tetrahedra that contains the region points for assigning // region attributes after the holes have been carved. regiontets = new triface[in->numberofregions]; @@ -23411,12 +22011,8 @@ void tetgenmesh::carveholes() // Search a tet containing the i-th region point. neightet.tet = NULL; randomsample(&(in->regionlist[i]), &neightet); - loc = locate(&(in->regionlist[i]), &neightet, 0); - if (loc != OUTSIDE) { + if (locate(&(in->regionlist[i]), &neightet) != OUTSIDE) { regiontets[i/5] = neightet; - if ((int) in->regionlist[i + 3] > maxattr) { - maxattr = (int) in->regionlist[i + 3]; - } } else { if (!b->quiet) { printf("Warning: The %d-th region point ", i/5+1); @@ -23427,61 +22023,50 @@ void tetgenmesh::carveholes() } } - - // Find and infect all exterior tets (in concave place and in holes). + // Collect all exterior tets (in concave place and in holes). for (i = 0; i < tetarray->objects; i++) { parytet = (triface *) fastlookup(tetarray, i); - // Check its three neighbors if it is not a hull tet. - if ((point) parytet->tet[7] != dummypoint) { - j = (parytet->ver & 3); // j is the current face number. - // Check the neighbors of the other three faces. - for (k = 0, j++; k < 3; k++, j++) { - decode(parytet->tet[j % 4], neightet); // neightet may be a hull tet. - if (!infected(neightet)) { - // Is neightet protected by a subface. - tspivot(neightet, checksh); - if (checksh.sh == NULL) { - // Not proected. Add it into the list. - // It should not be a hull tet. Since all unproected hull tets - // should have already been added into the list. - assert(!ishulltet(neightet)); // SELF_CHECK - infect(neightet); - tetarray->newindex((void **) &parytet1); - *parytet1 = neightet; - } else { - // It is protected. However, if neightet is a hull tet, it is - // also an exterior tet. Moverover, the subface is dead, i.e., - // both sides of it are exterior. - if ((point) neightet.tet[7] == dummypoint) { - infect(neightet); - tetarray->newindex((void **) &parytet1); - *parytet1 = neightet; - // Both sides of this subface are exterior. - stdissolve(checksh); - // Queue this subface (to be deleted later). - assert(!sinfected(checksh)); - //if (!sinfected(checksh)) { - sinfect(checksh); // Only queue it once. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - //} - hullsize--; - } - } + j = (parytet->ver & 3); // j is the current face number. + // Check the other three adjacent tets. + for (k = 1; k < 4; k++) { + decode(parytet->tet[(j + k) % 4], neightet); + // neightet may be a hull tet. + if (!infected(neightet)) { + // Is neightet protected by a subface. + if (!issubface(neightet)) { + // Not proected. Collect it. (It must not be a hull tet). + infect(neightet); + tetarray->newindex((void **) &parytet1); + *parytet1 = neightet; } else { - // Both sides of this face are in exterior. - // Check if there is a subface. - tspivot(neightet, checksh); - if (checksh.sh != NULL) { - if (!sinfected(checksh)) { - sinfect(checksh); // Only queue it once. - subfacstack->newindex((void **) &parysh); - *parysh = checksh; - } + // Protected. Check if it is a hull tet. + if (ishulltet(neightet)) { + // A hull tet. Collect it. + infect(neightet); + hullarray->newindex((void **) &parytet1); + *parytet1 = neightet; + // Both sides of this subface are exterior. + tspivot(neightet, checksh); + // Queue this subface (to be deleted later). + assert(!sinfected(checksh)); + sinfect(checksh); // Only queue it once. + subfacstack->newindex((void **) &parysh); + *parysh = checksh; } } - } // j, k - } + } else { + // Both sides of this face are in exterior. + // If there is a subface. It should be collected. + if (issubface(neightet)) { + tspivot(neightet, checksh); + if (!sinfected(checksh)) { + sinfect(checksh); + subfacstack->newindex((void **) &parysh); + *parysh = checksh; + } + } + } + } // j, k } // i if (b->regionattrib && (in->numberofregions > 0)) { @@ -23497,21 +22082,48 @@ void tetgenmesh::carveholes() } } + // Collect vertices which point to infected tets. These vertices + // may get deleted after the removal of exterior tets. + // If -Y1 option is used, collect all Steiner points for removal. + // The lists 'cavetetvertlist' and 'subvertstack' are re-used. + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + if ((pointtype(ptloop) != UNUSEDVERTEX) && + (pointtype(ptloop) != DUPLICATEDVERTEX)) { + decode(point2tet(ptloop), neightet); + if (infected(neightet)) { + cavetetvertlist->newindex((void **) &parypt); + *parypt = ptloop; + } + if (b->nobisect && (b->nobisect_param > 0)) { // -Y1 + // Queue it if it is a Steiner point. + if (pointmark(ptloop) > + (in->numberofpoints - (in->firstnumber ? 0 : 1))) { + subvertstack->newindex((void **) &parypt); + *parypt = ptloop; + } + } + } + ptloop = pointtraverse(); + } -if (!b->convex) { + if (!b->convex && (tetarray->objects > 0l)) { // No -c option. + // Remove exterior tets. Hull tets are updated. + arraypool *newhullfacearray; + triface hulltet, casface; + point pa, pb, pc; - // Create new hull tets. - // Update point-to-tet map, segment-to-tet map, and subface-to-tet map. - for (i = 0; i < tetarray->objects; i++) { - parytet = (triface *) fastlookup(tetarray, i); - if ((point) parytet->tet[7] != dummypoint) { - // We must check all four adjacent tets. + newhullfacearray = new arraypool(sizeof(triface), 10); + + // Create and save new hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); for (j = 0; j < 4; j++) { decode(parytet->tet[j], tetloop); if (!infected(tetloop)) { - // This face becomes a hull face. + // Found a new hull face (must be a subface). tspivot(tetloop, checksh); - assert(checksh.sh != NULL); // SELF_CHECK maketetrahedron(&hulltet); pa = org(tetloop); pb = dest(tetloop); @@ -23523,8 +22135,8 @@ if (!b->convex) { tsbond(hulltet, checksh); // Update the segment-to-tet map. for (k = 0; k < 3; k++) { - tsspivot1(tetloop, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(tetloop)) { + tsspivot1(tetloop, checkseg); tssbond1(hulltet, checkseg); sstbond1(checkseg, hulltet); } @@ -23532,313 +22144,221 @@ if (!b->convex) { eprevself(hulltet); } // Update the point-to-tet map. - ptr = encode(tetloop); - setpoint2tet(pa, ptr); - setpoint2tet(pb, ptr); - setpoint2tet(pc, ptr); - // Save this hull tet in list. - hullarray->newindex((void **) &parytet1); - *parytet1 = hulltet; - } + setpoint2tet(pa, (tetrahedron) tetloop.tet); + setpoint2tet(pb, (tetrahedron) tetloop.tet); + setpoint2tet(pc, (tetrahedron) tetloop.tet); + // Save the exterior tet at this hull face. It still holds pointer + // to the adjacent interior tet. Use it to connect new hull tets. + newhullfacearray->newindex((void **) &parytet1); + parytet1->tet = parytet->tet; + parytet1->ver = j; + } // if (!infected(tetloop)) } // j - } else { - // It is a hull tet. Clear the adjacent hull tets' connections to it. - // Our data structure ensures that the 3rd face opposites dummypoint. - for (j = 0; j < 3; j++) { - decode(parytet->tet[j], neightet); - if (neightet.tet != NULL) { - assert(ishulltet(neightet)); - if (!infected(neightet)) { - neightet.tet[neightet.ver & 3] = NULL; - } - } - } // j - } - } // i - - // Update the hull size. - hullsize += hullarray->objects; - - // Remove all exterior tetrahedra (including infected hull tets). - for (i = 0; i < tetarray->objects; i++) { - parytet = (triface *) fastlookup(tetarray, i); - tetrahedrondealloc(parytet->tet); - } // i - - tetarray->restart(); - - - if (subfacstack->objects > 0) { - // Remove all subfaces which do not attach to any tetrahedron. - // Segments which are not attached to any subfaces and tets - // are deleted too. - delsegcount = 0; - for (i = 0; i < subfacstack->objects; i++) { - parysh = (face *) fastlookup(subfacstack, i); - if (i == 0) { - if (b->verbose) { - printf("Warning: Removing an open face (%d, %d, %d)\n", - pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), - pointmark(sapex(*parysh))); - } - } - // Dissolve this subface from face links. - for (j = 0; j < 3; j++) { - spivot(*parysh, casingout); - sspivot(*parysh, checkseg); - if (casingout.sh != NULL) { - casingin = casingout; - while (1) { - spivot(casingin, checksh); - if (checksh.sh == parysh->sh) break; - casingin = checksh; - } - if (casingin.sh != casingout.sh) { - // Update the link: ... -> casingin -> casingout ->... - sbond1(casingin, casingout); - } else { - // Only one subface at this edge is left. - sdissolve(casingout); - } - if (checkseg.sh != NULL) { - // Make sure the segment does not connect to a dead one. - ssbond(casingout, checkseg); - } - } else { - if (checkseg.sh != NULL) { - // The segment is also dead. - if (delsegcount == 0) { - if (b->verbose) { - printf("Warning: Removing a dangling segment (%d, %d)\n", - pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); - } - } - shellfacedealloc(subsegs, checkseg.sh); - delsegcount++; - } - } - senextself(*parysh); - } // j - // Delete this subface. - shellfacedealloc(subfaces, parysh->sh); } // i - if (b->verbose) { - printf(" Deleted %ld subfaces.\n", subfacstack->objects); - if (delsegcount > 0) { - printf(" Deleted %ld segments.\n", delsegcount); - } - } - subfacstack->restart(); - } - - // Some vertices may be not belong to any tet. Mark them. - delvertcount = unuverts; - delsteinercount = 0l; - points->traversalinit(); - pa = pointtraverse(); - while (pa != NULL) { - if (pointtype(pa) != UNUSEDVERTEX) { - remflag = 0; - decode(point2tet(pa), neightet); - if ((neightet.tet == NULL) || (neightet.tet[4] == NULL)) { - remflag = 1; // It's a dead tet. - } else { - // Check if this tet contains pa. - ppt = (point *) &(neightet.tet[4]); - if (!((ppt[0] == pa) || (ppt[1] == pa) || - (ppt[2] == pa) || (ppt[3] == pa))) { - remflag = 1; // It's a wrong pointer. - } - } - if (remflag) { - // Found an exterior vertex. - if (pointmark(pa) > - (in->numberofpoints - (in->firstnumber ? 0 : 1))) { - if (pointtype(pa) == FREESEGVERTEX) { - st_segref_count--; - } else if (pointtype(pa) == FREEFACETVERTEX) { - st_facref_count--; - } else { - assert(pointtype(pa) == FREEVOLVERTEX); - st_volref_count--; //st_inpoly_count--; - } - delsteinercount++; // A Steiner point. - if (steinerleft > 0) steinerleft++; - } - setpointtype(pa, UNUSEDVERTEX); - unuverts++; - } else { - // This vertex survived. - if (b->nobisect && (b->nobisect_param > 1)) { // -Y2 - // Queue it if it is a Steiner point. - if ((pointtype(pa) == FREESEGVERTEX) || - (pointtype(pa) == FREEFACETVERTEX) || - (pointtype(pa) == FREEVOLVERTEX)) { - subvertstack->newindex((void **) &parypt); - *parypt = pa; - } - } - } - } - pa = pointtraverse(); - } - - if (b->verbose) { - if (unuverts > delvertcount) { - if (delsteinercount > 0l) { - if (unuverts > (delvertcount + delsteinercount)) { - printf(" Removed %ld exterior input vertices.\n", - unuverts - delvertcount - delsteinercount); - } - printf(" Removed %ld exterior Steiner vertices.\n", delsteinercount); - } else { - printf(" Removed %ld exterior input vertices.\n", - unuverts - delvertcount); - } - } - } - - - // Connect new hull tets. - for (i = 0; i < hullarray->objects; i++) { - parytet = (triface *) fastlookup(hullarray, i); - hulltet = *parytet; - for (j = 0; j < 3; j++) { - esym(hulltet, neightet); - if (neightet.tet[neightet.ver & 3] == NULL) { - tspivot(hulltet, checksh); - assert(checksh.sh != NULL); - // Get the next subface in the same face ring of checksh. It must - // exist, otherwise, checksh is either a dangling subface (which - // should be removed already), or it is not a hull face. - sfnext(checksh, casingout); - assert(casingout.sh != NULL); - // Go to the hull side. - sesymself(casingout); - stpivot(casingout, casface); - assert(ishulltet(casface)); - esymself(casface); - assert(casface.tet[casface.ver & 3] == NULL); - // Bond the two hull tets together. - bond(neightet, casface); - } - enextself(hulltet); - } - } - -} else { // '-c' option is set. - - - long bak_subface_count = subfaces->items; - long bak_segment_count = subsegs->items; - - // In this case, we regard every hull face/edge is a subface/segment. - for (i = 0; i < tetarray->objects; i++) { - parytet = (triface *) fastlookup(tetarray, i); - // Only need the hull tet to find convex hull faces. - if ((point) parytet->tet[7] == dummypoint) { - hulltet.tet = parytet->tet; - hulltet.ver = 3; // The hull face. - tspivot(hulltet, checksh); // SELF_CHECK - if (checksh.sh == NULL) { - // Create a subface. - makeshellface(subfaces, &checksh); - pa = org(hulltet); - pb = dest(hulltet); - pc = apex(hulltet); - setsorg(checksh, pa); - setsdest(checksh, pb); - setsapex(checksh, pc); - // Create the point-to-subface map. - sptr = sencode(checksh); - setpoint2sh(pa, sptr); - setpoint2sh(pb, sptr); - setpoint2sh(pc, sptr); - } - // Insert this subface. - // Note: Even the subface is already exist, it may have been - // disconnected from its adjacent tets. - tsbond(hulltet, checksh); - fsym(hulltet, neightet); - assert(infected(neightet)); - sesymself(checksh); - tsbond(neightet, checksh); - sesymself(checksh); - // Create three segments. + // Connect new hull tets. + for (i = 0; i < newhullfacearray->objects; i++) { + parytet = (triface *) fastlookup(newhullfacearray, i); + fsym(*parytet, neightet); + // Get the new hull tet. + fsym(neightet, hulltet); for (j = 0; j < 3; j++) { - tsspivot1(hulltet, checkseg); - if (checkseg.sh == NULL) { - // Create a segment. - makeshellface(subsegs, &checkseg); - pa = org(hulltet); - pb = dest(hulltet); - setshvertices(checkseg, pa, pb, NULL); - // Insert the segment into the mesh. - tetloop = hulltet; - pc = apex(hulltet); - checksh.sh = NULL; + esym(hulltet, casface); + if (casface.tet[casface.ver & 3] == NULL) { + // Since the boundary of the domain may not be a manifold, we + // find the adjacent hull face by traversing the tets in the + // exterior (which are all infected tets). + neightet = *parytet; while (1) { - tssbond1(tetloop, checkseg); - tspivot(tetloop, checksh); - if (checksh.sh != NULL) { - ssbond1(checksh, checkseg); - sbond1(checkseg, checksh); - } - fnextself(tetloop); - if (apex(tetloop) == pc) break; + fnextself(neightet); + if (!infected(neightet)) break; } - sstbond1(checkseg, tetloop); + if (!ishulltet(neightet)) { + // An interior tet. Get the new hull tet. + fsymself(neightet); + esymself(neightet); + } + // Bond them together. + bond(casface, neightet); } enextself(hulltet); + enextself(*parytet); + } // j + } // i + + if (subfacstack->objects > 0l) { + // Remove all subfaces which do not attach to any tetrahedron. + // Segments which are not attached to any subfaces and tets + // are deleted too. + face casingout, casingin; + long delsegcount = 0l; + + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + if (i == 0) { + if (b->verbose) { + printf("Warning: Removing an open face (%d, %d, %d)\n", + pointmark(sorg(*parysh)), pointmark(sdest(*parysh)), + pointmark(sapex(*parysh))); + } + } + // Dissolve this subface from face links. + for (j = 0; j < 3; j++) { + spivot(*parysh, casingout); + sspivot(*parysh, checkseg); + if (casingout.sh != NULL) { + casingin = casingout; + while (1) { + spivot(casingin, checksh); + if (checksh.sh == parysh->sh) break; + casingin = checksh; + } + if (casingin.sh != casingout.sh) { + // Update the link: ... -> casingin -> casingout ->... + sbond1(casingin, casingout); + } else { + // Only one subface at this edge is left. + sdissolve(casingout); + } + if (checkseg.sh != NULL) { + // Make sure the segment does not connect to a dead one. + ssbond(casingout, checkseg); + } + } else { + if (checkseg.sh != NULL) { + // The segment is also dead. + if (delsegcount == 0) { + if (b->verbose) { + printf("Warning: Removing a dangling segment (%d, %d)\n", + pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); + } + } + shellfacedealloc(subsegs, checkseg.sh); + delsegcount++; + } + } + senextself(*parysh); + } // j + // Delete this subface. + shellfacedealloc(subfaces, parysh->sh); + } // i + if (b->verbose) { + printf(" Deleted %ld subfaces.\n", subfacstack->objects); + if (delsegcount > 0) { + printf(" Deleted %ld segments.\n", delsegcount); + } } - // Save this hull tet in list. - hullarray->newindex((void **) &parytet1); - *parytet1 = hulltet; + subfacstack->restart(); + } // if (subfacstack->objects > 0l) + + if (cavetetvertlist->objects > 0l) { + // Some vertices may lie in exterior. Marke them as UNUSEDVERTEX. + long delvertcount = unuverts; + long delsteinercount = 0l; + + for (i = 0; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + decode(point2tet(*parypt), neightet); + if (infected(neightet)) { + // Found an exterior vertex. + if (pointmark(*parypt) > + (in->numberofpoints - (in->firstnumber ? 0 : 1))) { + // A Steiner point. + if (pointtype(*parypt) == FREESEGVERTEX) { + st_segref_count--; + } else if (pointtype(*parypt) == FREEFACETVERTEX) { + st_facref_count--; + } else { + assert(pointtype(*parypt) == FREEVOLVERTEX); + st_volref_count--; + } + delsteinercount++; + if (steinerleft > 0) steinerleft++; + } + setpointtype(*parypt, UNUSEDVERTEX); + unuverts++; + } + } + + if (b->verbose) { + if (unuverts > delvertcount) { + if (delsteinercount > 0l) { + if (unuverts > (delvertcount + delsteinercount)) { + printf(" Removed %ld exterior input vertices.\n", + unuverts - delvertcount - delsteinercount); + } + printf(" Removed %ld exterior Steiner vertices.\n", + delsteinercount); + } else { + printf(" Removed %ld exterior input vertices.\n", + unuverts - delvertcount); + } + } + } + cavetetvertlist->restart(); + // Comment: 'subvertstack' will be cleaned in routine + // suppresssteinerpoints(). + } // if (cavetetvertlist->objects > 0l) + + // Update the hull size. + hullsize += (newhullfacearray->objects - hullarray->objects); + + // Delete all exterior tets and old hull tets. + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + tetrahedrondealloc(parytet->tet); } - } // i + tetarray->restart(); - hullsize += hullarray->objects; - - if (subfacstack->objects > 0) { - // Uninfect the collected exterior subfaces. - for (i = 0; i < subfacstack->objects; i++) { - parysh = (face *) fastlookup(subfacstack, i); - suninfect(*parysh); + for (i = 0; i < hullarray->objects; i++) { + parytet = (triface *) fastlookup(hullarray, i); + tetrahedrondealloc(parytet->tet); } - } + hullarray->restart(); + + delete newhullfacearray; + } // if (!b->convex && (tetarray->objects > 0l)) + + if (b->convex && (tetarray->objects > 0l)) { // With -c option + // In this case, all exterior tets get a region marker '-1'. + assert(b->regionattrib > 0); // -A option must be enabled. + int attrnum = numelemattrib - 1; + + for (i = 0; i < tetarray->objects; i++) { + parytet = (triface *) fastlookup(tetarray, i); + setelemattribute(parytet->tet, attrnum, -1); + } + tetarray->restart(); - if (b->regionattrib) { - // Only the hull tets need to be uninfected. for (i = 0; i < hullarray->objects; i++) { parytet = (triface *) fastlookup(hullarray, i); uninfect(*parytet); } - } else { - // Uninfect all collected tets. - for (i = 0; i < tetarray->objects; i++) { - parytet = (triface *) fastlookup(tetarray, i); - uninfect(*parytet); + hullarray->restart(); + + if (subfacstack->objects > 0l) { + for (i = 0; i < subfacstack->objects; i++) { + parysh = (face *) fastlookup(subfacstack, i); + suninfect(*parysh); + } + subfacstack->restart(); } - } - tetarray->restart(); + if (cavetetvertlist->objects > 0l) { + cavetetvertlist->restart(); + } + } // if (b->convex && (tetarray->objects > 0l)) - if (b->verbose) { - printf(" Created %ld convex hull boundary faces.\n", - subfaces->items - bak_subface_count); - printf(" Created %ld convex hull boundary edges.\n", - subsegs->items - bak_segment_count); - } - -} // if (b->convex) - - - // Set region attributes (the -A option). - if (b->regionattrib) { + if (b->regionattrib) { // With -A option. if (!b->quiet) { printf("Spreading region attributes.\n"); } + REAL volume; + int attr, maxattr = 0; // Choose a small number here. + int attrnum = numelemattrib - 1; + // Comment: The element region marker is at the end of the list of + // the element attributes. + int regioncount = 0; // If has user-defined region attributes. if (in->numberofregions > 0) { @@ -23846,6 +22366,9 @@ if (!b->convex) { for (i = 0; i < 5 * in->numberofregions; i += 5) { if (regiontets[i/5].tet != NULL) { attr = (int) in->regionlist[i + 3]; + if (attr > maxattr) { + maxattr = attr; + } volume = in->regionlist[i + 4]; tetarray->restart(); // Re-use this array. infect(regiontets[i/5]); @@ -23864,8 +22387,7 @@ if (!b->convex) { // Is the adjacent already checked? if (!infected(neightet)) { // Is this side protected by a subface? - tspivot(neightet, checksh); - if (checksh.sh == NULL) { + if (!issubface(neightet)) { infect(neightet); tetarray->newindex((void **) &parytet); *parytet = neightet; @@ -23899,8 +22421,7 @@ if (!b->convex) { // Is the adjacent tet already checked? if (!infected(neightet)) { // Is this side protected by a subface? - tspivot(neightet, checksh); - if (checksh.sh == NULL) { + if (!issubface(neightet)) { infect(neightet); tetarray->newindex((void **) &parytet); *parytet = neightet; @@ -23923,49 +22444,49 @@ if (!b->convex) { tetloop.tet = tetrahedrontraverse(); } - if (b->verbose) { - assert(regioncount > 0); + //assert(regioncount > 0); if (regioncount > 1) { printf(" Found %d subdomains.\n", regioncount); } else { - printf(" Found 1 domain.\n"); + printf(" Found %d domain.\n", regioncount); } } } // if (b->regionattrib) - if (b->regionattrib && (in->numberofregions > 0)) { // If has -A option. + if (regiontets != NULL) { delete [] regiontets; } delete tetarray; delete hullarray; -if (!b->convex) { + if (!b->convex) { // No -c option + // The mesh is non-convex now. + nonconvex = 1; - // The mesh is non-convex now. - nonconvex = 1; - - - // Push all hull tets into 'flipstack'. - tetrahedrons->traversalinit(); - tetloop.ver = 11; // The face opposite to dummypoint. - tetloop.tet = alltetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - if ((point) tetloop.tet[7] == dummypoint) { - flippush(flipstack, &tetloop); - } + // Push all hull tets into 'flipstack'. + tetrahedrons->traversalinit(); + tetloop.ver = 11; // The face opposite to dummypoint. tetloop.tet = alltetrahedrontraverse(); - } + while (tetloop.tet != (tetrahedron *) NULL) { + if ((point) tetloop.tet[7] == dummypoint) { + fsym(tetloop, neightet); + flippush(flipstack, &neightet); + } + tetloop.tet = alltetrahedrontraverse(); + } - // Peel "slivers" off the hull. - lawsonflip3d(NULL, 4, 1, 0, 0); - - if (b->verbose && (opt_sliver_peels > 0l)) { - printf(" Peeled %ld hull slivers.\n", opt_sliver_peels); - } - -} + flipconstraints fc; + fc.enqflag = 2; + long sliver_peel_count = lawsonflip3d(&fc); + if (sliver_peel_count > 0l) { + if (b->verbose) { + printf(" Removed %ld hull slivers.\n", sliver_peel_count); + } + } + unflipqueue->restart(); + } // if (!b->convex) } /////////////////////////////////////////////////////////////////////////////// @@ -23989,20 +22510,33 @@ void tetgenmesh::reconstructmesh() REAL angtol, ang; int eextras, marker = 0; int bondflag; + int t1ver; int idx, i, j, k; if (!b->quiet) { printf("Reconstructing mesh ...\n"); } - // Default assume the mesh is non-convex. - nonconvex = 1; - // Create a map from indices to vertices. + if (b->convex) { // -c option. + // Assume the mesh is convex. Exterior tets have region attribute -1. + assert(in->numberoftetrahedronattributes > 0); + } else { + // Assume the mesh is non-convex. + nonconvex = 1; + } + + // Create a map from indices to vertices. makeindex2pointmap(idx2verlist); + // 'idx2verlist' has length 'in->numberofpoints + 1'. + if (in->firstnumber == 1) { + idx2verlist[0] = dummypoint; // Let 0th-entry be dummypoint. + } // Allocate an array that maps each vertex to its adjacent tets. ver2tetarray = new tetrahedron[in->numberofpoints + 1]; - for (i = 0; i < in->numberofpoints; i++) { + //for (i = 0; i < in->numberofpoints + 1; i++) { + for (i = in->firstnumber; i < in->numberofpoints + in->firstnumber; i++) { + setpointtype(idx2verlist[i], VOLVERTEX); // initial type. ver2tetarray[i] = NULL; } @@ -24012,7 +22546,6 @@ void tetgenmesh::reconstructmesh() idx = i * in->numberofcorners; for (j = 0; j < 4; j++) { p[j] = idx2verlist[in->tetrahedronlist[idx++]]; - setpointtype(p[j], VOLVERTEX); // initial type. } // Check the orientation. ori = orient3d(p[0], p[1], p[2], p[3]); @@ -24060,7 +22593,6 @@ void tetgenmesh::reconstructmesh() p[2] = apex(tetloop); // c prevchktet = tetloop; do { - assert(checktet.ver < 4); // SELF_CHECK q[0] = org(checktet); // a' q[1] = dest(checktet); // b' q[2] = apex(checktet); // c' @@ -24246,51 +22778,54 @@ void tetgenmesh::reconstructmesh() } // i } // if (in->trifacelist) - // Indentify subfaces from the mesh. - // Create subfaces for hull faces (if they're not subface yet) and - // interior faces which separate two different materials. - eextras = in->numberoftetrahedronattributes; - tetrahedrons->traversalinit(); - tetloop.tet = tetrahedrontraverse(); - while (tetloop.tet != (tetrahedron *) NULL) { - for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { - tspivot(tetloop, neighsh); - if (neighsh.sh == NULL) { - bondflag = 0; - fsym(tetloop, checktet); - if (ishulltet(checktet)) { - bondflag = 1; // A hull face. - } else { - if (eextras > 0) { - if (elemattribute(tetloop.tet, eextras - 1) != - elemattribute(checktet.tet, eextras - 1)) { - bondflag = 1; // An interior interface. - } + // Indentify subfaces from the mesh. + // Create subfaces for hull faces (if they're not subface yet) and + // interior faces which separate two different materials. + eextras = in->numberoftetrahedronattributes; + tetrahedrons->traversalinit(); + tetloop.tet = tetrahedrontraverse(); + while (tetloop.tet != (tetrahedron *) NULL) { + for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { + tspivot(tetloop, neighsh); + if (neighsh.sh == NULL) { + bondflag = 0; + fsym(tetloop, checktet); + if (ishulltet(checktet)) { + // A hull face. + if (!b->convex) { + bondflag = 1; // Insert a hull subface. + } + } else { + if (eextras > 0) { + if (elemattribute(tetloop.tet, eextras - 1) != + elemattribute(checktet.tet, eextras - 1)) { + bondflag = 1; // Insert an interior interface. } } - if (bondflag) { - // Create a new subface. - makeshellface(subfaces, &subloop); - p[0] = org(tetloop); - p[1] = dest(tetloop); - p[2] = apex(tetloop); - setshvertices(subloop, p[0], p[1], p[2]); - // Create the point-to-subface map. - sptr = sencode(subloop); - for (j = 0; j < 3; j++) { - setpointtype(p[j], FACETVERTEX); // initial type. - setpoint2sh(p[j], sptr); - } - setshellmark(subloop, 0); // Default marker. - // Insert the subface into the mesh. - tsbond(tetloop, subloop); - sesymself(subloop); - tsbond(checktet, subloop); - } // if (bondflag) - } // if (neighsh.sh == NULL) - } - tetloop.tet = tetrahedrontraverse(); + } + if (bondflag) { + // Create a new subface. + makeshellface(subfaces, &subloop); + p[0] = org(tetloop); + p[1] = dest(tetloop); + p[2] = apex(tetloop); + setshvertices(subloop, p[0], p[1], p[2]); + // Create the point-to-subface map. + sptr = sencode(subloop); + for (j = 0; j < 3; j++) { + setpointtype(p[j], FACETVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(subloop, 0); // Default marker. + // Insert the subface into the mesh. + tsbond(tetloop, subloop); + sesymself(subloop); + tsbond(checktet, subloop); + } // if (bondflag) + } // if (neighsh.sh == NULL) } + tetloop.tet = tetrahedrontraverse(); + } // Connect subfaces together. subfaces->traversalinit(); @@ -24324,11 +22859,8 @@ void tetgenmesh::reconstructmesh() subloop.sh = shellfacetraverse(subfaces); } - //if (b->verbose) { - // printf(" Created %ld subfaces.\n", subfaces->items); - //} - // Segments will be introudced. + // Segments will be introduced. if (in->edgelist != NULL) { // A .edge file is given. It may contain boundary edges. Insert them. for (i = 0; i < in->numberofedges; i++) { @@ -24390,90 +22922,187 @@ void tetgenmesh::reconstructmesh() } // i } // if (in->edgelist) - // Identify segments from the mesh. - // Create segments for non-manifold edges (which are shared by more - // than two subfaces), and for non-coplanar edges, i.e., two subfaces - // form an dihedral angle > 'b->facet_ang_tol' (degree). - angtol = b->facet_ang_tol / 180.0 * PI; - subfaces->traversalinit(); - subloop.shver = 0; - subloop.sh = shellfacetraverse(subfaces); - while (subloop.sh != (shellface *) NULL) { - for (i = 0; i < 3; i++) { - sspivot(subloop, segloop); - if (segloop.sh == NULL) { - // Check if this edge is a segment. - bondflag = 0; - // Counter the number of subfaces at this edge. - idx = 0; - nextsh = subloop; - while (1) { - idx++; - spivotself(nextsh); - if (nextsh.sh == subloop.sh) break; - } - if (idx != 2) { - // It's a non-manifold edge. Insert a segment. + // Identify segments from the mesh. + // Create segments for non-manifold edges (which are shared by more + // than two subfaces), and for non-coplanar edges, i.e., two subfaces + // form an dihedral angle > 'b->facet_ang_tol' (degree). + angtol = b->facet_ang_tol / 180.0 * PI; + subfaces->traversalinit(); + subloop.shver = 0; + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != (shellface *) NULL) { + for (i = 0; i < 3; i++) { + sspivot(subloop, segloop); + if (segloop.sh == NULL) { + // Check if this edge is a segment. + bondflag = 0; + // Counter the number of subfaces at this edge. + idx = 0; + nextsh = subloop; + while (1) { + idx++; + spivotself(nextsh); + if (nextsh.sh == subloop.sh) break; + } + if (idx != 2) { + // It's a non-manifold edge. Insert a segment. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + bondflag = 1; + } else { + spivot(subloop, neighsh); + if (shellmark(subloop) != shellmark(neighsh)) { + // It's an interior interface. Insert a segment. p[0] = sorg(subloop); p[1] = sdest(subloop); bondflag = 1; } else { - // Check the dihedral angle formed by the two subfaces. - spivot(subloop, neighsh); - p[0] = sorg(subloop); - p[1] = sdest(subloop); - p[2] = sapex(subloop); - p[3] = sapex(neighsh); - ang = facedihedral(p[0], p[1], p[2], p[3]); - if (ang > PI) ang = 2 * PI - ang; - if (ang < angtol) { - bondflag = 1; + if (!b->convex) { + // Check the dihedral angle formed by the two subfaces. + p[0] = sorg(subloop); + p[1] = sdest(subloop); + p[2] = sapex(subloop); + p[3] = sapex(neighsh); + ang = facedihedral(p[0], p[1], p[2], p[3]); + if (ang > PI) ang = 2 * PI - ang; + if (ang < angtol) { + bondflag = 1; + } } } - if (bondflag) { - // Create a new subface. - makeshellface(subsegs, &segloop); - setshvertices(segloop, p[0], p[1], NULL); - // Create the point-to-segment map. - sptr = sencode(segloop); - for (j = 0; j < 2; j++) { - setpointtype(p[j], RIDGEVERTEX); // initial type. - setpoint2sh(p[j], sptr); + } + if (bondflag) { + // Create a new segment. + makeshellface(subsegs, &segloop); + setshvertices(segloop, p[0], p[1], NULL); + // Create the point-to-segment map. + sptr = sencode(segloop); + for (j = 0; j < 2; j++) { + setpointtype(p[j], RIDGEVERTEX); // initial type. + setpoint2sh(p[j], sptr); + } + setshellmark(segloop, 0); // Initially has no marker. + // Insert the subface into the mesh. + stpivot(subloop, tetloop); + q[2] = apex(tetloop); + while (1) { + tssbond1(tetloop, segloop); + tspivot(tetloop, neighsh); + if (neighsh.sh != NULL) { + ssbond1(neighsh, segloop); } - setshellmark(segloop, marker); - // Insert the subface into the mesh. - stpivot(subloop, tetloop); - q[2] = apex(tetloop); - while (1) { - tssbond1(tetloop, segloop); - tspivot(tetloop, neighsh); - if (neighsh.sh != NULL) { - ssbond1(neighsh, segloop); - } - fnextself(tetloop); - if (apex(tetloop) == q[2]) break; - } // while (1) - // Remember an adjacent tet for this segment. - sstbond1(segloop, tetloop); - sbond1(segloop, subloop); - } // if (bondflag) - } // if (neighsh.sh == NULL) - senextself(subloop); - } - subloop.sh = shellfacetraverse(subfaces); - } + fnextself(tetloop); + if (apex(tetloop) == q[2]) break; + } // while (1) + // Remember an adjacent tet for this segment. + sstbond1(segloop, tetloop); + sbond1(segloop, subloop); + } // if (bondflag) + } // if (neighsh.sh == NULL) + senextself(subloop); + } // i + subloop.sh = shellfacetraverse(subfaces); + } // Remember the number of input segments. insegments = subsegs->items; - //if (b->verbose) { - // printf(" Created %ld segments.\n", subsegs->items); - //} + if (!b->nobisect || checkconstraints) { + // Mark Steiner points on segments and facets. + // - all vertices which remaining type FEACTVERTEX become + // Steiner points in facets (= FREEFACERVERTEX). + // - vertices on segment need to be checked. + face* segperverlist; + int* idx2seglist; + face parentseg, nextseg; + verttype vt; + REAL area, len, l1, l2; + int fmarker; + + makepoint2submap(subsegs, idx2seglist, segperverlist); + + points->traversalinit(); + point ptloop = pointtraverse(); + while (ptloop != NULL) { + vt = pointtype(ptloop); + if (vt == VOLVERTEX) { + setpointtype(ptloop, FREEVOLVERTEX); + st_volref_count++; + } else if (vt == FACETVERTEX) { + setpointtype(ptloop, FREEFACETVERTEX); + st_facref_count++; + } else if (vt == RIDGEVERTEX) { + idx = pointmark(ptloop) - in->firstnumber; + if ((idx2seglist[idx + 1] - idx2seglist[idx]) == 2) { + i = idx2seglist[idx]; + parentseg = segperverlist[i]; + nextseg = segperverlist[i + 1]; + sesymself(nextseg); + p[0] = sorg(nextseg); + p[1] = sdest(parentseg); + // Check if three points p[0], ptloop, p[2] are (nearly) collinear. + len = distance(p[0], p[1]); + l1 = distance(p[0], ptloop); + l2 = distance(ptloop, p[1]); + if (((l1 + l2 - len) / len) < b->epsilon) { + // They are (nearly) collinear. + setpointtype(ptloop, FREESEGVERTEX); + // Connect nextseg and parentseg together at ptloop. + senextself(nextseg); + senext2self(parentseg); + sbond(nextseg, parentseg); + st_segref_count++; + } + } + } + ptloop = pointtraverse(); + } + + // Are there area constraints? + if (b->quality && (in->facetconstraintlist != (REAL *) NULL)) { + // Set maximum area constraints on facets. + for (i = 0; i < in->numberoffacetconstraints; i++) { + fmarker = (int) in->facetconstraintlist[i * 2]; + area = in->facetconstraintlist[i * 2 + 1]; + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + if (shellmark(subloop) == fmarker) { + setareabound(subloop, area); + } + subloop.sh = shellfacetraverse(subfaces); + } + } + } + + // Are there length constraints? + if (b->quality && (in->segmentconstraintlist != (REAL *) NULL)) { + // Set maximum length constraints on segments. + int e1, e2; + for (i = 0; i < in->numberofsegmentconstraints; i++) { + e1 = (int) in->segmentconstraintlist[i * 3]; + e2 = (int) in->segmentconstraintlist[i * 3 + 1]; + len = in->segmentconstraintlist[i * 3 + 2]; + // Search for edge [e1, e2]. + idx = e1 - in->firstnumber; + for (j = idx2seglist[idx]; j < idx2seglist[idx + 1]; j++) { + parentseg = segperverlist[j]; + if (pointmark(sdest(parentseg)) == e2) { + setareabound(parentseg, len); + break; + } + } + } + } + + delete [] idx2seglist; + delete [] segperverlist; + } + // Set global flags. checksubsegflag = 1; checksubfaceflag = 1; - //nonconvex = 1; delete [] idx2verlist; delete [] ver2tetarray; @@ -24495,33 +23124,37 @@ int tetgenmesh::scoutpoint(point searchpt, triface *searchtet, int randflag) { point pa, pb, pc, pd; enum locateresult loc = OUTSIDE; - REAL vol, ori1, ori2, ori3, ori4; - int iter; + REAL vol, ori1, ori2 = 0, ori3 = 0, ori4 = 0; + int t1ver; - if (searchtet->tet == NULL) { - *searchtet = recenttet; + + // Randomly select a good starting tet. + if (randflag) { + randomsample(searchpt, searchtet); + } else { + if (searchtet->tet == NULL) { + *searchtet = recenttet; + } } + loc = locate(searchpt, searchtet); - iter = 0; - while (1) { - // Randonmly select a good starting tet. - if (randflag) { - randomsample(searchpt, searchtet); + if (loc == OUTSIDE) { + if (b->convex) { // -c option + // The point lies outside of the convex hull. + return (int) loc; } - loc = locate(searchpt, searchtet, 0); - if (loc == OUTSIDE) { - // Not found. This happens when the mesh is not convex. - if (!randflag) break; - iter++; - if (iter > 3) { - searchtet->tet = NULL; - break; - } - } else { - // Found the point. - break; + // Test if it lies nearly on the hull face. + // Reuse vol, ori1. + pa = org(*searchtet); + pb = dest(*searchtet); + pc = apex(*searchtet); + vol = triarea(pa, pb, pc); + ori1 = orient3dfast(pa, pb, pc, searchpt); + if (fabs(ori1 / vol) < b->epsilon) { + loc = ONFACE; // On face (or on edge, or on vertex). + fsymself(*searchtet); } - } // while (1) + } if (loc != OUTSIDE) { // Round the result of location. @@ -24529,11 +23162,11 @@ int tetgenmesh::scoutpoint(point searchpt, triface *searchtet, int randflag) pb = dest(*searchtet); pc = apex(*searchtet); pd = oppo(*searchtet); - vol = orient3d(pa, pb, pc, pd); - ori1 = orient3d(pa, pb, pc, searchpt); - ori2 = orient3d(pb, pa, pd, searchpt); - ori3 = orient3d(pc, pb, pd, searchpt); - ori4 = orient3d(pa, pc, pd, searchpt); + vol = orient3dfast(pa, pb, pc, pd); + ori1 = orient3dfast(pa, pb, pc, searchpt); + ori2 = orient3dfast(pb, pa, pd, searchpt); + ori3 = orient3dfast(pc, pb, pd, searchpt); + ori4 = orient3dfast(pa, pc, pd, searchpt); if (fabs(ori1 / vol) < b->epsilon) ori1 = 0; if (fabs(ori2 / vol) < b->epsilon) ori2 = 0; if (fabs(ori3 / vol) < b->epsilon) ori3 = 0; @@ -24548,30 +23181,31 @@ int tetgenmesh::scoutpoint(point searchpt, triface *searchtet, int randflag) pc = apex(*searchtet); pd = oppo(*searchtet); - vol = orient3d(pa, pb, pc, pd); - assert(vol < 0); // vol != 0 + vol = orient3dfast(pa, pb, pc, pd); + if (vol < 0) { + ori1 = orient3dfast(pa, pb, pc, searchpt); + if (fabs(ori1 / vol) < b->epsilon) ori1 = 0; // Rounding. + if (ori1 <= 0) { + ori2 = orient3dfast(pb, pa, pd, searchpt); + if (fabs(ori2 / vol) < b->epsilon) ori2 = 0; + if (ori2 <= 0) { + ori3 = orient3dfast(pc, pb, pd, searchpt); + if (fabs(ori3 / vol) < b->epsilon) ori3 = 0; + if (ori3 <= 0) { + ori4 = orient3dfast(pa, pc, pd, searchpt); + if (fabs(ori4 / vol) < b->epsilon) ori4 = 0; + if (ori4 <= 0) { + // Found the tet. Return its location. + break; + } // ori4 + } // ori3 + } // ori2 + } // ori1 + } - ori1 = orient3d(pa, pb, pc, searchpt); - if (fabs(ori1 / vol) < b->epsilon) ori1 = 0; // Rounding. - if (ori1 <= 0) { - ori2 = orient3d(pb, pa, pd, searchpt); - if (fabs(ori2 / vol) < b->epsilon) ori2 = 0; - if (ori2 <= 0) { - ori3 = orient3d(pc, pb, pd, searchpt); - if (fabs(ori3 / vol) < b->epsilon) ori3 = 0; - if (ori3 <= 0) { - ori4 = orient3d(pa, pc, pd, searchpt); - if (fabs(ori4 / vol) < b->epsilon) ori4 = 0; - if (ori4 <= 0) { - // Found the tet. Return its location. - break; - } // ori4 - } // ori3 - } // ori2 - } // ori1 - - searchtet->tet = bgm->tetrahedrontraverse(); + searchtet->tet = tetrahedrontraverse(); } // while (searchtet->tet != NULL) + nonregularcount++; // Re-use this counter. } if (searchtet->tet != NULL) { @@ -24678,11 +23312,11 @@ REAL tetgenmesh::getpointmeshsize(point searchpt, triface *searchtet, int iloc) if ((pts[0][pointmtrindex] > 0) && (pts[1][pointmtrindex] > 0) && (pts[2][pointmtrindex] > 0) && (pts[3][pointmtrindex] > 0)) { // P1 interpolation. - volume = orient3d(pts[0], pts[1], pts[2], pts[3]); - vol[0] = orient3d(searchpt, pts[1], pts[2], pts[3]); - vol[1] = orient3d(pts[0], searchpt, pts[2], pts[3]); - vol[2] = orient3d(pts[0], pts[1], searchpt, pts[3]); - vol[3] = orient3d(pts[0], pts[1], pts[2], searchpt); + volume = orient3dfast(pts[0], pts[1], pts[2], pts[3]); + vol[0] = orient3dfast(searchpt, pts[1], pts[2], pts[3]); + vol[1] = orient3dfast(pts[0], searchpt, pts[2], pts[3]); + vol[2] = orient3dfast(pts[0], pts[1], searchpt, pts[3]); + vol[3] = orient3dfast(pts[0], pts[1], pts[2], searchpt); for (i = 0; i < 4; i++) { wei[i] = fabs(vol[i] / volume); size += (wei[i] * pts[i][pointmtrindex]); @@ -24740,6 +23374,11 @@ void tetgenmesh::interpolatemeshsize() if (!b->quiet) { printf("Interpolating mesh size ...\n"); } + + long bak_nonregularcount = nonregularcount; + nonregularcount = 0l; // Count the number of (slow) global searches. + long baksmaples = bgm->samples; + bgm->samples = 3l; count = 0; // Count the number of interpolated points. // Interpolate sizes for all points in the current mesh. @@ -24776,45 +23415,220 @@ void tetgenmesh::interpolatemeshsize() if (b->verbose) { printf(" Interoplated %d points.\n", count); + if (nonregularcount > 0l) { + printf(" Performed %ld brute-force searches.\n", nonregularcount); + } printf(" Size rangle [%.17g, %.17g].\n", minval, maxval); } + + bgm->samples = baksmaples; + nonregularcount = bak_nonregularcount; } /////////////////////////////////////////////////////////////////////////////// // // // insertconstrainedpoints() Insert a list of points into the mesh. // // // +// Assumption: The bounding box of the insert point set should be no larger // +// than the bounding box of the mesh. (Required by point sorting). // +// // /////////////////////////////////////////////////////////////////////////////// +void tetgenmesh::insertconstrainedpoints(point *insertarray, int arylen, + int rejflag) +{ + triface searchtet, spintet; + face splitsh; + face splitseg; + insertvertexflags ivf; + flipconstraints fc; + int randflag = 0; + int t1ver; + int i; + + if (b->verbose) { + printf(" Inserting %d constrained points\n", arylen); + } + + if (b->no_sort) { // -b/1 option. + if (b->verbose) { + printf(" Using the input order.\n"); + } + } else { + if (b->verbose) { + printf(" Permuting vertices.\n"); + } + point swappoint; + int randindex; + srand(arylen); + for (i = 0; i < arylen; i++) { + randindex = rand() % (i + 1); + swappoint = insertarray[i]; + insertarray[i] = insertarray[randindex]; + insertarray[randindex] = swappoint; + } + if (b->brio_hilbert) { // -b1 option + if (b->verbose) { + printf(" Sorting vertices.\n"); + } + hilbert_init(in->mesh_dim); + int ngroup = 0; + brio_multiscale_sort(insertarray, arylen, b->brio_threshold, + b->brio_ratio, &ngroup); + } else { // -b0 option. + randflag = 1; + } // if (!b->brio_hilbert) + } // if (!b->no_sort) + + long bak_nonregularcount = nonregularcount; + nonregularcount = 0l; + long baksmaples = samples; + samples = 3l; // Use at least 3 samples. Updated in randomsample(). + + long bak_seg_count = st_segref_count; + long bak_fac_count = st_facref_count; + long bak_vol_count = st_volref_count; + + // Initialize the insertion parameters. + if (b->incrflip) { // -l option + // Use incremental flip algorithm. + ivf.bowywat = 0; + ivf.lawson = 1; + ivf.validflag = 0; // No need to validate the cavity. + fc.enqflag = 2; + } else { + // Use Bowyer-Watson algorithm. + ivf.bowywat = 1; + ivf.lawson = 0; + ivf.validflag = 1; // Validate the B-W cavity. + } + ivf.rejflag = rejflag; + ivf.chkencflag = 0; + ivf.sloc = (int) INSTAR; + ivf.sbowywat = 3; + ivf.splitbdflag = 1; + ivf.respectbdflag = 1; + ivf.assignmeshsize = b->metric; + + encseglist = new arraypool(sizeof(face), 8); + encshlist = new arraypool(sizeof(badface), 8); + + // Insert the points. + for (i = 0; i < arylen; i++) { + // Find the location of the inserted point. + // Do not use 'recenttet', since the mesh may be non-convex. + searchtet.tet = NULL; + ivf.iloc = scoutpoint(insertarray[i], &searchtet, randflag); + + // Decide the right type for this point. + setpointtype(insertarray[i], FREEVOLVERTEX); // Default. + splitsh.sh = NULL; + splitseg.sh = NULL; + if (ivf.iloc == (int) ONEDGE) { + if (issubseg(searchtet)) { + tsspivot1(searchtet, splitseg); + setpointtype(insertarray[i], FREESEGVERTEX); + //ivf.rejflag = 0; + } else { + // Check if it is a subface edge. + spintet = searchtet; + while (1) { + if (issubface(spintet)) { + tspivot(spintet, splitsh); + setpointtype(insertarray[i], FREEFACETVERTEX); + //ivf.rejflag |= 1; + break; + } + fnextself(spintet); + if (spintet.tet == searchtet.tet) break; + } + } + } else if (ivf.iloc == (int) ONFACE) { + if (issubface(searchtet)) { + tspivot(searchtet, splitsh); + setpointtype(insertarray[i], FREEFACETVERTEX); + //ivf.rejflag |= 1; + } + } + + // Now insert the point. + if (insertpoint(insertarray[i], &searchtet, &splitsh, &splitseg, &ivf)) { + if (flipstack != NULL) { + // There are queued faces. Use flips to recover Delaunayness. + lawsonflip3d(&fc); + // There may be unflippable edges. Ignore them. + unflipqueue->restart(); + } + // Update the Steiner counters. + if (pointtype(insertarray[i]) == FREESEGVERTEX) { + st_segref_count++; + } else if (pointtype(insertarray[i]) == FREEFACETVERTEX) { + st_facref_count++; + } else { + st_volref_count++; + } + } else { + // Point is not inserted. + //pointdealloc(insertarray[i]); + setpointtype(insertarray[i], UNUSEDVERTEX); + unuverts++; + encseglist->restart(); + encshlist->restart(); + } + } // i + + delete encseglist; + delete encshlist; + + if (b->verbose) { + printf(" Inserted %ld (%ld, %ld, %ld) vertices.\n", + st_segref_count + st_facref_count + st_volref_count - + (bak_seg_count + bak_fac_count + bak_vol_count), + st_segref_count - bak_seg_count, st_facref_count - bak_fac_count, + st_volref_count - bak_vol_count); + if (nonregularcount > 0l) { + printf(" Performed %ld brute-force searches.\n", nonregularcount); + } + } + + nonregularcount = bak_nonregularcount; + samples = baksmaples; +} + void tetgenmesh::insertconstrainedpoints(tetgenio *addio) { - triface searchtet, spintet; - face checksh, *splitsh; - face checkseg, *splitseg; - point newpt; - insertvertexflags ivf; + point *insertarray, newpt; REAL x, y, z, w; - int attribindex, mtrindex; - int randflag; - int count, index; - int loc; - int i, j; + int index, attribindex, mtrindex; + int arylen, i, j; if (!b->quiet) { printf("Inserting constrained points ...\n"); } - randflag = 1; // Randomly select start tet for point location. - count = 0; + insertarray = new point[addio->numberofpoints]; + arylen = 0; index = 0; attribindex = 0; mtrindex = 0; for (i = 0; i < addio->numberofpoints; i++) { + x = addio->pointlist[index++]; + y = addio->pointlist[index++]; + z = addio->pointlist[index++]; + // Test if this point lies inside the bounding box. + if ((x < xmin) || (x > xmax) || (y < ymin) || (y > ymax) || + (z < zmin) || (z > zmax)) { + if (b->verbose) { + printf("Warning: Point #%d lies outside the bounding box. Ignored\n", + i + in->firstnumber); + } + continue; + } makepoint(&newpt, UNUSEDVERTEX); - x = newpt[0] = addio->pointlist[index++]; - y = newpt[1] = addio->pointlist[index++]; - z = newpt[2] = addio->pointlist[index++]; + newpt[0] = x; + newpt[1] = y; + newpt[2] = z; // Read the point attributes. (Including point weights.) for (j = 0; j < addio->numberofpointattributes; j++) { newpt[3 + j] = addio->pointattributelist[attribindex++]; @@ -24840,88 +23654,231 @@ void tetgenmesh::insertconstrainedpoints(tetgenio *addio) newpt[3] = w; // Regular tetrahedralization. } } - - // Find the location of the inserted point. - searchtet.tet = NULL; - ivf.iloc = scoutpoint(newpt, &searchtet, randflag); - if (ivf.iloc != (int) OUTSIDE) { - // Found the point. - // Initialize the insertion parameters. - if (b->psc) { - ivf.bowywat = 0; // Do not enlarge the initial cavity. - ivf.validflag = 0; // Do not validate the initial cavity. - } else { - ivf.bowywat = 3; // Use the "Bowyer-Watson" algorithm to form cavity. - ivf.validflag = 1; // Validate the B-W cavity. - } - ivf.lawson = 3; // ??? - ivf.rejflag = 0; // ??? - ivf.chkencflag = 0; - ivf.sloc = ivf.iloc; - ivf.sbowywat = ivf.bowywat; // Surface mesh options. - ivf.splitbdflag = 1; - ivf.respectbdflag = 1; - ivf.assignmeshsize = b->metric; - - splitsh = NULL; - splitseg = NULL; - - // Set the right point type. - if (ivf.iloc == (int) ONEDGE) { - tsspivot1(searchtet, checkseg); - if (checkseg.sh != NULL) { - setpointtype(newpt, RIDGEVERTEX); - spivot(checkseg, checksh); - splitsh = &checksh; - splitseg = &checkseg; - } else { - // Check if it is a subface edge. - spintet = searchtet; - while (1) { - tspivot(spintet, checksh); - if (checksh.sh != NULL) { - setpointtype(newpt, FACETVERTEX); - splitsh = &checksh; - break; - } - fnextself(spintet); - if (spintet.tet == searchtet.tet) break; - } - } - } else if (ivf.iloc == (int) ONFACE) { - tspivot(searchtet, checksh); - if (checksh.sh != NULL) { - setpointtype(newpt, FACETVERTEX); - splitsh = &checksh; - } - } else { - setpointtype(newpt, VOLVERTEX); - } - - // Insert the vertex. - loc = insertvertex(newpt, &searchtet, splitsh, splitseg, &ivf); - - if (loc == ivf.iloc) { - // The point has been inserted. - lawsonflip3d(newpt, 4, 0, ivf.chkencflag, 0); - count++; - } else { - if (!b->quiet) { - printf("Warning: Failed to insert point #%d. Ignored.\n", i); - } - pointdealloc(newpt); - } - } else { - if (!b->quiet) { - printf("Warning: Can't locate add point #%d. Ignored.\n", i); - } - pointdealloc(newpt); - } + insertarray[arylen] = newpt; + arylen++; } // i - if (b->verbose) { - printf(" Inserted %d of %d vertices.\n", count, addio->numberofpoints); + // Insert the points. + int rejflag = 0; // Do not check encroachment. + if (b->metric) { // -m option. + rejflag |= 4; // Reject it if it lies in some protecting balls. } + + insertconstrainedpoints(insertarray, arylen, rejflag); + + delete [] insertarray; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// meshcoarsening() Deleting (selected) vertices. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::collectremovepoints(arraypool *remptlist) +{ + point ptloop, *parypt; + verttype vt; + + // If a mesh sizing function is given. Collect vertices whose mesh size + // is greater than its smallest edge length. + if (b->metric) { // -m option + REAL len, smlen; + int i; + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + if (ptloop[pointmtrindex] > 0) { + // Get the smallest edge length at this vertex. + getvertexstar(1, ptloop, cavetetlist, cavetetvertlist, NULL); + parypt = (point *) fastlookup(cavetetvertlist, 0); + smlen = distance(ptloop, *parypt); + for (i = 1; i < cavetetvertlist->objects; i++) { + parypt = (point *) fastlookup(cavetetvertlist, i); + len = distance(ptloop, *parypt); + if (len < smlen) { + smlen = len; + } + } + cavetetvertlist->restart(); + cavetetlist->restart(); + if (smlen < ptloop[pointmtrindex]) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + } + ptloop = pointtraverse(); + } + if (b->verbose > 1) { + printf(" Coarsen %ld oversized points.\n", remptlist->objects); + } + } + + // If 'in->pointmarkerlist' exists, Collect vertices with markers '-1'. + if (in->pointmarkerlist != NULL) { + long bak_count = remptlist->objects; + points->traversalinit(); + ptloop = pointtraverse(); + int index = 0; + while (ptloop != NULL) { + if (index < in->numberofpoints) { + if (in->pointmarkerlist[index] == -1) { + pinfect(ptloop); + remptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + } else { + // Remaining are not input points. Stop here. + break; + } + index++; + ptloop = pointtraverse(); + } + if (b->verbose > 1) { + printf(" Coarsen %ld marked points.\n", remptlist->objects - bak_count); + } + } // if (in->pointmarkerlist != NULL) + + if (b->coarsen_param > 0) { // -R1/# + // Remove a coarsen_percent number of interior points. + assert((b->coarsen_percent > 0) && (b->coarsen_percent <= 1.0)); + if (b->verbose > 1) { + printf(" Coarsen %g percent of interior points.\n", + b->coarsen_percent * 100.0); + } + arraypool *intptlist = new arraypool(sizeof(point *), 10); + // Count the total number of interior points. + points->traversalinit(); + ptloop = pointtraverse(); + while (ptloop != NULL) { + vt = pointtype(ptloop); + if ((vt == VOLVERTEX) || (vt == FREEVOLVERTEX) || + (vt == FREEFACETVERTEX) || (vt == FREESEGVERTEX)) { + intptlist->newindex((void **) &parypt); + *parypt = ptloop; + } + ptloop = pointtraverse(); + } + if (intptlist->objects > 0l) { + // Sort the list of points randomly. + point *parypt_i, swappt; + int randindex, i; + srand(intptlist->objects); + for (i = 0; i < intptlist->objects; i++) { + randindex = rand() % (i + 1); // randomnation(i + 1); + parypt_i = (point *) fastlookup(intptlist, i); + parypt = (point *) fastlookup(intptlist, randindex); + // Swap this two points. + swappt = *parypt_i; + *parypt_i = *parypt; + *parypt = swappt; + } + int remcount = (int) ((REAL) intptlist->objects * b->coarsen_percent); + // Return the first remcount points. + for (i = 0; i < remcount; i++) { + parypt_i = (point *) fastlookup(intptlist, i); + if (!pinfected(*parypt_i)) { + pinfected(*parypt_i); + remptlist->newindex((void **) &parypt); + *parypt = *parypt_i; + } + } + } + delete intptlist; + } + + // Unmark all collected vertices. + for (int i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + puninfect(*parypt); + } +} + +void tetgenmesh::meshcoarsening() +{ + arraypool *remptlist; + + if (!b->quiet) { + printf("Mesh coarsening ...\n"); + } + + // Collect the set of points to be removed + remptlist = new arraypool(sizeof(point *), 10); + collectremovepoints(remptlist); + + if (remptlist->objects == 0l) { + delete remptlist; + return; + } + + if (b->verbose) { + if (remptlist->objects > 0l) { + printf(" Removing %ld points...\n", remptlist->objects); + } + } + + point *parypt, *plastpt; + long ms = remptlist->objects; + int nit = 0; + int bak_fliplinklevel = b->fliplinklevel; + b->fliplinklevel = -1; + autofliplinklevel = 1; // Init value. + int i; + + while (1) { + + if (b->verbose > 1) { + printf(" Removing points [%s level = %2d] #: %ld.\n", + (b->fliplinklevel > 0) ? "fixed" : "auto", + (b->fliplinklevel > 0) ? b->fliplinklevel : autofliplinklevel, + remptlist->objects); + } + + // Remove the list of points. + for (i = 0; i < remptlist->objects; i++) { + parypt = (point *) fastlookup(remptlist, i); + assert(pointtype(*parypt) != UNUSEDVERTEX); + if (removevertexbyflips(*parypt)) { + // Move the last entry to the current place. + plastpt = (point *) fastlookup(remptlist, remptlist->objects - 1); + *parypt = *plastpt; + remptlist->objects--; + i--; + } + } + + if (remptlist->objects > 0l) { + if (b->fliplinklevel >= 0) { + break; // We have tried all levels. + } + if (remptlist->objects == ms) { + nit++; + if (nit >= 3) { + // Do the last round with unbounded flip link level. + b->fliplinklevel = 100000; + } + } else { + ms = remptlist->objects; + if (nit > 0) { + nit--; + } + } + autofliplinklevel+=b->fliplinklevelinc; + } else { + // All points are removed. + break; + } + } // while (1) + + if (remptlist->objects > 0l) { + if (b->verbose) { + printf(" %ld points are not removed !\n", remptlist->objects); + } + } + + b->fliplinklevel = bak_fliplinklevel; + delete remptlist; } //// //// @@ -24934,497 +23891,199 @@ void tetgenmesh::insertconstrainedpoints(tetgenio *addio) /////////////////////////////////////////////////////////////////////////////// // // -// marksharpsegments() Mark sharp segments. // +// makefacetverticesmap() Create a map from facet to its vertices. // // // -// A segment is SHARP if there are two facets intersecting at it with an // -// internal dihedral angle (*) less than an angle \theta. // -// // -// A theoretical value of \theta is arccos(1/3) \approx 70.54 degree. It is // -// possible to relax it in practice. Here we choose \theta = 65 degree. // -// // -// The minimum dihedral angle between facets (minfacetdihed) is calulcated. // +// All facets will be indexed (starting from 0). The map is saved in two // +// global arrays: 'idx2facetlist' and 'facetverticeslist'. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::marksharpsegments() +void tetgenmesh::makefacetverticesmap() { - triface adjtet; - face startsh, spinsh, neighsh; - face segloop, nextseg, prevseg; - point eorg, edest; - REAL ang, smallang; - bool issharp; - int sharpcount; - - // For storing extremely small dihedral angle. - face *parysh, *parysh1; - REAL exsmallang; - int exsharpcount; + arraypool *facetvertexlist, *vertlist, **paryvertlist; + face subloop, neighsh, *parysh, *parysh1; + point pa, *ppt, *parypt; + verttype vt; + int facetindex, totalvertices; int i, j, k; - if (b->verbose > 0) { - printf(" Marking sharp segments.\n"); + if (b->verbose) { + printf(" Creating the facet vertices map.\n"); } - minfacetdihed = PI; - smallang = 65.0 * PI / 180.0; // 65 degree. - exsmallang = 5.0 * PI / 180.0; // 5 degree. - sharpcount = exsharpcount = 0; + facetvertexlist = new arraypool(sizeof(arraypool *), 10); + facetindex = totalvertices = 0; - // A segment s may have been split into many subsegments. Operate the one - // which contains the origin of s. Then mark the rest of subsegments. - subsegs->traversalinit(); - segloop.sh = shellfacetraverse(subsegs); - while (segloop.sh != (shellface *) NULL) { - segloop.shver = 0; - senext2(segloop, prevseg); - spivotself(prevseg); - if (prevseg.sh == NULL) { - // Operate on this seg s. - issharp = false; - spivot(segloop, startsh); - if (startsh.sh != NULL) { - // First check if two facets form an acute dihedral angle at s. - eorg = sorg(segloop); - edest = sdest(segloop); - spinsh = startsh; - while (1) { - if (sorg(spinsh) != eorg) sesymself(spinsh); - // Only do test when the spinsh is faceing inward. - stpivot(spinsh, adjtet); - if (adjtet.tet != NULL) { - if (!ishulltet(adjtet)) { - // Get the subface on the adjacent facet. - spivot(spinsh, neighsh); - // Do not calculate if it is self-bonded. - if ((neighsh.sh != NULL) && (neighsh.sh != spinsh.sh)) { - // Calculate the dihedral angle between the two subfaces. - ang = facedihedral(eorg, edest, sapex(spinsh), sapex(neighsh)); - // Only do check if a sharp angle has not been found. - if (!issharp) issharp = (ang < smallang); - // Remember the smallest facet dihedral angle. - minfacetdihed = minfacetdihed < ang ? minfacetdihed : ang; - if (ang < exsmallang) { - // It's an extremely small dihedral angle. - // Mark the two facets. - // To avoid too many Steiner points, do not refine them. - if (shelltype(spinsh) != SHARP) { - setshelltype(spinsh, SHARP); - cavesegshlist->newindex((void **) &parysh); - *parysh = spinsh; - } - if (shelltype(neighsh) != SHARP) { - setshelltype(neighsh, SHARP); - cavesegshlist->newindex((void **) &parysh); - *parysh = neighsh; - } - exsharpcount++; + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + if (!sinfected(subloop)) { + // A new facet. Create its vertices list. + vertlist = new arraypool(sizeof(point *), 8); + ppt = (point *) &(subloop.sh[3]); + for (k = 0; k < 3; k++) { + vt = pointtype(ppt[k]); + if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + pinfect(ppt[k]); + vertlist->newindex((void **) &parypt); + *parypt = ppt[k]; + } + } + sinfect(subloop); + caveshlist->newindex((void **) &parysh); + *parysh = subloop; + for (i = 0; i < caveshlist->objects; i++) { + parysh = (face *) fastlookup(caveshlist, i); + setfacetindex(*parysh, facetindex); + for (j = 0; j < 3; j++) { + if (!isshsubseg(*parysh)) { + spivot(*parysh, neighsh); + assert(neighsh.sh != NULL); + if (!sinfected(neighsh)) { + pa = sapex(neighsh); + if (!pinfected(pa)) { + vt = pointtype(pa); + if ((vt != FREESEGVERTEX) && (vt != FREEFACETVERTEX)) { + pinfect(pa); + vertlist->newindex((void **) &parypt); + *parypt = pa; } } - } - } - // Go to the next facet. - spivotself(spinsh); - if (spinsh.sh == NULL) break; // A single subface case. - if (spinsh.sh == startsh.sh) break; - } - } // if (startsh.sh != NULL) - if (issharp) { - if (b->verbose > 2) { - printf(" Mark a sharp segment (%d, %d).\n", - pointmark(eorg), pointmark(edest)); - } - setshelltype(segloop, SHARP); - // The endpoint of this segment is acute. - if (pointtype(eorg) == RIDGEVERTEX) { - setpointtype(eorg, ACUTEVERTEX); - } else { - assert(pointtype(eorg) == ACUTEVERTEX); // SELF_CHECK - } - // Set the type for all subsegments at forwards. - edest = sdest(segloop); - senext(segloop, nextseg); - spivotself(nextseg); - while (nextseg.sh != NULL) { - setshelltype(nextseg, SHARP); - // Adjust the direction of nextseg. - nextseg.shver = 0; - if (sorg(nextseg) != edest) { - sesymself(nextseg); - } - assert(sorg(nextseg) == edest); - edest = sdest(nextseg); - // Go the next connected subsegment at edest. - senextself(nextseg); - spivotself(nextseg); - } - // The endpoint of this segment is acute. - if (pointtype(edest) == RIDGEVERTEX) { - setpointtype(edest, ACUTEVERTEX); - } else { - assert(pointtype(edest) == ACUTEVERTEX); // SELF_CHECK - } - sharpcount++; - } // if (issharp) - } // if (prevseg.sh == NULL) - segloop.sh = shellfacetraverse(subsegs); - } - - // Mark all facets at extremely small dihedral angles. - if (cavesegshlist->objects > 0) { - for (i = 0; i < cavesegshlist->objects; i++) { - parysh = (face *) fastlookup(cavesegshlist, i); - caveshlist->newindex((void **) &parysh1); - *parysh1 = *parysh; - for (j = 0; j < caveshlist->objects; j++) { - parysh1 = (face *) fastlookup(caveshlist, j); - spinsh = *parysh1; - for (k = 0; k < 3; k++) { - sspivot(spinsh, nextseg); - if (nextseg.sh == NULL) { - spivot(spinsh, neighsh); - if (shelltype(neighsh) != SHARP) { - setshelltype(neighsh, SHARP); + sinfect(neighsh); caveshlist->newindex((void **) &parysh1); *parysh1 = neighsh; } } - senextself(spinsh); - } // k - } // j + senextself(*parysh); + } + } // i + totalvertices += (int) vertlist->objects; + // Uninfect facet vertices. + for (k = 0; k < vertlist->objects; k++) { + parypt = (point *) fastlookup(vertlist, k); + puninfect(*parypt); + } caveshlist->restart(); - } // i - cavesegshlist->restart(); - } // if (cavesegshlist->objects > 0) + // Save this vertex list. + facetvertexlist->newindex((void **) &paryvertlist); + *paryvertlist = vertlist; + facetindex++; + } + subloop.sh = shellfacetraverse(subfaces); + } + + // All subfaces are infected. Uninfect them. + subfaces->traversalinit(); + subloop.sh = shellfacetraverse(subfaces); + while (subloop.sh != NULL) { + assert(sinfected(subloop)); + suninfect(subloop); + subloop.sh = shellfacetraverse(subfaces); + } if (b->verbose) { - if (sharpcount > 0) { - printf(" Found %d (%d) sharp segments.\n", sharpcount, exsharpcount); - } - printf(" Minimum fac-fac angle = %g.\n", minfacetdihed / PI * 180.0); + printf(" Found %ld facets.\n", facetvertexlist->objects); } + + idx2facetlist = new int[facetindex + 1]; + facetverticeslist = new point[totalvertices]; + + totalworkmemory += ((facetindex + 1) * sizeof(int) + + totalvertices * sizeof(point *)); + + idx2facetlist[0] = 0; + for (i = 0, k = 0; i < facetindex; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + idx2facetlist[i + 1] = (idx2facetlist[i] + (int) vertlist->objects); + for (j = 0; j < vertlist->objects; j++) { + parypt = (point *) fastlookup(vertlist, j); + facetverticeslist[k] = *parypt; + k++; + } + } + assert(k == totalvertices); + + // Free the lists. + for (i = 0; i < facetvertexlist->objects; i++) { + paryvertlist = (arraypool **) fastlookup(facetvertexlist, i); + vertlist = *paryvertlist; + delete vertlist; + } + delete facetvertexlist; } /////////////////////////////////////////////////////////////////////////////// // // -// decidefeaturepointsizes() Calculate sizes for all feature points. // -// // -// A feature point is either an acute vertex or a Steiner point on a sharp // -// segment. Each feature point p will be protected by a ball whose radius // -// is called its "feature size". // -// // -// NOTE: we should have already marked all features points in the two func- // -// tions: markacutevertices() and marksharpsegments(). Each feature point // -// has the type ACUTEVERTEX or FREESEGVERTEX. // -// // -// The feature size of a vertex is the minimum of the following sizes: // -// (0) the (approximated) local feature size (the distance to the second // -// nearest boundary) of the vertex; -// (1) the value specified in .mtr file (-m option); // -// (2) the cubic root of a fixed maximal volume constraint ('-a__'); // -// (3) the cubic root of a maximal volume constraint in a region ('-a'); // -// (4) the square root of a maximal area constraint in a .var file; // -// (5) a maximal length constraint in a .var file; // -// // -// If 'b->nobisect' ('-Y' option) is set, every input vertex has a size. It // -// is used to prevent creating too close Steiner points. // +// Check whether two segments, or a segment and a facet, or two facets are // +// adjacent to each other. // // // /////////////////////////////////////////////////////////////////////////////// -void tetgenmesh::decidefeaturepointsizes() +int tetgenmesh::segsegadjacent(face *seg1, face *seg2) { - arraypool *tetlist, *verlist; - triface starttet, *parytet; - face checksh, parentsh, shloop; - face checkseg, prevseg, nextseg, testseg; - point ploop, adjpt, e1, e2, *parypt; - REAL lfs_0, lfs_1, lfs_2; - REAL len, vol, maxlen = 0.0, varlen; - REAL ang, a, a1, a2, a3, prjpt[3], n[3]; - int featureflag, featurecount; - int i, j; + int segidx1 = getfacetindex(*seg1); + int segidx2 = getfacetindex(*seg2); - if (b->verbose > 0) { - printf(" Deciding feature-point sizes.\n"); + if (segidx1 == segidx2) return 0; + + point pa1 = segmentendpointslist[segidx1 * 2]; + point pb1 = segmentendpointslist[segidx1 * 2 + 1]; + point pa2 = segmentendpointslist[segidx2 * 2]; + point pb2 = segmentendpointslist[segidx2 * 2 + 1]; + + if ((pa1 == pa2) || (pa1 == pb2) || (pb1 == pa2) || (pb1 == pb2)) { + return 1; } - - // Initialize working lists. - tetlist = cavetetlist; - verlist = cavetetvertlist; - - if (b->fixedvolume) { - // A fixed volume constraint is imposed. This gives an upper bound of - // the maximal radius of the protect ball of a vertex. - maxlen = pow(6.0 * b->maxvolume, 1.0 / 3.0); - } - - // First, assign a size of p if p is a feature point or an input point and - // the -Y option is used. - featurecount = 0; - points->traversalinit(); - ploop = pointtraverse(); - while (ploop != (point) NULL) { - // Check if it is a feature point. - featureflag = 0; - // Only calculate the size if it has a size zero. - // The point may already has a positive size (-m option). - if (ploop[pointmtrindex] == 0) { - if (pointtype(ploop) == ACUTEVERTEX) { - featureflag = 1; - } else { - if (b->nobisect) { // '-Y' option - if ((pointtype(ploop) == RIDGEVERTEX) || - (pointtype(ploop) == FACETVERTEX) || - (pointtype(ploop) == VOLVERTEX)) { - featureflag = 1; // It is an input vertex. - } - } - } - } - if (featureflag) { - // Form star(p). - getvertexstar(1, ploop, tetlist, verlist, NULL); - // Calculate lfs_0(p), i.e., the smallest distance from p to a vertex. - // We approximate it by taking the distance of p to its nearest - // vertex in Link(p). - lfs_0 = longest; - for (i = 0; i < verlist->objects; i++) { - parypt = (point *) fastlookup(verlist, i); - adjpt = * parypt; - if (adjpt == dummypoint) { - continue; // Skip a dummypoint. - } - if (pointtype(adjpt) == FREESEGVERTEX) { - // A Steiner point. Get the subsegment. - sdecode(point2sh(adjpt), checkseg); - assert(checkseg.sh != NULL); - checkseg.shver = 0; - if (sdest(checkseg) != adjpt) { - sesymself(checkseg); - } - assert(sdest(checkseg) == adjpt); - // It is possible that the original segment of 'adjpt' does not - // have 'ploop' as an endpoint. - if (sorg(checkseg) == ploop) { - // Find the other end point of the original segment. - nextseg = checkseg; - while (1) { - senext(nextseg, testseg); - spivotself(testseg); - if (testseg.sh == NULL) break; - // Go to the next subseg. - nextseg = testseg; - // Adjust the direction of the nextseg. - nextseg.shver = 0; - if (sorg(nextseg) != adjpt) { - sesymself(nextseg); - } - assert(sorg(nextseg) == adjpt); - adjpt = sdest(nextseg); - } - } - } else if (pointtype(adjpt) == FREEFACETVERTEX) { - // Ignore a Steiner point on facet. - continue; - } else if (pointtype(adjpt) == FREEVOLVERTEX) { - // Ignore a Steiner point in volume. - continue; - } - len = distance(ploop, adjpt); - if (lfs_0 > len) lfs_0 = len; - } // i - assert(lfs_0 < longest); // SELF_CHECK - ploop[pointmtrindex] = lfs_0; - // Calculate lfs_1(p), i.e., the smallest distance from p to a segment. - // We approximate it by restricting the segments in Link(p). - lfs_1 = lfs_0; - for (i = 0; i < tetlist->objects; i++) { - parytet = (triface *) fastlookup(tetlist, i); - for (j = 0; j < 3; j++) { - tsspivot1(*parytet, checkseg); - if (checkseg.sh != NULL) { - e1 = sorg(checkseg); - e2 = sdest(checkseg); - // Only do calculation if the projeciton of 'p' lies inside the - // segment [e1, e2]. - ang = interiorangle(ploop, e1, e2, NULL); - ang *= 2.0; - if (ang > PI) { - len = shortdistance(ploop, e1, e2); - if (lfs_1 > len) { - lfs_1 = len; - } - } - } - enextself(*parytet); - } // j - } // i - if (ploop[pointmtrindex] > lfs_1) { - ploop[pointmtrindex] = lfs_1; - } - // Calculate lfs_2(p), i.e., the smallest distance from p to a facet. - // We approximate it by restricting the facets in Link(p). - lfs_2 = lfs_0; - for (i = 0; i < tetlist->objects; i++) { - parytet = (triface *) fastlookup(tetlist, i); - tspivot(*parytet, checksh); - if (checksh.sh != NULL) { - adjpt = sorg(checksh); - e1 = sdest(checksh); - e2 = sapex(checksh); - // Only do calculation if the projeciton of 'p' lies inside the - // subface [adjpt, e1, e2]. - projpt2face(ploop, adjpt, e1, e2, prjpt); - facenormal(adjpt, e1, e2, n, 1, NULL); - a = sqrt(dot(n, n)); // area of [adjpt, e1, e2]. - if (a > 0) { - facenormal(adjpt, e1, prjpt, n, 1, NULL); - a1 = sqrt(dot(n, n)); - facenormal(e1, e2, prjpt, n, 1, NULL); - a2 = sqrt(dot(n, n)); - facenormal(e2, adjpt, prjpt, n, 1, NULL); - a3 = sqrt(dot(n, n)); - if ((fabs(a1 + a2 + a3 - a) / a) < b->epsilon) { - len = distance(ploop, prjpt); - if (lfs_2 > len) { - lfs_2 = len; - } - } - } else { - assert(0); // a degenerate triangle. - } // if (a > 0) - } - } - if (ploop[pointmtrindex] > lfs_2) { - ploop[pointmtrindex] = lfs_2; - } - if (b->fixedvolume) { - // A fixed volume constraint is imposed. Adjust H(p) <= maxlen. - if (ploop[pointmtrindex] > maxlen) { - ploop[pointmtrindex] = maxlen; - } - } - if (b->varvolume) { - // Variant volume constraints are imposed. Adjust H(p) <= varlen. - for (i = 0; i < tetlist->objects; i++) { - parytet = (triface *) fastlookup(tetlist, i); - starttet = *parytet; - vol = volumebound(starttet.tet); - if (vol > 0.0) { - varlen = pow(6 * vol, 1.0 / 3.0); - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; - } - } - } - } - // The size is calculated. - assert(ploop[pointmtrindex] > 0); // SELF_CHECK - // Clear working lists. - tetlist->restart(); - verlist->restart(); - featurecount++; - } // if (featureflag) - ploop = pointtraverse(); - } - - if (b->verbose) { - printf(" %d feature points.\n", featurecount); - } - - // Second only assign sizes for all Steiner points which were inserted on - // sharp segments. The sizes are interpolated from the endpoints of - // the segments. - featurecount = 0; - points->traversalinit(); - ploop = pointtraverse(); - while (ploop != (point) NULL) { - if (ploop[pointmtrindex] == 0.0) { - if (pointtype(ploop) == FREESEGVERTEX) { - // A Steiner point on segment. - featureflag = 0; - sdecode(point2sh(ploop), checkseg); - assert(checkseg.sh != NULL); - checkseg.shver = 0; - e1 = farsorg(checkseg); // The origin of this seg. - e2 = farsdest(checkseg); // The dest of this seg. - if (b->nobisect) { // '-Y' option. - assert(e1[pointmtrindex] > 0); // SELF_CHECK - assert(e2[pointmtrindex] > 0); // SELF_CHECK - featureflag = 1; - } else { - if ((e1[pointmtrindex] > 0) && (e2[pointmtrindex] > 0)) { - featureflag = 1; - } - } - if (featureflag) { - len = distance(e1, e2); - lfs_0 = distance(e1, ploop); // Re-use lfs_0. - ploop[pointmtrindex] = e1[pointmtrindex] - + (lfs_0 / len) * (e2[pointmtrindex] - e1[pointmtrindex]); - featurecount++; - } // if (featureflag) - } - } // if (ploop[pointmtrindex] == 0.0) - ploop = pointtraverse(); - } - - if (b->verbose && (featurecount > 0)) { - printf(" %d Steiner feature points.\n", featurecount); - } - - if (checkconstraints) { - // A .var file exists. Adjust feature sizes. And make sure that every - // corner of a constraining facet get a size. - if (in->facetconstraintlist) { - // Have facet area constrains. - subfaces->traversalinit(); - shloop.sh = shellfacetraverse(subfaces); - while (shloop.sh != (shellface *) NULL) { - varlen = areabound(shloop); - if (varlen > 0.0) { - // Check if the three corners are feature points. - varlen = sqrt(varlen); - for (j = 0; j < 3; j++) { - ploop = (point) shloop.sh[3 + j]; - if (ploop[pointmtrindex] > 0) { - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; - } - } else { - // This corner has no size yet. Set it. - ploop[pointmtrindex] = varlen; - } - } // j - } - shloop.sh = shellfacetraverse(subfaces); - } - } - if (in->segmentconstraintlist) { - // Have facet area constrains. - subsegs->traversalinit(); - shloop.sh = shellfacetraverse(subsegs); - while (shloop.sh != (shellface *) NULL) { - varlen = areabound(shloop); - if (varlen > 0.0) { - // Check if the two endpoints are feature points. - for (j = 0; j < 2; j++) { - ploop = (point) shloop.sh[3 + j]; - if (ploop[pointmtrindex] > 0.0) { - if (ploop[pointmtrindex] > varlen) { - ploop[pointmtrindex] = varlen; - } - } else { - ploop[pointmtrindex] = varlen; - } - } // j - } - shloop.sh = shellfacetraverse(subsegs); - } - } - } // if (checkconstraints) + return 0; } +int tetgenmesh::segfacetadjacent(face *subseg, face *subsh) +{ + int segidx = getfacetindex(*subseg); + point pa = segmentendpointslist[segidx * 2]; + point pb = segmentendpointslist[segidx * 2 + 1]; + + pinfect(pa); + pinfect(pb); + + int fidx = getfacetindex(*subsh); + int count = 0, i; + + for (i = idx2facetlist[fidx]; i < idx2facetlist[fidx+1]; i++) { + if (pinfected(facetverticeslist[i])) count++; + } + + puninfect(pa); + puninfect(pb); + + return count == 1; +} + +int tetgenmesh::facetfacetadjacent(face *subsh1, face *subsh2) +{ + int count = 0, i; + + int fidx1 = getfacetindex(*subsh1); + int fidx2 = getfacetindex(*subsh2); + + if (fidx1 == fidx2) return 0; + + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + pinfect(facetverticeslist[i]); + } + + for (i = idx2facetlist[fidx2]; i < idx2facetlist[fidx2+1]; i++) { + if (pinfected(facetverticeslist[i])) count++; + } + + // Uninfect the vertices. + for (i = idx2facetlist[fidx1]; i < idx2facetlist[fidx1+1]; i++) { + puninfect(facetverticeslist[i]); + } + + return count > 0; +} /////////////////////////////////////////////////////////////////////////////// // // @@ -25434,22 +24093,24 @@ void tetgenmesh::decidefeaturepointsizes() int tetgenmesh::checkseg4encroach(point pa, point pb, point checkpt) { - REAL ang; - REAL prjpt[3], u, v, t; - // Check if the point lies inside the diametrical sphere of this seg. - ang = interiorangle(checkpt, pa, pb, NULL); - ang *= 2.0; // Compare it to PI/2 (90 degree). + REAL v1[3], v2[3]; - if (ang > PI) { + v1[0] = pa[0] - checkpt[0]; + v1[1] = pa[1] - checkpt[1]; + v1[2] = pa[2] - checkpt[2]; + v2[0] = pb[0] - checkpt[0]; + v2[1] = pb[1] - checkpt[1]; + v2[2] = pb[2] - checkpt[2]; + + if (dot(v1, v2) < 0) { // Inside. - if (b->metric || b->nobisect) { // -m or -Y option. + if (b->metric) { // -m option. if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0)) { - // In this case, we're sure that the projection of 'checkpt' lies - // inside the segment [a,b]. Check if 'checkpt' lies inside the - // protecting region of this seg. + // The projection of 'checkpt' lies inside the segment [a,b]. + REAL prjpt[3], u, v, t; projpt2edge(checkpt, pa, pb, prjpt); - // Get the mesh size at the location 'prjpt'. + // Interoplate the mesh size at the location 'prjpt'. u = distance(pa, pb); v = distance(pa, prjpt); t = v / u; @@ -25487,21 +24148,11 @@ int tetgenmesh::checkseg4encroach(point pa, point pb, point checkpt) int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) { - triface searchtet, spintet; - point forg, fdest, eapex; - REAL ccent[3], len, r, d, diff; + REAL ccent[3], len, r; int i; - REAL ti, tj, t, midpt[3]; - REAL ang; - int eid; - - forg = sorg(*chkseg); - fdest = sdest(*chkseg); - - if (b->verbose > 2) { - printf(" Check segment (%d, %d)\n", pointmark(forg), pointmark(fdest)); - } + point forg = sorg(*chkseg); + point fdest = sdest(*chkseg); // Initialize the return values. encpt = NULL; @@ -25516,21 +24167,13 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) // First check its quality. if (checkconstraints && (areabound(*chkseg) > 0.0)) { if (len > areabound(*chkseg)) { - if (b->verbose > 2) { - printf(" has too large size, len = %g (> %g)\n", len, - areabound(*chkseg)); - } qflag = 1; return 1; } } - if (b->fixedvolume) { // if (b->varvolume || b->fixedvolume) { + if (b->fixedvolume) { if ((len * len * len) > b->maxvolume) { - if (b->verbose > 2) { - printf(" has too large size, len^3 = %g (> %g)\n", len*len*len, - b->maxvolume); - } qflag = 1; return 1; } @@ -25545,33 +24188,15 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) } } - if (b->psc) { - // Check if it satisfies the approximation requirement. - eid = shellmark(*chkseg); - if ((pointtype(forg) == ACUTEVERTEX)||(pointtype(forg) == RIDGEVERTEX)) { - ti = in->getvertexparamonedge(in->geomhandle, pointmark(forg), eid); - } else { - ti = pointgeomuv(forg, 0); - } - if ((pointtype(fdest) == ACUTEVERTEX)||(pointtype(fdest) == RIDGEVERTEX)) { - tj = in->getvertexparamonedge(in->geomhandle, pointmark(fdest), eid); - } else { - tj = pointgeomuv(fdest, 0); - } - t = 0.5 * (ti + tj); - in->getsteineronedge(in->geomhandle, eid, t, midpt); - ang = interiorangle(midpt, forg, fdest, NULL) / PI * 180.0; - if (ang < b->facet_ang_tol) { - // Refine this segment. - if (b->verbose > 2) { - printf(" has bad approx, ang = %g\n", ang); - } - qflag = 1; - return 1; - } - } // if (b->psc) // Second check if it is encroached. + // Comment: There may exist more than one encroaching points of this segment. + // The 'encpt' returns the one which is closet to it. + triface searchtet, spintet; + point eapex; + REAL d, diff, smdist = 0; + int t1ver; + sstpivot1(*chkseg, searchtet); spintet = searchtet; while (1) { @@ -25582,8 +24207,21 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) if (fabs(diff) / r < b->epsilon) diff = 0.0; // Rounding. if (diff < 0) { // This segment is encroached by eapex. - encpt = eapex; - break; + if (useinsertradius) { + if (encpt == NULL) { + encpt = eapex; + smdist = d; + } else { + // Choose the closet encroaching point. + if (d < smdist) { + encpt = eapex; + smdist = d; + } + } + } else { + encpt = eapex; + break; + } } } fnextself(spintet); @@ -25591,9 +24229,6 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) } // while (1) if (encpt != NULL) { - if (b->verbose > 2) { - printf(" is encroached by %d\n", pointmark(encpt)); - } return 1; } @@ -25612,79 +24247,136 @@ int tetgenmesh::checkseg4split(face *chkseg, point& encpt, int& qflag) // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::splitsegment(face *splitseg, point encpt, int qflag, +int tetgenmesh::splitsegment(face *splitseg, point encpt, REAL rrp, + point encpt1, point encpt2, int qflag, int chkencflag) { - triface searchtet; - face searchsh; - point newpt, pa, pb; - insertvertexflags ivf; - REAL len; //, len1; - int loc; - //int i; + point pa = sorg(*splitseg); + point pb = sdest(*splitseg); - pa = sorg(*splitseg); - pb = sdest(*splitseg); - len = distance(pa, pb); - if (b->verbose > 2) { - printf(" Split segment (%d, %d).\n", pointmark(pa), pointmark(pb)); - } - if (qflag == 0) { - if (shelltype(*splitseg) == SHARP) { - // Do not split it (due to a very small angle) even it is encroached. - // Avoid creating too many Steiner points. - return 0; - } - // Quickly check if we CAN split this segment. - if (encpt == NULL) { - // Do not split this segment if the length is smaller than the mesh - // size at one of its endpoints. - if ((len < pa[pointmtrindex]) || (len < pb[pointmtrindex])) { - return 0; + if ((encpt == NULL) && (qflag == 0)) { + if (useinsertradius) { + // Do not split this segment if the length is smaller than the smaller + // insertion radius at its endpoints. + REAL len = distance(pa, pb); + REAL smrrv = getpointinsradius(pa); + REAL rrv = getpointinsradius(pb); + if (rrv > 0) { + if (smrrv > 0) { + if (rrv < smrrv) { + smrrv = rrv; + } + } else { + smrrv = rrv; + } + } + if (smrrv > 0) { + if ((fabs(smrrv - len) / len) < b->epsilon) smrrv = len; + if (len < smrrv) { + return 0; + } } } } + if (b->nobisect) { // With -Y option. + // Only split this segment if it is allowed to be split. + if (checkconstraints) { + // Check if it has a non-zero length bound. + if (areabound(*splitseg) == 0) { + // It is not allowed. However, if all of facets containing this seg + // is allowed to be split, we still split it. + face parentsh, spinsh; + //splitseg.shver = 0; + spivot(*splitseg, parentsh); + if (parentsh.sh == NULL) { + return 0; // A dangling segment. Do not split it. + } + spinsh = parentsh; + while (1) { + if (areabound(spinsh) == 0) break; + spivotself(spinsh); + if (spinsh.sh == parentsh.sh) break; + } + if (areabound(spinsh) == 0) { + // All facets at this seg are not allowed to be split. + return 0; // Do not split it. + } + } + } else { + return 0; // Do not split this segment. + } + } // if (b->nobisect) + + triface searchtet; + face searchsh; + point newpt; + insertvertexflags ivf; + makepoint(&newpt, FREESEGVERTEX); getsteinerptonsegment(splitseg, encpt, newpt); - // Split the segment by the Bowyer-Watson algorithm. sstpivot1(*splitseg, searchtet); ivf.iloc = (int) ONEDGE; - if (b->psc) { - ivf.bowywat = 0; // Do not enlarge the initial cavity. - ivf.validflag = 0; // Do not validate the initial cavity. - } else { - ivf.bowywat = 3; // Preserve subsegments and subfaces; - ivf.validflag = 1; // Validate the B-W cavity. - } - ivf.lawson = b->conforming ? 3 : 1; // Check flip for internal new faces?. + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + ivf.validflag = 1; // Validate the B-W cavity. + ivf.lawson = 2; // Do flips to recover Delaunayness. ivf.rejflag = 0; // Do not check encroachment of new segments/facets. - if ((encpt == NULL) && (qflag == 0)) { + if (b->metric) { ivf.rejflag |= 4; // Do check encroachment of protecting balls. } ivf.chkencflag = chkencflag; - ivf.sloc = ivf.iloc; - ivf.sbowywat = ivf.bowywat; // Surface mesh options. + ivf.sloc = (int) INSTAR; // ivf.iloc; + ivf.sbowywat = 3; // ivf.bowywat; // Surface mesh options. ivf.splitbdflag = 1; ivf.respectbdflag = 1; - ivf.assignmeshsize = 1; + ivf.assignmeshsize = b->metric; + ivf.smlenflag = useinsertradius; - loc = insertvertex(newpt, &searchtet, &searchsh, splitseg, &ivf); - if (loc == (int) ONEDGE) { - if (b->verbose > 2) { - printf(" Point inserted successfully on segment.\n"); - } - // Flip non-locally Delaunay faces at the link of its star. - lawsonflip3d(newpt, 4, 0, chkencflag, 0); + if (insertpoint(newpt, &searchtet, &searchsh, splitseg, &ivf)) { st_segref_count++; if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + // Update 'rv' (to be the shortest distance). + REAL rv = ivf.smlen, rp; + if (pointtype(ivf.parentpt) == FREESEGVERTEX) { + face parentseg1, parentseg2; + sdecode(point2sh(newpt), parentseg1); + sdecode(point2sh(ivf.parentpt), parentseg2); + if (segsegadjacent(&parentseg1, &parentseg2)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } else if (pointtype(ivf.parentpt) == FREEFACETVERTEX) { + face parentseg, parentsh; + sdecode(point2sh(newpt), parentseg); + sdecode(point2sh(ivf.parentpt), parentsh); + if (segfacetadjacent(&parentseg, &parentsh)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } + setpointinsradius(newpt, rv); + } + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); + } return 1; } else { + // Point is not inserted. pointdealloc(newpt); return 0; } @@ -25698,7 +24390,7 @@ int tetgenmesh::splitsegment(face *splitseg, point encpt, int qflag, void tetgenmesh::repairencsegs(int chkencflag) { - badface *bface; + face *bface; point encpt = NULL; int qflag = 0; @@ -25706,20 +24398,25 @@ void tetgenmesh::repairencsegs(int chkencflag) // if an unlimited number of Steiner points is allowed. while ((badsubsegs->items > 0) && (steinerleft != 0)) { badsubsegs->traversalinit(); - bface = badfacetraverse(badsubsegs); + bface = (face *) badsubsegs->traverse(); while ((bface != NULL) && (steinerleft != 0)) { - // A queued segment may have been deleted (split). - if (bface->ss.sh[3] != NULL) { - // A queued segment may have been processed. - if (smarktest2ed(bface->ss)) { - sunmarktest2(bface->ss); - if (checkseg4split(&(bface->ss), encpt, qflag)) { - splitsegment(&(bface->ss), encpt, qflag, chkencflag); + // Skip a deleleted element. + if (bface->shver >= 0) { + // A queued segment may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued segment may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + if (checkseg4split(bface, encpt, qflag)) { + splitsegment(bface, encpt, 0, NULL, NULL, qflag, chkencflag); + } } } + // Remove this entry from list. + bface->shver = -1; // Signal it as a deleted element. + badsubsegs->dealloc((void *) bface); } - badfacedealloc(badsubsegs, bface); // Remove this entry from list. - bface = badfacetraverse(badsubsegs); + bface = (face *) badsubsegs->traverse(); } } @@ -25732,19 +24429,37 @@ void tetgenmesh::repairencsegs(int chkencflag) assert(0); // Unknown case. } badsubsegs->traversalinit(); - bface = badfacetraverse(badsubsegs); + bface = (face *) badsubsegs->traverse(); while (bface != NULL) { - if (bface->ss.sh[3] != NULL) { - if (smarktest2ed(bface->ss)) { - sunmarktest2(bface->ss); + // Skip a deleleted element. + if (bface->shver >= 0) { + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } } } - bface = badfacetraverse(badsubsegs); + bface = (face *) badsubsegs->traverse(); } badsubsegs->restart(); } } +/////////////////////////////////////////////////////////////////////////////// +// // +// enqueuesubface() Queue a subface or a subsegment for encroachment chk. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::enqueuesubface(memorypool *pool, face *chkface) +{ + if (!smarktest2ed(*chkface)) { + smarktest2(*chkface); // Only queue it once. + face *queface = (face *) pool->alloc(); + *queface = *chkface; + } +} + /////////////////////////////////////////////////////////////////////////////// // // // checkfac4encroach() Check if a subface is encroached by a point. // @@ -25755,8 +24470,6 @@ int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, REAL* cent, REAL* r) { REAL rd, len; - REAL prjpt[3], n[3]; - REAL a, a1, a2, a3; circumsphere(pa, pb, pc, NULL, cent, &rd); assert(rd != 0); @@ -25765,10 +24478,12 @@ int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, if (len < rd) { // The point lies inside the circumsphere of this face. - if (b->metric || b->nobisect) { // -m or -Y option. + if (b->metric) { // -m option. if ((pa[pointmtrindex] > 0) && (pb[pointmtrindex] > 0) && (pc[pointmtrindex] > 0)) { // Get the projection of 'checkpt' in the plane of pa, pb, and pc. + REAL prjpt[3], n[3]; + REAL a, a1, a2, a3; projpt2face(checkpt, pa, pb, pc, prjpt); // Get the face area of [a,b,c]. facenormal(pa, pb, pc, n, 1, NULL); @@ -25790,10 +24505,6 @@ int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, if (len < rd) { return 1; // Encroached. } - } else { - // The projection lies outside the face. - // In this case, 'p' must close to another face or a segment than - // to this one. We ignore this boundary face. } } else { return 1; // No protecting ball. Encroached. @@ -25824,14 +24535,10 @@ int tetgenmesh::checkfac4encroach(point pa, point pb, point pc, point checkpt, int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, REAL *cent) { - triface searchtet; - face checksh; // *parysh; - face checkseg; point pa, pb, pc; - REAL area, rd, len, sintheta; + REAL area, rd, len; REAL A[4][4], rhs[4], D; int indx[4]; - REAL elen[3]; int i; encpt = NULL; @@ -25841,11 +24548,6 @@ int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, pb = sdest(*chkfac); pc = sapex(*chkfac); - if (b->verbose > 2) { - printf(" Check subface (%d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc)); - } - // Compute the coefficient matrix A (3x3). A[0][0] = pb[0] - pa[0]; A[0][1] = pb[1] - pa[1]; @@ -25858,100 +24560,97 @@ int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, area = 0.5 * sqrt(dot(A[2], A[2])); // The area of [a,b,c]. // Compute the right hand side vector b (3x1). - elen[0] = dot(A[0], A[0]); // edge [a,b] - elen[1] = dot(A[1], A[1]); // edge [a,c] - rhs[0] = 0.5 * elen[0]; - rhs[1] = 0.5 * elen[1]; + rhs[0] = 0.5 * dot(A[0], A[0]); // edge [a,b] + rhs[1] = 0.5 * dot(A[1], A[1]); // edge [a,c] rhs[2] = 0.0; // Solve the 3 by 3 equations use LU decomposition with partial - // pivoting and backward and forward substitute.. - if (lu_decmp(A, 3, indx, &D, 0)) { - lu_solve(A, 3, indx, rhs, 0); - cent[0] = pa[0] + rhs[0]; - cent[1] = pa[1] + rhs[1]; - cent[2] = pa[2] + rhs[2]; - rd = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); + // pivoting and backward and forward substitute. + if (!lu_decmp(A, 3, indx, &D, 0)) { + // A degenerate triangle. + assert(0); + } - if (b->verbose > 3) { - printf(" circent: (%g, %g, %g)\n", cent[0], cent[1], cent[2]); - printf(" cirradi: %g\n", rd); + lu_solve(A, 3, indx, rhs, 0); + cent[0] = pa[0] + rhs[0]; + cent[1] = pa[1] + rhs[1]; + cent[2] = pa[2] + rhs[2]; + rd = sqrt(rhs[0] * rhs[0] + rhs[1] * rhs[1] + rhs[2] * rhs[2]); + + if (checkconstraints && (areabound(*chkfac) > 0.0)) { + // Check if the subface has too big area. + if (area > areabound(*chkfac)) { + qflag = 1; + return 1; } + } - // Check the quality (radius-edge ratio) of this subface. - // Re-use variables 'A', 'rhs', and 'D'. - A[2][0] = pb[0] - pc[0]; - A[2][1] = pb[1] - pc[1]; - A[2][2] = pb[2] - pc[2]; - elen[2] = dot(A[2], A[2]); // edge [b,c] - // Get the shortest edge length in 'D'. - D = elen[0]; // edge [a,b] - for (i = 1; i < 3; i++) { - if (D > elen[i]) D = elen[i]; + if (b->fixedvolume) { + if ((area * sqrt(area)) > b->maxvolume) { + qflag = 1; + return 1; } + } + if (b->varvolume) { + triface adjtet; + REAL volbnd; + int t1ver; - D = sqrt(D); - if (b->verbose > 3) { - printf(" shortest edge length = %g\n", D); - } - - rhs[3] = rd / D; // The radius-edge ratio. - - // Check if this subface is nearly degenerate. - sintheta = 1.0 / (2.0 * rhs[3]); - if (sintheta < sintheta_tol) { - // Do not split this subface. Save it in list. - if (b->verbose > 1) { - printf(" !! A degenerated subface, theta = %g (deg)\n", - asin(sintheta) / PI * 180.0); - } - return 0; // Do not split a degenerated subface. - } - - if (checkconstraints && (areabound(*chkfac) > 0.0)) { - // Check if the subface has too big area. - if (area > areabound(*chkfac)) { - if (b->verbose > 2) { - printf(" has too big area: %g (> %g)\n", area, - areabound(*chkfac)); - } + stpivot(*chkfac, adjtet); + if (!ishulltet(adjtet)) { + volbnd = volumebound(adjtet.tet); + if ((volbnd > 0) && (area * sqrt(area)) > volbnd) { qflag = 1; return 1; } } - - if (b->metric) { // -m option. Check mesh size. - // Check if the ccent lies outside one of the prot.balls at vertices. - if (((pa[pointmtrindex] > 0) && (rd > pa[pointmtrindex])) || - ((pb[pointmtrindex] > 0) && (rd > pb[pointmtrindex])) || - ((pc[pointmtrindex] > 0) && (rd > pc[pointmtrindex]))) { - qflag = 1; // Enforce mesh size. + fsymself(adjtet); + if (!ishulltet(adjtet)) { + volbnd = volumebound(adjtet.tet); + if ((volbnd > 0) && (area * sqrt(area)) > volbnd) { + qflag = 1; return 1; } } + } - - // Check if this subface is locally encroached. - for (i = 0; i < 2; i++) { - stpivot(*chkfac, searchtet); - if (!ishulltet(searchtet)) { - len = distance(oppo(searchtet), cent); - if ((fabs(len - rd) / rd) < b->epsilon) len = rd;// Rounding. - if (len < rd) { - if (b->verbose > 2) { - printf(" is encroached by point %d\n", - pointmark(oppo(searchtet))); - } - encpt = oppo(searchtet); - return 1; - } - } - sesymself(*chkfac); + if (b->metric) { // -m option. Check mesh size. + // Check if the ccent lies outside one of the prot.balls at vertices. + if (((pa[pointmtrindex] > 0) && (rd > pa[pointmtrindex])) || + ((pb[pointmtrindex] > 0) && (rd > pb[pointmtrindex])) || + ((pc[pointmtrindex] > 0) && (rd > pc[pointmtrindex]))) { + qflag = 1; // Enforce mesh size. + return 1; } - } + } - return 0; + triface searchtet; + REAL smlen = 0; + + // Check if this subface is locally encroached. + for (i = 0; i < 2; i++) { + stpivot(*chkfac, searchtet); + if (!ishulltet(searchtet)) { + len = distance(oppo(searchtet), cent); + if ((fabs(len - rd) / rd) < b->epsilon) len = rd;// Rounding. + if (len < rd) { + if (smlen == 0) { + smlen = len; + encpt = oppo(searchtet); + } else { + if (len < smlen) { + smlen = len; + encpt = oppo(searchtet); + } + } + //return 1; + } + } + sesymself(*chkfac); + } + + return encpt != NULL; //return 0; } /////////////////////////////////////////////////////////////////////////////// @@ -25960,125 +24659,171 @@ int tetgenmesh::checkfac4split(face *chkfac, point& encpt, int& qflag, // // // The subface may be encroached, or in bad-quality. It is split at its cir- // // cumcenter ('ccent'). Do not split it if 'ccent' encroaches upon any seg- // -// ments. Instead, one of the encroached segments is split. It is possible // -// that none of the encorached segments can be split. // +// ment. Instead, one of the encroached segments is split. It is possible // +// that none of the encroached segments can be split. // // // // The return value indicates whether a new point is inserted (> 0) or not // -// (= 0). Furthermore, it is inserted on an encorached segment (= 1) or in- // -// side the facet (= 2). // +// (= 0). Furthermore, it is inserted on an encroached segment (= 1) or // +// in-side the facet (= 2). // +// // +// 'encpt' is a vertex encroaching upon this subface, i.e., it causes the // +// split of this subface. If 'encpt' is NULL, then the cause of the split // +// this subface is a rejected tet circumcenter 'p', and 'encpt1' is the // +// parent of 'p'. // // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::splitsubface(face *splitfac, point encpt, int qflag, - REAL *ccent, int chkencflag) +int tetgenmesh::splitsubface(face *splitfac, point encpt, point encpt1, + int qflag, REAL *ccent, int chkencflag) { - badface *bface; - triface searchtet; + point pa = sorg(*splitfac); + point pb = sdest(*splitfac); + point pc = sapex(*splitfac); + + + + if (b->nobisect) { // With -Y option. + if (checkconstraints) { + // Only split if it is allowed to be split. + // Check if this facet has a non-zero constraint. + if (areabound(*splitfac) == 0) { + return 0; // Do not split it. + } + } else { + return 0; + } + } // if (b->nobisect) + face searchsh; - face checkseg, *paryseg; - point newpt, pa, pb, pc; insertvertexflags ivf; - REAL rd; - int splitflag; - int loc; + point newpt; + REAL rv = 0., rp; // Insertion radius of newpt. int i; - - pa = sorg(*splitfac); - pb = sdest(*splitfac); - pc = sapex(*splitfac); - - if (b->verbose > 2) { - printf(" Split subface (%d, %d, %d).\n", pointmark(pa), pointmark(pb), - pointmark(pc)); - } - - - // Quickly check if we CAN split this subface. - if (qflag == 0) { - // Do not split this subface if it forms a very small dihedral with - // another facet. Avoid creating too many Steiner points. - if (shelltype(*splitfac) == SHARP) { - return 0; - } - // Do not split this subface if the 'ccent' lies inside the protect balls - // of one of its vertices. - rd = distance(ccent, pa); - if ((rd <= pa[pointmtrindex]) || (rd <= pb[pointmtrindex]) || - (rd <= pc[pointmtrindex])) { - return 0; - } - } - // Initialize the inserting point. makepoint(&newpt, FREEFACETVERTEX); + // Split the subface at its circumcenter. + for (i = 0; i < 3; i++) newpt[i] = ccent[i]; - // Split the subface at its circumcenter. - for (i = 0; i < 3; i++) newpt[i] = ccent[i]; - // Search a subface which contains 'newpt'. - searchsh = *splitfac; - // Calculate an above point. It lies above the plane containing - // the subface [a,b,c], and save it in dummypoint. Moreover, - // the vector cent->dummypoint is the normal of the plane. - calculateabovepoint4(newpt, pa, pb, pc); - // Parameters: 'aflag' = 1, - above point exists. - // 'cflag' = 0, - non-convex, check co-planarity of the result. - // 'rflag' = 0, - no need to round the locating result. - ivf.iloc = (int) slocate(newpt, &searchsh, 1, 0, 0); - if ((ivf.iloc == (int) ONFACE) || (ivf.iloc == (int) ONEDGE)) { - // Insert this point. - } else { - pointdealloc(newpt); - return 0; + if (useinsertradius) { + if (encpt != NULL) { + rv = distance(newpt, encpt); + if (pointtype(encpt) == FREESEGVERTEX) { + face parentseg; + sdecode(point2sh(encpt), parentseg); + if (segfacetadjacent(&parentseg, splitfac)) { + rp = getpointinsradius(encpt); + if (rv < (sqrt(2.0) * rp)) { + // This insertion may cause no termination. + pointdealloc(newpt); + return 0; // Reject the insertion of newpt. + } + } + } else if (pointtype(encpt) == FREEFACETVERTEX) { + face parentsh; + sdecode(point2sh(encpt), parentsh); + if (facetfacetadjacent(&parentsh, splitfac)) { + rp = getpointinsradius(encpt); + if (rv < rp) { + pointdealloc(newpt); + return 0; // Reject the insertion of newpt. + } + } + } } + } // if (useinsertradius) + // Search a subface which contains 'newpt'. + searchsh = *splitfac; + // Calculate an above point. It lies above the plane containing + // the subface [a,b,c], and save it in dummypoint. Moreover, + // the vector cent->dummypoint is the normal of the plane. + calculateabovepoint4(newpt, pa, pb, pc); + // Parameters: 'aflag' = 1, - above point exists. + // 'cflag' = 0, - non-convex, check co-planarity of the result. + // 'rflag' = 0, - no need to round the locating result. + ivf.iloc = (int) slocate(newpt, &searchsh, 1, 0, 0); + + if (!((ivf.iloc == (int) ONFACE) || (ivf.iloc == (int) ONEDGE))) { + pointdealloc(newpt); + return 0; + } + + + triface searchtet; + face *paryseg; + int splitflag; // Insert the point. stpivot(searchsh, searchtet); //assert((ivf.iloc == (int) ONFACE) || (ivf.iloc == (int) ONEDGE)); - // Split the subface by the Bowyer-Watson algorithm. - ivf.bowywat = 3; // Preserve segments and subfaces. - ivf.lawson = b->conforming ? 3 : 1; + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + ivf.lawson = 2; ivf.rejflag = 1; // Do check the encroachment of segments. - if (qflag == 0) { - ivf.rejflag |= 4; // Reject it if it encroached upon any vertex. + if (b->metric) { + ivf.rejflag |= 4; // Do check encroachment of protecting balls. } ivf.chkencflag = chkencflag; - ivf.sloc = ivf.iloc; - ivf.sbowywat = ivf.bowywat; + ivf.sloc = (int) INSTAR; // ivf.iloc; + ivf.sbowywat = 3; // ivf.bowywat; ivf.splitbdflag = 1; ivf.validflag = 1; ivf.respectbdflag = 1; - ivf.assignmeshsize = 1; + ivf.assignmeshsize = b->metric; ivf.refineflag = 2; ivf.refinesh = searchsh; + ivf.smlenflag = useinsertradius; // Update the insertion radius. - loc = insertvertex(newpt, &searchtet, &searchsh, NULL, &ivf); - if (loc == (int) ivf.iloc) { - if (b->verbose > 2) { - printf(" Point inserted successfully on facet.\n"); - } - // Flip not locally Delaunay link facets. - lawsonflip3d(newpt, 4, 0, chkencflag, 0); + if (insertpoint(newpt, &searchtet, &searchsh, NULL, &ivf)) { st_facref_count++; if (steinerleft > 0) steinerleft--; + if (useinsertradius) { + // Update 'rv' (to be the shortest distance). + rv = ivf.smlen; + if (pointtype(ivf.parentpt) == FREESEGVERTEX) { + face parentseg, parentsh; + sdecode(point2sh(ivf.parentpt), parentseg); + sdecode(point2sh(newpt), parentsh); + if (segfacetadjacent(&parentseg, &parentsh)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < (sqrt(2.0) * rp)) { + rv = sqrt(2.0) * rp; // The relaxed insertion radius of 'newpt'. + } + } + } else if (pointtype(ivf.parentpt) == FREEFACETVERTEX) { + face parentsh1, parentsh2; + sdecode(point2sh(ivf.parentpt), parentsh1); + sdecode(point2sh(newpt), parentsh2); + if (facetfacetadjacent(&parentsh1, &parentsh2)) { + rp = getpointinsradius(ivf.parentpt); + if (rv < rp) { + rv = rp; // The relaxed insertion radius of 'newpt'. + } + } + } + setpointinsradius(newpt, rv); + } // if (useinsertradius) + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); + } return 1; } else { // Point was not inserted. - if (loc == (int) ENCSEGMENT) { - if (b->verbose > 2) { - printf(" Point encroached upon %ld segments.\n", - encseglist->objects); - } - assert(encseglist->objects > 0); - pointdealloc(newpt); + pointdealloc(newpt); + if (ivf.iloc == (int) ENCSEGMENT) { // Select an encroached segment and split it. splitflag = 0; for (i = 0; i < encseglist->objects; i++) { paryseg = (face *) fastlookup(encseglist, i); - if (splitsegment(paryseg, NULL, qflag, chkencflag | 1)) { + if (splitsegment(paryseg, NULL, rv, encpt, encpt1, qflag, + chkencflag | 1)) { splitflag = 1; // A point is inserted on a segment. break; } @@ -26088,22 +24833,18 @@ int tetgenmesh::splitsubface(face *splitfac, point encpt, int qflag, // Some segments may need to be repaired. repairencsegs(chkencflag | 1); // Queue this subface if it is still alive and not queued. - if (splitfac->sh[3] != NULL) { - if (!smarktest2ed(*splitfac)) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = *splitfac; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(*splitfac); // An alive badface. - } - } + //if ((splitfac->sh != NULL) && (splitfac->sh[3] != NULL)) { + // // Only queue it if 'qflag' is set. + // if (qflag) { + // enqueuesubface(badsubfacs, splitfac); + // } + //} } return splitflag; } else { - pointdealloc(newpt); return 0; } } - } /////////////////////////////////////////////////////////////////////////////// @@ -26114,7 +24855,7 @@ int tetgenmesh::splitsubface(face *splitfac, point encpt, int qflag, void tetgenmesh::repairencfacs(int chkencflag) { - badface *bface; + face *bface; point encpt = NULL; int qflag = 0; REAL ccent[3]; @@ -26123,20 +24864,24 @@ void tetgenmesh::repairencfacs(int chkencflag) // if an unlimited number of Steiner points is allowed. while ((badsubfacs->items > 0) && (steinerleft != 0)) { badsubfacs->traversalinit(); - bface = badfacetraverse(badsubfacs); + bface = (face *) badsubfacs->traverse(); while ((bface != NULL) && (steinerleft != 0)) { - // A queued subface may have been deleted (split). - if (bface->ss.sh[3] != NULL) { - // A queued subface may have been processed. - if (smarktest2ed(bface->ss)) { - sunmarktest2(bface->ss); - if (checkfac4split(&(bface->ss), encpt, qflag, ccent)) { - splitsubface(&(bface->ss), encpt, qflag, ccent, chkencflag); + // Skip a deleted element. + if (bface->shver >= 0) { + // A queued subface may have been deleted (split). + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + // A queued subface may have been processed. + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + if (checkfac4split(bface, encpt, qflag, ccent)) { + splitsubface(bface, encpt, NULL, qflag, ccent, chkencflag); + } } } + bface->shver = -1; // Signal it as a deleted element. + badsubfacs->dealloc((void *) bface); // Remove this entry from list. } - badfacedealloc(badsubfacs, bface); // Remove this entry from list. - bface = badfacetraverse(badsubfacs); + bface = (face *) badsubfacs->traverse(); } } @@ -26149,37 +24894,61 @@ void tetgenmesh::repairencfacs(int chkencflag) assert(0); // Unknown case. } badsubfacs->traversalinit(); - bface = badfacetraverse(badsubfacs); + bface = (face *) badsubfacs->traverse(); while (bface != NULL) { - if (bface->ss.sh[3] != NULL) { - if (smarktest2ed(bface->ss)) { - sunmarktest2(bface->ss); + // Skip a deleted element. + if (bface->shver >= 0) { + if ((bface->sh != NULL) && (bface->sh[3] != NULL)) { + if (smarktest2ed(*bface)) { + sunmarktest2(*bface); + } } } - bface = badfacetraverse(badsubfacs); + bface = (face *) badsubfacs->traverse(); } badsubfacs->restart(); } } +/////////////////////////////////////////////////////////////////////////////// +// // +// enqueuetetrahedron() Queue a tetrahedron for quality check. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::enqueuetetrahedron(triface *chktet) +{ + if (!marktest2ed(*chktet)) { + marktest2(*chktet); // Only queue it once. + triface *quetet = (triface *) badtetrahedrons->alloc(); + *quetet = *chktet; + } +} + /////////////////////////////////////////////////////////////////////////////// // // // checktet4split() Check if the tet needs to be split. // // // /////////////////////////////////////////////////////////////////////////////// - int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) { point pa, pb, pc, pd, *ppt; REAL vda[3], vdb[3], vdc[3]; REAL vab[3], vbc[3], vca[3]; REAL N[4][3], L[4], cosd[6], elen[6]; - REAL maxcosd, vol, volbnd, smlen, rd; + REAL maxcosd, vol, volbnd, smlen = 0, rd; REAL A[4][4], rhs[4], D; int indx[4]; int i, j; + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(chktet->tet, numelemattrib - 1) == -1.0) { + return 0; + } + } + qflag = 0; pd = (point) chktet->tet[7]; @@ -26191,11 +24960,6 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) pb = (point) chktet->tet[5]; pc = (point) chktet->tet[6]; - if (b->verbose > 2) { - printf(" Check tet (%d, %d, %d, %d)\n", pointmark(pa), - pointmark(pb), pointmark(pc), pointmark(pd)); - } - // Get the edge vectors vda: d->a, vdb: d->b, vdc: d->c. // Set the matrix A = [vda, vdb, vdc]^T. for (i = 0; i < 3; i++) A[0][i] = vda[i] = pa[i] - pd[i]; @@ -26209,22 +24973,14 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) if (!lu_decmp(A, 3, indx, &D, 0)) { // A degenerated tet (vol = 0). - if (b->verbose > 3) { - printf(" Min dihed = 0 (degree)\n"); - } - // Return its barycenter. - for (i = 0; i < 3; i++) { - ccent[i] = 0.25 * (pa[i] + pb[i] + pc[i] + pd[i]); - } - return 1; + // This is possible due to the use of exact arithmetic. We temporarily + // leave this tet. It should be fixed by mesh optimization. + return 0; } // Check volume if '-a#' and '-a' options are used. if (b->varvolume || b->fixedvolume) { vol = fabs(A[indx[0]][0] * A[indx[1]][1] * A[indx[2]][2]) / 6.0; - if (b->verbose > 3) { - printf(" volume = %g.\n", vol); - } if (b->fixedvolume) { if (vol > b->maxvolume) { qflag = 1; @@ -26247,6 +25003,26 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) } } + if (b->metric) { // -m option. Check mesh size. + // Calculate the circumradius of this tet. + rhs[0] = 0.5 * dot(vda, vda); + rhs[1] = 0.5 * dot(vdb, vdb); + rhs[2] = 0.5 * dot(vdc, vdc); + lu_solve(A, 3, indx, rhs, 0); + for (i = 0; i < 3; i++) ccent[i] = pd[i] + rhs[i]; + rd = sqrt(dot(rhs, rhs)); + // Check if the ccent lies outside one of the prot.balls at vertices. + ppt = (point *) &(chktet->tet[4]); + for (i = 0; i < 4; i++) { + if (ppt[i][pointmtrindex] > 0) { + if (rd > ppt[i][pointmtrindex]) { + qflag = 1; // Enforce mesh size. + return 1; + } + } + } + } + if (in->tetunsuitable != NULL) { // Execute the user-defined meshing sizing evaluation. if ((*(in->tetunsuitable))(pa, pb, pc, pd, NULL, 0)) { @@ -26257,11 +25033,61 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) lu_solve(A, 3, indx, rhs, 0); for (i = 0; i < 3; i++) ccent[i] = pd[i] + rhs[i]; return 1; - } else { - return 0; // Do not split this tet. } } + if (useinsertradius) { + // Do not split this tet if the shortest edge is shorter than the + // insertion radius of one of its endpoints. + triface checkedge; + point e1, e2; + REAL rrv, smrrv; + + // Get the shortest edge of this tet. + checkedge.tet = chktet->tet; + for (i = 0; i < 6; i++) { + checkedge.ver = edge2ver[i]; + e1 = org(checkedge); + e2 = dest(checkedge); + elen[i] = distance(e1, e2); + if (i == 0) { + smlen = elen[i]; + j = 0; + } else { + if (elen[i] < smlen) { + smlen = elen[i]; + j = i; + } + } + } + // Check if the edge is too short. + checkedge.ver = edge2ver[j]; + // Get the smallest rrv of e1 and e2. + // Note: if rrv of e1 and e2 is zero. Do not use it. + e1 = org(checkedge); + smrrv = getpointinsradius(e1); + e2 = dest(checkedge); + rrv = getpointinsradius(e2); + if (rrv > 0) { + if (smrrv > 0) { + if (rrv < smrrv) { + smrrv = rrv; + } + } else { + smrrv = rrv; + } + } + if (smrrv > 0) { + // To avoid rounding error, round smrrv before doing comparison. + if ((fabs(smrrv - smlen) / smlen) < b->epsilon) { + smrrv = smlen; + } + if (smrrv > smlen) { + return 0; + } + } + } // if (useinsertradius) + // Check the radius-edge ratio. Set by -q#. if (b->minratio > 0) { // Calculate the circumcenter and radius of this tet. @@ -26271,24 +25097,23 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) lu_solve(A, 3, indx, rhs, 0); for (i = 0; i < 3; i++) ccent[i] = pd[i] + rhs[i]; rd = sqrt(dot(rhs, rhs)); - // Calculate the shortest edge length. - elen[0] = dot(vda, vda); - elen[1] = dot(vdb, vdb); - elen[2] = dot(vdc, vdc); - elen[3] = dot(vab, vab); - elen[4] = dot(vbc, vbc); - elen[5] = dot(vca, vca); - smlen = elen[0]; //sidx = 0; - for (i = 1; i < 6; i++) { - if (smlen > elen[i]) { - smlen = elen[i]; //sidx = i; + if (!useinsertradius) { + // Calculate the shortest edge length. + elen[0] = dot(vda, vda); + elen[1] = dot(vdb, vdb); + elen[2] = dot(vdc, vdc); + elen[3] = dot(vab, vab); + elen[4] = dot(vbc, vbc); + elen[5] = dot(vca, vca); + smlen = elen[0]; //sidx = 0; + for (i = 1; i < 6; i++) { + if (smlen > elen[i]) { + smlen = elen[i]; //sidx = i; + } } + smlen = sqrt(smlen); } - smlen = sqrt(smlen); D = rd / smlen; - if (b->verbose > 3) { - printf(" Ratio-edge ratio = %g, smlen = %g\n", D, smlen); - } if (D > b->minratio) { // A bad radius-edge ratio. return 1; @@ -26319,7 +25144,7 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) cosd[3] = -dot(N[1], N[2]); // Edge ad, ac cosd[4] = -dot(N[1], N[3]); cosd[5] = -dot(N[2], N[3]); // Edge ab - // Get the smallest diehedral angle. + // Get the smallest dihedral angle. //maxcosd = mincosd = cosd[0]; maxcosd = cosd[0]; for (i = 1; i < 6; i++) { @@ -26327,9 +25152,6 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) maxcosd = (cosd[i] > maxcosd ? cosd[i] : maxcosd); //mincosd = (cosd[i] < mincosd ? cosd[i] : maxcosd); } - if (b->verbose > 3) { - printf(" Min dihed = %g (degree)\n", acos(maxcosd) / PI * 180.0); - } if (maxcosd > cosmindihed) { // Calculate the circumcenter of this tet. // A bad dihedral angle. @@ -26345,26 +25167,6 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) } } - if (b->metric) { // -m option. Check mesh size. - // Calculate the circumradius of this tet. - rhs[0] = 0.5 * dot(vda, vda); - rhs[1] = 0.5 * dot(vdb, vdb); - rhs[2] = 0.5 * dot(vdc, vdc); - lu_solve(A, 3, indx, rhs, 0); - for (i = 0; i < 3; i++) ccent[i] = pd[i] + rhs[i]; - rd = sqrt(dot(rhs, rhs)); - // Check if the ccent lies outside one of the prot.balls at vertices. - ppt = (point *) &(chktet->tet[4]); - for (i = 0; i < 4; i++) { - if (ppt[i][pointmtrindex] > 0) { - if (rd > ppt[i][pointmtrindex]) { - qflag = 1; // Enforce mesh size. - return 1; - } - } - } - } - return 0; } @@ -26377,49 +25179,33 @@ int tetgenmesh::checktet4split(triface *chktet, int &qflag, REAL *ccent) int tetgenmesh::splittetrahedron(triface* splittet, int qflag, REAL *ccent, int chkencflag) { - badface *bface; triface searchtet; - face checkseg, *paryseg; - point newpt, pa, *ppt = NULL; + face *paryseg; + point newpt; + badface *bface; insertvertexflags ivf; - REAL rd; int splitflag; - int loc; int i; - if (b->verbose > 2) { - ppt = (point *) &(splittet->tet[4]); - printf(" Split tet (%d, %d, %d, %d).\n", pointmark(ppt[0]), - pointmark(ppt[1]), pointmark(ppt[2]), pointmark(ppt[3])); - } - if (qflag == 0) { - // It is a bad quality tet (not due to mesh size). - // It can be split if 'ccent' does not encroach upon any prot. balls. - // Do a quick check if the 'ccent' lies inside the protect balls - // of one of the vertices of this tet. - ppt = (point *) &(splittet->tet[4]); - rd = distance(ccent, ppt[0]); - if ((rd <= ppt[0][pointmtrindex]) || (rd <= ppt[1][pointmtrindex]) || - (rd <= ppt[2][pointmtrindex]) || (rd <= ppt[3][pointmtrindex])) { - if (b->verbose > 2) { - printf(" Encroaching a protecting ball. Rejected.\n"); - } - return 0; - } - } + REAL rv = 0.; // Insertion radius of 'newpt'. makepoint(&newpt, FREEVOLVERTEX); for (i = 0; i < 3; i++) newpt[i] = ccent[i]; + if (useinsertradius) { + rv = distance(newpt, org(*splittet)); + setpointinsradius(newpt, rv); + } searchtet = *splittet; ivf.iloc = (int) OUTSIDE; - ivf.bowywat = 3; // Preserve subsegments and subfaces; - ivf.lawson = b->conforming ? 3 : 1; + // Use Bowyer-Watson algorithm. Preserve subsegments and subfaces; + ivf.bowywat = 3; + ivf.lawson = 2; ivf.rejflag = 3; // Do check for encroached segments and subfaces. - if (qflag == 0) { + if (b->metric) { ivf.rejflag |= 4; // Reject it if it lies in some protecting balls. } ivf.chkencflag = chkencflag; @@ -26427,115 +25213,82 @@ int tetgenmesh::splittetrahedron(triface* splittet, int qflag, REAL *ccent, ivf.splitbdflag = 0; // No use. ivf.validflag = 1; ivf.respectbdflag = 1; - ivf.assignmeshsize = 1; + ivf.assignmeshsize = b->metric; ivf.refineflag = 1; ivf.refinetet = *splittet; - loc = insertvertex(newpt, &searchtet, NULL, NULL, &ivf); - if (loc == (int) ENCSEGMENT) { - if (b->verbose > 2) { - printf(" Point encroached upon %ld segments.\n", - encseglist->objects); - } - pointdealloc(newpt); - assert(encseglist->objects > 0); - splitflag = 0; - if (!b->nobisect) { // not -Y option - // Select an encroached segment and split it. - for (i = 0; i < encseglist->objects; i++) { - paryseg = (face *) fastlookup(encseglist, i); - if (splitsegment(paryseg, NULL, qflag, chkencflag | 3)) { - splitflag = 1; // A point is inserted on a segment. - break; - } - } - } // if (!b->nobisect) - encseglist->restart(); - if (splitflag) { - // Some segments may need to be repaired. - repairencsegs(chkencflag | 3); - // Some subfaces may need to be repaired. - repairencfacs(chkencflag | 2); - // Queue the tet if it is still alive and not queued. - if (splittet->tet[4] != NULL) { - if (!marktest2ed(*splittet)) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = *splittet; - marktest2(bface->tt); // Only queue it once. - bface->forg = org(*splittet); // An alive badface. - } - } - } - return splitflag; - } else if (loc == (int) ENCSUBFACE) { - if (b->verbose > 2) { - printf(" Point encroached upon %ld subfaces.\n", - encshlist->objects); - } - pointdealloc(newpt); - assert(encshlist->objects > 0); - splitflag = 0; - if (!b->nobisect) { // not -Y option - // Select an encroached subface and split it. - for (i = 0; i < encshlist->objects; i++) { - bface = (badface *) fastlookup(encshlist, i); - if (splitsubface(&(bface->ss),NULL,qflag,bface->cent,chkencflag | 2)) { - splitflag = 1; // A point is inserted on a subface or a segment. - break; - } - } - } // if (!b->nobisect) - encshlist->restart(); - if (splitflag) { - assert(badsubsegs->items == 0l); // repairencsegs(chkencflag | 3); - // Some subfaces may need to be repaired. - repairencfacs(chkencflag | 2); - // Queue the tet if it is still alive. - if (splittet->tet[4] != NULL) { - if (!marktest2ed(*splittet)) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = *splittet; - marktest2(bface->tt); // Only queue it once. - bface->forg = org(*splittet); // An alive badface. - } - } - } - return splitflag; - } else if (loc == (int) OUTSIDE) { - // There exists not boundary conforming segments/subfaces. - pointdealloc(newpt); - } else if (loc == (int) ONVERTEX) { - // Found a coincident vertex. It should be a Steiner point. - pa = org(searchtet); - assert(pointtype(pa) == FREEVOLVERTEX); - // Delete this new point. - pointdealloc(newpt); - } else if (loc == (int) NEARVERTEX) { - // The point lies very close to an existing point. - pa = point2ppt(newpt); - assert(pointtype(pa) == FREEVOLVERTEX); - // Delete this new point. - pointdealloc(newpt); - } else if (loc == (int) ENCVERTEX) { - // The new point encoraches upon some protecting balls. Rejected. - pointdealloc(newpt); - } else if (loc == (int) BADELEMENT) { - pointdealloc(newpt); - } else { - if (b->verbose > 2) { - printf(" Point inserted successfully.\n"); - } - // Recover Delaunayness. - lawsonflip3d(newpt, 4, 0, chkencflag, 0); + if (insertpoint(newpt, &searchtet, NULL, NULL, &ivf)) { // Vertex is inserted. st_volref_count++; if (steinerleft > 0) steinerleft--; + if (flipstack != NULL) { + flipconstraints fc; + fc.chkencflag = chkencflag; + fc.enqflag = 2; + lawsonflip3d(&fc); + unflipqueue->restart(); + } return 1; + } else { + // Point is not inserted. + pointdealloc(newpt); + // Check if there are encroached segments/subfaces. + if (ivf.iloc == (int) ENCSEGMENT) { + splitflag = 0; + //if (!b->nobisect) { // not -Y option + if (!b->nobisect || checkconstraints) { + // Select an encroached segment and split it. + for (i = 0; i < encseglist->objects; i++) { + paryseg = (face *) fastlookup(encseglist, i); + if (splitsegment(paryseg, NULL, rv, org(*splittet), NULL, qflag, + chkencflag | 3)) { + splitflag = 1; // A point is inserted on a segment. + break; + } + } + } // if (!b->nobisect) + encseglist->restart(); + if (splitflag) { + // Some segments may need to be repaired. + repairencsegs(chkencflag | 3); + // Some subfaces may need to be repaired. + repairencfacs(chkencflag | 2); + // Queue the tet if it is still alive and not queued. + if ((splittet->tet != NULL) && (splittet->tet[4] != NULL)) { + enqueuetetrahedron(splittet); + } + } + return splitflag; + } else if (ivf.iloc == (int) ENCSUBFACE) { + splitflag = 0; + //if (!b->nobisect) { // not -Y option + if (!b->nobisect || checkconstraints) { + // Select an encroached subface and split it. + for (i = 0; i < encshlist->objects; i++) { + bface = (badface *) fastlookup(encshlist, i); + if (splitsubface(&(bface->ss), NULL, org(*splittet), qflag, + bface->cent, chkencflag | 2)){ + splitflag = 1; // A point is inserted on a subface or a segment. + break; + } + } + } // if (!b->nobisect) + encshlist->restart(); + if (splitflag) { + assert(badsubsegs->items == 0l); + // Some subfaces may need to be repaired. + repairencfacs(chkencflag | 2); + // Queue the tet if it is still alive. + if ((splittet->tet != NULL) && (splittet->tet[4] != NULL)) { + enqueuetetrahedron(splittet); + } + } + return splitflag; + } + return 0; } - - return 0; } /////////////////////////////////////////////////////////////////////////////// @@ -26546,28 +25299,33 @@ int tetgenmesh::splittetrahedron(triface* splittet, int qflag, REAL *ccent, void tetgenmesh::repairbadtets(int chkencflag) { - badface *bface; + triface *bface; REAL ccent[3]; int qflag = 0; + // Loop until the pool 'badsubfacs' is empty. Note that steinerleft == -1 // if an unlimited number of Steiner points is allowed. while ((badtetrahedrons->items > 0) && (steinerleft != 0)) { badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); + bface = (triface *) badtetrahedrons->traverse(); while ((bface != NULL) && (steinerleft != 0)) { - // A queued tet may have been deleted. - if (!isdeadtet(bface->tt)) { - // A queued tet may have been processed. - if (marktest2ed(bface->tt)) { - unmarktest2(bface->tt); - if (checktet4split(&(bface->tt), qflag, ccent)) { - splittetrahedron(&(bface->tt), qflag, ccent, chkencflag); + // Skip a deleted element. + if (bface->ver >= 0) { + // A queued tet may have been deleted. + if (!isdeadtet(*bface)) { + // A queued tet may have been processed. + if (marktest2ed(*bface)) { + unmarktest2(*bface); + if (checktet4split(bface, qflag, ccent)) { + splittetrahedron(bface, qflag, ccent, chkencflag); + } } } + bface->ver = -1; // Signal it as a deleted element. + badtetrahedrons->dealloc((void *) bface); } - badfacedealloc(badtetrahedrons, bface); - bface = badfacetraverse(badtetrahedrons); + bface = (triface *) badtetrahedrons->traverse(); } } @@ -26581,30 +25339,31 @@ void tetgenmesh::repairbadtets(int chkencflag) } // Unmark all queued tet. badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); + bface = (triface *) badtetrahedrons->traverse(); while (bface != NULL) { - if (!isdeadtet(bface->tt)) { - if (marktest2ed(bface->tt)) { - unmarktest2(bface->tt); + // Skip a deleted element. + if (bface->ver >= 0) { + if (!isdeadtet(*bface)) { + if (marktest2ed(*bface)) { + unmarktest2(*bface); + } } } - bface = badfacetraverse(badtetrahedrons); + bface = (triface *) badtetrahedrons->traverse(); } // Clear the pool. badtetrahedrons->restart(); } } - /////////////////////////////////////////////////////////////////////////////// // // -// enforcequality() Refine the mesh. // +// delaunayrefinement() Refine the mesh by Delaunay refinement. // // // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::delaunayrefinement() { - badface *bface; triface checktet; face checksh; face checkseg; @@ -26640,19 +25399,20 @@ void tetgenmesh::delaunayrefinement() } } - if (b->refine || b->nobisect) { // '-r' or '-Y' option. - markacutevertices(); + if (useinsertradius) { + if ((b->plc && b->nobisect) || b->refine) { // '-pY' or '-r' option. + makesegmentendpointsmap(); + } + makefacetverticesmap(); } - marksharpsegments(); - - decidefeaturepointsizes(); - encseglist = new arraypool(sizeof(face), 8); encshlist = new arraypool(sizeof(badface), 8); - if (!b->nobisect) { // if no '-Y' option + + //if (!b->nobisect) { // if no '-Y' option + if (!b->nobisect || checkconstraints) { if (b->verbose) { printf(" Splitting encroached subsegments.\n"); } @@ -26661,17 +25421,14 @@ void tetgenmesh::delaunayrefinement() steinercount = points->items; // Initialize the pool of encroached subsegments. - badsubsegs = new memorypool(sizeof(badface), b->shellfaceperblock, - memorypool::POINTER, 0); + badsubsegs = new memorypool(sizeof(face), b->shellfaceperblock, + sizeof(void *), 0); // Add all segments into the pool. subsegs->traversalinit(); checkseg.sh = shellfacetraverse(subsegs); while (checkseg.sh != (shellface *) NULL) { - bface = (badface *) badsubsegs->alloc(); - bface->ss = checkseg; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checkseg); // An alive badface. + enqueuesubface(badsubsegs, &checkseg); checkseg.sh = shellfacetraverse(subsegs); } @@ -26682,7 +25439,6 @@ void tetgenmesh::delaunayrefinement() printf(" Added %ld Steiner points.\n", points->items - steinercount); } - if (b->reflevel > 1) { // '-D2' option if (b->verbose) { printf(" Splitting encroached subfaces.\n"); @@ -26694,17 +25450,14 @@ void tetgenmesh::delaunayrefinement() bak_facref_count = st_facref_count; // Initialize the pool of encroached subfaces. - badsubfacs = new memorypool(sizeof(badface), b->shellfaceperblock, - memorypool::POINTER, 0); + badsubfacs = new memorypool(sizeof(face), b->shellfaceperblock, + sizeof(void *), 0); // Add all subfaces into the pool. subfaces->traversalinit(); checksh.sh = shellfacetraverse(subfaces); while (checksh.sh != (shellface *) NULL) { - bface = (badface *) badsubfacs->alloc(); - bface->ss = checksh; - smarktest2(bface->ss); // Only queue it once. - bface->forg = sorg(checksh); // An alive badface. + enqueuesubface(badsubfacs, &checksh); checksh.sh = shellfacetraverse(subfaces); } @@ -26716,7 +25469,6 @@ void tetgenmesh::delaunayrefinement() points->items-steinercount, st_segref_count-bak_segref_count, st_facref_count-bak_facref_count); } - } // if (b->reflevel > 1) } // if (!b->nobisect) @@ -26735,17 +25487,13 @@ void tetgenmesh::delaunayrefinement() cosmindihed = cos(b->mindihedral / 180.0 * PI); // Initialize the pool of bad quality tetrahedra. - badtetrahedrons = new memorypool(sizeof(badface), b->tetrahedraperblock, - memorypool::POINTER, 0); - + badtetrahedrons = new memorypool(sizeof(triface), b->tetrahedraperblock, + sizeof(void *), 0); // Add all tetrahedra (no hull tets) into the pool. tetrahedrons->traversalinit(); checktet.tet = tetrahedrontraverse(); while (checktet.tet != NULL) { - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = checktet; - marktest2(bface->tt); // Only queue it once. - bface->forg = org(checktet); // An alive badface. + enqueuetetrahedron(&checktet); checktet.tet = tetrahedrontraverse(); } @@ -26758,10 +25506,15 @@ void tetgenmesh::delaunayrefinement() st_segref_count - bak_segref_count, st_facref_count - bak_facref_count, st_volref_count - bak_volref_count); + } + } // if (b->reflevel > 2) + + if (b->verbose) { + if (flip23count + flip32count + flip44count > bak_flipcount) { printf(" Performed %ld flips.\n", flip23count + flip32count + flip44count - bak_flipcount); } - } // if (b->reflevel > 2) + } if (steinerleft == 0) { if (!b->quiet) { @@ -26771,16 +25524,21 @@ void tetgenmesh::delaunayrefinement() } } + delete encseglist; delete encshlist; - if (!b->nobisect) { + //if (!b->nobisect) { + if (!b->nobisect || checkconstraints) { + totalworkmemory += (badsubsegs->maxitems * badsubsegs->itembytes); delete badsubsegs; if (b->reflevel > 1) { + totalworkmemory += (badsubfacs->maxitems * badsubfacs->itembytes); delete badsubfacs; } } if (b->reflevel > 2) { + totalworkmemory += (badtetrahedrons->maxitems*badtetrahedrons->itembytes); delete badtetrahedrons; } } @@ -26793,6 +25551,267 @@ void tetgenmesh::delaunayrefinement() //// //// //// //// +/////////////////////////////////////////////////////////////////////////////// +// // +// lawsonflip3d() A three-dimensional Lawson's algorithm. // +// // +/////////////////////////////////////////////////////////////////////////////// + +long tetgenmesh::lawsonflip3d(flipconstraints *fc) +{ + triface fliptets[5], neightet, hulltet; + face checksh, casingout; + badface *popface, *bface; + point pd, pe, *pts; + REAL sign, ori; + long flipcount, totalcount = 0l; + long sliver_peels = 0l; + int t1ver; + int i; + + + while (1) { + + if (b->verbose > 2) { + printf(" Lawson flip %ld faces.\n", flippool->items); + } + flipcount = 0l; + + while (flipstack != (badface *) NULL) { + // Pop a face from the stack. + popface = flipstack; + fliptets[0] = popface->tt; + flipstack = flipstack->nextitem; // The next top item in stack. + flippool->dealloc((void *) popface); + + // Skip it if it is a dead tet (destroyed by previous flips). + if (isdeadtet(fliptets[0])) continue; + // Skip it if it is not the same tet as we saved. + if (!facemarked(fliptets[0])) continue; + + unmarkface(fliptets[0]); + + if (ishulltet(fliptets[0])) continue; + + fsym(fliptets[0], fliptets[1]); + if (ishulltet(fliptets[1])) { + if (nonconvex) { + // Check if 'fliptets[0]' it is a hull sliver. + tspivot(fliptets[0], checksh); + for (i = 0; i < 3; i++) { + if (!isshsubseg(checksh)) { + spivot(checksh, casingout); + //assert(casingout.sh != NULL); + if (sorg(checksh) != sdest(casingout)) sesymself(casingout); + stpivot(casingout, neightet); + if (neightet.tet == fliptets[0].tet) { + // Found a hull sliver 'neightet'. Let it be [e,d,a,b], where + // [e,d,a] and [d,e,b] are hull faces. + edestoppo(neightet, hulltet); // [a,b,e,d] + fsymself(hulltet); // [b,a,e,#] + if (oppo(hulltet) == dummypoint) { + pe = org(neightet); + if ((pointtype(pe) == FREEFACETVERTEX) || + (pointtype(pe) == FREESEGVERTEX)) { + removevertexbyflips(pe); + } + } else { + eorgoppo(neightet, hulltet); // [b,a,d,e] + fsymself(hulltet); // [a,b,d,#] + if (oppo(hulltet) == dummypoint) { + pd = dest(neightet); + if ((pointtype(pd) == FREEFACETVERTEX) || + (pointtype(pd) == FREESEGVERTEX)) { + removevertexbyflips(pd); + } + } else { + // Perform a 3-to-2 flip to remove the sliver. + fliptets[0] = neightet; // [e,d,a,b] + fnext(fliptets[0], fliptets[1]); // [e,d,b,c] + fnext(fliptets[1], fliptets[2]); // [e,d,c,a] + flip32(fliptets, 1, fc); + // Update counters. + flip32count--; + flip22count--; + sliver_peels++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + } + } + break; + } // if (neightet.tet == fliptets[0].tet) + } // if (!isshsubseg(checksh)) + senextself(checksh); + } // i + } // if (nonconvex) + continue; + } + + if (checksubfaceflag) { + // Do not flip if it is a subface. + if (issubface(fliptets[0])) continue; + } + + // Test whether the face is locally Delaunay or not. + pts = (point *) fliptets[1].tet; + sign = insphere_s(pts[4], pts[5], pts[6], pts[7], oppo(fliptets[0])); + + if (sign < 0) { + // A non-Delaunay face. Try to flip it. + pd = oppo(fliptets[0]); + pe = oppo(fliptets[1]); + + // Check the convexity of its three edges. Stop checking either a + // locally non-convex edge (ori < 0) or a flat edge (ori = 0) is + // encountered, and 'fliptet' represents that edge. + for (i = 0; i < 3; i++) { + ori = orient3d(org(fliptets[0]), dest(fliptets[0]), pd, pe); + if (ori <= 0) break; + enextself(fliptets[0]); + } + + if (ori > 0) { + // A 2-to-3 flip is found. + // [0] [a,b,c,d], + // [1] [b,a,c,e]. no dummypoint. + flip23(fliptets, 0, fc); + flipcount++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } else { // ori <= 0 + // The edge ('fliptets[0]' = [a',b',c',d]) is non-convex or flat, + // where the edge [a',b'] is one of [a,b], [b,c], and [c,a]. + if (checksubsegflag) { + // Do not flip if it is a segment. + if (issubseg(fliptets[0])) continue; + } + // Check if there are three or four tets sharing at this edge. + esymself(fliptets[0]); // [b,a,d,c] + for (i = 0; i < 3; i++) { + fnext(fliptets[i], fliptets[i+1]); + } + if (fliptets[3].tet == fliptets[0].tet) { + // A 3-to-2 flip is found. (No hull tet.) + flip32(fliptets, 0, fc); + flipcount++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } else { + // There are more than 3 tets at this edge. + fnext(fliptets[3], fliptets[4]); + if (fliptets[4].tet == fliptets[0].tet) { + // There are exactly 4 tets at this edge. + if (nonconvex) { + if (apex(fliptets[3]) == dummypoint) { + // This edge is locally non-convex on the hull. + // It can be removed by a 4-to-4 flip. + ori = 0; + } + } // if (nonconvex) + if (ori == 0) { + // A 4-to-4 flip is found. (Two hull tets may be involved.) + // Current tets in 'fliptets': + // [0] [b,a,d,c] (d may be newpt) + // [1] [b,a,c,e] + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + esymself(fliptets[0]); // [a,b,c,d] + // A 2-to-3 flip replaces face [a,b,c] by edge [e,d]. + // This creates a degenerate tet [e,d,a,b] (tmpfliptets[0]). + // It will be removed by the followed 3-to-2 flip. + flip23(fliptets, 0, fc); // No hull tet. + fnext(fliptets[3], fliptets[1]); + fnext(fliptets[1], fliptets[2]); + // Current tets in 'fliptets': + // [0] [...] + // [1] [b,a,d,e] (degenerated, d may be new point). + // [2] [b,a,e,f] (f may be dummypoint) + // [3] [b,a,f,d] + // A 3-to-2 flip replaces edge [b,a] by face [d,e,f]. + // Hull tets may be involved (f may be dummypoint). + flip32(&(fliptets[1]), (apex(fliptets[3]) == dummypoint), fc); + flipcount++; + flip23count--; + flip32count--; + flip44count++; + if (fc->remove_ndelaunay_edge) { + // Update the volume (must be decreased). + //assert(fc->tetprism_vol_sum <= 0); + tetprism_vol_sum += fc->tetprism_vol_sum; + fc->tetprism_vol_sum = 0.0; // Clear it. + } + continue; + } // if (ori == 0) + } + } + } // if (ori <= 0) + + // This non-Delaunay face is unflippable. Save it. + unflipqueue->newindex((void **) &bface); + bface->tt = fliptets[0]; + bface->forg = org(fliptets[0]); + bface->fdest = dest(fliptets[0]); + bface->fapex = apex(fliptets[0]); + } // if (sign < 0) + } // while (flipstack) + + if (b->verbose > 2) { + if (flipcount > 0) { + printf(" Performed %ld flips.\n", flipcount); + } + } + // Accumulate the counter of flips. + totalcount += flipcount; + + assert(flippool->items == 0l); + // Return if no unflippable faces left. + if (unflipqueue->objects == 0l) break; + // Return if no flip has been performed. + if (flipcount == 0l) break; + + // Try to flip the unflippable faces. + for (i = 0; i < unflipqueue->objects; i++) { + bface = (badface *) fastlookup(unflipqueue, i); + if (!isdeadtet(bface->tt) && + (org(bface->tt) == bface->forg) && + (dest(bface->tt) == bface->fdest) && + (apex(bface->tt) == bface->fapex)) { + flippush(flipstack, &(bface->tt)); + } + } + unflipqueue->restart(); + + } // while (1) + + if (b->verbose > 2) { + if (totalcount > 0) { + printf(" Performed %ld flips.\n", totalcount); + } + if (sliver_peels > 0) { + printf(" Removed %ld hull slivers.\n", sliver_peels); + } + if (unflipqueue->objects > 0l) { + printf(" %ld unflippable edges remained.\n", unflipqueue->objects); + } + } + + return totalcount + sliver_peels; +} + /////////////////////////////////////////////////////////////////////////////// // // // recoverdelaunay() Recovery the locally Delaunay property. // @@ -26802,8 +25821,8 @@ void tetgenmesh::delaunayrefinement() void tetgenmesh::recoverdelaunay() { arraypool *flipqueue, *nextflipqueue, *swapqueue; - badface *bface, *parybface; triface tetloop, neightet, *parytet; + badface *bface, *parybface; point *ppt; flipconstraints fc; int i, j; @@ -26812,28 +25831,16 @@ void tetgenmesh::recoverdelaunay() printf("Recovering Delaunayness...\n"); } - //if (b->verbose) { - // printf(" max_flipstarsize = %d.\n", b->optmaxflipstarsize); - // printf(" max_fliplinklevel = %d.\n", b->delmaxfliplevel); - //} - - calc_tetprism_vol = 1; tetprism_vol_sum = 0.0; // Initialize it. - assert(flipstack == NULL); - assert(unflipqueue->objects == 0l); - // Put all interior faces of the mesh into 'flipstack'. tetrahedrons->traversalinit(); tetloop.tet = tetrahedrontraverse(); while (tetloop.tet != NULL) { for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { - // Avoid queue a face twice. - fsym(tetloop, neightet); - if (!ishulltet(neightet)) { - if (!facemarked(neightet)) { - flippush(flipstack, &tetloop); - } + decode(tetloop.tet[tetloop.ver], neightet); + if (!facemarked(neightet)) { + flippush(flipstack, &tetloop); } } ppt = (point *) &(tetloop.tet[4]); @@ -26841,6 +25848,10 @@ void tetgenmesh::recoverdelaunay() tetloop.tet = tetrahedrontraverse(); } + // Calulate a relatively lower bound for small improvement. + // Used to avoid rounding error in volume calculation. + fc.bak_tetprism_vol = tetprism_vol_sum * b->epsilon * 1e-3; + if (b->verbose) { printf(" Initial obj = %.17g\n", tetprism_vol_sum); } @@ -26848,37 +25859,35 @@ void tetgenmesh::recoverdelaunay() if (b->verbose > 1) { printf(" Recover Delaunay [Lawson] : %ld\n", flippool->items); } - assert(unflipqueue->objects == 0l); // First only use the basic Lawson's flip. - lawsonflip3d(NULL, 4, 0, 0, 1); + fc.remove_ndelaunay_edge = 1; + fc.enqflag = 2; + + lawsonflip3d(&fc); if (b->verbose > 1) { - printf(" New obj = %.17g\n", tetprism_vol_sum); + printf(" obj (after Lawson) = %.17g\n", tetprism_vol_sum); } if (unflipqueue->objects == 0l) { - // The mesh is Delaunay. - return; + return; // The mesh is Delaunay. } - // Set the common options. - fc.remove_ndelaunay_edge = 1; fc.unflip = 1; // Unflip if the edge is not flipped. - fc.collectnewtets = 1; + fc.collectnewtets = 1; // new tets are returned in 'cavetetlist'. + fc.enqflag = 0; - autofliplinklevel = 1; // Init value. - b->fliplinklevel = -1; + autofliplinklevel = 1; // Init level. + b->fliplinklevel = -1; // No fixed level. // For efficiency reason, we limit the maximium size of the edge star. - // 'b->optmaxflipstarsize' is set by -OOOOO (5 Os), default is 10. int bakmaxflipstarsize = b->flipstarsize; - b->flipstarsize = 10; //b->optmaxflipstarsize; + b->flipstarsize = 10; // default flipqueue = new arraypool(sizeof(badface), 10); nextflipqueue = new arraypool(sizeof(badface), 10); - - + // Swap the two flip queues. swapqueue = flipqueue; flipqueue = unflipqueue; @@ -26886,60 +25895,61 @@ void tetgenmesh::recoverdelaunay() while (flipqueue->objects > 0l) { - while (flipqueue->objects > 0l) { + if (b->verbose > 1) { + printf(" Recover Delaunay [level = %2d] #: %ld.\n", + autofliplinklevel, flipqueue->objects); + } - if (b->verbose > 1) { - printf(" Recover Delaunay [level = %2d] #: %ld.\n", - autofliplinklevel, flipqueue->objects); - } - - for (i = 0; i < flipqueue->objects; i++) { - bface = (badface *) fastlookup(flipqueue, i); - if (getedge(bface->forg, bface->fdest, &bface->tt)) { - // Remember the the objective value (volume of all tetprisms). - fc.bak_tetprism_vol = tetprism_vol_sum; - if (removeedgebyflips(&(bface->tt), &fc) == 2) { - if (b->verbose > 2) { - printf(" Decreased quantity: %.17g.\n", - fc.bak_tetprism_vol - tetprism_vol_sum); + for (i = 0; i < flipqueue->objects; i++) { + bface = (badface *) fastlookup(flipqueue, i); + if (getedge(bface->forg, bface->fdest, &bface->tt)) { + if (removeedgebyflips(&(bface->tt), &fc) == 2) { + tetprism_vol_sum += fc.tetprism_vol_sum; + fc.tetprism_vol_sum = 0.0; // Clear it. + // Queue new faces for flips. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + // A queued new tet may be dead. + if (!isdeadtet(*parytet)) { + for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { + // Avoid queue a face twice. + decode(parytet->tet[parytet->ver], neightet); + if (!facemarked(neightet)) { + flippush(flipstack, parytet); + } + } // parytet->ver } - // Queue new faces for flips. - for (j = 0; j < cavetetlist->objects; j++) { - parytet = (triface *) fastlookup(cavetetlist, j); - // A queued new tet may be dead. - if (!isdeadtet(*parytet)) { - for (parytet->ver = 0; parytet->ver < 4; parytet->ver++) { - // Avoid queue a face twice. - fsym(*parytet, neightet); - if (!facemarked(neightet)) { - flippush(flipstack, parytet); - } - } // parytet->ver - } - } // j - cavetetlist->restart(); - // Remove locally non-Delaunay faces. New non-Delaunay edges - // may be found. They are saved in 'unflipqueue'. - lawsonflip3d(NULL, 4, 0, 0, 1); - } else { - // Unable to remove this edge. Save it. - nextflipqueue->newindex((void **) &parybface); + } // j + cavetetlist->restart(); + // Remove locally non-Delaunay faces. New non-Delaunay edges + // may be found. They are saved in 'unflipqueue'. + fc.enqflag = 2; + lawsonflip3d(&fc); + fc.enqflag = 0; + // There may be unflipable faces. Add them in flipqueue. + for (j = 0; j < unflipqueue->objects; j++) { + bface = (badface *) fastlookup(unflipqueue, j); + flipqueue->newindex((void **) &parybface); *parybface = *bface; } + unflipqueue->restart(); + } else { + // Unable to remove this edge. Save it. + nextflipqueue->newindex((void **) &parybface); + *parybface = *bface; + // Normally, it should be zero. + //assert(fc.tetprism_vol_sum == 0.0); + // However, due to rounding errors, a tiny value may appear. + fc.tetprism_vol_sum = 0.0; } - } // i - - flipqueue->restart(); - - // Swap the two flip queues. - swapqueue = flipqueue; - flipqueue = unflipqueue; - unflipqueue = swapqueue; - } // while (flipqueue->objects > 0l) + } + } // i if (b->verbose > 1) { - printf(" New obj = %.17g.\n", tetprism_vol_sum); + printf(" obj (after level %d) = %.17g.\n", autofliplinklevel, + tetprism_vol_sum); } + flipqueue->restart(); // Swap the two flip queues. swapqueue = flipqueue; @@ -26947,14 +25957,13 @@ void tetgenmesh::recoverdelaunay() nextflipqueue = swapqueue; if (flipqueue->objects > 0l) { - // 'b->delmaxfliplevel' is set by -OOOO, default is 1. + // default 'b->delmaxfliplevel' is 1. if (autofliplinklevel >= b->delmaxfliplevel) { // For efficiency reason, we do not search too far. break; } autofliplinklevel+=b->fliplinklevelinc; } - } // while (flipqueue->objects > 0l) if (flipqueue->objects > 0l) { @@ -26963,16 +25972,13 @@ void tetgenmesh::recoverdelaunay() } } - b->flipstarsize = bakmaxflipstarsize; - - delete nextflipqueue; - delete flipqueue; - - calc_tetprism_vol = 0; - if (b->verbose) { - printf(" Final obj = %.17g\n", tetprism_vol_sum); + printf(" Final obj = %.17g\n", tetprism_vol_sum); } + + b->flipstarsize = bakmaxflipstarsize; + delete flipqueue; + delete nextflipqueue; } /////////////////////////////////////////////////////////////////////////////// @@ -26985,11 +25991,7 @@ int tetgenmesh::gettetrahedron(point pa, point pb, point pc, point pd, triface *searchtet) { triface spintet; - - if (b->verbose > 2) { - printf(" Get tet [%d,%d,%d,%d].\n", pointmark(pa), pointmark(pb), - pointmark(pc), pointmark(pd)); - } + int t1ver; if (getedge(pa, pb, searchtet)) { spintet = *searchtet; @@ -27051,6 +26053,7 @@ long tetgenmesh::improvequalitybyflips() fc.remove_large_angle = 1; fc.unflip = 1; fc.collectnewtets = 1; + fc.checkflipeligibility = 1; totalremcount = 0l; @@ -27080,34 +26083,30 @@ long tetgenmesh::improvequalitybyflips() // Here we simply re-compute them. Slow!!. ppt = (point *) & (bface->tt.tet[4]); tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, - &maxdd, NULL); + &bface->key, NULL); bface->forg = ppt[0]; bface->fdest = ppt[1]; bface->fapex = ppt[2]; bface->foppo = ppt[3]; bface->tt.ver = 11; } + if (bface->key == 0) { + // Re-comput the quality values. Due to smoothing operations. + ppt = (point *) & (bface->tt.tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, + &bface->key, NULL); + } cosdd = bface->cent; remflag = 0; for (i = 0; (i < 6) && !remflag; i++) { if (cosdd[i] < cosmaxdihed) { // Found a large dihedral angle. bface->tt.ver = edge2ver[i]; // Go to the edge. - if (b->verbose > 2) { - printf(" Found a large angle [%d,%d,%d,%d] (%g).\n", - pointmark(org(bface->tt)), pointmark(dest(bface->tt)), - pointmark(apex(bface->tt)), pointmark(oppo(bface->tt)), - acos(cosdd[i]) / PI * 180.0); - } fc.cosdihed_in = cosdd[i]; fc.cosdihed_out = 0.0; // 90 degree. n = removeedgebyflips(&(bface->tt), &fc); if (n == 2) { // Edge is flipped. - if (b->verbose > 2) { - printf(" Reduced a large angle to %g degree.\n", - acos(fc.cosdihed_out) / PI * 180.0); - } remflag = 1; if (fc.cosdihed_out < cosmaxdihed) { // Queue new bad tets for further improvements. @@ -27115,8 +26114,6 @@ long tetgenmesh::improvequalitybyflips() parytet = (triface *) fastlookup(cavetetlist, j); if (!isdeadtet(*parytet)) { ppt = (point *) & (parytet->tet[4]); - //if (!marktest2ed(*parytet)) { - assert(!marktest2ed(*parytet)); // SELF_CHECK // Do not test a hull tet. if (ppt[3] != dummypoint) { tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], ncosdd, @@ -27135,8 +26132,7 @@ long tetgenmesh::improvequalitybyflips() parybface->cent[n] = ncosdd[n]; } } - } // if (ppt[3] != dummypoint) { - //} + } // if (ppt[3] != dummypoint) } } // j } // if (fc.cosdihed_out < cosmaxdihed) @@ -27208,8 +26204,8 @@ long tetgenmesh::improvequalitybyflips() // has two orientations, ccw or cw, with respect to 'p'. 'ccw' indicates // // the orientation is ccw (1) or not (0). // // // -// 'of' is a structure contains the parameters of the objective function. It // -// is needed by the evaluation of the function value. // +// 'opm' is a structure contains the parameters of the objective function. // +// It is needed by the evaluation of the function value. // // // // The return value indicates weather the point is smoothed or not. // // // @@ -27230,16 +26226,6 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, int numdirs, iter; int i, j, k; - if (b->verbose > 2) { - printf(" Smooth a point: %ld faces.\n", linkfacelist->objects); - if (opm->min_max_dihedangle) { - printf(" Init value = %g (degree).\n", - acos(opm->initval - 1.0) / PI * 180.0); - } else { - printf(" Init value = %g.\n", opm->initval); - } - } - // Decide the number of moving directions. numdirs = (int) linkfacelist->objects; if (numdirs > opm->numofsearchdirs) { @@ -27292,7 +26278,8 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, if (ori < 0.0) { // Calcuate the objective function value. if (opm->max_min_volume) { - val = -ori; + //val = -ori; + val = - orient3dfast(pa, pb, pc, nextpt); } else if (opm->max_min_aspectratio) { val = tetaspectratio(pa, pb, pc, nextpt); } else if (opm->min_max_dihedangle) { @@ -27305,8 +26292,10 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, } } else { // ori >= 0.0; // An invalid new tet. + // This may happen if the mesh contains inverted elements. if (opm->max_min_volume) { - val = -ori; + //val = -ori; + val = - orient3dfast(pa, pb, pc, nextpt); } else { // Discard this point. break; // j @@ -27326,7 +26315,6 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, } // j if (j == linkfacelist->objects) { // The function value has been improved. - assert(minval > opm->imprval); opm->imprval = minval; // Save the new location of the point. for (j = 0; j < 3; j++) bestpt[j] = nextpt[j]; @@ -27372,60 +26360,16 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, } // while (1) if (iter > 0) { - // The point has been smooothed. - opm->smthiter = iter; // Remember the number of iterations. - if (b->verbose > 2) { - printf(" Smoothed: %d iterations.\n", iter); - if (opm->min_max_dihedangle) { - printf(" Fina value = %g (degree).\n", - acos(opm->imprval - 1.0) / PI * 180.0); - } else { - printf(" Fina value = %g.\n", opm->imprval); - } - } + // The point has been smoothed. + opm->smthiter = iter; // Remember the number of iterations. // The point has been smoothed. Update it to its new position. for (i = 0; i < 3; i++) smtpt[i] = startpt[i]; - - if (opm->flipflag) { - // Push all affected faces into 'flipstack'. - triface starttet, neightet; - for (i = 0; i < linkfacelist->objects; i++) { - parytet = (triface *) fastlookup(linkfacelist, i); - starttet = *parytet; - for (starttet.ver = 0; starttet.ver < 4; starttet.ver++) { - fsym(starttet, neightet); - if (!infected(neightet)) { - flippush(flipstack, &starttet); - } - } - infect(*parytet); - } - for (i = 0; i < linkfacelist->objects; i++) { - parytet = (triface *) fastlookup(linkfacelist, i); - uninfect(*parytet); - } - } else if (opm->checkencflag) { - // Push all affected tets into pool. - badface *bface; - for (i = 0; i < linkfacelist->objects; i++) { - parytet = (triface *) fastlookup(linkfacelist, i); - if (!marktest2ed(*parytet)) { - marktest2(*parytet); // Only queue it once. - bface = (badface *) badtetrahedrons->alloc(); - bface->tt = *parytet; - bface->forg = org(bface->tt); - } - } - } - } else { - if (b->verbose > 2) { - printf(" Not smoothed.\n"); - } } return iter; } + /////////////////////////////////////////////////////////////////////////////// // // // improvequalitysmoothing() Improve mesh quality by smoothing. // @@ -27435,31 +26379,30 @@ int tetgenmesh::smoothpoint(point smtpt, arraypool *linkfacelist, int ccw, long tetgenmesh::improvequalitybysmoothing(optparameters *opm) { arraypool *flipqueue, *swapqueue; + triface *parytet; badface *bface, *parybface; point *ppt; long totalsmtcount, smtcount; int smtflag; - int iter, i, k; + int iter, i, j, k; //assert(unflipqueue->objects > 0l); flipqueue = new arraypool(sizeof(badface), 10); - totalsmtcount = 0l; - // Swap the two flip queues. swapqueue = flipqueue; flipqueue = unflipqueue; unflipqueue = swapqueue; + totalsmtcount = 0l; iter = 0; while (flipqueue->objects > 0l) { smtcount = 0l; - //while (flipqueue->objects > 0l) { if (b->verbose > 1) { - printf(" Improving mesh qualiy by smoothing [%d]#: %ld.\n", + printf(" Improving mesh quality by smoothing [%d]#: %ld.\n", iter, flipqueue->objects); } @@ -27467,6 +26410,7 @@ long tetgenmesh::improvequalitybysmoothing(optparameters *opm) bface = (badface *) fastlookup(flipqueue, k); if (gettetrahedron(bface->forg, bface->fdest, bface->fapex, bface->foppo, &bface->tt)) { + // Operate on it if it is not in 'unflipqueue'. if (!marktested(bface->tt)) { // Here we simply re-compute the quality. Since other smoothing // operation may have moved the vertices of this tet. @@ -27476,10 +26420,7 @@ long tetgenmesh::improvequalitybysmoothing(optparameters *opm) if (bface->key < cossmtdihed) { // if (maxdd < cosslidihed) { // It is a sliver. Try to smooth its vertices. smtflag = 0; - //if (opm->min_max_dihedangle) { - opm->initval = bface->key + 1.0; - //opm->checkencflag = 4; // Queue affected tets. - //} + opm->initval = bface->key + 1.0; for (i = 0; (i < 4) && !smtflag; i++) { if (pointtype(ppt[i]) == FREEVOLVERTEX) { getvertexstar(1, ppt[i], cavetetlist, NULL, NULL); @@ -27492,78 +26433,61 @@ long tetgenmesh::improvequalitybysmoothing(optparameters *opm) opm->smthiter = 0; // reset smoothpoint(ppt[i], cavetetlist, 1, opm); } + // This tet is modifed. smtcount++; - } + if ((opm->imprval - 1.0) < cossmtdihed) { + // There are slivers in new tets. Queue them. + for (j = 0; j < cavetetlist->objects; j++) { + parytet = (triface *) fastlookup(cavetetlist, j); + assert(!isdeadtet(*parytet)); + // Operate it if it is not in 'unflipqueue'. + if (!marktested(*parytet)) { + // Evaluate its quality. + // Re-use ppt, bface->key, bface->cent. + ppt = (point *) & (parytet->tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], + bface->cent, &bface->key, NULL); + if (bface->key < cossmtdihed) { + // A new sliver. Queue it. + marktest(*parytet); // It is in unflipqueue. + unflipqueue->newindex((void **) &parybface); + parybface->tt = *parytet; + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.ver = 11; + parybface->key = 0.0; + } + } + } // j + } // if ((opm->imprval - 1.0) < cossmtdihed) + } // if (smtflag) cavetetlist->restart(); - } + } // if (pointtype(ppt[i]) == FREEVOLVERTEX) } // i - if (smtflag) { - // This tet is modifed. - smtcount++; - if ((opm->imprval - 1.0) < cossmtdihed) { - // Queue new slivers. - badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); - while (bface != NULL) { - assert(!isdeadtet(bface->tt)); - assert(marktest2ed(bface->tt)); - unmarktest2(bface->tt); - if (!marktested(bface->tt)) { - ppt = (point *) & (bface->tt.tet[4]); - tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, - &(bface->key), NULL); - if (bface->key < cossmtdihed) { - // A new sliver. Queue it. - marktest(bface->tt); // It is in unflipqueue. - bface->forg = ppt[0]; - bface->fdest = ppt[1]; - bface->fapex = ppt[2]; - bface->foppo = ppt[3]; - bface->tt.ver = 11; - unflipqueue->newindex((void **) &parybface); - *parybface = *bface; - } - } - bface = badfacetraverse(badtetrahedrons); - } - } else { - // No new slivers. Only unmark the queued tets. - badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); - while (bface != NULL) { - assert(!isdeadtet(bface->tt)); - assert(marktest2ed(bface->tt)); - unmarktest2(bface->tt); - bface = badfacetraverse(badtetrahedrons); - } - } - badtetrahedrons->restart(); - } else { + if (!smtflag) { // Didn't smooth. Queue it again. - // Adjust the vertices for flipping. marktest(bface->tt); // It is in unflipqueue. - bface->forg = ppt[0]; - bface->fdest = ppt[1]; - bface->fapex = ppt[2]; - bface->foppo = ppt[3]; - bface->tt.ver = 11; unflipqueue->newindex((void **) &parybface); - *parybface = *bface; + parybface->tt = bface->tt; + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.ver = 11; + parybface->key = 0.0; } - } // if (maxdd < cosslidihed) + } // if (maxdd < cosslidihed) } // if (!marktested(...)) - } // gettetrahedron(...) + } // if (gettetrahedron(...)) } // k flipqueue->restart(); - // } // while - // Unmark the tets in unflipqueue. for (i = 0; i < unflipqueue->objects; i++) { bface = (badface *) fastlookup(unflipqueue, i); - assert(!isdeadtet(bface->tt)); - assert(marktested(bface->tt)); unmarktest(bface->tt); } @@ -27603,24 +26527,20 @@ int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) { triface *abtets; triface searchtet, spintet, *parytet; - face checkseg; point pa, pb, steinerpt; optparameters opm; insertvertexflags ivf; REAL smtpt[3], midpt[3]; int success; - int loc; + int t1ver; int n, i; // 'slitet' is [c,d,a,b], where [c,d] has a big dihedral angle. // Go to the opposite edge [a,b]. - eprev(*slitet, searchtet); - esymself(searchtet); - enextself(searchtet); // [a,b,c,d]. + edestoppo(*slitet, searchtet); // [a,b,c,d]. // Do not split a segment. - tsspivot1(searchtet, checkseg); - if (checkseg.sh != NULL) { + if (issubseg(searchtet)) { return 0; } @@ -27690,19 +26610,11 @@ int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) cavetetlist->restart(); if (!success) { - if (b->verbose > 2) { - printf(" Unable to relocate the initial point.\n"); - } delete [] abtets; return 0; } - if (steinerleft == 0) { - // The desired number of Steiner points is reached. - return 0; - } - // Insert the Steiner point. makepoint(&steinerpt, FREEVOLVERTEX); for (i = 0; i < 3; i++) steinerpt[i] = smtpt[i]; @@ -27713,24 +26625,22 @@ int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) caveoldtetlist->newindex((void **) &parytet); *parytet = abtets[i]; } + searchtet = abtets[0]; // No need point location. + if (b->metric) { + locate(steinerpt, &searchtet); // For size interpolation. + } + + delete [] abtets; + ivf.iloc = (int) INSTAR; - ivf.bowywat = 0; // Do not use Bowyer-Watson algorithm. - ivf.lawson = 0; // Do not flip. - ivf.rejflag = 0; ivf.chkencflag = chkencflag; - ivf.sloc = 0; - ivf.sbowywat = 0; - ivf.splitbdflag = 0; - ivf.validflag = 0; - ivf.respectbdflag = 0; - ivf.assignmeshsize = 0; + ivf.assignmeshsize = b->metric; - loc = insertvertex(steinerpt, &searchtet, NULL, NULL, &ivf); - if (loc == (int) INSTAR) { + if (insertpoint(steinerpt, &searchtet, NULL, NULL, &ivf)) { // The vertex has been inserted. - st_volref_count++; //st_inpoly_count++; + st_volref_count++; if (steinerleft > 0) steinerleft--; return 1; } else { @@ -27738,8 +26648,6 @@ int tetgenmesh::splitsliver(triface *slitet, REAL cosd, int chkencflag) pointdealloc(steinerpt); return 0; } - - delete [] abtets; } /////////////////////////////////////////////////////////////////////////////// @@ -27752,24 +26660,24 @@ long tetgenmesh::removeslivers(int chkencflag) { arraypool *flipqueue, *swapqueue; badface *bface, *parybface; + triface slitet, *parytet; point *ppt; - REAL *cosdd; + REAL cosdd[6], maxcosd; long totalsptcount, sptcount; - int iter, j, k; + int iter, i, j, k; //assert(unflipqueue->objects > 0l); flipqueue = new arraypool(sizeof(badface), 10); - totalsptcount = 0l; - // Swap the two flip queues. swapqueue = flipqueue; flipqueue = unflipqueue; unflipqueue = swapqueue; + totalsptcount = 0l; iter = 0; - while (flipqueue->objects > 0l) { + while ((flipqueue->objects > 0l) && (steinerleft != 0)) { sptcount = 0l; @@ -27778,74 +26686,63 @@ long tetgenmesh::removeslivers(int chkencflag) iter, flipqueue->objects); } - for (k = 0; k < flipqueue->objects; k++) { + for (k = 0; (k < flipqueue->objects) && (steinerleft != 0); k++) { bface = (badface *) fastlookup(flipqueue, k); if (gettetrahedron(bface->forg, bface->fdest, bface->fapex, bface->foppo, &bface->tt)) { - //if (!marktested(bface->tt)) { - // Here we simply re-compute the quality. Since other smoothing + if ((bface->key == 0) || (bface->tt.ver != 11)) { + // Here we need to re-compute the quality. Since other smoothing // operation may have moved the vertices of this tet. ppt = (point *) & (bface->tt.tet[4]); tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, &bface->key, NULL); - if (bface->key < cosslidihed) { - // It is a sliver. Try to split it. - cosdd = bface->cent; - for (j = 0; j < 6; j++) { - if (cosdd[j] < cosslidihed) { - // Found a large dihedral angle. - bface->tt.ver = edge2ver[j]; // Go to the edge. - if (b->verbose > 2) { - printf(" Found a bad tet [%d,%d,%d,%d] (%g).\n", - pointmark(org(bface->tt)), pointmark(dest(bface->tt)), - pointmark(apex(bface->tt)), pointmark(oppo(bface->tt)), - acos(cosdd[j]) / PI * 180.0); - } - if (splitsliver(&(bface->tt), cosdd[j], chkencflag)) { - sptcount++; - break; + } + if (bface->key < cosslidihed) { + // It is a sliver. Try to split it. + slitet.tet = bface->tt.tet; + //cosdd = bface->cent; + for (j = 0; j < 6; j++) { + if (bface->cent[j] < cosslidihed) { + // Found a large dihedral angle. + slitet.ver = edge2ver[j]; // Go to the edge. + if (splitsliver(&slitet, bface->cent[j], chkencflag)) { + sptcount++; + break; + } + } + } // j + if (j < 6) { + // A sliver is split. Queue new slivers. + badtetrahedrons->traversalinit(); + parytet = (triface *) badtetrahedrons->traverse(); + while (parytet != NULL) { + unmarktest2(*parytet); + ppt = (point *) & (parytet->tet[4]); + tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], cosdd, + &maxcosd, NULL); + if (maxcosd < cosslidihed) { + // A new sliver. Queue it. + unflipqueue->newindex((void **) &parybface); + parybface->forg = ppt[0]; + parybface->fdest = ppt[1]; + parybface->fapex = ppt[2]; + parybface->foppo = ppt[3]; + parybface->tt.tet = parytet->tet; + parybface->tt.ver = 11; + parybface->key = maxcosd; + for (i = 0; i < 6; i++) { + parybface->cent[i] = cosdd[i]; } } - } // j - if (j < 6) { - // A sliver is split. Queue new slivers. - badtetrahedrons->traversalinit(); - bface = badfacetraverse(badtetrahedrons); - while (bface != NULL) { - assert(!isdeadtet(bface->tt)); - assert(marktest2ed(bface->tt)); - unmarktest2(bface->tt); - ppt = (point *) & (bface->tt.tet[4]); - tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], bface->cent, - &(bface->key), NULL); - if (bface->key < cosslidihed) { - // A new sliver. Queue it. - //marktest(bface->tt); // It is in unflipqueue. - bface->forg = ppt[0]; - bface->fdest = ppt[1]; - bface->fapex = ppt[2]; - bface->foppo = ppt[3]; - bface->tt.ver = 11; - unflipqueue->newindex((void **) &parybface); - *parybface = *bface; - } - bface = badfacetraverse(badtetrahedrons); - } - badtetrahedrons->restart(); - } else { - // Didn't split. Queue it again. - // Adjust the vertices for flipping. - //marktest(bface->tt); // It is in unflipqueue. - bface->forg = ppt[0]; - bface->fdest = ppt[1]; - bface->fapex = ppt[2]; - bface->foppo = ppt[3]; - bface->tt.ver = 11; - unflipqueue->newindex((void **) &parybface); - *parybface = *bface; - } // if (j == 6) - } // if (bface->key < cosslidihed) - // } // if (!marktested(bface->tt)) + parytet = (triface *) badtetrahedrons->traverse(); + } + badtetrahedrons->restart(); + } else { + // Didn't split. Queue it again. + unflipqueue->newindex((void **) &parybface); + *parybface = *bface; + } // if (j == 6) + } // if (bface->key < cosslidihed) } // if (gettetrahedron(...)) } // k @@ -27902,24 +26799,34 @@ void tetgenmesh::optimizemesh() printf("Optimizing mesh...\n"); } + optpasses = ((1 << b->optlevel) - 1); + if (b->verbose) { printf(" Optimization level = %d.\n", b->optlevel); printf(" Optimization scheme = %d.\n", b->optscheme); + printf(" Number of iteration = %d.\n", optpasses); printf(" Min_Max dihed angle = %g.\n", b->optmaxdihedral); } - optpasses = ((1 << b->optlevel) - 1); - totalsmtcount = totalsptcount = totalremcount = 0l; cosmaxdihed = cos(b->optmaxdihedral / 180.0 * PI); cossmtdihed = cos(b->optminsmtdihed / 180.0 * PI); cosslidihed = cos(b->optminslidihed / 180.0 * PI); + int attrnum = numelemattrib - 1; + // Put all bad tetrahedra into array. tetrahedrons->traversalinit(); checktet.tet = tetrahedrontraverse(); while (checktet.tet != NULL) { + if (b->convex) { // -c + // Skip this tet if it lies in the exterior. + if (elemattribute(checktet.tet, attrnum) == -1.0) { + checktet.tet = tetrahedrontraverse(); + continue; + } + } ppt = (point *) & (checktet.tet[4]); tetalldihedral(ppt[0], ppt[1], ppt[2], ppt[3], ncosdd, &maxdd, NULL); if (maxdd < cosmaxdihed) { @@ -27943,16 +26850,16 @@ void tetgenmesh::optimizemesh() if ((unflipqueue->objects > 0l) && ((b->optscheme & 2) || (b->optscheme & 4))) { - - badtetrahedrons = new memorypool(sizeof(badface), b->tetrahedraperblock, - memorypool::POINTER, 0); + // The pool is only used by removeslivers(). + badtetrahedrons = new memorypool(sizeof(triface), b->tetrahedraperblock, + sizeof(void *), 0); // Smoothing options. opm.min_max_dihedangle = 1; opm.numofsearchdirs = 10; // opm.searchstep = 0.001; opm.maxiter = 30; // Limit the maximum iterations. - opm.checkencflag = 4; // Queue affected tets after smoothing. + //opm.checkencflag = 4; // Queue affected tets after smoothing. chkencflag = 4; // Queue affected tets after splitting a sliver. iter = 0; @@ -28000,13 +26907,13 @@ void tetgenmesh::optimizemesh() if (b->verbose) { if (totalremcount > 0l) { - printf(" Removed %ld bad tets.\n", totalremcount); + printf(" Removed %ld edges.\n", totalremcount); } if (totalsmtcount > 0l) { printf(" Smoothed %ld points.\n", totalsmtcount); } if (totalsptcount > 0l) { - printf(" Split %ld bad tets.\n", totalsptcount); + printf(" Split %ld slivers.\n", totalsptcount); } } } @@ -28019,6 +26926,32 @@ void tetgenmesh::optimizemesh() //// //// //// //// +/////////////////////////////////////////////////////////////////////////////// +// // +// printfcomma() Print a (large) number with the 'thousands separator'. // +// // +// The following code was simply copied from "stackoverflow". // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::printfcomma(unsigned long n) +{ + unsigned long n2 = 0; + int scale = 1; + while (n >= 1000) { + n2 = n2 + scale * (n % 1000); + n /= 1000; + scale *= 1000; + } + printf ("%ld", n); + while (scale != 1) { + scale /= 1000; + n = n2 / scale; + n2 = n2 % scale; + printf (",%03ld", n); + } +} + /////////////////////////////////////////////////////////////////////////////// // // // checkmesh() Test the mesh for topological consistency. // @@ -28164,12 +27097,12 @@ int tetgenmesh::checkmesh(int topoflag) // // /////////////////////////////////////////////////////////////////////////////// -int tetgenmesh::checkshells(/*int sub2tet*/) +int tetgenmesh::checkshells() { triface neightet, symtet; face shloop, spinsh, nextsh; face checkseg; - point pa, pb; //, *ppt; + point pa, pb; int bakcount; int horrors, i; @@ -28197,10 +27130,10 @@ int tetgenmesh::checkshells(/*int sub2tet*/) while ((nextsh.sh != NULL) && (nextsh.sh != shloop.sh)) { if (nextsh.sh[3] == NULL) { printf(" !! !! Wrong subface-subface connection (Dead subface).\n"); - printf(" First: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" First: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); - printf(" Second: x%lx (DEAD)\n", (unsigned long) nextsh.sh); + printf(" Second: x%lx (DEAD)\n", (uintptr_t) nextsh.sh); horrors++; break; } @@ -28208,10 +27141,10 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (!(((sorg(nextsh) == pa) && (sdest(nextsh) == pb)) || ((sorg(nextsh) == pb) && (sdest(nextsh) == pa)))) { printf(" !! !! Wrong subface-subface connection.\n"); - printf(" First: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" First: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); - printf(" Scond: x%lx (%d, %d, %d).\n", (unsigned long) nextsh.sh, + printf(" Scond: x%lx (%d, %d, %d).\n", (uintptr_t) nextsh.sh, pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), pointmark(sapex(nextsh))); horrors++; @@ -28220,10 +27153,10 @@ int tetgenmesh::checkshells(/*int sub2tet*/) // Check they should not have the same apex. if (sapex(nextsh) == sapex(spinsh)) { printf(" !! !! Existing two duplicated subfaces.\n"); - printf(" First: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" First: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); - printf(" Scond: x%lx (%d, %d, %d).\n", (unsigned long) nextsh.sh, + printf(" Scond: x%lx (%d, %d, %d).\n", (uintptr_t) nextsh.sh, pointmark(sorg(nextsh)), pointmark(sdest(nextsh)), pointmark(sapex(nextsh))); horrors++; @@ -28237,19 +27170,19 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (checkseg.sh != NULL) { if (checkseg.sh[3] == NULL) { printf(" !! !! Wrong subface-subseg connection (Dead subseg).\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) shloop.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, pointmark(sorg(shloop)), pointmark(sdest(shloop)), pointmark(sapex(shloop))); - printf(" Sub: x%lx (Dead)\n", (unsigned long) checkseg.sh); + printf(" Sub: x%lx (Dead)\n", (uintptr_t) checkseg.sh); horrors++; } else { if (!(((sorg(checkseg) == pa) && (sdest(checkseg) == pb)) || ((sorg(checkseg) == pb) && (sdest(checkseg) == pa)))) { printf(" !! !! Wrong subface-subseg connection.\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) shloop.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, pointmark(sorg(shloop)), pointmark(sdest(shloop)), pointmark(sapex(shloop))); - printf(" Seg: x%lx (%d, %d).\n", (unsigned long) checkseg.sh, + printf(" Seg: x%lx (%d, %d).\n", (uintptr_t) checkseg.sh, pointmark(sorg(checkseg)), pointmark(sdest(checkseg))); horrors++; } @@ -28263,20 +27196,20 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (neightet.tet != NULL) { if (neightet.tet[4] == NULL) { printf(" !! !! Wrong sub-to-tet connection (Dead tet)\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) shloop.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, pointmark(sorg(shloop)), pointmark(sdest(shloop)), pointmark(sapex(shloop))); - printf(" Tet: x%lx (DEAD)\n", (unsigned long) neightet.tet); + printf(" Tet: x%lx (DEAD)\n", (uintptr_t) neightet.tet); horrors++; } else { if (!((sorg(shloop) == org(neightet)) && (sdest(shloop) == dest(neightet)))) { printf(" !! !! Wrong sub-to-tet connection\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) shloop.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) shloop.sh, pointmark(sorg(shloop)), pointmark(sdest(shloop)), pointmark(sapex(shloop))); printf(" Tet: x%lx (%d, %d, %d, %d).\n", - (unsigned long) neightet.tet, pointmark(org(neightet)), + (uintptr_t) neightet.tet, pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), pointmark(oppo(neightet))); horrors++; @@ -28285,11 +27218,11 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (!((sorg(spinsh) == org(neightet)) && (sdest(spinsh) == dest(neightet)))) { printf(" !! !! Wrong tet-sub connection.\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); printf(" Tet: x%lx (%d, %d, %d, %d).\n", - (unsigned long) neightet.tet, pointmark(org(neightet)), + (uintptr_t) neightet.tet, pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), pointmark(oppo(neightet))); horrors++; @@ -28300,11 +27233,11 @@ int tetgenmesh::checkshells(/*int sub2tet*/) if (!((sorg(spinsh) == org(symtet)) && (sdest(spinsh) == dest(symtet)))) { printf(" !! !! Wrong tet-sub connection.\n"); - printf(" Sub: x%lx (%d, %d, %d).\n", (unsigned long) spinsh.sh, + printf(" Sub: x%lx (%d, %d, %d).\n", (uintptr_t) spinsh.sh, pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), pointmark(sapex(spinsh))); printf(" Tet: x%lx (%d, %d, %d, %d).\n", - (unsigned long) symtet.tet, pointmark(org(symtet)), + (uintptr_t) symtet.tet, pointmark(org(symtet)), pointmark(dest(symtet)), pointmark(apex(symtet)), pointmark(oppo(symtet))); horrors++; @@ -28359,8 +27292,10 @@ int tetgenmesh::checksegments() face sseg, checkseg; point pa, pb; int miscount; + int t1ver; int horrors, i; + if (!b->quiet) { printf(" Checking tet->seg connections...\n"); } @@ -28384,9 +27319,9 @@ int tetgenmesh::checksegments() ((org(tetloop) == pb) && (dest(tetloop) == pa)))) { printf(" !! Wrong tet-seg connection.\n"); printf(" Tet: x%lx (%d, %d, %d, %d) - Seg: x%lx (%d, %d).\n", - (unsigned long) tetloop.tet, pointmark(org(tetloop)), + (uintptr_t) tetloop.tet, pointmark(org(tetloop)), pointmark(dest(tetloop)), pointmark(apex(tetloop)), - pointmark(oppo(tetloop)), (unsigned long) sseg.sh, + pointmark(oppo(tetloop)), (uintptr_t) sseg.sh, pointmark(pa), pointmark(pb)); horrors++; } else { @@ -28397,11 +27332,11 @@ int tetgenmesh::checksegments() if (checkseg.sh != sseg.sh) { printf(" !! Wrong tet->seg connection.\n"); printf(" Tet: x%lx (%d, %d, %d, %d) - ", - (unsigned long) neightet.tet, pointmark(org(neightet)), + (uintptr_t) neightet.tet, pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), pointmark(oppo(neightet))); if (checkseg.sh != NULL) { - printf("Seg x%lx (%d, %d).\n", (unsigned long) checkseg.sh, + printf("Seg x%lx (%d, %d).\n", (uintptr_t) checkseg.sh, pointmark(sorg(checkseg)),pointmark(sdest(checkseg))); } else { printf("Seg: NULL.\n"); @@ -28421,9 +27356,9 @@ int tetgenmesh::checksegments() ((org(neightet) == pb) && (dest(neightet) == pa)))) { printf(" !! Wrong seg->tet connection (Wrong edge).\n"); printf(" Tet: x%lx (%d, %d, %d, %d) - Seg: x%lx (%d, %d).\n", - (unsigned long) neightet.tet, pointmark(org(neightet)), + (uintptr_t) neightet.tet, pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), - pointmark(oppo(neightet)), (unsigned long) sseg.sh, + pointmark(oppo(neightet)), (uintptr_t) sseg.sh, pointmark(pa), pointmark(pb)); horrors++; } @@ -28440,7 +27375,7 @@ int tetgenmesh::checksegments() printf(" !! A marked edge: (%d, %d, %d, %d) -- x%lx %d.\n", pointmark(org(neightet)), pointmark(dest(neightet)), pointmark(apex(neightet)), pointmark(oppo(neightet)), - (unsigned long) neightet.tet, neightet.ver); + (uintptr_t) neightet.tet, neightet.ver); // Check if all tets at the edge are marked. spintet = neightet; while (1) { @@ -28449,7 +27384,7 @@ int tetgenmesh::checksegments() printf(" !! !! An unmarked edge (%d, %d, %d, %d) -- x%lx %d.\n", pointmark(org(spintet)), pointmark(dest(spintet)), pointmark(apex(spintet)), pointmark(oppo(spintet)), - (unsigned long) spintet.tet, spintet.ver); + (uintptr_t) spintet.tet, spintet.ver); horrors++; } if (spintet.tet == neightet.tet) break; @@ -28482,7 +27417,7 @@ int tetgenmesh::checksegments() // sesymself(spinsh); // printf(" !! Wrong ori at subface (%d, %d, %d) -- x%lx %d\n", // pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), - // pointmark(sapex(spinsh)), (unsigned long) spinsh.sh, + // pointmark(sapex(spinsh)), (uintptr_t) spinsh.sh, // spinsh.shver); // horrors++; //} @@ -28495,7 +27430,7 @@ int tetgenmesh::checksegments() printf(" !! !! No seg at tet (%d, %d, %d, %d) -- x%lx %d\n", pointmark(org(spintet)), pointmark(dest(spintet)), pointmark(apex(spintet)), pointmark(oppo(spintet)), - (unsigned long) spintet.tet, spintet.ver); + (uintptr_t) spintet.tet, spintet.ver); horrors++; } if (checkseg.sh != sseg.sh) { @@ -28514,7 +27449,7 @@ int tetgenmesh::checksegments() } else { printf(" !! Wrong seg-subface (%d, %d, %d) -- x%lx %d connect\n", pointmark(sorg(spinsh)), pointmark(sdest(spinsh)), - pointmark(sapex(spinsh)), (unsigned long) spinsh.sh, + pointmark(sapex(spinsh)), (uintptr_t) spinsh.sh, spinsh.shver); horrors++; break; @@ -28795,6 +27730,7 @@ int tetgenmesh::checkconforming(int flag) REAL cent[3], radius, dist, diff, rd, len; bool enq; int encsubsegs, encsubfaces; + int t1ver; int i; REAL A[4][4], rhs[4], D; @@ -28950,10 +27886,10 @@ void tetgenmesh::qualitystatistics() REAL tetaspect, tetradius; REAL smalldiangle, bigdiangle; REAL smallfaangle, bigfaangle; - int radiustable[12]; - int aspecttable[16]; - int dihedangletable[18]; - int faceangletable[18]; + unsigned long radiustable[12]; + unsigned long aspecttable[16]; + unsigned long dihedangletable[18]; + unsigned long faceangletable[18]; int indx[4]; int radiusindex; int aspectindex; @@ -28981,10 +27917,10 @@ void tetgenmesh::qualitystatistics() aspectratiotable[8] = 25.0; aspectratiotable[9] = 50.0; aspectratiotable[10] = 100.0; aspectratiotable[11] = 0.0; - for (i = 0; i < 12; i++) radiustable[i] = 0; - for (i = 0; i < 12; i++) aspecttable[i] = 0; - for (i = 0; i < 18; i++) dihedangletable[i] = 0; - for (i = 0; i < 18; i++) faceangletable[i] = 0; + for (i = 0; i < 12; i++) radiustable[i] = 0l; + for (i = 0; i < 12; i++) aspecttable[i] = 0l; + for (i = 0; i < 18; i++) dihedangletable[i] = 0l; + for (i = 0; i < 18; i++) faceangletable[i] = 0l; minaltitude = xmax - xmin + ymax - ymin + zmax - zmin; minaltitude = minaltitude * minaltitude; @@ -28998,16 +27934,26 @@ void tetgenmesh::qualitystatistics() biggestdiangle = biggestfaangle = 0.0; + int attrnum = numelemattrib - 1; + // Loop all elements, calculate quality parameters for each element. tetrahedrons->traversalinit(); tetloop.tet = tetrahedrontraverse(); while (tetloop.tet != (tetrahedron *) NULL) { + if (b->convex) { + // Skip tets in the exterior. + if (elemattribute(tetloop.tet, attrnum) == -1.0) { + tetloop.tet = tetrahedrontraverse(); + continue; + } + } + // Get four vertices: p0, p1, p2, p3. for (i = 0; i < 4; i++) p[i] = (point) tetloop.tet[4 + i]; // Get the tet volume. - tetvol = orient3d(p[1], p[0], p[2], p[3]) / 6.0; + tetvol = orient3dfast(p[1], p[0], p[2], p[3]) / 6.0; total_tet_vol += tetvol; total_tetprism_vol += tetprismvol(p[0], p[1], p[2], p[3]); @@ -29027,7 +27973,7 @@ void tetgenmesh::qualitystatistics() for (i = 0; i < 3; i++) V[4][i] = p[2][i] - p[1][i]; // V[4]: p1->p2. for (i = 0; i < 3; i++) V[5][i] = p[0][i] - p[2][i]; // V[5]: p2->p0. - // Get the squares of the edge lengthes. + // Get the squares of the edge lengths. for (i = 0; i < 6; i++) edgelength[i] = dot(V[i], V[i]); // Calculate the longest and shortest edge length. @@ -29169,7 +28115,7 @@ void tetgenmesh::qualitystatistics() - // Calulate the largest and smallest face angles. + // Calculate the largest and smallest face angles. for (tetloop.ver = 0; tetloop.ver < 4; tetloop.ver++) { fsym(tetloop, neightet); // Only do the calulation once for a face. @@ -29256,16 +28202,16 @@ void tetgenmesh::qualitystatistics() smallestdiangle, sbuf); printf(" Aspect ratio histogram:\n"); - printf(" < %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", + printf(" < %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", aspectratiotable[0], aspecttable[0], aspectratiotable[5], aspectratiotable[6], aspecttable[6]); for (i = 1; i < 5; i++) { - printf(" %6.6g - %-6.6g : %8d | %6.6g - %-6.6g : %8d\n", + printf(" %6.6g - %-6.6g : %8ld | %6.6g - %-6.6g : %8ld\n", aspectratiotable[i - 1], aspectratiotable[i], aspecttable[i], aspectratiotable[i + 5], aspectratiotable[i + 6], aspecttable[i + 6]); } - printf(" %6.6g - %-6.6g : %8d | %6.6g - : %8d\n", + printf(" %6.6g - %-6.6g : %8ld | %6.6g - : %8ld\n", aspectratiotable[4], aspectratiotable[5], aspecttable[5], aspectratiotable[10], aspecttable[11]); printf(" (A tetrahedron's aspect ratio is its longest edge length"); @@ -29274,7 +28220,7 @@ void tetgenmesh::qualitystatistics() printf(" Face angle histogram:\n"); for (i = 0; i < 9; i++) { - printf(" %3d - %3d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %3d degrees: %8ld | %3d - %3d degrees: %8ld\n", i * 10, i * 10 + 10, faceangletable[i], i * 10 + 90, i * 10 + 100, faceangletable[i + 9]); } @@ -29286,20 +28232,20 @@ void tetgenmesh::qualitystatistics() printf(" Dihedral angle histogram:\n"); // Print the three two rows: - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", 0, 5, dihedangletable[0], 80, 110, dihedangletable[9]); - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", 5, 10, dihedangletable[1], 110, 120, dihedangletable[10]); // Print the third to seventh rows. for (i = 2; i < 7; i++) { - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", (i - 1) * 10, (i - 1) * 10 + 10, dihedangletable[i], (i - 1) * 10 + 110, (i - 1) * 10 + 120, dihedangletable[i + 9]); } // Print the last two rows. - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", 60, 70, dihedangletable[7], 170, 175, dihedangletable[16]); - printf(" %3d - %2d degrees: %8d | %3d - %3d degrees: %8d\n", + printf(" %3d - %2d degrees: %8ld | %3d - %3d degrees: %8ld\n", 70, 80, dihedangletable[8], 175, 180, dihedangletable[17]); if (minfacetdihed != PI) { printf(" Minimum input dihedral angle is %g (degree).\n", @@ -29311,6 +28257,96 @@ void tetgenmesh::qualitystatistics() } +/////////////////////////////////////////////////////////////////////////////// +// // +// memorystatistics() Report the memory usage. // +// // +/////////////////////////////////////////////////////////////////////////////// + +void tetgenmesh::memorystatistics() +{ + printf("Memory usage statistics:\n\n"); + + // Count the number of blocks of tetrahedra. + int tetblocks = 0; + tetrahedrons->pathblock = tetrahedrons->firstblock; + while (tetrahedrons->pathblock != NULL) { + tetblocks++; + tetrahedrons->pathblock = (void **) *(tetrahedrons->pathblock); + } + + // Calculate the total memory (in bytes) used by storing meshes. + unsigned long totalmeshmemory = 0l, totalt2shmemory = 0l; + totalmeshmemory = points->maxitems * points->itembytes + + tetrahedrons->maxitems * tetrahedrons->itembytes; + if (b->plc || b->refine) { + totalmeshmemory += (subfaces->maxitems * subfaces->itembytes + + subsegs->maxitems * subsegs->itembytes); + totalt2shmemory = (tet2subpool->maxitems * tet2subpool->itembytes + + tet2segpool->maxitems * tet2segpool->itembytes); + } + + unsigned long totalalgomemory = 0l; + totalalgomemory = cavetetlist->totalmemory + cavebdrylist->totalmemory + + caveoldtetlist->totalmemory + + flippool->maxitems * flippool->itembytes; + if (b->plc || b->refine) { + totalalgomemory += (subsegstack->totalmemory + subfacstack->totalmemory + + subvertstack->totalmemory + + caveshlist->totalmemory + caveshbdlist->totalmemory + + cavesegshlist->totalmemory + + cavetetshlist->totalmemory + + cavetetseglist->totalmemory + + caveencshlist->totalmemory + + caveencseglist->totalmemory + + cavetetvertlist->totalmemory + + unflipqueue->totalmemory); + } + + printf(" Maximum number of tetrahedra: %ld\n", tetrahedrons->maxitems); + printf(" Maximum number of tet blocks (blocksize = %d): %d\n", + b->tetrahedraperblock, tetblocks); + /* + if (b->plc || b->refine) { + printf(" Approximate memory for tetrahedral mesh (bytes): %ld\n", + totalmeshmemory); + + printf(" Approximate memory for extra pointers (bytes): %ld\n", + totalt2shmemory); + } else { + printf(" Approximate memory for tetrahedralization (bytes): %ld\n", + totalmeshmemory); + } + printf(" Approximate memory for algorithms (bytes): %ld\n", + totalalgomemory); + printf(" Approximate memory for working arrays (bytes): %ld\n", + totalworkmemory); + printf(" Approximate total used memory (bytes): %ld\n", + totalmeshmemory + totalt2shmemory + totalalgomemory + + totalworkmemory); + */ + if (b->plc || b->refine) { + printf(" Approximate memory for tetrahedral mesh (bytes): "); + printfcomma(totalmeshmemory); printf("\n"); + + printf(" Approximate memory for extra pointers (bytes): "); + printfcomma(totalt2shmemory); printf("\n"); + } else { + printf(" Approximate memory for tetrahedralization (bytes): "); + printfcomma(totalmeshmemory); printf("\n"); + } + printf(" Approximate memory for algorithms (bytes): "); + printfcomma(totalalgomemory); printf("\n"); + printf(" Approximate memory for working arrays (bytes): "); + printfcomma(totalworkmemory); printf("\n"); + printf(" Approximate total used memory (bytes): "); + printfcomma(totalmeshmemory + totalt2shmemory + totalalgomemory + + totalworkmemory); + printf("\n"); + + printf("\n"); +} + /////////////////////////////////////////////////////////////////////////////// // // // statistics() Print all sorts of cool facts. // @@ -29343,23 +28379,34 @@ void tetgenmesh::statistics() } printf(" Mesh tetrahedra: %ld\n", tetnumber); printf(" Mesh faces: %ld\n", facenumber); - printf(" Mesh edges: %ld\n", meshedges); + if (meshedges > 0l) { + printf(" Mesh edges: %ld\n", meshedges); + } else { + if (!nonconvex) { + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + meshedges = vsize + facenumber - tetnumber - 1; + printf(" Mesh edges: %ld\n", meshedges); + } + } if (b->plc || b->refine) { - printf(" Mesh boundary faces: %ld\n", subfaces->items); - printf(" Mesh boundary edges: %ld\n", subsegs->items); - if (st_segref_count > 0l) { - printf(" Steiner points on boundary edges: %ld\n", st_segref_count); + printf(" Mesh faces on facets: %ld\n", subfaces->items); + printf(" Mesh edges on segments: %ld\n", subsegs->items); + if (st_volref_count > 0l) { + printf(" Steiner points inside domain: %ld\n", st_volref_count); } if (st_facref_count > 0l) { - printf(" Steiner points on boundary faces: %ld\n", st_facref_count); + printf(" Steiner points on facets: %ld\n", st_facref_count); } - if (st_volref_count > 0l) { - printf(" Steiner points in mesh domain: %ld\n", st_volref_count); + if (st_segref_count > 0l) { + printf(" Steiner points on segments: %ld\n", st_segref_count); } } else { printf(" Convex hull faces: %ld\n", hullsize); - printf(" Convex hull edges: %ld\n", meshhulledges); + if (meshhulledges > 0l) { + printf(" Convex hull edges: %ld\n", meshhulledges); + } } if (b->weighted) { // -w option printf(" Skipped non-regular points: %ld\n", nonregularcount); @@ -29373,6 +28420,9 @@ void tetgenmesh::statistics() qualitystatistics(); } } + if (tetrahedrons->items > 0l) { + memorystatistics(); + } } } @@ -29404,7 +28454,7 @@ void tetgenmesh::jettisonnodes() int remcount; if (!b->quiet) { - printf("Jettisoning redundants points.\n"); + printf("Jettisoning redundant points.\n"); } points->traversalinit(); @@ -29433,11 +28483,11 @@ void tetgenmesh::jettisonnodes() pointloop = pointtraverse(); } if (b->verbose) { - printf(" %d duplicated vertices are removed.\n", dupverts); - printf(" %d unused vertices are removed.\n", unuverts); + printf(" %ld duplicated vertices are removed.\n", dupverts); + printf(" %ld unused vertices are removed.\n", unuverts); } - dupverts = 0; - unuverts = 0; + dupverts = 0l; + unuverts = 0l; // The following line ensures that dead items in the pool of nodes cannot // be allocated for the new created nodes. This ensures that the input @@ -29461,6 +28511,7 @@ void tetgenmesh::highorder() point *extralist, *adjextralist; point torg, tdest, newpoint; int highorderindex; + int t1ver; int i, j; if (!b->quiet) { @@ -29470,7 +28521,7 @@ void tetgenmesh::highorder() // Initialize the 'highordertable'. highordertable = new point[tetrahedrons->items * 6]; if (highordertable == (point *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } // This will overwrite the slot for element markers. @@ -29547,28 +28598,18 @@ void tetgenmesh::highorder() // This routine is called when '-p' or '-r', and '-E' options are used. The // // total number of edges depends on the genus of the input surface mesh. // // // +// NOTE: This routine must be called after outelements(). So all elements // +// have been indexed. // +// // /////////////////////////////////////////////////////////////////////////////// void tetgenmesh::numberedges() { triface worktet, spintet; - int firstindex, eindex; int ishulledge; + int t1ver; int i; - // Determine the first index (0 or 1). - firstindex = b->zeroindex ? 0 : in->firstnumber; - - // First indexing all tetrahedra. - tetrahedrons->traversalinit(); - eindex = firstindex; - worktet.tet = tetrahedrontraverse(); - while (worktet.tet != NULL) { - setelemindex(worktet.tet, eindex); - eindex++; - worktet.tet = tetrahedrontraverse(); - } - meshedges = meshhulledges = 0l; tetrahedrons->traversalinit(); @@ -29643,7 +28684,7 @@ void tetgenmesh::outnodes(tetgenio* out) outfile = fopen(outnodefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outnodefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } // Number of points, number of dimensions, number of point attributes, // and number of boundary markers (zero or one). @@ -29653,14 +28694,14 @@ void tetgenmesh::outnodes(tetgenio* out) out->pointlist = new REAL[points->items * 3]; if (out->pointlist == (REAL *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } // Allocate space for 'pointattributelist' if necessary; if (nextras > 0) { out->pointattributelist = new REAL[points->items * nextras]; if (out->pointattributelist == (REAL *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } // Allocate space for 'pointmarkerlist' if necessary; @@ -29668,14 +28709,14 @@ void tetgenmesh::outnodes(tetgenio* out) out->pointmarkerlist = new int[points->items]; if (out->pointmarkerlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } if (b->psc) { out->pointparamlist = new tetgenio::pointparam[points->items]; if (out->pointparamlist == NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } out->numberofpoints = points->items; @@ -29830,7 +28871,7 @@ void tetgenmesh::outmetrics(tetgenio* out) outfile = fopen(outmtrfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outmtrfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of points, number of point metrices, // fprintf(outfile, "%ld %d\n", points->items, sizeoftensor + 3); @@ -29840,7 +28881,7 @@ void tetgenmesh::outmetrics(tetgenio* out) // out->pointmtrlist = new REAL[points->items * (sizeoftensor + 3)]; out->pointmtrlist = new REAL[points->items]; if (out->pointmtrlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } out->numberofpointmtrs = 1; // (sizeoftensor + 3); mtrindex = 0; @@ -29884,7 +28925,6 @@ void tetgenmesh::outelements(tetgenio* out) FILE *outfile = NULL; char outelefilename[FILENAMESIZE]; tetrahedron* tptr; - triface worktet, spintet; point p1, p2, p3, p4; point *extralist; REAL *talist = NULL; @@ -29895,7 +28935,6 @@ void tetgenmesh::outelements(tetgenio* out) int highorderindex = 11; int elementnumber; int eextras; - int ishulledge; int i; if (out == (tetgenio *) NULL) { @@ -29919,7 +28958,7 @@ void tetgenmesh::outelements(tetgenio* out) outfile = fopen(outelefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outelefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } // Number of tetras, points per tetra, attributes per tetra. fprintf(outfile, "%ld %d %d\n", ntets, b->order == 1 ? 4 : 10, eextras); @@ -29928,14 +28967,14 @@ void tetgenmesh::outelements(tetgenio* out) out->tetrahedronlist = new int[ntets * (b->order == 1 ? 4 : 10)]; if (out->tetrahedronlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } // Allocate memory for output tetrahedron attributes if necessary. if (eextras > 0) { out->tetrahedronattributelist = new REAL[ntets * eextras]; if (out->tetrahedronattributelist == (REAL *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } out->numberoftetrahedra = ntets; @@ -29949,7 +28988,7 @@ void tetgenmesh::outelements(tetgenio* out) // Determine the first index (0 or 1). firstindex = b->zeroindex ? 0 : in->firstnumber; - shift = 0; // Default no shiftment. + shift = 0; // Default no shift. if ((in->firstnumber == 1) && (firstindex == 0)) { shift = 1; // Shift the output indices by 1. } @@ -30008,36 +29047,6 @@ void tetgenmesh::outelements(tetgenio* out) elementnumber++; } - // Count the number of edges (# Voronoi faces). - meshedges = meshhulledges = 0l; - - tetrahedrons->traversalinit(); - tptr = tetrahedrontraverse(); - while (tptr != (tetrahedron *) NULL) { - // Count the number of Voronoi faces. Look at the six edges of this - // tet. Count an edge only if this tet's pointer is smaller than - // those of other non-hull tets which share this edge. - worktet.tet = tptr; - for (i = 0; i < 6; i++) { - worktet.ver = edge2ver[i]; - ishulledge = 0; - fnext(worktet, spintet); - do { - if (!ishulltet(spintet)) { - if (elemindex(spintet.tet) < elemindex(worktet.tet)) break; - } else { - ishulledge = 1; - } - fnextself(spintet); - } while (spintet.tet != worktet.tet); - // Count this edge if no adjacent tets are smaller than this tet. - if (spintet.tet == worktet.tet) { - meshedges++; - if (ishulledge) meshhulledges++; - } - } - tptr = tetrahedrontraverse(); - } if (out == (tetgenio *) NULL) { fprintf(outfile, "# Generated by %s\n", b->commandline); @@ -30064,13 +29073,13 @@ void tetgenmesh::outfaces(tetgenio* out) int faceid, marker = 0; int firstindex, shift; int facenumber; - int index; + int index = 0; // For -o2 option. triface workface; point *extralist, pp[3] = {0,0,0}; int highorderindex = 11; - int i; + int o2index = 0, i; if (out == (tetgenio *) NULL) { strcpy(facefilename, b->outfilename); @@ -30092,23 +29101,25 @@ void tetgenmesh::outfaces(tetgenio* out) outfile = fopen(facefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } fprintf(outfile, "%ld %d\n", faces, !b->nobound); } else { // Allocate memory for 'trifacelist'. - //out->trifacelist = new int[faces * 3]; - out->trifacelist = new int[faces * (b->order == 1 ? 3 : 6)]; + out->trifacelist = new int[faces * 3]; if (out->trifacelist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2facelist = new int[faces * 3]; } // Allocate memory for 'trifacemarkerlist' if necessary. if (!b->nobound) { out->trifacemarkerlist = new int[faces]; if (out->trifacemarkerlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } if (b->neighout > 1) { @@ -30116,13 +29127,12 @@ void tetgenmesh::outfaces(tetgenio* out) out->adjtetlist = new int[faces * 2]; if (out->adjtetlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } } out->numberoftrifaces = faces; elist = out->trifacelist; emlist = out->trifacemarkerlist; - index = 0; } // Determine the first index (0 or 1). @@ -30150,7 +29160,8 @@ void tetgenmesh::outfaces(tetgenio* out) if (b->order == 2) { // -o2 // Get the three extra vertices on edges. extralist = (point *) (tface.tet[highorderindex]); - workface = tface; + // The extra vertices are on edges opposite the corners. + enext(tface, workface); for (i = 0; i < 3; i++) { pp[i] = extralist[ver2edge[workface.ver]]; enextself(workface); @@ -30209,9 +29220,9 @@ void tetgenmesh::outfaces(tetgenio* out) elist[index++] = pointmark(tdest) - shift; elist[index++] = pointmark(tapex) - shift; if (b->order == 2) { // -o2 - elist[index++] = pointmark(pp[0]) - shift; - elist[index++] = pointmark(pp[1]) - shift; - elist[index++] = pointmark(pp[2]) - shift; + out->o2facelist[o2index++] = pointmark(pp[0]) - shift; + out->o2facelist[o2index++] = pointmark(pp[1]) - shift; + out->o2facelist[o2index++] = pointmark(pp[2]) - shift; } if (!b->nobound) { emlist[facenumber - in->firstnumber] = marker; @@ -30269,7 +29280,7 @@ void tetgenmesh::outhullfaces(tetgenio* out) outfile = fopen(facefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } fprintf(outfile, "%ld 0\n", hullsize); } else { @@ -30277,7 +29288,7 @@ void tetgenmesh::outhullfaces(tetgenio* out) out->trifacelist = new int[hullsize * 3]; if (out->trifacelist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } out->numberoftrifaces = hullsize; elist = out->trifacelist; @@ -30350,9 +29361,11 @@ void tetgenmesh::outsubfaces(tetgenio* out) // For -o2 option. triface workface; - point *extralist, pp[3]; + point *extralist, pp[3] = {0,0,0}; int highorderindex = 11; - int i; + int o2index = 0, i; + + int t1ver; // used by fsymself() if (out == (tetgenio *) NULL) { strcpy(facefilename, b->outfilename); @@ -30371,29 +29384,31 @@ void tetgenmesh::outsubfaces(tetgenio* out) outfile = fopen(facefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", facefilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of subfaces. fprintf(outfile, "%ld %d\n", subfaces->items, !b->nobound); } else { // Allocate memory for 'trifacelist'. - //out->trifacelist = new int[subfaces->items * 3]; - out->trifacelist = new int[subfaces->items * (b->order == 1 ? 3 : 6)]; + out->trifacelist = new int[subfaces->items * 3]; if (out->trifacelist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2facelist = new int[subfaces->items * 3]; } if (!b->nobound) { // Allocate memory for 'trifacemarkerlist'. out->trifacemarkerlist = new int[subfaces->items]; if (out->trifacemarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } if (b->neighout > 1) { // '-nn' switch. out->adjtetlist = new int[subfaces->items * 2]; if (out->adjtetlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } out->numberoftrifaces = subfaces->items; @@ -30448,11 +29463,19 @@ void tetgenmesh::outsubfaces(tetgenio* out) } } if (!b->nobound) { - if (in->facetmarkerlist) { - faceid = shellmark(faceloop) - 1; - marker = in->facetmarkerlist[faceid]; + if (b->refine) { // -r option. + if (in->trifacemarkerlist) { + marker = shellmark(faceloop); + } else { + marker = 1; // Default marker for a subface is 1. + } } else { - marker = 1; // Default marker for a subface is 1. + if (in->facetmarkerlist) { + faceid = shellmark(faceloop) - 1; + marker = in->facetmarkerlist[faceid]; + } else { + marker = 1; // Default marker for a subface is 1. + } } } if (b->neighout > 1) { @@ -30489,9 +29512,9 @@ void tetgenmesh::outsubfaces(tetgenio* out) elist[index++] = pointmark(tdest) - shift; elist[index++] = pointmark(tapex) - shift; if (b->order == 2) { // -o2 - elist[index++] = pointmark(pp[0]) - shift; - elist[index++] = pointmark(pp[1]) - shift; - elist[index++] = pointmark(pp[2]) - shift; + out->o2facelist[o2index++] = pointmark(pp[0]) - shift; + out->o2facelist[o2index++] = pointmark(pp[1]) - shift; + out->o2facelist[o2index++] = pointmark(pp[2]) - shift; } if (!b->nobound) { emlist[index1++] = marker; @@ -30531,12 +29554,14 @@ void tetgenmesh::outedges(tetgenio* out) int ishulledge; int firstindex, shift; int edgenumber, marker; - int index, index1; + int index = 0, index1 = 0, index2 = 0; + int t1ver; int i; // For -o2 option. point *extralist, pp = NULL; int highorderindex = 11; + int o2index = 0; if (out == (tetgenio *) NULL) { strcpy(edgefilename, b->outfilename); @@ -30551,30 +29576,47 @@ void tetgenmesh::outedges(tetgenio* out) } } + if (meshedges == 0l) { + if (nonconvex) { + numberedges(); // Count the edges. + } else { + // Use Euler's characteristic to get the numbe of edges. + // It states V - E + F - C = 1, hence E = V + F - C - 1. + long tsize = tetrahedrons->items - hullsize; + long fsize = (tsize * 4l + hullsize) / 2l; + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + meshedges = vsize + fsize - tsize - 1; + } + } + if (out == (tetgenio *) NULL) { outfile = fopen(edgefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", edgefilename); - terminatetetgen(1); + terminatetetgen(this, 1); } // Write the number of edges, boundary markers (0 or 1). fprintf(outfile, "%ld %d\n", meshedges, !b->nobound); } else { // Allocate memory for 'edgelist'. - //out->edgelist = new int[meshedges * 2]; - out->edgelist = new int[meshedges * (b->order == 1 ? 2 : 3)]; + out->edgelist = new int[meshedges * 2]; if (out->edgelist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->order == 2) { // -o2 switch + out->o2edgelist = new int[meshedges]; } if (!b->nobound) { out->edgemarkerlist = new int[meshedges]; } + if (b->neighout > 1) { // '-nn' switch. + out->edgeadjtetlist = new int[meshedges]; + } out->numberofedges = meshedges; elist = out->edgelist; emlist = out->edgemarkerlist; - index = 0; - index1 = 0; // if (!b->nobound) } // Determine the first index (0 or 1). @@ -30588,9 +29630,7 @@ void tetgenmesh::outedges(tetgenio* out) tetloop.tet = tetrahedrontraverse(); edgenumber = firstindex; // in->firstnumber; while (tetloop.tet != (tetrahedron *) NULL) { - // Count the number of Voronoi faces. Look at the six edges of this - // tet. Count an edge only if this tet's pointer is smaller than - // those of other non-hull tets which share this edge. + // Count the number of Voronoi faces. worktet.tet = tetloop.tet; for (i = 0; i < 6; i++) { worktet.ver = edge2ver[i]; @@ -30624,7 +29664,7 @@ void tetgenmesh::outedges(tetgenio* out) elist[index++] = pointmark(torg) - shift; elist[index++] = pointmark(tdest) - shift; if (b->order == 2) { // -o2 - elist[index++] = pointmark(pp) - shift; + out->o2edgelist[o2index++] = pointmark(pp) - shift; } } if (!b->nobound) { @@ -30649,6 +29689,13 @@ void tetgenmesh::outedges(tetgenio* out) emlist[index1++] = marker; } } + if (b->neighout > 1) { // '-nn' switch. + if (out == (tetgenio *) NULL) { + fprintf(outfile, " %d", elemindex(tetloop.tet)); + } else { + out->edgeadjtetlist[index2++] = elemindex(tetloop.tet); + } + } if (out == (tetgenio *) NULL) { fprintf(outfile, "\n"); } @@ -30686,6 +29733,13 @@ void tetgenmesh::outsubsegments(tetgenio* out) triface workface, spintet; point *extralist, pp = NULL; int highorderindex = 11; + int o2index = 0; + + // For -nn option. + int neigh = -1; + int index2 = 0; + + int t1ver; // used by fsymself() if (out == (tetgenio *) NULL) { strcpy(edgefilename, b->outfilename); @@ -30704,20 +29758,25 @@ void tetgenmesh::outsubsegments(tetgenio* out) outfile = fopen(edgefilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", edgefilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of subsegments. fprintf(outfile, "%ld 1\n", subsegs->items); } else { // Allocate memory for 'edgelist'. - //out->edgelist = new int[subsegs->items * 2]; out->edgelist = new int[subsegs->items * (b->order == 1 ? 2 : 3)]; if (out->edgelist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->order == 2) { + out->o2edgelist = new int[subsegs->items]; } out->edgemarkerlist = new int[subsegs->items]; if (out->edgemarkerlist == (int *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); + } + if (b->neighout > 1) { + out->edgeadjtetlist = new int[subsegs->items]; } out->numberofedges = subsegs->items; elist = out->edgelist; @@ -30738,8 +29797,7 @@ void tetgenmesh::outsubsegments(tetgenio* out) while (edgeloop.sh != (shellface *) NULL) { torg = sorg(edgeloop); tdest = sdest(edgeloop); - if (b->order == 2) { // -o2 - // Get the extra vertex on this edge. + if ((b->order == 2) || (b->neighout > 1)) { sstpivot1(edgeloop, workface); if (workface.tet != NULL) { // We must find a non-hull tet. @@ -30753,12 +29811,24 @@ void tetgenmesh::outsubsegments(tetgenio* out) assert(!ishulltet(spintet)); workface = spintet; } + } + } + if (b->order == 2) { // -o2 + // Get the extra vertex on this edge. + if (workface.tet != NULL) { extralist = (point *) workface.tet[highorderindex]; pp = extralist[ver2edge[workface.ver]]; } else { pp = torg; // There is no extra node available. } } + if (b->neighout > 1) { // -nn + if (workface.tet != NULL) { + neigh = elemindex(workface.tet); + } else { + neigh = -1; + } + } marker = shellmark(edgeloop); if (marker == 0) { marker = 1; // Default marker of a boundary edge is 1. @@ -30769,15 +29839,22 @@ void tetgenmesh::outsubsegments(tetgenio* out) if (b->order == 2) { // -o2 fprintf(outfile, " %4d", pointmark(pp) - shift); } - fprintf(outfile, " %d\n", marker); + fprintf(outfile, " %d", marker); + if (b->neighout > 1) { // -nn + fprintf(outfile, " %4d", neigh); + } + fprintf(outfile, "\n"); } else { // Output three vertices of this face; elist[index++] = pointmark(torg) - shift; elist[index++] = pointmark(tdest) - shift; if (b->order == 2) { // -o2 - elist[index++] = pointmark(pp) - shift; + out->o2edgelist[o2index++] = pointmark(pp) - shift; } out->edgemarkerlist[i++] = marker; + if (b->neighout > 1) { // -nn + out->edgeadjtetlist[index2++] = neigh; + } } edgenumber++; edgeloop.sh = shellfacetraverse(subsegs); @@ -30826,7 +29903,7 @@ void tetgenmesh::outneighbors(tetgenio* out) outfile = fopen(neighborfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", neighborfilename); - terminatetetgen(1); + terminatetetgen(this, 1); } // Number of tetrahedra, four faces per tetrahedron. fprintf(outfile, "%ld %d\n", ntets, 4); @@ -30835,7 +29912,7 @@ void tetgenmesh::outneighbors(tetgenio* out) out->neighborlist = new int[ntets * 4]; if (out->neighborlist == (int *) NULL) { printf("Error: Out of memory.\n"); - terminatetetgen(1); + terminatetetgen(this, 1); } nlist = out->neighborlist; } @@ -30885,18 +29962,13 @@ void tetgenmesh::outneighbors(tetgenio* out) // Voronoi edge connects two Voronoi vertices at two sides of a common Dela- // // unay face. At a face of convex hull, it becomes a ray (goto the infinity).// // A Voronoi face is the convex hull of all Voronoi vertices around a common // -// Delaunay edge. It is a closed polygon for any interal Delaunay edge. At a // +// Delaunay edge. It is a closed polygon for any internal Delaunay edge. At a// // ridge, it is unbounded. Each Voronoi cell is the convex hull of all Vor- // // onoi vertices around a common Delaunay vertex. It is a polytope for any // // internal Delaunay vertex. It is an unbounded polyhedron for a Delaunay // // vertex belonging to the convex hull. // // // -// aunay tetrahedralization - the power diagram - of the weighted point set. // -// Note that the vertices of the power disgram are the centers of the ortho- // -// spheres of the tetrahedra. // -// // // NOTE: This routine is only used when the input is only a set of point. // -// // // Comment: Special thanks to Victor Liu for finding and fixing few bugs. // // // /////////////////////////////////////////////////////////////////////////////// @@ -30906,7 +29978,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) FILE *outfile = NULL; char outfilename[FILENAMESIZE]; tetgenio::voroedge *vedge = NULL; - tetgenio::vorofacet *vfacet; + tetgenio::vorofacet *vfacet = NULL; arraypool *tetlist, *ptlist; triface tetloop, worktet, spintet, firsttet; point pt[4], ploop, neipt; @@ -30919,6 +29991,8 @@ void tetgenmesh::outvoronoi(tetgenio* out) int index, shift, end1, end2; int i, j; + int t1ver; // used by fsymself() + // Output Voronoi vertices to .v.node file. if (out == (tetgenio *) NULL) { strcpy(outfilename, b->outfilename); @@ -30959,14 +30033,15 @@ void tetgenmesh::outvoronoi(tetgenio* out) // The number of Delaunay faces (Voronoi edges). faces = (4l * ntets + hullsize) / 2l; // The number of Delaunay edges (Voronoi faces). - // edges = points->items + faces - ntets - 1; - edges = meshedges; // Counted in outelements() or numberedges(); + long vsize = points->items - dupverts - unuverts; + if (b->weighted) vsize -= nonregularcount; + edges = vsize + faces - ntets - 1; if (out == (tetgenio *) NULL) { outfile = fopen(outfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of voronoi points, 3 dim, no attributes, no marker. fprintf(outfile, "%ld 3 0 0\n", ntets); @@ -30975,7 +30050,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) out->numberofvpoints = (int) ntets; out->vpointlist = new REAL[out->numberofvpoints * 3]; if (out->vpointlist == (REAL *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } @@ -31031,7 +30106,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) outfile = fopen(outfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of Voronoi edges, no marker. fprintf(outfile, "%ld 0\n", faces); @@ -31133,7 +30208,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) outfile = fopen(outfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of Voronoi faces. fprintf(outfile, "%ld 0\n", edges); @@ -31141,7 +30216,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) out->numberofvfacets = edges; out->vfacetlist = new tetgenio::vorofacet[out->numberofvfacets]; if (out->vfacetlist == (tetgenio::vorofacet *) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } @@ -31247,7 +30322,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) outfile = fopen(outfilename, "w"); if (outfile == (FILE *) NULL) { printf("File I/O Error: Cannot create file %s.\n", outfilename); - terminatetetgen(3); + terminatetetgen(this, 3); } // Number of Voronoi cells. fprintf(outfile, "%ld\n", points->items - unuverts - dupverts); @@ -31255,7 +30330,7 @@ void tetgenmesh::outvoronoi(tetgenio* out) out->numberofvcells = points->items - unuverts - dupverts; out->vcelllist = new int*[out->numberofvcells]; if (out->vcelllist == (int **) NULL) { - terminatetetgen(1); + terminatetetgen(this, 1); } } @@ -31634,6 +30709,11 @@ void tetgenmesh::outmesh2vtk(char* ofilename) int nnodes = 4; int celltype = 10; + if (b->order == 2) { + printf(" Write VTK not implemented for order 2 elements \n"); + return; + } + int NEL = tetrahedrons->items - hullsize; int NN = points->items; @@ -31681,10 +30761,6 @@ void tetgenmesh::outmesh2vtk(char* ofilename) tetrahedrons->traversalinit(); tptr = tetrahedrontraverse(); //elementnumber = firstindex; // in->firstnumber; - if (b->order == 2) { - printf(" Write VTK not implemented for order 2 elements \n"); - return; - } while (tptr != (tetrahedron *) NULL) { if (!b->reversetetori) { p1 = (point) tptr[4]; @@ -31710,6 +30786,20 @@ void tetgenmesh::outmesh2vtk(char* ofilename) } fprintf(outfile, "\n"); + if (numelemattrib > 0) { + // Output tetrahedra region attributes. + fprintf(outfile, "CELL_DATA %d\n", NEL); + fprintf(outfile, "SCALARS cell_scalars int 1\n"); + fprintf(outfile, "LOOKUP_TABLE default\n"); + tetrahedrons->traversalinit(); + tptr = tetrahedrontraverse(); + while (tptr != (tetrahedron *) NULL) { + fprintf(outfile, "%d\n", (int) elemattribute(tptr, numelemattrib - 1)); + tptr = tetrahedrontraverse(); + } + fprintf(outfile, "\n"); + } + fclose(outfile); } @@ -31749,7 +30839,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, tetgenio *addin, tetgenio *bgmin) { tetgenmesh m; - clock_t tv[10], ts[5]; // Timing informations (defined in time.h) + clock_t tv[12], ts[5]; // Timing informations (defined in time.h) REAL cps = (REAL) CLOCKS_PER_SEC; tv[0] = clock(); @@ -31758,7 +30848,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.in = in; m.addin = addin; - if ((b->metric) && (bgmin->numberofpoints > 0)) { + if (b->metric && bgmin && (bgmin->numberofpoints > 0)) { m.bgm = new tetgenmesh(); // Create an empty background mesh. m.bgm->b = b; m.bgm->in = bgmin; @@ -31767,14 +30857,14 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.initializepools(); m.transfernodes(); - exactinit(b->noexact, b->nostaticfilter, m.xmax - m.xmin, - m.ymax - m.ymin, m.zmax - m.zmin); + exactinit(b->verbose, b->noexact, b->nostaticfilter, + m.xmax - m.xmin, m.ymax - m.ymin, m.zmax - m.zmin); tv[1] = clock(); - if (b->refine) { + if (b->refine) { // -r m.reconstructmesh(); - } else { // b->plc + } else { // -p m.incrementaldelaunay(ts[0]); } @@ -31791,7 +30881,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, } } - if (b->plc) { // -p + if (b->plc && !b->refine) { // -p m.meshsurface(); ts[0] = clock(); @@ -31846,7 +30936,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, tv[4] = clock(); - if (b->plc) { // -p + if (b->plc && !b->refine) { // -p if (b->nobisect) { // -Y m.recoverboundary(ts[0]); } else { @@ -31856,7 +30946,12 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, ts[1] = clock(); if (!b->quiet) { - printf("Boundary recovery seconds: %g\n", ((REAL)(ts[1]-tv[4])) / cps); + if (b->nobisect) { + printf("Boundary recovery "); + } else { + printf("Constrained Delaunay "); + } + printf("seconds: %g\n", ((REAL)(ts[1] - tv[4])) / cps); if (b->verbose) { printf(" Segment recovery seconds: %g\n",((REAL)(ts[0]-tv[4]))/ cps); printf(" Facet recovery seconds: %g\n", ((REAL)(ts[1]-ts[0])) / cps); @@ -31868,69 +30963,86 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, ts[2] = clock(); if (!b->quiet) { - printf("Exterior tets removal seconds: %g\n", - ((REAL)(ts[2]-ts[1])) / cps); + printf("Exterior tets removal seconds: %g\n",((REAL)(ts[2]-ts[1]))/cps); } if (b->nobisect) { // -Y - m.suppresssteinerpoints(); + if (m.subvertstack->objects > 0l) { + m.suppresssteinerpoints(); - ts[3] = clock(); + ts[3] = clock(); - if (!b->quiet) { - printf("Steiner suppression seconds: %g\n",((REAL)(ts[3]-ts[2]))/cps); - } - - m.recoverdelaunay(); - - ts[4] = clock(); - - if (!b->quiet) { - printf("Delaunay recovery seconds: %g\n", ((REAL)(ts[4]-ts[3])) / cps); + if (!b->quiet) { + printf("Steiner suppression seconds: %g\n", + ((REAL)(ts[3]-ts[2]))/cps); + } } } } tv[5] = clock(); + if (b->coarsen) { // -R + m.meshcoarsening(); + } + + tv[6] = clock(); + + if (!b->quiet) { + if (b->coarsen) { + printf("Mesh coarsening seconds: %g\n", ((REAL)(tv[6] - tv[5])) / cps); + } + } + + if ((b->plc && b->nobisect) || b->coarsen) { + m.recoverdelaunay(); + } + + tv[7] = clock(); + + if (!b->quiet) { + if ((b->plc && b->nobisect) || b->coarsen) { + printf("Delaunay recovery seconds: %g\n", ((REAL)(tv[7] - tv[6]))/cps); + } + } + if ((b->plc || b->refine) && b->insertaddpoints) { // -i if ((addin != NULL) && (addin->numberofpoints > 0)) { m.insertconstrainedpoints(addin); } } - tv[6] = clock(); + tv[8] = clock(); if (!b->quiet) { - if ((b->plc || b->refine) && b->insertaddpoints) { + if ((b->plc || b->refine) && b->insertaddpoints) { // -i if ((addin != NULL) && (addin->numberofpoints > 0)) { - printf("Constrained points seconds: %g\n", ((REAL)(tv[6]-tv[5]))/cps); + printf("Constrained points seconds: %g\n", ((REAL)(tv[8]-tv[7]))/cps); } } } - if (b->quality) { - m.delaunayrefinement(); + m.delaunayrefinement(); } - tv[7] = clock(); + tv[9] = clock(); if (!b->quiet) { if (b->quality) { - printf("Refinement seconds: %g\n", ((REAL)(tv[7] - tv[6])) / cps); + printf("Refinement seconds: %g\n", ((REAL)(tv[9] - tv[8])) / cps); } } - if ((b->plc || b->refine) && (b->optlevel > 0) && !b->conforming) { + if ((b->plc || b->refine) && (b->optlevel > 0)) { m.optimizemesh(); } - tv[8] = clock(); + tv[10] = clock(); if (!b->quiet) { - if ((b->plc || b->refine) && (b->optlevel > 0) && !b->conforming) { - printf("Optimization seconds: %g\n", ((REAL)(tv[8] - tv[7])) / cps); + if ((b->plc || b->refine) && (b->optlevel > 0)) { + printf("Optimization seconds: %g\n", ((REAL)(tv[10] - tv[9])) / cps); } } @@ -31939,7 +31051,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, m.jettisonnodes(); } - if (b->order == 2) { + if ((b->order == 2) && !b->convex) { m.highorder(); } @@ -31964,7 +31076,6 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, if (!b->quiet) { printf("NOT writing an .ele file.\n"); } - m.numberedges(); } else { if (m.tetrahedrons->items > 0l) { m.outelements(out); @@ -32037,11 +31148,11 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, } - tv[9] = clock(); + tv[11] = clock(); if (!b->quiet) { - printf("\nOutput seconds: %g\n", ((REAL)(tv[9] - tv[8])) / cps); - printf("Total running seconds: %g\n", ((REAL)(tv[9] - tv[0])) / cps); + printf("\nOutput seconds: %g\n", ((REAL)(tv[11] - tv[10])) / cps); + printf("Total running seconds: %g\n", ((REAL)(tv[11] - tv[0])) / cps); } if (b->docheck) { @@ -32064,7 +31175,7 @@ void tetrahedralize(tetgenbehavior *b, tetgenio *in, tetgenio *out, /////////////////////////////////////////////////////////////////////////////// // // -// main() The entrance for running TetGen from command line. // +// main() The command line interface of TetGen. // // // /////////////////////////////////////////////////////////////////////////////// @@ -32074,7 +31185,7 @@ int main(int argc, char *argv[]) /////////////////////////////////////////////////////////////////////////////// // // -// tetrahedralize() The entrance for calling TetGen from another program. // +// tetrahedralize() The library interface of TetGen. // // // /////////////////////////////////////////////////////////////////////////////// @@ -32091,17 +31202,17 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, tetgenio in, addin, bgmin; if (!b.parse_commandline(argc, argv)) { - terminatetetgen(10); + terminatetetgen(NULL, 10); } // Read input files. if (b.refine) { // -r if (!in.load_tetmesh(b.infilename, (int) b.object)) { - terminatetetgen(10); + terminatetetgen(NULL, 10); } } else { // -p if (!in.load_plc(b.infilename, (int) b.object)) { - terminatetetgen(10); + terminatetetgen(NULL, 10); } } if (b.insertaddpoints) { // -i @@ -32120,7 +31231,7 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, #else // with TETLIBRARY if (!b.parse_commandline(switches)) { - terminatetetgen(10); + terminatetetgen(NULL, 10); } tetrahedralize(&b, in, out, addin, bgmin); diff --git a/external/tetgen/tetgen.h b/external/tetgen/tetgen.h index e02cbefa4..3196e031f 100644 --- a/external/tetgen/tetgen.h +++ b/external/tetgen/tetgen.h @@ -2,17 +2,10 @@ // // // TetGen // // // -// A Quality Tetrahedral Mesh Generator and 3D Delaunay Triangulator // +// A Quality Tetrahedral Mesh Generator and A 3D Delaunay Triangulator // // // // Version 1.5 // -// October 06, 2012 // -// // -// Copyright (C) 2002--2012 // -// Hang Si // -// Research Group: Numerical Mathematics and Scientific Computing // -// Weierstrass Institute for Applied Analysis and Stochastics (WIAS) // -// Mohrenstr. 39, 10117 Berlin, Germany // -// Hang.Si@wias-berlin.de // +// November 4, 2013 // // // // TetGen is freely available through the website: http://www.tetgen.org. // // It may be copied, modified, and redistributed for non-commercial use. // @@ -24,13 +17,6 @@ #ifndef tetgenH #define tetgenH -#include -#include -#include -#include -#include -#include - // To compile TetGen as a library instead of an executable program, define // the TETLIBRARY symbol. @@ -42,9 +28,28 @@ // #define NDEBUG -// TetGen uses the double precision for a real number. +// TetGen default uses the double precision (64 bit) for a real number. +// Alternatively, one can use the single precision (32 bit) 'float' if the +// memory is limited. -#define REAL double +#define REAL double // #define REAL float + +// Maximum number of characters in a file name (including the null). + +#define FILENAMESIZE 1024 + +// Maximum number of chars in a line read from a file (including the null). + +#define INPUTLINESIZE 2048 + +// TetGen only uses the C standard library. + +#include +#include +#include +#include +#include +#include // The types 'intptr_t' and 'uintptr_t' are signed and unsigned integer types, // respectively. They are guaranteed to be the same width as a pointer. @@ -70,31 +75,39 @@ # include #endif -// Maximum number of characters in a file name (including the null). - -#define FILENAMESIZE 1024 - -// Maximum number of chars in a line read from a file (including the null). - -#define INPUTLINESIZE 2048 - /////////////////////////////////////////////////////////////////////////////// // // // tetgenio // // // -// A structure for transfering data into and out of TetGen's mesh structure. // +// A structure for transferring data into and out of TetGen's mesh structure,// +// 'tetgenmesh' (declared below). // // // -// It holds a collection of arrays of data, i.e., points, facets, tetrahedra,// -// and so forth. It contains functions to read and write (input and output) // -// files of TetGen as well as other supported mesh files. // +// The input of TetGen is either a 3D point set, or a 3D piecewise linear // +// complex (PLC), or a tetrahedral mesh. Depending on the input object and // +// the specified options, the output of TetGen is either a Delaunay (or wei- // +// ghted Delaunay) tetrahedralization, or a constrained (Delaunay) tetrahed- // +// ralization, or a quality tetrahedral mesh. // +// // +// A piecewise linear complex (PLC) represents a 3D polyhedral domain with // +// possibly internal boundaries(subdomains). It is introduced in [Miller et // +// al, 1996]. Basically it is a set of "cells", i.e., vertices, edges, poly- // +// gons, and polyhedra, and the intersection of any two of its cells is the // +// union of other cells of it. // +// // +// TetGen uses a set of files to describe the inputs and outputs. Each file // +// is identified from its file extension (.node, .ele, .face, .edge, etc). // +// // +// The 'tetgenio' structure is a collection of arrays of data, i.e., points, // +// facets, tetrahedra, and so forth. It contains functions to read and write // +// (input and output) files of TetGen as well as other supported mesh files. // // // // Once an object of tetgenio is declared, no array is created. One has to // // allocate enough memory for them. On deletion of this object, the memory // // occupied by these arrays needs to be freed. The routine deinitialize() // // will be automatically called. It frees the memory for an array if it is // // not a NULL. Note that it assumes that the memory is allocated by the C++ // -// "new" operator. Otherwise, the user must priorily free them by theirself // -// and set the pointers to NULLs. // +// "new" operator. Otherwise, the user is responsible to free them and all // +// pointers must be NULL before the call of the destructor. // // // /////////////////////////////////////////////////////////////////////////////// @@ -106,7 +119,7 @@ public: // convex. Each polygon contains a number of corners (points) and the same // number of sides (edges). The points of the polygon must be given in // either counterclockwise or clockwise order and they form a ring, so - // every two consective points forms an edge of the polygon. + // every two consecutive points forms an edge of the polygon. typedef struct { int *vertexlist; int numberofvertices; @@ -146,19 +159,6 @@ public: int *elist; } vorofacet; - // The periodic boundary condition group data structure. A "pbcgroup" - // contains the definition of a pbc and the list of pbc point pairs. - // 'fmark1' and 'fmark2' are the facetmarkers of the two pbc facets f1 - // and f2, respectively. 'transmat' is the transformation matrix which - // maps a point in f1 into f2. An array of pbc point pairs are saved - // in 'pointpairlist'. The first point pair is at indices [0] and [1], - // followed by remaining pairs. Two integers per pair. - typedef struct { - int fmark1, fmark2; - REAL transmat[4][4]; - int numberofpointpairs; - int *pointpairlist; - } pbcgroup; // Additional parameters associated with an input (or mesh) vertex. // These informations are provided by CAD libraries. @@ -168,9 +168,6 @@ public: int type; // 0, 1, or 2. } pointparam; - // A callback function for mesh refinement. - typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); - // Callback functions for meshing PSCs. typedef REAL (* GetVertexParamOnEdge)(void*, int, int); typedef void (* GetSteinerOnEdge)(void*, int, REAL, REAL*); @@ -178,6 +175,9 @@ public: typedef void (* GetEdgeSteinerParamOnFace)(void*, int, REAL, int, REAL*); typedef void (* GetSteinerOnFace)(void*, int, REAL*, REAL*); + // A callback function for mesh refinement. + typedef bool (* TetSizeFunc)(REAL*, REAL*, REAL*, REAL*, REAL*, REAL); + // Items are numbered starting from 'firstnumber' (0 or 1), default is 0. int firstnumber; @@ -205,15 +205,16 @@ public: int numberofpointattributes; int numberofpointmtrs; - // 'elementlist': An array of element (tetrahedron) corners. The first - // element's first corner is at index [0], followed by its other corners, - // followed by any other nodes if the element represents a nonlinear - // element. Each element occupies 'numberofcorners' ints. - // 'elementattributelist': An array of element attributes. Each - // element's attributes occupy 'numberofelementattributes' REALs. - // 'elementconstraintlist': An array of constraints, i.e. tetrahedron's + // 'tetrahedronlist': An array of tetrahedron corners. The first + // tetrahedron's first corner is at index [0], followed by its other + // corners, followed by six nodes on the edges of the tetrahedron if the + // second order option (-o2) is applied. Each tetrahedron occupies + // 'numberofcorners' ints. The second order nodes are ouput only. + // 'tetrahedronattributelist': An array of tetrahedron attributes. Each + // tetrahedron's attributes occupy 'numberoftetrahedronattributes' REALs. + // 'tetrahedronvolumelist': An array of constraints, i.e. tetrahedron's // volume; one REAL per element. Input only. - // 'neighborlist': An array of element neighbors; 4 ints per element. + // 'neighborlist': An array of tetrahedron neighbors; 4 ints per element. // Output only. int *tetrahedronlist; REAL *tetrahedronattributelist; @@ -265,29 +266,37 @@ public: REAL *segmentconstraintlist; int numberofsegmentconstraints; - // 'pbcgrouplist': An array of periodic boundary condition groups. - pbcgroup *pbcgrouplist; - int numberofpbcgroups; // 'trifacelist': An array of face (triangle) corners. The first face's - // corners are at indices [0], [1] and [2], followed by the remaining + // three corners are at indices [0], [1] and [2], followed by the remaining // faces. Three ints per face. + // 'trifacemarkerlist': An array of face markers; one int per face. + // 'o2facelist': An array of second order nodes (on the edges) of the face. + // It is output only if the second order option (-o2) is applied. The + // first face's three second order nodes are at [0], [1], and [2], + // followed by the remaining faces. Three ints per face. // 'adjtetlist': An array of adjacent tetrahedra to the faces. The first // face's two adjacent tetrahedra are at indices [0] and [1], followed by // the remaining faces. A '-1' indicates outside (no adj. tet). This list // is output when '-nn' switch is used. Output only. - // 'trifacemarkerlist': An array of face markers; one int per face. int *trifacelist; - int *adjtetlist; int *trifacemarkerlist; + int *o2facelist; + int *adjtetlist; int numberoftrifaces; - // `edgelist': An array of edge endpoints. The first edge's endpoints - // are at indices [0] and [1], followed by the remaining edges. Two - // ints per edge. - // `edgemarkerlist': An array of edge markers; one int per edge. + // 'edgelist': An array of edge endpoints. The first edge's endpoints + // are at indices [0] and [1], followed by the remaining edges. + // Two ints per edge. + // 'edgemarkerlist': An array of edge markers; one int per edge. + // 'o2edgelist': An array of midpoints of edges. It is output only if the + // second order option (-o2) is applied. One int per edge. + // 'edgeadjtetlist': An array of adjacent tetrahedra to the edges. One + // tetrahedron (an integer) per edge. int *edgelist; int *edgemarkerlist; + int *o2edgelist; + int *edgeadjtetlist; int numberofedges; // 'vpointlist': An array of Voronoi vertex coordinates (like pointlist). @@ -305,9 +314,6 @@ public: int numberofvfacets; int numberofvcells; - // A callback function. - TetSizeFunc tetunsuitable; - // Variable (and callback functions) for meshing PSCs. void *geomhandle; GetVertexParamOnEdge getvertexparamonedge; @@ -316,6 +322,9 @@ public: GetEdgeSteinerParamOnFace getedgesteinerparamonface; GetSteinerOnFace getsteineronface; + // A callback function. + TetSizeFunc tetunsuitable; + // Input & output routines. bool load_node_call(FILE* infile, int markers, int uvflag, char*); bool load_node(char*); @@ -363,8 +372,8 @@ public: // Initialize routine. void initialize() { - firstnumber = 0; // Default item index is numbered from Zero. - mesh_dim = 3; // Default mesh dimension is 3. + firstnumber = 0; + mesh_dim = 3; useindex = 1; pointlist = (REAL *) NULL; @@ -381,22 +390,25 @@ public: tetrahedronvolumelist = (REAL *) NULL; neighborlist = (int *) NULL; numberoftetrahedra = 0; - numberofcorners = 4; // Default is 4 nodes per element. + numberofcorners = 4; numberoftetrahedronattributes = 0; trifacelist = (int *) NULL; - adjtetlist = (int *) NULL; trifacemarkerlist = (int *) NULL; + o2facelist = (int *) NULL; + adjtetlist = (int *) NULL; numberoftrifaces = 0; + edgelist = (int *) NULL; + edgemarkerlist = (int *) NULL; + o2edgelist = (int *) NULL; + edgeadjtetlist = (int *) NULL; + numberofedges = 0; + facetlist = (facet *) NULL; facetmarkerlist = (int *) NULL; numberoffacets = 0; - edgelist = (int *) NULL; - edgemarkerlist = (int *) NULL; - numberofedges = 0; - holelist = (REAL *) NULL; numberofholes = 0; @@ -408,8 +420,6 @@ public: segmentconstraintlist = (REAL *) NULL; numberofsegmentconstraints = 0; - pbcgrouplist = (pbcgroup *) NULL; - numberofpbcgroups = 0; vpointlist = (REAL *) NULL; vedgelist = (voroedge *) NULL; @@ -430,17 +440,12 @@ public: getsteineronface = NULL; } - // Free the memory allocated in 'tetgenio'. + // Free the memory allocated in 'tetgenio'. Note that it assumes that the + // memory was allocated by the "new" operator (C++). void deinitialize() { - facet *f; - polygon *p; - pbcgroup *pg; int i, j; - // Notice that this routine assumes that the memory was allocated by - // C++ memory allocation operator 'new'. - if (pointlist != (REAL *) NULL) { delete [] pointlist; } @@ -473,12 +478,15 @@ public: if (trifacelist != (int *) NULL) { delete [] trifacelist; } - if (adjtetlist != (int *) NULL) { - delete [] adjtetlist; - } if (trifacemarkerlist != (int *) NULL) { delete [] trifacemarkerlist; } + if (o2facelist != (int *) NULL) { + delete [] o2facelist; + } + if (adjtetlist != (int *) NULL) { + delete [] adjtetlist; + } if (edgelist != (int *) NULL) { delete [] edgelist; @@ -486,8 +494,16 @@ public: if (edgemarkerlist != (int *) NULL) { delete [] edgemarkerlist; } + if (o2edgelist != (int *) NULL) { + delete [] o2edgelist; + } + if (edgeadjtetlist != (int *) NULL) { + delete [] edgeadjtetlist; + } if (facetlist != (facet *) NULL) { + facet *f; + polygon *p; for (i = 0; i < numberoffacets; i++) { f = &facetlist[i]; for (j = 0; j < f->numberofpolygons; j++) { @@ -517,15 +533,6 @@ public: if (segmentconstraintlist != (REAL *) NULL) { delete [] segmentconstraintlist; } - if (pbcgrouplist != (pbcgroup *) NULL) { - for (i = 0; i < numberofpbcgroups; i++) { - pg = &(pbcgrouplist[i]); - if (pg->pointpairlist != (int *) NULL) { - delete [] pg->pointpairlist; - } - } - delete [] pbcgrouplist; - } if (vpointlist != (REAL *) NULL) { delete [] vpointlist; } @@ -557,12 +564,15 @@ public: // tetgenbehavior // // // // A structure for maintaining the switches and parameters used by TetGen's // -// meshing algorithms. They are specified by the command line arguments. // +// mesh data structure and algorithms. // // // -// NOTE: Some of the switches are incompatinle to each other, while some are // -// depend on others. The routine parse_commandline() sets the switches from // -// the command line (a list of strings). Morover, it checks the consistency // -// of the applied switches. // +// All switches and parameters are initialized with default values. They can // +// be set by the command line arguments (a list of strings) of TetGen. // +// // +// NOTE: Some of the switches are incompatible. While some may depend on // +// other switches. The routine parse_commandline() sets the switches from // +// the command line (a list of strings) and checks the consistency of the // +// applied switches. // // // /////////////////////////////////////////////////////////////////////////////// @@ -570,12 +580,13 @@ class tetgenbehavior { public: - // The list of switches of TetGen. + // Switches of TetGen. int plc; // '-p', 0. int psc; // '-s', 0. int refine; // '-r', 0. int quality; // '-q', 0. int nobisect; // '-Y', 0. + int coarsen; // '-R', 0. int weighted; // '-w', 0. int brio_hilbert; // '-b', 1. int incrflip; // '-l', 0. @@ -588,7 +599,8 @@ public: int insertaddpoints; // '-i', 0. int diagnose; // '-d', 0. int convex; // '-c', 0. - int nomerge; // '-M', 0. + int nomergefacet; // '-M', 0. + int nomergevertex; // '-M', 0. int noexact; // '-X', 0. int nostaticfilter; // '-X', 0. int zeroindex; // '-z', 0. @@ -609,14 +621,14 @@ public: int quiet; // '-Q', 0. int verbose; // '-V', 0. - // The list of parameters of TetGen. - int vertexperblock; // 4092. - int tetrahedraperblock; // 8188. - int shellfaceperblock; // 4092. - int nobisect_param; // '-Y', 1. + // Parameters of TetGen. + int vertexperblock; // '-x', 4092. + int tetrahedraperblock; // '-x', 8188. + int shellfaceperblock; // '-x', 2044. + int nobisect_param; // '-Y', 2. + int addsteiner_algo; // '-Y/', 1. + int coarsen_param; // '-R', 0. int weighted_param; // '-w', 0. - int hilbert_order; // -1. - int hilbert_limit; // 8. int fliplinklevel; // -1. int flipstarsize; // -1. int fliplinklevelinc; // 1. @@ -626,6 +638,11 @@ public: int delmaxfliplevel; // 1. int order; // '-o', 1. int steinerleft; // '-S', 0. + int no_sort; // 0. + int hilbert_order; // '-b///', 52. + int hilbert_limit; // '-b//' 8. + int brio_threshold; // '-b' 64. + REAL brio_ratio; // '-b/' 0.125. REAL facet_ang_tol; // '-p', 179.9. REAL maxvolume; // '-a', -1.0. REAL minratio; // '-q', 0.0. @@ -635,6 +652,7 @@ public: REAL optminslidihed; // 179.0. REAL epsilon; // '-T', 1.0e-8. REAL minedgelength; // 0.0. + REAL coarsen_percent; // -R1/#, 1.0. // Strings of command line arguments and input/output file names. char commandline[1024]; @@ -645,6 +663,16 @@ public: // The input object of TetGen. They are recognized by either the input // file extensions or by the specified options. + // Currently the following objects are supported: + // - NODES, a list of nodes (.node); + // - POLY, a piecewise linear complex (.poly or .smesh); + // - OFF, a polyhedron (.off, Geomview's file format); + // - PLY, a polyhedron (.ply, file format from gatech, only ASCII); + // - STL, a surface mesh (.stl, stereolithography format); + // - MEDIT, a surface mesh (.mesh, Medit's file format); + // - MESH, a tetrahedral mesh (.ele). + // If no extension is available, the imposed command line switch + // (-p or -r) implies the object. enum objecttype {NODES, POLY, OFF, PLY, STL, MEDIT, VTK, MESH} object; @@ -665,6 +693,7 @@ public: refine = 0; quality = 0; nobisect = 0; + coarsen = 0; metric = 0; weighted = 0; brio_hilbert = 1; @@ -691,7 +720,8 @@ public: noelewritten = 0; nofacewritten = 0; noiterationnum = 0; - nomerge = 0; + nomergefacet = 0; + nomergevertex = 0; nojettison = 0; reversetetori = 0; docheck = 0; @@ -701,10 +731,10 @@ public: vertexperblock = 4092; tetrahedraperblock = 8188; shellfaceperblock = 4092; - nobisect_param = 1; + nobisect_param = 2; + addsteiner_algo = 1; + coarsen_param = 0; weighted_param = 0; - hilbert_order = -1; - hilbert_limit = 8; fliplinklevel = -1; // No limit on linklevel. flipstarsize = -1; // No limit on flip star size. fliplinklevelinc = 1; @@ -714,15 +744,21 @@ public: delmaxfliplevel = 1; order = 1; steinerleft = -1; + no_sort = 0; + hilbert_order = 52; //-1; + hilbert_limit = 8; + brio_threshold = 64; + brio_ratio = 0.125; facet_ang_tol = 179.9; maxvolume = -1.0; minratio = 2.0; - mindihedral = 5.0; + mindihedral = 0.0; // 5.0; optmaxdihedral = 165.00; // without -q, default is 179.0 optminsmtdihed = 179.00; // without -q, default is 179.999 optminslidihed = 179.00; // without -q, default is 179.999 epsilon = 1.0e-8; minedgelength = 0.0; + coarsen_percent = 1.0; object = NODES; commandline[0] = '\0'; @@ -744,37 +780,35 @@ public: // test. Each of these tests is performed by evaluating the sign of a deter- // // minant of a matrix whose entries are the coordinates of these points. If // // the computation is performed by using the floating-point numbers, e.g., // -// the single or double numbers in C/C++, roundoff error may cause an incor- // -// rect result. This may either lead to a wrong result or eventually lead to // -// a failure of the program. // +// the single or double precision numbers in C/C++, roundoff error may cause // +// an incorrect result. This may either lead to a wrong result or eventually // +// lead to a failure of the program. Computing the predicates exactly will // +// avoid the error and make the program robust. // // // -// Various techniques are developed to avoid roundoff errors, such as exact // -// multi-precision computations, interval arthmetics, adaptive exact arthme- // -// tics, and filtered exact arthmetics, etc. Devillers and Pion give a nice // -// discussion and comparisons of these techniques for robustly computing the // -// Delaunay triangulations [Devillers and Pion 2002]. // +// The following routines are the robust geometric predicates for 3D orient- // +// ation test and point-in-sphere test. They were implemented by Shewchuk. // +// The source code are generously provided by him in the public domain, // +// http://www.cs.cmu.edu/~quake/robust.html. predicates.cxx is a C++ version // +// of the original C code. // // // -// The following routines implemented the orientation test and the point-in- // -// sphere test use the adaptive exact floating-point arithmetics [Shewchuk // -// 1997]. They are generously provided by Jonathan Schewchuk in the public // -// domain, http://www.cs.cmu.edu/~quake/robust.html. The source code are in // -// file "predicates.cxx". // +// The original predicates of Shewchuk only use "dynamic filters", i.e., it // +// computes the error at run time step by step. TetGen first adds a "static // +// filter" in each predicate. It estimates the maximal possible error in all // +// cases. So it can safely and quickly answer many easy cases. // // // /////////////////////////////////////////////////////////////////////////////// -void exactinit(int, int, REAL, REAL, REAL); +void exactinit(int, int, int, REAL, REAL, REAL); REAL orient3d(REAL *pa, REAL *pb, REAL *pc, REAL *pd); REAL insphere(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe); REAL orient4d(REAL *pa, REAL *pb, REAL *pc, REAL *pd, REAL *pe, REAL ah, REAL bh, REAL ch, REAL dh, REAL eh); -void predicates_statistics(int weighted); /////////////////////////////////////////////////////////////////////////////// // // // tetgenmesh // // // -// A structure containing the mesh data structure and the implementations of // -// tetrahedral meshing algorithms of TetGen. // +// A structure for creating and updating tetrahedral meshes. // // // /////////////////////////////////////////////////////////////////////////////// @@ -782,39 +816,21 @@ class tetgenmesh { public: - // Labels that signify the type of a vertex. - enum verttype {UNUSEDVERTEX, DUPLICATEDVERTEX, RIDGEVERTEX, ACUTEVERTEX, - FACETVERTEX, VOLVERTEX, FREESEGVERTEX, FREEFACETVERTEX, - FREEVOLVERTEX, NREGULARVERTEX, DEADVERTEX}; - - // Labels that signify the type of a subsegment. - enum shestype {NSHARP, SHARP, FAKESH}; - - // Labels that signify the result of triangle-triangle intersection test. - enum interresult {DISJOINT, INTERSECT, SHAREVERT, SHAREEDGE, SHAREFACE, - TOUCHEDGE, TOUCHFACE, ACROSSVERT, ACROSSEDGE, ACROSSFACE, - COLLISIONFACE, ACROSSSEG, ACROSSSUB}; - - // Labels that signify the result of point location. - enum locateresult {OUTSIDE, INTETRAHEDRON, ONFACE, ONEDGE, ONVERTEX, INSTAR, - ENCVERTEX, ENCSEGMENT, ENCSUBFACE, NEARVERTEX, - NONREGULAR, BADELEMENT}; - /////////////////////////////////////////////////////////////////////////////// // // // Mesh data structure // // // -// A tetrahedral mesh of a 3D domain is a 3D simplicial complex T whose und- // -// erlying space is homeomorphic to the domain. T contains a 2D subcomplex S // -// which is a triangular mesh of the boundary of the domain. S contains a 1D // -// subcomplex L which is a linear mesh of the boundary of the surface. Faces // -// and edges in S and L are respectivly called subfaces and segments to dis- // -// tinguish them from others in T. // +// A tetrahedral mesh T of a 3D piecewise linear complex (PLC) X is a 3D // +// simplicial complex whose underlying space is equal to the space of X. T // +// contains a 2D subcomplex S which is a triangular mesh of the boundary of // +// X. S contains a 1D subcomplex L which is a linear mesh of the boundary of // +// S. Faces and edges in S and L are respectively called subfaces and segme- // +// nts to distinguish them from others in T. // // // -// TetGen stores the tetrahedra and vertices of T. Each tetrahedron contains // -// pointers to its vertices and adjacent tetrahedra. Each vertex stores its // -// x-, y-, and z-coordinates. The faces and edges of T are implicitly repre- // -// sented by tetrahedra. +// TetGen stores the tetrahedra and vertices of T. The basic structure of a // +// tetrahedron contains pointers to its vertices and adjacent tetrahedra. A // +// vertex stores its x-, y-, and z-coordinates, and a pointer to a tetrahed- // +// ron containing it. Both tetrahedra and vertices may contain user data. // // // // Each face of T belongs to either two tetrahedra or one tetrahedron. In // // the latter case, the face is an exterior boundary face of T. TetGen adds // @@ -822,20 +838,20 @@ public: // "infinite vertex" (which has no geometric coordinates). One can imagine // // such a vertex lies in 4D space and is visible by all exterior boundary // // faces. The extended set of tetrahedra (including the infinite vertex) is // -// a tetrahedralization of a compact 3-manifold without bounday. It has the // +// a tetrahedralization of a 3-pseudomanifold without boundary. It has the // // property that every face is shared by exactly two tetrahedra. // // // -// TetGen stores explicitly the subfaces and segments (which are in surface // -// mesh S and the linear mesh L, respectively. Additional informations are // -// stored in tetrahedra and subfaces to remember their relations. // +// The current version of TetGen stores explicitly the subfaces and segments // +// (which are in surface mesh S and the linear mesh L), respectively. Extra // +// pointers are allocated in tetrahedra and subfaces to point each others. // // // /////////////////////////////////////////////////////////////////////////////// // The tetrahedron data structure. It includes the following fields: // - a list of four adjoining tetrahedra; // - a list of four vertices; - // - a list of four subfaces (optional, for -p switch); - // - a list of six segments (optional, for -p switch); + // - a pointer to a list of four subfaces (optional, for -p switch); + // - a pointer to a list of six segments (optional, for -p switch); // - a list of user-defined floating-point attributes (optional); // - a volume constraint (optional, for -a switch); // - an integer of element marker (and flags); @@ -851,8 +867,7 @@ public: // - two adjoining tetrahedra; // - an area constraint (optional, for -q switch); // - an integer for boundary marker; - // - an integer for type: SHARPSEGMENT, NONSHARPSEGMENT, ...; - // - an integer for pbc group (optional, if in->pbcgrouplist exists); + // - an integer for type, flags, etc. typedef REAL **shellface; @@ -875,25 +890,23 @@ public: /////////////////////////////////////////////////////////////////////////////// // // -// Ordered tetrahedra // +// Handles // // // -// The four vertices of a tetrahedron can be permuted in 24 different seque- // -// nces. We call each sequence resulted by an even permutation an "ordered // -// tetrahedron". There are total 12 ordered tetrahedra. They form a group // -// which is isomorphic to the alternating group of 4 elements. Geometrically,// -// if we direct the three edges within a face of a tetrahedron by the count- // -// erclockwise order viewed from the opposite vertex of this face (using ei- // -// ther right-hand or left-hand rule). There are total twelve directed edges // -// in the tetrahedron. Each of them corresponds to an ordered tetrahedron. // +// Navigation and manipulation in a tetrahedralization are accomplished by // +// operating on structures referred as ``handles". A handle is a pair (t,v), // +// where t is a pointer to a tetrahedron, and v is a 4-bit integer, in the // +// range from 0 to 11. v is called the ``version'' of a tetrahedron, it rep- // +// resents a directed edge of a specific face of the tetrahedron. // // // -// We represent an order tetrahedron by a pair (t, v), where t is a pointer // -// to the tetrahedron and v is a four-bit integer, in the range from 0 to 11,// -// identifying the ordered version of the tetrahedron. Assume the faces of // -// the tetrahedron is numbered from 0 to 3, and the edges in a face is numb- // -// ered from 0 to 2. Then the two lower bits of v encode the face number, // -// and the two higher bits of v encode the edge number in that face. // +// There are 12 even permutations of the four vertices, each of them corres- // +// ponds to a directed edge (a version) of the tetrahedron. The 12 versions // +// can be grouped into 4 distinct ``edge rings'' in 4 ``oriented faces'' of // +// this tetrahedron. One can encode each version (a directed edge) into a // +// 4-bit integer such that the two upper bits encode the index (from 0 to 2) // +// of this edge in the edge ring, and the two lower bits encode the index ( // +// from 0 to 3) of the oriented face which contains this edge. // // // -// The four vertices of a tetrahedron are indexed from 0 to 3 (accodring to // +// The four vertices of a tetrahedron are indexed from 0 to 3 (according to // // their storage in the data structure). Give each face the same index as // // the node opposite it in the tetrahedron. Denote the edge connecting face // // i to face j as i/j. We number the twelve versions as follows: // @@ -905,19 +918,10 @@ public: // face 2 | 2 (2/3) 6 (2/1) 10 (2/0) // // face 3 | 3 (3/0) 7 (3/1) 11 (3/2) // // // -// Ordered triangles // -// // -// The three vertices of a triangle can be permuted in 6 different sequences // -// which form a group isomorphic to the symmetric group of 3 elements. Each // -// permutation of the vertices is called an ordered triangle. The first two // -// vertices of an ordered triangle defines an directed edge. There are total // -// six directed edge in the triangle. They can be divided into two groups, // -// which correspond the two orientations of the triangle, respectively. // -// // -// We represent an ordered triangle by a pair (s, v), where s is a pointer // -// to the triangle and v is a three-bit integer, in the range from 0 to 5, // -// identifying the directed edge of the triangle. Using the first bit of v // -// to identify the orientation, the other two bits of v identify the edge. // +// Similarly, navigation and manipulation in a (boundary) triangulation are // +// done by using handles of triangles. Each handle is a pair (s, v), where s // +// is a pointer to a triangle, and v is a version in the range from 0 to 5. // +// Each version corresponds to a directed edge of this triangle. // // // // Number the three vertices of a triangle from 0 to 2 (according to their // // storage in the data structure). Give each edge the same index as the node // @@ -928,8 +932,8 @@ public: // ccw orieation | 0 2 4 // // cw orieation | 1 3 5 // // // -// In the following, a 'triface' is an order tetrahedron, and a 'face' is an // -// orider triangle. // +// In the following, a 'triface' is a handle of tetrahedron, and a 'face' is // +// a handle of a triangle. // // // /////////////////////////////////////////////////////////////////////////////// @@ -955,202 +959,21 @@ public: } }; -/////////////////////////////////////////////////////////////////////////////// -// // -// badface // -// // -// A multiple usages structure. Despite of its name, a 'badface' can be used // -// to represent the following objects: // -// - a face of a tetrahedron which is (possibly) non-Delaunay; // -// - an encroached subsegment or subface; // -// - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // -// - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // -// - a recently flipped face (saved for undoing the flip later). // -// // -/////////////////////////////////////////////////////////////////////////////// - - class badface { - public: - triface tt; - face ss; - REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. - point forg, fdest, fapex, foppo, noppo; - badface *previtem, *nextitem; - badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), - previtem(0), nextitem(0) {} - }; - -/////////////////////////////////////////////////////////////////////////////// -// // -// insertvertexflags // -// // -// A collection of flags that pass to the routine insertvertex(). // -// // -/////////////////////////////////////////////////////////////////////////////// - - class insertvertexflags { - - public: - - int iloc, bowywat, lawson; - int rejflag, chkencflag; - int sloc, sbowywat; - int splitbdflag, validflag, respectbdflag; - int assignmeshsize; - - // Used by Delaunay refinement. - int refineflag; // 0, 1, 2, 3 - triface refinetet; - face refinesh; - - insertvertexflags() { - // All flags are initialized as 0. - iloc = bowywat = lawson = 0; - rejflag = chkencflag = 0; - sloc = sbowywat = 0; - splitbdflag = validflag = respectbdflag = 0; - assignmeshsize = 0; - - refineflag = 0; - refinetet.tet = NULL; - refinesh.sh = NULL; - } - }; - -/////////////////////////////////////////////////////////////////////////////// -// // -// flipconstraints // -// // -// A structure of a collection of data (options and parameters) which pass // -// to the edge flip function flipnm(). // -// // -/////////////////////////////////////////////////////////////////////////////// - - class flipconstraints { - - public: - - point seg[2]; // A constraining edge to be recovered. - point fac[3]; // A constraining face to be recovered. - point remvert; // A vertex to be removed. - - // Control flags - int unflip; // Undo the performed flips. - int collectnewtets; // Collect the new tets created by flips. - int collectencsegflag; - - // Optimization flags. - int remove_ndelaunay_edge; // Remove a non-Delaunay edge. - REAL bak_tetprism_vol; // The value to be minimized. - int remove_large_angle; // Remove a large dihedral angle at edge. - REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). - REAL cosdihed_out; // The improved cosine of the dihedral angle. - - // Internal counters. - int maxflippedlinklevelcount; // Maximal flipped link levels. - int misfliplinklevelcount; // Number of missed flip possibilities. - int chrismastreecount; // Number of Chrismas trees (unflippable case). - int convexhulledgecount; // Number of convex hull edges (unflippable case). - int encsegcount; // Number of hitted segments. - int rejf23count, rejf32count; // Number of rejections by checkflipeligi.. - - void clearcounters() { - maxflippedlinklevelcount = 0; - misfliplinklevelcount = 0; - chrismastreecount = 0; - convexhulledgecount = 0; - encsegcount = 0; - rejf23count = rejf32count = 0; - } - - flipconstraints() { - seg[0] = NULL; - fac[0] = NULL; - remvert = NULL; - - unflip = 0; - collectnewtets = 0; - collectencsegflag = 0; - - remove_ndelaunay_edge = 0; - bak_tetprism_vol = 0.0; - remove_large_angle = 0; - cosdihed_in = 0.0; - cosdihed_out = 0.0; - - clearcounters(); - } - }; - -/////////////////////////////////////////////////////////////////////////////// -// // -// optparameters // -// // -// Optimization options and parameters. // -// // -/////////////////////////////////////////////////////////////////////////////// - - class optparameters { - - public: - - // The one of goals of optimization. - int max_min_volume; // Maximize the minimum volume. - int max_min_aspectratio; // Maximize the minimum aspect ratio. - int min_max_dihedangle; // Minimize the maxumum dihedral angle. - - // The initial and improved value. - REAL initval, imprval; - - int numofsearchdirs; - REAL searchstep; - int maxiter; // Maximum smoothing iterations (disabled by -1). - int smthiter; // Performed iterations. - - int expstarflag; - int expstarcount; - - int flipflag; - int checkencflag; - - optparameters() { - max_min_volume = 0; - max_min_aspectratio = 0; - min_max_dihedangle = 0; - - initval = imprval = 0.0; - - numofsearchdirs = 10; - searchstep = 0.01; - maxiter = -1; // Unlimited smoothing iterations. - smthiter = 0; - - expstarflag = 0; - expstarcount = 0; - - flipflag = 0; - checkencflag = 0; - } - }; - - /////////////////////////////////////////////////////////////////////////////// // // // Arraypool // // // -// A dynamic linear array. // -// (It is from Shewchuk's Starbase.c, which is provided as part of Stellar, // -// a program for improving tetrahedral meshes.) // +// A dynamic linear array. (It is written by J. Shewchuk) // // // // Each arraypool contains an array of pointers to a number of blocks. Each // // block contains the same fixed number of objects. Each index of the array // -// addesses a particular object in the pool. The most significant bits add- // +// addresses a particular object in the pool. The most significant bits add- // // ress the index of the block containing the object. The less significant // // bits address this object within the block. // // // // 'objectbytes' is the size of one object in blocks; 'log2objectsperblock' // // is the base-2 logarithm of 'objectsperblock'; 'objects' counts the number // -// of allocated objects; 'totalmemory' is the totoal memorypool in bytes. // +// of allocated objects; 'totalmemory' is the total memory in bytes. // // // /////////////////////////////////////////////////////////////////////////////// @@ -1160,7 +983,8 @@ public: int objectbytes; int objectsperblock; - int log2objectsperblock; + int log2objectsperblock; + int objectsperblockmark; int toparraylen; char **toparray; long objects; @@ -1182,14 +1006,13 @@ public: #define fastlookup(pool, index) \ (void *) ((pool)->toparray[(index) >> (pool)->log2objectsperblock] + \ - ((index) & ((pool)->objectsperblock - 1)) * (pool)->objectbytes) + ((index) & (pool)->objectsperblockmark) * (pool)->objectbytes) /////////////////////////////////////////////////////////////////////////////// // // // Memorypool // // // -// A type used to allocate memory. // -// (It is from Shewchuk's triangle.c.) // +// A structure for memory allocation. (It is written by J. Shewchuk) // // // // firstblock is the first block of items. nowblock is the block from which // // items are currently being allocated. nextitem points to the next slab // @@ -1204,32 +1027,17 @@ public: // to be traversed. pathitemsleft is the number of items that remain to // // be traversed in pathblock. // // // -// itemwordtype is set to POINTER or FLOATINGPOINT, and is used to suggest // -// what sort of word the record is primarily made up of. alignbytes // -// determines how new records should be aligned in memory. itembytes and // -// itemwords are the length of a record in bytes (after rounding up) and // -// words. itemsperblock is the number of items allocated at once in a // -// single block. items is the number of currently allocated items. // -// maxitems is the maximum number of items that have been allocated at // -// once; it is the current number of items plus the number of records kept // -// on deaditemstack. // -// // /////////////////////////////////////////////////////////////////////////////// class memorypool { public: - // Labels that signify whether a record consists primarily of pointers - // or of floating-point words. Used for data alignment. - enum wordtype {POINTER, FLOATINGPOINT}; - void **firstblock, **nowblock; void *nextitem; void *deaditemstack; void **pathblock; void *pathitem; - wordtype itemwordtype; int alignbytes; int itembytes, itemwords; int itemsperblock; @@ -1238,10 +1046,10 @@ public: int pathitemsleft; memorypool(); - memorypool(int, int, enum wordtype, int); + memorypool(int, int, int, int); ~memorypool(); - void poolinit(int, int, enum wordtype, int); + void poolinit(int, int, int, int); void restart(); void *alloc(); void dealloc(void*); @@ -1251,7 +1059,196 @@ public: /////////////////////////////////////////////////////////////////////////////// // // -// Class variables // +// badface // +// // +// Despite of its name, a 'badface' can be used to represent one of the // +// following objects: // +// - a face of a tetrahedron which is (possibly) non-Delaunay; // +// - an encroached subsegment or subface; // +// - a bad-quality tetrahedron, i.e, has too large radius-edge ratio; // +// - a sliver, i.e., has good radius-edge ratio but nearly zero volume; // +// - a recently flipped face (saved for undoing the flip later). // +// // +/////////////////////////////////////////////////////////////////////////////// + + class badface { + public: + triface tt; + face ss; + REAL key, cent[6]; // circumcenter or cos(dihedral angles) at 6 edges. + point forg, fdest, fapex, foppo, noppo; + badface *nextitem; + badface() : key(0), forg(0), fdest(0), fapex(0), foppo(0), noppo(0), + nextitem(0) {} + }; + +/////////////////////////////////////////////////////////////////////////////// +// // +// insertvertexflags // +// // +// A collection of flags that pass to the routine insertvertex(). // +// // +/////////////////////////////////////////////////////////////////////////////// + + class insertvertexflags { + + public: + + int iloc; // input/output. + int bowywat, lawson; + int splitbdflag, validflag, respectbdflag; + int rejflag, chkencflag, cdtflag; + int assignmeshsize; + int sloc, sbowywat; + + // Used by Delaunay refinement. + int refineflag; // 0, 1, 2, 3 + triface refinetet; + face refinesh; + int smlenflag; // for useinsertradius. + REAL smlen; // for useinsertradius. + point parentpt; + + insertvertexflags() { + iloc = bowywat = lawson = 0; + splitbdflag = validflag = respectbdflag = 0; + rejflag = chkencflag = cdtflag = 0; + assignmeshsize = 0; + sloc = sbowywat = 0; + + refineflag = 0; + refinetet.tet = NULL; + refinesh.sh = NULL; + smlenflag = 0; + smlen = 0.0; + } + }; + +/////////////////////////////////////////////////////////////////////////////// +// // +// flipconstraints // +// // +// A structure of a collection of data (options and parameters) which pass // +// to the edge flip function flipnm(). // +// // +/////////////////////////////////////////////////////////////////////////////// + + class flipconstraints { + + public: + + // Elementary flip flags. + int enqflag; // (= flipflag) + int chkencflag; + + // Control flags + int unflip; // Undo the performed flips. + int collectnewtets; // Collect the new tets created by flips. + int collectencsegflag; + + // Optimization flags. + int remove_ndelaunay_edge; // Remove a non-Delaunay edge. + REAL bak_tetprism_vol; // The value to be minimized. + REAL tetprism_vol_sum; + int remove_large_angle; // Remove a large dihedral angle at edge. + REAL cosdihed_in; // The input cosine of the dihedral angle (> 0). + REAL cosdihed_out; // The improved cosine of the dihedral angle. + + // Boundary recovery flags. + int checkflipeligibility; + point seg[2]; // A constraining edge to be recovered. + point fac[3]; // A constraining face to be recovered. + point remvert; // A vertex to be removed. + + + flipconstraints() { + enqflag = 0; + chkencflag = 0; + + unflip = 0; + collectnewtets = 0; + collectencsegflag = 0; + + remove_ndelaunay_edge = 0; + bak_tetprism_vol = 0.0; + tetprism_vol_sum = 0.0; + remove_large_angle = 0; + cosdihed_in = 0.0; + cosdihed_out = 0.0; + + checkflipeligibility = 0; + seg[0] = NULL; + fac[0] = NULL; + remvert = NULL; + } + }; + +/////////////////////////////////////////////////////////////////////////////// +// // +// optparameters // +// // +// Optimization options and parameters. // +// // +/////////////////////////////////////////////////////////////////////////////// + + class optparameters { + + public: + + // The one of goals of optimization. + int max_min_volume; // Maximize the minimum volume. + int max_min_aspectratio; // Maximize the minimum aspect ratio. + int min_max_dihedangle; // Minimize the maximum dihedral angle. + + // The initial and improved value. + REAL initval, imprval; + + int numofsearchdirs; + REAL searchstep; + int maxiter; // Maximum smoothing iterations (disabled by -1). + int smthiter; // Performed iterations. + + + optparameters() { + max_min_volume = 0; + max_min_aspectratio = 0; + min_max_dihedangle = 0; + + initval = imprval = 0.0; + + numofsearchdirs = 10; + searchstep = 0.01; + maxiter = -1; // Unlimited smoothing iterations. + smthiter = 0; + + } + }; + + +/////////////////////////////////////////////////////////////////////////////// +// // +// Labels (enumeration declarations) used by TetGen. // +// // +/////////////////////////////////////////////////////////////////////////////// + + // Labels that signify the type of a vertex. + enum verttype {UNUSEDVERTEX, DUPLICATEDVERTEX, RIDGEVERTEX, ACUTEVERTEX, + FACETVERTEX, VOLVERTEX, FREESEGVERTEX, FREEFACETVERTEX, + FREEVOLVERTEX, NREGULARVERTEX, DEADVERTEX}; + + // Labels that signify the result of triangle-triangle intersection test. + enum interresult {DISJOINT, INTERSECT, SHAREVERT, SHAREEDGE, SHAREFACE, + TOUCHEDGE, TOUCHFACE, ACROSSVERT, ACROSSEDGE, ACROSSFACE, + COLLISIONFACE, ACROSSSEG, ACROSSSUB}; + + // Labels that signify the result of point location. + enum locateresult {UNKNOWN, OUTSIDE, INTETRAHEDRON, ONFACE, ONEDGE, ONVERTEX, + ENCVERTEX, ENCSEGMENT, ENCSUBFACE, NEARVERTEX, NONREGULAR, + INSTAR, BADELEMENT}; + +/////////////////////////////////////////////////////////////////////////////// +// // +// Variables of TetGen // // // /////////////////////////////////////////////////////////////////////////////// @@ -1264,9 +1261,8 @@ public: // Pointer to a background mesh (contains size specification map). tetgenmesh *bgm; - // Memorypools to store mesh elements: tetrahedra, subfaces, segments, - // and vertices. And memorypools for storing pointers which connect - // tetrahedra and subfaces and segments. + // Memorypools to store mesh elements (points, tetrahedra, subfaces, and + // segments) and extra pointers between tetrahedra, subfaces, and segments. memorypool *tetrahedrons, *subfaces, *subsegs, *points; memorypool *tet2subpool, *tet2segpool; @@ -1275,10 +1271,8 @@ public: // A memorypool to store faces to be flipped. memorypool *flippool; - // A stack of faces to be flipped. - badface *flipstack; - // A queue to store unflippable elements. - arraypool *unflipqueue; + arraypool *unflipqueue; + badface *flipstack; // Arrays used for point insertion (the Bowyer-Watson algorithm). arraypool *cavetetlist, *cavebdrylist, *caveoldtetlist; @@ -1288,20 +1282,20 @@ public: // Stacks used for CDT construction and boundary recovery. arraypool *subsegstack, *subfacstack, *subvertstack; - arraypool *suppsteinerptlist; + + // Arrays of encroached segments and subfaces (for mesh refinement). + arraypool *encseglist, *encshlist; + + // The map between facets to their vertices (for mesh refinement). + int *idx2facetlist; + point *facetverticeslist; + + // The map between segments to their endpoints (for mesh refinement). + point *segmentendpointslist; // The infinite vertex. point dummypoint; - - // Two handles used for facet recovery in CDT. - triface firsttopface, firstbotface; - // Three points define a plane (used in formcavity()). - point plane_pa, plane_pb, plane_pc; - - // Two arraies of encroached segments and subfaces (in mesh refinement). - arraypool *encseglist, *encshlist; - - // Pointer to a recently visited tetrahedron, subface. + // The recently visited tetrahedron, subface. triface recenttet; face recentsh; @@ -1312,21 +1306,14 @@ public: // tetrahedra (only used when -o2 switch is selected). point *highordertable; - // Other variables. - REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. - REAL longest; // The longest possible edge length. - long hullsize; // Number of faces of convex hull. - long insegments; // Number of input segments. - long meshedges; // Number of output mesh edges. - long meshhulledges; // Number of hull mesh edges. - int steinerleft; // Number of Steiner points not yet used. + // Various variables. int numpointattrib; // Number of point attributes. + int numelemattrib; // Number of tetrahedron attributes. int sizeoftensor; // Number of REALs per metric tensor. int pointmtrindex; // Index to find the metric tensor of a point. int pointparamindex; // Index to find the u,v coordinates of a point. int point2simindex; // Index to find a simplex adjacent to a point. int pointmarkindex; // Index to find boundary marker of a point. - int numelemattrib; // Number of tetrahedron attributes. int elemattribindex; // Index to find attributes of a tetrahedron. int volumeboundindex; // Index to find volume bound of a tetrahedron. int elemmarkerindex; // Index to find marker of a tetrahedron. @@ -1334,71 +1321,64 @@ public: int areaboundindex; // Index to find area bound of a subface. int checksubsegflag; // Are there segments in the tetrahedralization yet? int checksubfaceflag; // Are there subfaces in the tetrahedralization yet? - int checkinverttetflag; // Are there inverted (degenerated) tets yet? int checkconstraints; // Are there variant (node, seg, facet) constraints? int nonconvex; // Is current mesh non-convex? - int dupverts; // Are there duplicated vertices? - int unuverts; // Are there unused vertices? + int autofliplinklevel; // The increase of link levels, default is 1. + int useinsertradius; // Save the insertion radius for Steiner points. long samples; // Number of random samples for point location. unsigned long randomseed; // Current random number seed. REAL cosmaxdihed, cosmindihed; // The cosine values of max/min dihedral. - REAL cossmtdihed; // The cosine value of a bad dihedral tobe smoothed. + REAL cossmtdihed; // The cosine value of a bad dihedral to be smoothed. REAL cosslidihed; // The cosine value of the max dihedral of a sliver. REAL minfaceang, minfacetdihed; // The minimum input (dihedral) angles. - REAL sintheta_tol; // The tolerance for sin(small angle). - int autofliplinklevel; // The increasement of link levels, default is 1. - int calc_tetprism_vol; // Flag to calculate the tetrahedral-prism'volume. REAL tetprism_vol_sum; // The total volume of tetrahedral-prisms (in 4D). + REAL longest; // The longest possible edge length. + REAL xmax, xmin, ymax, ymin, zmax, zmin; // Bounding box of points. - // Algorithm statistical counters. - int max_hcurve_depth_count; - long ptloc_count, ptloc_max_count; - long insphere_sos_count, orient4d_sos_count; + // Counters. + long insegments; // Number of input segments. + long hullsize; // Number of exterior boundary faces. + long meshedges; // Number of mesh edges. + long meshhulledges; // Number of boundary mesh edges. + long steinerleft; // Number of Steiner points not yet used. + long dupverts; // Are there duplicated vertices? + long unuverts; // Are there unused vertices? + long nonregularcount; // Are there non-regular vertices? + long st_segref_count, st_facref_count, st_volref_count; // Steiner points. + long fillregioncount, cavitycount, cavityexpcount; long flip14count, flip26count, flipn2ncount; - long flip23count, flip32count, flip44count, flip22count; - long maxbowatcavsize, totalbowatcavsize, totaldeadtets; - long triedgcount, triedgcopcount; - long across_face_count, across_edge_count, across_max_count; - long fillregioncount, missingsubfacecount, crossingtetcount; - long cavitycount, cavityexpcount, maxcavsize, maxregionsize; - long maxcrossfacecount, maxflipsequence; - long dbg_ignore_facecount, dbg_unflip_facecount; - long ccent_relocate_count; - long opt_sliver_peels; - long r1count, r2count, r3count; - long maxfliplinklevel, maxflipstarsize; - long flipstarcount, sucflipstarcount, skpflipstarcount; - long st_segref_count, st_facref_count, st_volref_count; - long nonregularcount; - long rejrefinetetcount, rejrefineshcount; + long flip23count, flip32count, flip44count, flip41count; + long flip31count, flip22count; + unsigned long totalworkmemory; // Total memory used by working arrays. /////////////////////////////////////////////////////////////////////////////// // // // Mesh manipulation primitives // // // -// Mesh manipulation primitives are indeed very simple functions which take // -// one or two handles as parameters, perform basic operations such as "glue // -// two tetrahedra at a face", "return the origin of a tetrahedron", "return // -// the subface adjoining at the face of a tetrahedron", and so on. // -// // /////////////////////////////////////////////////////////////////////////////// // Fast lookup tables for mesh manipulation primitives. - static int mod12[36], mod6[18]; + static int bondtbl[12][12], fsymtbl[12][12]; + static int esymtbl[12], enexttbl[12], eprevtbl[12]; + static int enextesymtbl[12], eprevesymtbl[12]; + static int eorgoppotbl[12], edestoppotbl[12]; + static int facepivot1[12], facepivot2[12][12]; static int orgpivot[12], destpivot[12], apexpivot[12], oppopivot[12]; - static int edgepivot[12], ver2edge[12], edge2ver[6]; + static int tsbondtbl[12][6], stbondtbl[12][6]; + static int tspivottbl[12][6], stpivottbl[12][6]; + static int ver2edge[12], edge2ver[6], epivot[12]; static int sorgpivot [6], sdestpivot[6], sapexpivot[6]; - static int snextpivot[6], epivot[4]; + static int snextpivot[6]; + + void inittables(); // Primitives for tetrahedra. - inline void decode(tetrahedron ptr, triface& t); inline tetrahedron encode(triface& t); inline tetrahedron encode2(tetrahedron* ptr, int ver); + inline void decode(tetrahedron ptr, triface& t); inline void bond(triface& t1, triface& t2); inline void dissolve(triface& t); - inline void fsym(triface& t1, triface& t2); - inline void fsymself(triface& t); inline void esym(triface& t1, triface& t2); inline void esymself(triface& t); inline void enext(triface& t1, triface& t2); @@ -1409,6 +1389,12 @@ public: inline void enextesymself(triface& t); inline void eprevesym(triface& t1, triface& t2); inline void eprevesymself(triface& t); + inline void eorgoppo(triface& t1, triface& t2); + inline void eorgoppoself(triface& t); + inline void edestoppo(triface& t1, triface& t2); + inline void edestoppoself(triface& t); + inline void fsym(triface& t1, triface& t2); + inline void fsymself(triface& t); inline void fnext(triface& t1, triface& t2); inline void fnextself(triface& t); inline point org (triface& t); @@ -1470,14 +1456,10 @@ public: inline void senextself(face& s); inline void senext2(face& s1, face& s2); inline void senext2self(face& s); - inline void sfnext(face& s1, face& s2); - inline void sfnextself(face& s); inline REAL areabound(face& s); inline void setareabound(face& s, REAL value); inline int shellmark(face& s); inline void setshellmark(face& s, int value); - inline enum shestype shelltype(face& s); - inline void setshelltype(face& s, enum shestype value); inline void sinfect(face& s); inline void suninfect(face& s); inline bool sinfected(face& s); @@ -1490,6 +1472,8 @@ public: inline void smarktest3(face& s); inline void sunmarktest3(face& s); inline bool smarktest3ed(face& s); + inline void setfacetindex(face& f, int value); + inline int getfacetindex(face& f); // Primitives for interacting tetrahedra and subfaces. inline void tsbond(triface& t, face& s); @@ -1541,6 +1525,8 @@ public: inline void setpoint2ppt(point pt, point value); inline tetrahedron point2bgmtet(point pt); inline void setpoint2bgmtet(point pt, tetrahedron value); + inline void setpointinsradius(point pt, REAL value); + inline REAL getpointinsradius(point pt); // Advanced primitives. inline void point2tetorg(point pt, triface& t); @@ -1559,8 +1545,6 @@ public: tetrahedron *alltetrahedrontraverse(); void shellfacedealloc(memorypool*, shellface*); shellface *shellfacetraverse(memorypool*); - void badfacedealloc(memorypool*, badface*); - badface *badfacetraverse(memorypool*); void pointdealloc(point); point pointtraverse(); @@ -1574,17 +1558,40 @@ public: /////////////////////////////////////////////////////////////////////////////// // // -// Geometric predicates and calculations // +// Advanced geometric predicates and calculations // +// // +// TetGen uses a simplified symbolic perturbation scheme from Edelsbrunner, // +// et al [*]. Hence the point-in-sphere test never returns a zero. The idea // +// is to perturb the weights of vertices in the fourth dimension. TetGen // +// uses the indices of the vertices decide the amount of perturbation. It is // +// implemented in the routine insphere_s(). +// // +// The routine tri_edge_test() determines whether or not a triangle and an // +// edge intersect in 3D. If they intersect, their intersection type is also // +// reported. This test is a combination of n 3D orientation tests (n is bet- // +// ween 3 and 9). It uses the robust orient3d() test to make the branch dec- // +// isions. The routine tri_tri_test() determines whether or not two triang- // +// les intersect in 3D. It also uses the robust orient3d() test. // +// // +// There are a number of routines to calculate geometrical quantities, e.g., // +// circumcenters, angles, dihedral angles, face normals, face areas, etc. // +// They are so far done by the default floating-point arithmetics which are // +// non-robust. They should be improved in the future. // // // /////////////////////////////////////////////////////////////////////////////// - // Triangle-edge intersection test + // Symbolic perturbations (robust) + REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); + REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, + REAL, REAL, REAL, REAL, REAL); + + // Triangle-edge intersection test (robust) int tri_edge_2d(point, point, point, point, point, point, int, int*, int*); - int tri_edge_tail(point, point, point, point, point, point, REAL, REAL, int, + int tri_edge_tail(point, point, point, point, point, point, REAL, REAL, int, int*, int*); int tri_edge_test(point, point, point, point, point, point, int, int*, int*); - // Triangle-triangle intersection test + // Triangle-triangle intersection test (robust) int tri_edge_inter_tail(point, point, point, point, point, REAL, REAL); int tri_tri_inter(point, point, point, point, point, point); @@ -1594,13 +1601,12 @@ public: bool lu_decmp(REAL lu[4][4], int n, int* ps, REAL* d, int N); void lu_solve(REAL lu[4][4], int n, int* ps, REAL* b, int N); - // Geometric predicates + // An embedded 2-dimensional geometric predicate (non-robust) REAL incircle3d(point pa, point pb, point pc, point pd); - REAL insphere_s(REAL*, REAL*, REAL*, REAL*, REAL*); - REAL orient4d_s(REAL*, REAL*, REAL*, REAL*, REAL*, - REAL, REAL, REAL, REAL, REAL); - // Geometric calculations + // Geometric calculations (non-robust) + REAL orient3dfast(REAL *pa, REAL *pb, REAL *pc, REAL *pd); + inline REAL norm2(REAL x, REAL y, REAL z); inline REAL distance(REAL* p1, REAL* p2); void facenormal(point pa, point pb, point pc, REAL *n, int pivot, REAL *lav); REAL shortdistance(REAL* p, REAL* e1, REAL* e2); @@ -1615,36 +1621,111 @@ public: bool circumsphere(REAL*, REAL*, REAL*, REAL*, REAL* cent, REAL* radius); bool orthosphere(REAL*,REAL*,REAL*,REAL*,REAL,REAL,REAL,REAL,REAL*,REAL*); void planelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); + int linelineint(REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*, REAL*); REAL tetprismvol(REAL* pa, REAL* pb, REAL* pc, REAL* pd); + bool calculateabovepoint(arraypool*, point*, point*, point*); + void calculateabovepoint4(point, point, point, point); /////////////////////////////////////////////////////////////////////////////// // // // Local mesh transformations // // // +// A local transformation replaces a small set of tetrahedra with another // +// set of tetrahedra which fills the same space and the same boundaries. // +// In 3D, the most simplest local transformations are the elementary flips // +// performed within the convex hull of five vertices: 2-to-3, 3-to-2, 1-to-4,// +// and 4-to-1 flips, where the numbers indicate the number of tetrahedra // +// before and after each flip. The 1-to-4 and 4-to-1 flip involve inserting // +// or deleting a vertex, respectively. // +// There are complex local transformations which can be decomposed as a // +// combination of elementary flips. For example,a 4-to-4 flip which replaces // +// two coplanar edges can be regarded by a 2-to-3 flip and a 3-to-2 flip. // +// Note that the first 2-to-3 flip will temporarily create a degenerate tet- // +// rahedron which is removed immediately by the followed 3-to-2 flip. More // +// generally, a n-to-m flip, where n > 3, m = (n - 2) * 2, which removes an // +// edge can be done by first performing a sequence of (n - 3) 2-to-3 flips // +// followed by a 3-to-2 flip. // +// // +// The routines flip23(), flip32(), and flip41() perform the three element- // +// ray flips. The flip14() is available inside the routine insertpoint(). // +// // +// The routines flipnm() and flipnm_post() implement a generalized edge flip // +// algorithm which uses a combination of elementary flips. // +// // +// The routine insertpoint() implements a variant of Bowyer-Watson's cavity // +// algorithm to insert a vertex. It works for arbitrary tetrahedralization, // +// either Delaunay, or constrained Delaunay, or non-Delaunay. // +// // /////////////////////////////////////////////////////////////////////////////// - void flippush(badface*&, triface*); - // The elementary flips. - void flip23(triface*, int, int, int); - void flip32(triface*, int, int, int); - void flip41(triface*, int, int, int); + void flip23(triface*, int, flipconstraints* fc); + void flip32(triface*, int, flipconstraints* fc); + void flip41(triface*, int, flipconstraints* fc); // A generalized edge flip. int flipnm(triface*, int n, int level, int, flipconstraints* fc); int flipnm_post(triface*, int n, int nn, int, flipconstraints* fc); - // Incremental flips. - long lawsonflip3d(point, int flipflag, int, int, int flipedgeflag); - // Point insertion. - int insertvertex(point newpt, triface *searchtet, face *splitsh, face*, - insertvertexflags *ivf); + int insertpoint(point, triface*, face*, face*, insertvertexflags*); + void insertpoint_abort(face*, insertvertexflags*); /////////////////////////////////////////////////////////////////////////////// // // // Delaunay tetrahedralization // // // +// The routine incrementaldelaunay() implemented two incremental algorithms // +// for constructing Delaunay tetrahedralizations (DTs): the Bowyer-Watson // +// (B-W) algorithm and the incremental flip algorithm of Edelsbrunner and // +// Shah, "Incremental topological flipping works for regular triangulation," // +// Algorithmica, 15:233-241, 1996. // +// // +// The routine incrementalflip() implements the flip algorithm of [Edelsbru- // +// nner and Shah, 1996]. It flips a queue of locally non-Delaunay faces (in // +// an arbitrary order). The success is guaranteed when the Delaunay tetrah- // +// edralization is constructed incrementally by adding one vertex at a time. // +// // +// The routine locate() finds a tetrahedron contains a new point in current // +// DT. It uses a simple stochastic walk algorithm: starting from an arbitr- // +// ary tetrahedron in DT, it finds the destination by visit one tetrahedron // +// at a time, randomly chooses a tetrahedron if there are more than one // +// choices. This algorithm terminates due to Edelsbrunner's acyclic theorem. // +// Choose a good starting tetrahedron is crucial to the speed of the walk. // +// TetGen originally uses the "jump-and-walk" algorithm of Muecke, E.P., // +// Saias, I., and Zhu, B. "Fast Randomized Point Location Without Preproces- // +// sing." In Proceedings of the 12th ACM Symposium on Computational Geometry,// +// 274-283, 1996. It first randomly samples several tetrahedra in the DT // +// and then choosing the closet one to start walking. // +// The above algorithm slows download dramatically as the number of points // +// grows -- reported in Amenta, N., Choi, S. and Rote, G., "Incremental // +// construction con {BRIO}," In Proceedings of 19th ACM Symposium on // +// Computational Geometry, 211-219, 2003. On the other hand, Liu and // +// Snoeyink showed that the point location can be made in constant time if // +// the points are pre-sorted so that the nearby points in space have nearby // +// indices, then adding the points in this order. They sorted the points // +// along the 3D Hilbert curve. // +// // +// The routine hilbert_sort3() sorts a set of 3D points along the 3D Hilbert // +// curve. It recursively splits a point set according to the Hilbert indices // +// mapped to the subboxes of the bounding box of the point set. // +// The Hilbert indices is calculated by Butz's algorithm in 1971. A nice // +// exposition of this algorithm can be found in the paper of Hamilton, C., // +// "Compact Hilbert Indices", Technical Report CS-2006-07, Computer Science, // +// Dalhousie University, 2006 (the Section 2). My implementation also refer- // +// enced Steven Witham's implementation of "Hilbert walk" (hopefully, it is // +// still available at: http://www.tiac.net/~sw/2008/10/Hilbert/). // +// // +// TetGen sorts the points using the method in the paper of Boissonnat,J.-D.,// +// Devillers, O. and Hornus, S. "Incremental Construction of the Delaunay // +// Triangulation and the Delaunay Graph in Medium Dimension," In Proceedings // +// of the 25th ACM Symposium on Computational Geometry, 2009. // +// It first randomly sorts the points into subgroups using the Biased Rand-// +// omized Insertion Ordering (BRIO) of Amenta et al 2003, then sorts the // +// points in each subgroup along the 3D Hilbert curve. Inserting points in // +// this order ensures a randomized "sprinkling" of the points over the // +// domain, while sorting of each subset ensures locality. // +// // /////////////////////////////////////////////////////////////////////////////// void transfernodes(); @@ -1656,11 +1737,16 @@ public: REAL, REAL, REAL, REAL, REAL, REAL); void hilbert_sort3(point* vertexarray, int arraysize, int e, int d, REAL, REAL, REAL, REAL, REAL, REAL, int depth); + void brio_multiscale_sort(point*,int,int threshold,REAL ratio,int* depth); // Point location. unsigned long randomnation(unsigned int choices); void randomsample(point searchpt, triface *searchtet); - enum locateresult locate(point searchpt, triface *searchtet, int); + enum locateresult locate(point searchpt, triface *searchtet); + + // Incremental flips. + void flippush(badface*&, triface*); + int incrementalflip(point newpt, int, flipconstraints *fc); // Incremental Delaunay construction. void initialdelaunay(point pa, point pb, point pc, point pd); @@ -1672,14 +1758,11 @@ public: // // /////////////////////////////////////////////////////////////////////////////// - bool calculateabovepoint(arraypool*, point*, point*, point*); - void calculateabovepoint4(point, point, point, point); - void flipshpush(face*); void flip22(face*, int, int); void flip31(face*, int); long lawsonflip(); - int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat); + int sinsertvertex(point newpt, face*, face*, int iloc, int bowywat, int); int sremovevertex(point delpt, face*, face*, int lawson); enum locateresult slocate(point, face*, int, int, int); @@ -1701,36 +1784,109 @@ public: // // // Constrained Delaunay tetrahedralization // // // +// A constrained Delaunay tetrahedralization (CDT) is a variation of a Dela- // +// unay tetrahedralization (DT) that is constrained to respect the boundary // +// of a 3D PLC (domain). In a CDT of a 3D PLC, every vertex or edge of the // +// PLC is also a vertex or an edge of the CDT, every polygon of the PLC is a // +// union of triangles of the CDT. A crucial difference between a CDT and a // +// DT is that triangles in the PLC's polygons are not required to be locally // +// Delaunay, which frees the CDT to better respect the PLC's polygons. CDTs // +// have optimal properties similar to those of DTs. // +// // +// Steiner Points and Steiner CDTs. It is known that even a simple 3D polyh- // +// edron may not have a tetrahedralization which only uses its own vertices. // +// Some extra points, so-called "Steiner points" are needed in order to form // +// a tetrahedralization of such polyhedron. It is true for tetrahedralizing // +// a 3D PLC as well. A Steiner CDT of a 3D PLC is a CDT containing Steiner // +// points. The CDT algorithms of TetGen in general create Steiner CDTs. // +// Almost all of the Steiner points are added in the edges of the PLC. They // +// guarantee the existence of a CDT of the modified PLC. // +// // +// The routine constraineddelaunay() starts from a DT of the vertices of a // +// PLC and creates a (Steiner) CDT of the PLC (including Steiner points). It // +// is constructed by two steps, (1) segment recovery and (2) facet (polygon) // +// recovery. Each step is accomplished by its own algorithm. // +// // +// The routine delaunizesegments() implements the segment recovery algorithm // +// of Si, H. and Gaertner, K. "Meshing Piecewise Linear Complexes by Constr- // +// ained Delaunay Tetrahedralizations," In Proceedings of the 14th Internat- // +// ional Meshing Roundtable, 147--163, 2005. It adds Steiner points into // +// non-Delaunay segments until all subsegments appear together in a DT. The // +// running time of this algorithm is proportional to the number of added // +// Steiner points. // +// // +// There are two incremental facet recovery algorithms: the cavity re-trian- // +// gulation algorithm of Si, H. and Gaertner, K. "3D Boundary Recovery by // +// Constrained Delaunay Tetrahedralization," International Journal for Numer-// +// ical Methods in Engineering, 85:1341-1364, 2011, and the flip algorithm // +// of Shewchuk, J. "Updating and Constructing Constrained Delaunay and // +// Constrained Regular Triangulations by Flips." In Proceedings of the 19th // +// ACM Symposium on Computational Geometry, 86-95, 2003. // +// // +// It is guaranteed in theory, no Steiner point is needed in both algorithms // +// However, a facet with non-coplanar vertices might cause the additions of // +// Steiner points. It is discussed in the paper of Si, H., and Shewchuk, J.,// +// "Incrementally Constructing and Updating Constrained Delaunay // +// Tetrahedralizations with Finite Precision Coordinates." In Proceedings of // +// the 21th International Meshing Roundtable, 2012. // +// // +// Our implementation of the facet recovery algorithms recover a "missing // +// region" at a time. Each missing region is a subset of connected interiors // +// of a polygon. The routine formcavity() creates the cavity of crossing // +// tetrahedra of the missing region. // +// // +// The cavity re-triangulation algorithm is implemented by three subroutines,// +// delaunizecavity(), fillcavity(), and carvecavity(). Since it may fail due // +// to non-coplanar vertices, the subroutine restorecavity() is used to rest- // +// ore the original cavity. // +// // +// The routine flipinsertfacet() implements the flip algorithm. The subrout- // +// ine flipcertify() is used to maintain the priority queue of flips. // +// // +// The routine refineregion() is called when the facet recovery algorithm // +// fail to recover a missing region. It inserts Steiner points to refine the // +// missing region. In order to avoid inserting Steiner points very close to // +// existing segments. The classical encroachment rules of the Delaunay // +// refinement algorithm are used to choose the Steiner points. // +// // +// The routine constrainedfacets() does the facet recovery by using either // +// the cavity re-triangulation algorithm (default) or the flip algorithm. It // +// results a CDT of the (modified) PLC (including Steiner points). // +// // /////////////////////////////////////////////////////////////////////////////// - void markacutevertices(); - - void reportselfintersect(face *seg, face *shface); + void makesegmentendpointsmap(); enum interresult finddirection(triface* searchtet, point endpt); enum interresult scoutsegment(point, point, triface*, point*, arraypool*); - void getsteinerptonsegment(face* seg, point refpt, point steinpt); + int getsteinerptonsegment(face* seg, point refpt, point steinpt); void delaunizesegments(); enum interresult scoutsubface(face* searchsh, triface* searchtet); - void formmissingregion(face*, arraypool*, arraypool*, arraypool*, arraypool*); - int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); + void formregion(face*, arraypool*, arraypool*, arraypool*); + int scoutcrossedge(triface& crosstet, arraypool*, arraypool*); bool formcavity(triface*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*); - // Facet recovery by local re-tetrahedralization [Si and Gaertner'05,'11]. + // Facet recovery by cavity re-triangulation [Si and Gaertner 2011]. void delaunizecavity(arraypool*, arraypool*, arraypool*, arraypool*, arraypool*, arraypool*); - bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*); + bool fillcavity(arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*, triface* crossedge); void carvecavity(arraypool*, arraypool*, arraypool*); - void restorecavity(arraypool*, arraypool*, arraypool*); + void restorecavity(arraypool*, arraypool*, arraypool*, arraypool*); - // Facet recovery by flips [Shewchuk'03]. - void flipcertify(triface *chkface, badface **pqueue); + // Facet recovery by flips [Shewchuk 2003]. + void flipcertify(triface *chkface, badface **pqueue, point, point, point); void flipinsertfacet(arraypool*, arraypool*, arraypool*, arraypool*); bool fillregion(arraypool* missingshs, arraypool*, arraypool* newshs); - void refineregion(); + + int insertpoint_cdt(point, triface*, face*, face*, insertvertexflags*, + arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); + void refineregion(face&, arraypool*, arraypool*, arraypool*, arraypool*, + arraypool*, arraypool*); void constrainedfacets(); @@ -1750,6 +1906,7 @@ public: int recoveredgebyflips(point, point, triface*, int fullsearch); int add_steinerpt_in_schoenhardtpoly(triface*, int, int chkencflag); + int add_steinerpt_in_segment(face*, int searchlevel); int addsteiner4recoversegment(face*, int); int recoversegments(arraypool*, int fullsearch, int steinerflag); @@ -1761,7 +1918,7 @@ public: int reduceedgesatvertex(point startpt, arraypool* endptlist); int removevertexbyflips(point steinerpt); - int suppressssteinerpoint(point steinerpt); + int suppressbdrysteinerpoint(point steinerpt); int suppresssteinerpoints(); void recoverboundary(clock_t&); @@ -1780,32 +1937,68 @@ public: REAL getpointmeshsize(point, triface*, int iloc); void interpolatemeshsize(); + void insertconstrainedpoints(point *insertarray, int arylen, int rejflag); void insertconstrainedpoints(tetgenio *addio); + void collectremovepoints(arraypool *remptlist); + void meshcoarsening(); + /////////////////////////////////////////////////////////////////////////////// // // // Mesh refinement // // // +// The purpose of mesh refinement is to obtain a tetrahedral mesh with well- // +// -shaped tetrahedra and appropriate mesh size. It is necessary to insert // +// new Steiner points to achieve this property. The questions are (1) how to // +// choose the Steiner points? and (2) how to insert them? // +// // +// Delaunay refinement is a technique first developed by Chew [1989] and // +// Ruppert [1993, 1995] to generate quality triangular meshes in the plane. // +// It provides guarantee on the smallest angle of the triangles. Rupper's // +// algorithm guarantees that the mesh is size-optimal (to within a constant // +// factor) among all meshes with the same quality. // +// Shewchuk generalized Ruppert's algorithm into 3D in his PhD thesis // +// [Shewchuk 1997]. A short version of his algorithm appears in "Tetrahedral // +// Mesh Generation by Delaunay Refinement," In Proceedings of the 14th ACM // +// Symposium on Computational Geometry, 86-95, 1998. It guarantees that all // +// tetrahedra of the output mesh have a "radius-edge ratio" (equivalent to // +// the minimal face angle) bounded. However, it does not remove slivers, a // +// type of very flat tetrahedra which can have no small face angles but have // +// very small (and large) dihedral angles. Moreover, it may not terminate if // +// the input PLC contains "sharp features", e.g., two edges (or two facets) // +// meet at an acute angle (or dihedral angle). // +// // +// TetGen uses the basic Delaunay refinement scheme to insert Steiner points.// +// While it always maintains a constrained Delaunay mesh. The algorithm is // +// described in Si, H., "Adaptive Constrained Delaunay Mesh Generation," // +// International Journal for Numerical Methods in Engineering, 75:856-880. // +// This algorithm always terminates and sharp features are easily preserved. // +// The mesh has good quality (same as Shewchuk's Delaunay refinement algori- // +// thm) in the bulk of the mesh domain. Moreover, it supports the generation // +// of adaptive mesh according to a (isotropic) mesh sizing function. // +// // /////////////////////////////////////////////////////////////////////////////// - void marksharpsegments(); - void decidefeaturepointsizes(); + void makefacetverticesmap(); + int segsegadjacent(face *, face *); + int segfacetadjacent(face *checkseg, face *checksh); + int facetfacetadjacent(face *, face *); int checkseg4encroach(point pa, point pb, point checkpt); int checkseg4split(face *chkseg, point&, int&); - int splitsegment(face *splitseg, point encpt, int qflag, int chkencflag); + int splitsegment(face *splitseg, point encpt, REAL, point, point, int, int); void repairencsegs(int chkencflag); + void enqueuesubface(memorypool*, face*); int checkfac4encroach(point, point, point, point checkpt, REAL*, REAL*); int checkfac4split(face *chkfac, point& encpt, int& qflag, REAL *ccent); - int splitsubface(face *splitfac, point encpt, int qflag, REAL *ccent, - int chkencflag); + int splitsubface(face *splitfac, point, point, int qflag, REAL *ccent, int); void repairencfacs(int chkencflag); + void enqueuetetrahedron(triface*); int checktet4split(triface *chktet, int& qflag, REAL *ccent); - int splittetrahedron(triface* splittet,int qflag,REAL *ccent,int chkencflag); + int splittetrahedron(triface* splittet,int qflag,REAL *ccent, int); void repairbadtets(int chkencflag); - void insertsinks(); void delaunayrefinement(); @@ -1815,6 +2008,7 @@ public: // // /////////////////////////////////////////////////////////////////////////////// + long lawsonflip3d(flipconstraints *fc); void recoverdelaunay(); int gettetrahedron(point, point, point, point, triface *); @@ -1843,7 +2037,9 @@ public: int checkconforming(int); // Mesh statistics. + void printfcomma(unsigned long n); void qualitystatistics(); + void memorystatistics(); void statistics(); /////////////////////////////////////////////////////////////////////////////// @@ -1870,6 +2066,7 @@ public: void outmesh2vtk(char*); + /////////////////////////////////////////////////////////////////////////////// // // // Constructor & destructor // @@ -1897,100 +2094,82 @@ public: caveshlist = caveshbdlist = cavesegshlist = NULL; subsegstack = subfacstack = subvertstack = NULL; - suppsteinerptlist = NULL; encseglist = encshlist = NULL; + idx2facetlist = NULL; + facetverticeslist = NULL; + segmentendpointslist = NULL; highordertable = NULL; - plane_pa = plane_pb = plane_pc = (point) NULL; - - xmax = xmin = ymax = ymin = zmax = zmin = 0.0; - longest = 0.0; - hullsize = 0l; - insegments = 0l; - meshedges = meshhulledges = 0l; - steinerleft = -1; - numpointattrib = 0; + numpointattrib = numelemattrib = 0; sizeoftensor = 0; pointmtrindex = 0; pointparamindex = 0; pointmarkindex = 0; point2simindex = 0; - numelemattrib = 0; elemattribindex = 0; volumeboundindex = 0; shmarkindex = 0; areaboundindex = 0; checksubsegflag = 0; checksubfaceflag = 0; - checkinverttetflag = 0; checkconstraints = 0; nonconvex = 0; - dupverts = 0; - unuverts = 0; + autofliplinklevel = 1; + useinsertradius = 0; samples = 0l; randomseed = 1l; minfaceang = minfacetdihed = PI; - sintheta_tol = sin(0.001 * PI / 180.0); - autofliplinklevel = 1; - calc_tetprism_vol = 0; tetprism_vol_sum = 0.0; + longest = 0.0; + xmax = xmin = ymax = ymin = zmax = zmin = 0.0; - ptloc_count = ptloc_max_count = 0l; - insphere_sos_count = orient4d_sos_count = 0l; - flip14count = flip26count = flipn2ncount = 0l; - flip23count = flip32count = flip44count = flip22count = 0l; - maxbowatcavsize = totalbowatcavsize = totaldeadtets = 0l; - triedgcount = triedgcopcount = 0l; - across_face_count = across_edge_count = across_max_count = 0l; - fillregioncount = missingsubfacecount = crossingtetcount = 0l; - cavitycount = cavityexpcount = 0l; - maxcavsize = maxregionsize = 0l; - maxcrossfacecount = maxflipsequence = 0l; - dbg_ignore_facecount = dbg_unflip_facecount = 0l; - ccent_relocate_count = 0l; - opt_sliver_peels = 0l; - r1count = r2count = r3count = 0l; - st_segref_count = st_facref_count = st_volref_count = 0l; + insegments = 0l; + hullsize = 0l; + meshedges = meshhulledges = 0l; + steinerleft = -1; + dupverts = 0l; + unuverts = 0l; nonregularcount = 0l; + st_segref_count = st_facref_count = st_volref_count = 0l; + fillregioncount = cavitycount = cavityexpcount = 0l; + flip14count = flip26count = flipn2ncount = 0l; + flip23count = flip32count = flip44count = flip41count = 0l; + flip22count = flip31count = 0l; + totalworkmemory = 0l; - maxfliplinklevel = maxflipstarsize = 0l; - flipstarcount = sucflipstarcount = skpflipstarcount = 0l; - rejrefinetetcount = rejrefineshcount = 0l; } // tetgenmesh() - ~tetgenmesh() + void freememory() { if (bgm != NULL) { delete bgm; } + if (points != (memorypool *) NULL) { + delete points; + delete [] dummypoint; + } + if (tetrahedrons != (memorypool *) NULL) { delete tetrahedrons; } + if (subfaces != (memorypool *) NULL) { delete subfaces; - } - if (subsegs != (memorypool *) NULL) { delete subsegs; } - if (points != (memorypool *) NULL) { - delete points; - } + if (tet2segpool != NULL) { delete tet2segpool; - } - if (tet2subpool != NULL) { delete tet2subpool; } + if (flippool != NULL) { delete flippool; delete unflipqueue; } - if (dummypoint != (point) NULL) { - delete [] dummypoint; - } if (cavetetlist != NULL) { delete cavetetlist; @@ -2015,13 +2194,23 @@ public: delete subvertstack; } - if (suppsteinerptlist != NULL) { - delete suppsteinerptlist; + if (idx2facetlist != NULL) { + delete [] idx2facetlist; + delete [] facetverticeslist; + } + + if (segmentendpointslist != NULL) { + delete [] segmentendpointslist; } if (highordertable != NULL) { delete [] highordertable; } + } + + ~tetgenmesh() + { + freememory(); } // ~tetgenmesh() }; // End of class tetgenmesh. @@ -2037,7 +2226,7 @@ public: // must not be a NULL. 'out' is another object of 'tetgenio' for storing the // // generated tetrahedral mesh. It can be a NULL. If so, the output will be // // saved to file(s). If 'bgmin' != NULL, it contains a background mesh which // -// defines a mesh size distruction function. // +// defines a mesh size function. // // // /////////////////////////////////////////////////////////////////////////////// @@ -2055,8 +2244,12 @@ void tetrahedralize(char *switches, tetgenio *in, tetgenio *out, // // /////////////////////////////////////////////////////////////////////////////// -inline void terminatetetgen(int x) +inline void terminatetetgen(tetgenmesh *m, int x) { + // Release the allocated memory. + if (m) { + m->freememory(); + } #ifdef TETLIBRARY throw x; #else @@ -2074,15 +2267,15 @@ inline void terminatetetgen(int x) printf("Hint: use -d option to detect all self-intersections.\n"); break; case 4: - printf("A very small input feature was size detected. Program stopped.\n"); + printf("A very small input feature size was detected. Program stopped.\n"); printf("Hint: use -T option to set a smaller tolerance.\n"); break; case 5: - printf("Two very clsoe input facets were detected. Program stopped.\n"); + printf("Two very close input facets were detected. Program stopped.\n"); printf("Hint: use -Y option to avoid adding Steiner points in boundary.\n"); break; case 10: - printf("An input error was detected Program stopped.\n"); + printf("An input error was detected. Program stopped.\n"); break; } // switch (x) exit(x); @@ -2091,24 +2284,12 @@ inline void terminatetetgen(int x) /////////////////////////////////////////////////////////////////////////////// // // -// Inline functions of mesh data structures // +// Primitives for tetrahedra // // // /////////////////////////////////////////////////////////////////////////////// -// -// Begin of primitives for tetrahedra -// - -// decode() converts a pointer to an ordered tetrahedron. The version is -// extracted from the four least significant bits of the pointer. - -inline void tetgenmesh::decode(tetrahedron ptr, triface& t) { - (t).ver = (int) ((uintptr_t) (ptr) & (uintptr_t) 15); - (t).tet = (tetrahedron *) ((uintptr_t) (ptr) ^ (uintptr_t) (t).ver); -} - -// encode() compress an ordered tetrahedron into a single pointer. It -// relies on the assumption that all tetrahedra are aligned to sixteen- +// encode() compress a handle into a single pointer. It relies on the +// assumption that all addresses of tetrahedra are aligned to sixteen- // byte boundaries, so that the last four significant bits are zero. inline tetgenmesh::tetrahedron tetgenmesh::encode(triface& t) { @@ -2119,73 +2300,49 @@ inline tetgenmesh::tetrahedron tetgenmesh::encode2(tetrahedron* ptr, int ver) { return (tetrahedron) ((uintptr_t) (ptr) | (uintptr_t) (ver)); } -// bond() connects two adjacent tetrahedra together. t1 and t2 must refer -// to the same face and the same edge. Note that the edge directions of -// t1 and t2 are reversed. -// Since an edge of t1 can be bonded to any of the three edges of t2. We -// choose to bond the edge of t2 which is symmetric to the 0-th edge of -// t1, and vice versa. Now assume t1 is at i-th edge and t2 is at j-th -// edge, where i, j in {0, 1, 2}. The edge in t2 symmetric to 0-th edge -// of t1 is (i + j) modulo 3, and vice versa. Since the edge number is -// coded in the two higher bits of the version, i.e., i, j in {0, 4, 8}. -// Therefore the edge in t2 symmetric to 0-th edge of t1 becomes -// (i + j) modulo 12, and vice versa. -/* -inline void tetgenmesh::bond(triface& t1, triface& t2) { - (t1).tet[(t1).ver & 3] = encode2((t2).tet, - ((t2).ver & 3) + mod12[((t1).ver & 12) + ((t2).ver & 12)]); - (t2).tet[(t2).ver & 3] = encode2((t1).tet, - ((t1).ver & 3) + mod12[((t1).ver & 12) + ((t2).ver & 12)]); +// decode() converts a pointer to a handle. The version is extracted from +// the four least significant bits of the pointer. + +inline void tetgenmesh::decode(tetrahedron ptr, triface& t) { + (t).ver = (int) ((uintptr_t) (ptr) & (uintptr_t) 15); + (t).tet = (tetrahedron *) ((uintptr_t) (ptr) ^ (uintptr_t) (t).ver); } -*/ -// Comment: The following code seems faster than the above code when -// it is compiled with the optimization option, e.g., -O3. + +// bond() connects two tetrahedra together. (t1,v1) and (t2,v2) must +// refer to the same face and the same edge. + inline void tetgenmesh::bond(triface& t1, triface& t2) { - (t1).tet[(t1).ver & 3] = encode2((t2).tet, - ((t2).ver & 3) + (((t1).ver & 12) + ((t2).ver & 12)) % 12); - (t2).tet[(t2).ver & 3] = encode2((t1).tet, - ((t1).ver & 3) + (((t1).ver & 12) + ((t2).ver & 12)) % 12); + t1.tet[t1.ver & 3] = encode2(t2.tet, bondtbl[t1.ver][t2.ver]); + t2.tet[t2.ver & 3] = encode2(t1.tet, bondtbl[t2.ver][t1.ver]); } + // dissolve() a bond (from one side). inline void tetgenmesh::dissolve(triface& t) { t.tet[t.ver & 3] = NULL; } -// fsym() finds the adjacent tetrahedron at the same face and the same edge. - -inline void tetgenmesh::fsym(triface& t1, triface& t2) { - decode((t1).tet[(t1).ver & 3], t2); - (t2).ver = mod12[(t2).ver + 12 - ((t1).ver & 12)]; -} - -inline void tetgenmesh::fsymself(triface& t) { - int offset = 12 - ((t).ver & 12); - decode((t).tet[(t).ver & 3], t); - (t).ver = mod12[(t).ver + offset]; -} - // enext() finds the next edge (counterclockwise) in the same face. inline void tetgenmesh::enext(triface& t1, triface& t2) { - (t2).tet = (t1).tet; - (t2).ver = mod12[(t1).ver + 4]; + t2.tet = t1.tet; + t2.ver = enexttbl[t1.ver]; } inline void tetgenmesh::enextself(triface& t) { - (t).ver = mod12[(t).ver + 4]; + t.ver = enexttbl[t.ver]; } // eprev() finds the next edge (clockwise) in the same face. inline void tetgenmesh::eprev(triface& t1, triface& t2) { - (t2).tet = (t1).tet; - (t2).ver = mod12[(t1).ver + 8]; + t2.tet = t1.tet; + t2.ver = eprevtbl[t1.ver]; } inline void tetgenmesh::eprevself(triface& t) { - (t).ver = mod12[(t).ver + 8]; + t.ver = eprevtbl[t.ver]; } // esym() finds the reversed edge. It is in the other face of the @@ -2193,51 +2350,87 @@ inline void tetgenmesh::eprevself(triface& t) { inline void tetgenmesh::esym(triface& t1, triface& t2) { (t2).tet = (t1).tet; - (t2).ver = edgepivot[(t1).ver]; + (t2).ver = esymtbl[(t1).ver]; } inline void tetgenmesh::esymself(triface& t) { - (t).ver = edgepivot[(t).ver]; + (t).ver = esymtbl[(t).ver]; } // enextesym() finds the reversed edge of the next edge. It is in the other // face of the same tetrahedron. It is the combination esym() * enext(). inline void tetgenmesh::enextesym(triface& t1, triface& t2) { - enext(t1, t2); - esymself(t2); + t2.tet = t1.tet; + t2.ver = enextesymtbl[t1.ver]; } inline void tetgenmesh::enextesymself(triface& t) { - enextself(t); - esymself(t); + t.ver = enextesymtbl[t.ver]; } // eprevesym() finds the reversed edge of the previous edge. inline void tetgenmesh::eprevesym(triface& t1, triface& t2) { - eprev(t1, t2); - esymself(t2); + t2.tet = t1.tet; + t2.ver = eprevesymtbl[t1.ver]; } inline void tetgenmesh::eprevesymself(triface& t) { - eprevself(t); - esymself(t); + t.ver = eprevesymtbl[t.ver]; } +// eorgoppo() Finds the opposite face of the origin of the current edge. +// Return the opposite edge of the current edge. + +inline void tetgenmesh::eorgoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = eorgoppotbl[t1.ver]; +} + +inline void tetgenmesh::eorgoppoself(triface& t) { + t.ver = eorgoppotbl[t.ver]; +} + +// edestoppo() Finds the opposite face of the destination of the current +// edge. Return the opposite edge of the current edge. + +inline void tetgenmesh::edestoppo(triface& t1, triface& t2) { + t2.tet = t1.tet; + t2.ver = edestoppotbl[t1.ver]; +} + +inline void tetgenmesh::edestoppoself(triface& t) { + t.ver = edestoppotbl[t.ver]; +} + +// fsym() finds the adjacent tetrahedron at the same face and the same edge. + +inline void tetgenmesh::fsym(triface& t1, triface& t2) { + decode((t1).tet[(t1).ver & 3], t2); + t2.ver = fsymtbl[t1.ver][t2.ver]; +} + + +#define fsymself(t) \ + t1ver = (t).ver; \ + decode((t).tet[(t).ver & 3], (t));\ + (t).ver = fsymtbl[t1ver][(t).ver] + // fnext() finds the next face while rotating about an edge according to // a right-hand rule. The face is in the adjacent tetrahedron. It is // the combination: fsym() * esym(). inline void tetgenmesh::fnext(triface& t1, triface& t2) { - esym(t1, t2); - fsymself(t2); + decode(t1.tet[facepivot1[t1.ver]], t2); + t2.ver = facepivot2[t1.ver][t2.ver]; } -inline void tetgenmesh::fnextself(triface& t) { - esymself(t); - fsymself(t); -} + +#define fnextself(t) \ + t1ver = (t).ver; \ + decode((t).tet[facepivot1[(t).ver]], (t)); \ + (t).ver = facepivot2[t1ver][(t).ver] // The following primtives get or set the origin, destination, face apex, @@ -2338,8 +2531,6 @@ inline void tetgenmesh::uninfect(triface& t) { ((int *) (t.tet))[elemmarkerindex] &= ~1; } -// Test a tetrahedron for viral infection. - inline bool tetgenmesh::infected(triface& t) { return (((int *) (t.tet))[elemmarkerindex] & 1) != 0; } @@ -2408,7 +2599,7 @@ inline bool tetgenmesh::marktest2ed(triface& t) { return (((int *) (t.tet))[elemmarkerindex] & (int) (4096)) != 0; } -// elemcounter(), setelemcounter() -- primitives to read or ser a (samll) +// elemcounter(), setelemcounter() -- primitives to read or ser a (small) // integer counter in this tet. It is saved from the 16th bit. On 32 bit // system, the range of the counter is [0, 2^15 = 32768]. @@ -2431,7 +2622,6 @@ inline void tetgenmesh::increaseelemcounter(triface& t) { inline void tetgenmesh::decreaseelemcounter(triface& t) { int c = elemcounter(t); - assert(c > 0); // Never get a negative counter. setelemcounter(t, c - 1); } @@ -2447,13 +2637,11 @@ inline bool tetgenmesh::isdeadtet(triface& t) { return ((t.tet == NULL) || (t.tet[4] == NULL)); } -// -// End of primitives for tetrahedra -// - -// -// Begin of primitives for subfaces/subsegments -// +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for subfaces and subsegments // +// // +/////////////////////////////////////////////////////////////////////////////// // Each subface contains three pointers to its neighboring subfaces, with // edge versions. To save memory, both information are kept in a single @@ -2568,7 +2756,7 @@ inline void tetgenmesh::sesymself(face& s) s.shver ^= 1; } -// senext() finds the next edge (counterclockwise) in the same orientaion +// senext() finds the next edge (counterclockwise) in the same orientation // of this face. inline void tetgenmesh::senext(face& s1, face& s2) @@ -2593,73 +2781,6 @@ inline void tetgenmesh::senext2self(face& s) s.shver = snextpivot[snextpivot[s.shver]]; } -// sfnext() finds the next face (s2) in the same face ring of s1. -// s2 and s1 have the same edge orientation. -// s2 is found through the following determinations. -// If the edge of s1 is not a segment, then s2 = spivot(s1). -// Otherwise, suppose the segment's 0th version is [a,b]. -// To find the next face in the face ring we have two cases: -// (1) s1 is edge [a,b], then s2 = spivot(s1). -// (2) s1 is edge [b,a], then s1 = spivot(s2). -// In the case (2), we need to travese in the face ring of [a,b] to -// get s2. -// Comment: The correctness of this function is guaranteed by the -// surface mesh data structure, i.e., all subfaces at the face ring -// of [a,b] have the same edge orientation as [a,b]. - -inline void tetgenmesh::sfnext(face& s1, face& s2) -{ - face seg, s3; - - spivot(s1, s2); - - if (s2.sh != NULL) { - sspivot(s1, seg); - if (seg.sh != NULL) { - seg.shver = 0; - if (sorg(s1) != sorg(seg)) { - while (1) { - spivot(s2, s3); - if (s3.sh == s1.sh) break; - s2 = s3; - } - sesymself(s2); - } - } else { - if (sorg(s2) != sorg(s1)) { - sesymself(s2); - } - } - } -} - -inline void tetgenmesh::sfnextself(face& s) -{ - face seg, s2, s3; - - spivot(s, s2); - - if (s2.sh != NULL) { - sspivot(s, seg); - if (seg.sh != NULL) { - seg.shver = 0; - if (sorg(s) != sorg(seg)) { - while (1) { - spivot(s2, s3); - if (s3.sh == s.sh) break; - s2 = s3; - } - sesymself(s2); - } - } else { - if (sorg(s2) != sorg(s)) { - sesymself(s2); - } - } - } - - s = s2; -} // Check or set a subface's maximum area bound. @@ -2687,22 +2808,9 @@ inline void tetgenmesh::setshellmark(face& s, int value) } -// These two primitives set or read the type of the subface or subsegment. - -inline enum tetgenmesh::shestype tetgenmesh::shelltype(face& s) -{ - return (enum shestype) ((((int *) (s.sh))[shmarkindex + 1]) >> 8); -} - -inline void tetgenmesh::setshelltype(face& s, enum shestype value) -{ - ((int *) (s.sh))[shmarkindex + 1] = ((int) value << 8) + - ((((int *) ((s).sh))[shmarkindex + 1]) & 255); -} - // sinfect(), sinfected(), suninfect() -- primitives to flag or unflag a -// subface. The last bit of ((int *) ((s).sh))[shmarkindex+1] is flaged. +// subface. The last bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. inline void tetgenmesh::sinfect(face& s) { @@ -2724,7 +2832,7 @@ inline bool tetgenmesh::sinfected(face& s) } // smarktest(), smarktested(), sunmarktest() -- primitives to flag or unflag -// a subface.The last 2nd bit of the integer is flaged. +// a subface. The last 2nd bit of the integer is flagged. inline void tetgenmesh::smarktest(face& s) { @@ -2744,7 +2852,7 @@ inline bool tetgenmesh::smarktested(face& s) } // smarktest2(), smarktest2ed(), sunmarktest2() -- primitives to flag or -// unflag a subface. The last 3rd bit of the integer is flaged. +// unflag a subface. The last 3rd bit of the integer is flagged. inline void tetgenmesh::smarktest2(face& s) { @@ -2763,7 +2871,7 @@ inline bool tetgenmesh::smarktest2ed(face& s) return ((((int *) ((s).sh))[shmarkindex+1] & (int) 4) != 0); } -// The last 4th bit of ((int *) ((s).sh))[shmarkindex+1] is flaged. +// The last 4th bit of ((int *) ((s).sh))[shmarkindex+1] is flagged. inline void tetgenmesh::smarktest3(face& s) { @@ -2782,50 +2890,48 @@ inline bool tetgenmesh::smarktest3ed(face& s) return ((((int *) ((s).sh))[shmarkindex+1] & (int) 8) != 0); } -// -// End of primitives for subfaces/subsegments -// -// -// Begin of primitives for interacting between tetrahedra and subfaces -// +// Each facet has a unique index (automatically indexed). Starting from '0'. +// We save this index in the same field of the shell type. + +inline void tetgenmesh::setfacetindex(face& s, int value) +{ + ((int *) (s.sh))[shmarkindex + 2] = value; +} + +inline int tetgenmesh::getfacetindex(face& s) +{ + return ((int *) (s.sh))[shmarkindex + 2]; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for interacting between tetrahedra and subfaces // +// // +/////////////////////////////////////////////////////////////////////////////// + // tsbond() bond a tetrahedron (t) and a subface (s) together. // Note that t and s must be the same face and the same edge. Moreover, // t and s have the same orientation. // Since the edge number in t and in s can be any number in {0,1,2}. We bond // the edge in s which corresponds to t's 0th edge, and vice versa. -inline void tetgenmesh::tsbond(triface& t, face& s) +inline void tetgenmesh::tsbond(triface& t, face& s) { - int soffset, toffset, ver; - if ((t).tet[9] == NULL) { // Allocate space for this tet. (t).tet[9] = (tetrahedron) tet2subpool->alloc(); - // NULL all fields in this space. + // Initialize. for (int i = 0; i < 4; i++) { ((shellface *) (t).tet[9])[i] = NULL; } } - - assert(org(t) == sorg(s)); // FOR DEBUG - - if (((s).shver & 1) == 0) { - // t and s have the same orientation. - soffset = mod6[6 - (((t).ver & 12) >> 1)]; // {0,2,4} - toffset = mod12[12 - (((s).shver & 6) << 1)]; // {0,4,8} - } else { - // t and s have revsered orientations. - soffset = (((t).ver & 12) >> 1); // {0,2,4} - toffset = (((s).shver & 6) << 1); // {0,4,8} - } - // Bond t <== s. - ver = ((s).shver & 1) + mod6[((s).shver & 6) + soffset]; - ((shellface *) (t).tet[9])[(t).ver & 3] = sencode2((s).sh, ver); + ((shellface *) (t).tet[9])[(t).ver & 3] = + sencode2((s).sh, tsbondtbl[t.ver][s.shver]); // Bond s <== t. - ver = ((t).ver & 3) + mod12[((t).ver & 12) + toffset]; - s.sh[9 + ((s).shver & 1)] = (shellface) encode2((t).tet, ver); + s.sh[9 + ((s).shver & 1)] = + (shellface) encode2((t).tet, stbondtbl[t.ver][s.shver]); } // tspivot() finds a subface (s) abutting on the given tetrahdera (t). @@ -2835,47 +2941,37 @@ inline void tetgenmesh::tsbond(triface& t, face& s) inline void tetgenmesh::tspivot(triface& t, face& s) { - int soffset; - if ((t).tet[9] == NULL) { (s).sh = NULL; return; } - // Get the attached subface s. sdecode(((shellface *) (t).tet[9])[(t).ver & 3], (s)); - - // Set the right edge in s. - if (((s).shver & 1) == 0) { - soffset = (((t).ver & 12) >> 1); // {0,2,4} - } else { - soffset = mod6[6 - (((t).ver & 12) >> 1)]; // {0,2,4} - } - (s).shver = ((s).shver & 1) + mod6[((s).shver & 6) + soffset]; + (s).shver = tspivottbl[t.ver][s.shver]; } +// Quickly check if the handle (t, v) is a subface. +#define issubface(t) \ + ((t).tet[9] && ((t).tet[9])[(t).ver & 3]) + // stpivot() finds a tetrahedron (t) abutting a given subface (s). // Return the t (if it exists) with the same edge and the same // orientation of s. inline void tetgenmesh::stpivot(face& s, triface& t) { - int toffset; - decode((tetrahedron) s.sh[9 + (s.shver & 1)], t); - if ((t).tet == NULL) { return; } - - if (((s).shver & 1) == 0) { - toffset = (((s).shver & 6) << 1); // {0,4,8} - } else { - toffset = mod12[12 - (((s).shver & 6) << 1)]; // {0,4,8} - } - (t).ver = ((t).ver & 3) + mod12[((t).ver & 12) + toffset]; + (t).ver = stpivottbl[t.ver][s.shver]; } +// Quickly check if this subface is attached to a tetrahedron. + +#define isshtet(s) \ + ((s).sh[9 + ((s).shver & 1)]) + // tsdissolve() dissolve a bond (from the tetrahedron side). inline void tetgenmesh::tsdissolve(triface& t) @@ -2893,13 +2989,11 @@ inline void tetgenmesh::stdissolve(face& s) (s).sh[10] = NULL; } -// -// End of primitives for interacting between tetrahedra and subfaces -// - -// -// Begin of primitives for interacting between subfaces and subsegs -// +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for interacting between subfaces and segments // +// // +/////////////////////////////////////////////////////////////////////////////// // ssbond() bond a subface to a subsegment. @@ -2926,24 +3020,26 @@ inline void tetgenmesh::ssdissolve(face& s) inline void tetgenmesh::sspivot(face& s, face& edge) { - shellface sptr = (shellface) s.sh[6 + (s.shver >> 1)]; - sdecode(sptr, edge); + sdecode((shellface) s.sh[6 + (s.shver >> 1)], edge); } -// -// End of primitives for interacting between subfaces and subsegs -// +// Quickly check if the edge is a subsegment. -// -// Begin of primitives for interacting between tet and subsegs. -// +#define isshsubseg(s) \ + ((s).sh[6 + ((s).shver >> 1)]) + +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for interacting between tetrahedra and segments // +// // +/////////////////////////////////////////////////////////////////////////////// inline void tetgenmesh::tssbond1(triface& t, face& s) { if ((t).tet[8] == NULL) { // Allocate space for this tet. (t).tet[8] = (tetrahedron) tet2segpool->alloc(); - // NULL all fields in this space. + // Initialization. for (int i = 0; i < 6; i++) { ((shellface *) (t).tet[8])[i] = NULL; } @@ -2977,18 +3073,21 @@ inline void tetgenmesh::tsspivot1(triface& t, face& s) } } +// Quickly check whether 't' is a segment or not. + +#define issubseg(t) \ + ((t).tet[8] && ((t).tet[8])[ver2edge[(t).ver]]) + inline void tetgenmesh::sstpivot1(face& s, triface& t) { decode((tetrahedron) s.sh[9], t); } -// -// End of primitives for interacting between tet and subsegs. -// - -// -// Begin of primitives for points -// +/////////////////////////////////////////////////////////////////////////////// +// // +// Primitives for points // +// // +/////////////////////////////////////////////////////////////////////////////// inline int tetgenmesh::pointmark(point pt) { return ((int *) (pt))[pointmarkindex]; @@ -3031,7 +3130,7 @@ inline void tetgenmesh::setpointgeomuv(point pt, int i, REAL value) { } // pinfect(), puninfect(), pinfected() -- primitives to flag or unflag -// a point. The last bit of the integer '[pointindex+1]' is flaged. +// a point. The last bit of the integer '[pointindex+1]' is flagged. inline void tetgenmesh::pinfect(point pt) { ((int *) (pt))[pointmarkindex + 1] |= (int) 1; @@ -3045,8 +3144,8 @@ inline bool tetgenmesh::pinfected(point pt) { return (((int *) (pt))[pointmarkindex + 1] & (int) 1) != 0; } -// pmarktest(), punmarktest(), pmarktested() -- primitives to mark or unmark -// a point. +// pmarktest(), punmarktest(), pmarktested() -- more primitives to +// flag or unflag a point. inline void tetgenmesh::pmarktest(point pt) { ((int *) (pt))[pointmarkindex + 1] |= (int) 2; @@ -3060,8 +3159,6 @@ inline bool tetgenmesh::pmarktested(point pt) { return (((int *) (pt))[pointmarkindex + 1] & (int) 2) != 0; } -// pmarktest2(), ... - inline void tetgenmesh::pmarktest2(point pt) { ((int *) (pt))[pointmarkindex + 1] |= (int) 4; } @@ -3074,8 +3171,6 @@ inline bool tetgenmesh::pmarktest2ed(point pt) { return (((int *) (pt))[pointmarkindex + 1] & (int) 4) != 0; } -// pmarktest3(), ... - inline void tetgenmesh::pmarktest3(point pt) { ((int *) (pt))[pointmarkindex + 1] |= (int) 8; } @@ -3088,7 +3183,6 @@ inline bool tetgenmesh::pmarktest3ed(point pt) { return (((int *) (pt))[pointmarkindex + 1] & (int) 8) != 0; } - // These following primitives set and read a pointer to a tetrahedron // a subface/subsegment, a point, or a tet of background mesh. @@ -3126,6 +3220,17 @@ inline void tetgenmesh::setpoint2bgmtet(point pt, tetrahedron value) { } +// The primitives for saving and getting the insertion radius. +inline void tetgenmesh::setpointinsradius(point pt, REAL value) +{ + pt[pointmtrindex + sizeoftensor - 1] = value; +} + +inline REAL tetgenmesh::getpointinsradius(point pt) +{ + return pt[pointmtrindex + sizeoftensor - 1]; +} + // point2tetorg() Get the tetrahedron whose origin is the point. inline void tetgenmesh::point2tetorg(point pa, triface& searchtet) @@ -3171,7 +3276,6 @@ inline tetgenmesh::point tetgenmesh::farsorg(face& s) spivotself(neighsh); if (neighsh.sh == NULL) break; if (sorg(neighsh) != sorg(travesh)) sesymself(neighsh); - assert(sorg(neighsh) == sorg(travesh)); // SELF_CHECK senext2(neighsh, travesh); } return sorg(travesh); @@ -3187,25 +3291,24 @@ inline tetgenmesh::point tetgenmesh::farsdest(face& s) spivotself(neighsh); if (neighsh.sh == NULL) break; if (sdest(neighsh) != sdest(travesh)) sesymself(neighsh); - assert(sdest(neighsh) == sdest(travesh)); // SELF_CHECK senext(neighsh, travesh); } return sdest(travesh); } -// -// End of primitives for points -// +/////////////////////////////////////////////////////////////////////////////// +// // +// Linear algebra operators. // +// // +/////////////////////////////////////////////////////////////////////////////// // dot() returns the dot product: v1 dot v2. - inline REAL tetgenmesh::dot(REAL* v1, REAL* v2) { return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; } // cross() computes the cross product: n = v1 cross v2. - inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) { n[0] = v1[1] * v2[2] - v2[1] * v1[2]; @@ -3213,8 +3316,7 @@ inline void tetgenmesh::cross(REAL* v1, REAL* v2, REAL* n) n[2] = v1[0] * v2[1] - v2[0] * v1[1]; } -// distance() computs the Euclidean distance between two points. - +// distance() computes the Euclidean distance between two points. inline REAL tetgenmesh::distance(REAL* p1, REAL* p2) { return sqrt((p2[0] - p1[0]) * (p2[0] - p1[0]) + @@ -3222,24 +3324,11 @@ inline REAL tetgenmesh::distance(REAL* p1, REAL* p2) (p2[2] - p1[2]) * (p2[2] - p1[2])); } -// Linear algebra operators. +inline REAL tetgenmesh::norm2(REAL x, REAL y, REAL z) +{ + return (x) * (x) + (y) * (y) + (z) * (z); +} -#define NORM2(x, y, z) ((x) * (x) + (y) * (y) + (z) * (z)) - -#define DIST(p1, p2) \ - sqrt(NORM2((p2)[0] - (p1)[0], (p2)[1] - (p1)[1], (p2)[2] - (p1)[2])) - -#define DOT(v1, v2) \ - ((v1)[0] * (v2)[0] + (v1)[1] * (v2)[1] + (v1)[2] * (v2)[2]) - -#define CROSS(v1, v2, n) \ - (n)[0] = (v1)[1] * (v2)[2] - (v2)[1] * (v1)[2];\ - (n)[1] = -((v1)[0] * (v2)[2] - (v2)[0] * (v1)[2]);\ - (n)[2] = (v1)[0] * (v2)[1] - (v2)[0] * (v1)[1] - -#define SETVECTOR3(V, a0, a1, a2) (V)[0] = (a0); (V)[1] = (a1); (V)[2] = (a2) - -#define SWAP2(a0, a1, tmp) (tmp) = (a0); (a0) = (a1); (a1) = (tmp) #endif // #ifndef tetgenH

3LQDioU~Y_CQ$;zD zP=6Z+IKXGgy`E?kGjXXE+^C_LjOG@^Auf;`CdM{iG8@{N7#pvdZ3$}n6~MWr#UzSX zwo;N$>{UF(8GywTDO1el9E!vGOE$8>B}8V!b%4ZHJ}2AK0@o*;&$dj)b!1&~EY*FR zv?PA>qm?3O7Zl7*<2wISk#eDT4%b-%r znj@t$;CFEGnnx^Nyyju(x+0rrPCaa?Xo7vz+9R;tyUat5TDBA^-l7OE>&d(t$$XiZ zCGH^@P5qlEfYIGtk*jhHs*}?aLrrrJWD(m*AlXHp6#w#y$1G(mzK5yDFT(8ybzk21 zm}PXyhs|W8rDEE{ZymF=N~?EJ6>QH$Iq@*o44u{NxtxKFlgLptetQ|92q(Q2%rCSW|4kxi(FKkw{KN`qI zPNZBf%vZ<^13B41Lf&n(UdSB_=`S8r0`^r%%QjjoFo7)(koITPrhvs6z-p8F2f^U{$?q2bKa_&ADdYr;cWt6q{BV77vti>^Mxv6>T zGt?YhF+B>Cui(SSoUH1DRqo~lzWFqIu$w&bj3o#!;tV(gy{3MV+sV6`|9r+0Y+A%~ z@aaDNYqQIU(nb;wT8gL{_C>59a502=_=nh{3cK6DcKZ;k4v0CH7})r)dE8mpHr4o= z_oA==*L)&#b1`_ zfb9)IZ+e?(`X@Vn21;A*RlG!&+Ih@52wO9jcROeKJfvAP36Q*6UZU;#?hJex$I(4) zr0(eue{{|gQs*~G6Av1k!&Ppev&Fjxpzhmg21;;}XM#PQOwj4+8g&fc@o&ysk~DiZzjof@U*;fKLwAOY+bWB{z*_ZY5tI;2 zV{s?d1r&BfHjljkMfxe;^@1g|YUf=fevcV=BVH73B%8ZyLSOjPFS>m-S>6IDE#yR0 z1OC$m%T%rDL0EIfC0a4{ffNGyC;NW>UprTeFPv`O*S1p}Q6L|DB970Tv z;GbNxRM$cx_z%~xPP)gB0BqXL-_U0^uX5c|)7rf{IMtF@A_f=R>scT#YqKH@hNXbiZb5IOz9`C%P{{hd{JmnV_6|=*XrR43FbFTuuh7h=KnH3>vzm zfikv$L$tx65pk$Y9DXdsVK8wxk6e=?R3Z^76Jef`n?!hHBmu^-bQ2SbzOOU>2nzkf z2cf&k`1chA-(>{vLBZEh@Cx|}{h@$toNKz* zaSWl_V6(QGjWT)P9c9uGr|9Fp;NRc0gp}MW$!Y!RHJk6hY3btmLpahtj1Jn{ch{%N zy@p5JvXpcEmh#U4P3Aprp$aBO@Oif^VPWMdX=oV}iG&)H3Rn{0+>7EZKDbrx@-54B z?bSv;;5MdHtGWF)EK0xRn{Pwao3WkW!nf|f)l_=Upi&XPv6baluDtdFrj}Qt55Sb{ zHvox@>U?fCW@|m~SUj|Uhw$NdV5b{<00D7a1eyZT08DXwe)kZ*>5e5f@W5&awMGn~ zDD2kD)N2@o=Xgnh0f_S|!V((!CFq=(@2g@^-8mU0gHyrHktYZ_5q)QpZ7W4VvW(_q z1)37b3 z2dN$j>4#Ern8I3S72oHue2Qiqao4g{(^v9acP*`&?fD5TNTsm#Ycw2bm7SDq4M zXT&t)gs4!S=RLGka2*y(38EKIU1RWg;Umj8pA270AXS4r5$a&m-X_P17Zi@_x6Jlu ztC;rb;&!Z$3l#$!tbv;UxB zW|z^Wa2y@=D{UM7!jfrPomt_f<-SSlx{8;6ZOPD1tmI2xTZZGZB9Awg zvQTr@eq*VZP&|cNKc)#WCx0k{GdPH7M_v4gLxG5X%7OYHT0`1E1fgu-La3jxkBAKT z5+Lyn|MiWfou$h{a4WPh>BD!AwpjSgx0W8JYh1jwM29A>pa{_twxvM*iu>c-raqUG zm7Ol|i)p;+I~Wz+U(UaHXK7#ktvBsgznWAcu`f34?}$S_h~GAa%u|wVgA(Ybzknp$ zuoQ`#|F5O8?^1+Fu-T*mi)}2X0Ox25@Fj2juO-PRhLCov%Qy7tv;kuQD&AfGw zA6JEIfI0wNj>7HWr~bv>eY0hm;qNWcCfsQ;AP>`-2rs@Y&yu2zSjyk!VVDeGIz}FRJ z)nUp1cTrZgmPaX2yY2Z4EHYqcy-!Hg)xlRGqWBIv1ntA$1c zZN+0^BMEYZl?Y;#@1(d!v7woTievdS=fx2NT3loTDt^sJm0T(|O57Du+>O^Q#+C)Y zD?uEt;F3XMCKQ86N}ix(k&<^|n|Z;H9Ya`Jmf zJqPs*B{q?tE`>{RZM>ZKD!~?3`hFR~dYQZ6E5_)U6}WA9fd6;mtK5Tm$Rswvt+Tmd zH(stJ>xrvVzbMHDm{K!EN!G>W^A>dud=36|(OinT#nQ~qrPzFvPaBLW|3xlX%z=; zEn+s0^+dH9gIzLYNX`Ugh@ls(36MCc5j@6Uq1O=FP+WQvdXqv2DD+H18=A?daEq@M zx`aZ1LTE!Nd6v*46}sfaB0dy(65$Ok<5t3V&-<&Wknd)Jzh4#1qf>b)rHM;?pJmC>;$yP9bnoygQI7u09k+Gq-Aw6=i2r zA<7d(nFXqSK;qzn> zhOQQMU5fB|-mC)NFyHCTdTJeJ@<1QfM)O?DKlNdFos7@(K_2Ykdwtj@Xcvb2GT*WX zTH}%AX?0;Nf@%|wM&a)IxDCCz+Yz@YM!3eb;cI+ZZOC!imo+sVoH5x?ncXUslDEF@hkVpJditjsI zF9DO%8d`8`e$9{h>P2P16f>T7^=A<^{!Au5UCg`Xx2HKBgC6+B6TkRv!tcG}HG_C< z!y`#O{n^)gNlBbyUcq1cqc6P4{R7xHRTGkwNbcXunyy4zDn)wxMzPsOY?g{S{9FL5 zQSL=63gD5v4ObRcdmU$Q=$0`N6kW`hB_hR~$}0!5YLOc;10nEt65P|=`#Twdl5wMW zibJR*Ekt&Y-zZ|=@Tr09>r%H{O70SD#=W3Hteok$%oahcqN!3D;G6{zI2U%7U+%)x z0O7wmiM|QVRJQp;{M%sG*p$J~2D3P=#T@P%!WyLY9WGnv8BFe+?W-X`;^GU9T4zUv z-a=@jeFYPGmqMErdLE&T#`_*_u?T2+RU&QM7&cq4(MkUD1IGZ=xVqmelmp3 z&`N*7yOv==WnN4sblrsPX=E5me}?@AMtQOJIiLHKdGXa{n7g)RGT(s^+Wg71r{m4< zmtiHfk%q@T6rQ%qQ)(D>&&Cc=bqOo06`#!eg|hf+6&`9>aNH5T_(I2mL6}~C`}cuH zm&+z++)Mp4*){+cWiLhWGyZ2ND_`r?Q_7a{W5lUxG$S4FE>1s@5GD?h1M6COZHDoP zWDmz?9$uC;PU{bz6jNt>3u21-f5g-SG2k(g?%{Zpmjpq3L!fN)d4OcW zWt;yjaGrKLaV`x`5h*h*=E_LC!w993cg25R=81R-lo14q5@2^Q{shuvo{10%8R?|H z!E$yguUVc2CH5kq+%L!$nJ7M|dpEglir91>7jQKKAv8c$a4p2&2}R~fF7}vE89e!f z$jh8xo>el1bU{&o$2O!6_0)qDka9yJi!^@!@QY!rnrU|?tH4T|IvpP(vEsLTlq;!z z2scNCEBT4SthkRnjx2yLHbP8QA$$rjmki7ZKD;8UteqLl7gS`C+K*%Tv5IVe@uaE5 z@E_6g9{-^do6v6lXbUW1k;0}aSONAD}#)Vv)A z0nw)bK^vmh_T*P0iwS&SWmYAw(Osn8$YKLiL4io`hI!R$(U<)8_@yeLfm56r1pVjl zLhw_SS(xt$qOqk%%93*K*wnelOIBea{wa6z8HkmNnbe(L3N+Z?}Xw#C)R-pl9VMJyg>wuv$Tsc<2|tJ z{L2WIXu89XMzAVrcW#2_ivx7SVd0gPoX5@)hX{+AYjp1Y+}vD;b595pR09z%79tWS ze4F^rfR`6j0T*c`0h-Yb`K9^vYO(W%;<6B*y%c z{8l8ZiYtOWYBE2sBa_v11HnAvcTw1RykSjNmR_^3$*O8q*Kk`+mgrM$Qa)XZdHk2s zW4^=h)?{IED_5IcXnC8L+XxKI)2$?B7m_5!T+1L`jCf!}fjdf17F+?Ev+tT8bpHc* zn_8@g##Zz7wOBY_Q@K!!?a`vr`1iG0lvp~-s0Byq&8C(vE ziBT8VgOm8ldd#N^!#rd*aNE_zp_rj{@%{mVlTD9X#P5jsG+i9(J%qQagQix!HlJ9B z#d^N{NwTBsN{OAh$RvKE4h!%+0yxGhiiTLZo}0)YN3np4O8_r)H>vkpOhpT#kav@M zkN=6+Y`}bZdn@zRn%3f?>3} zv2~em-MXx zE=Ghg<5h$&mVFqZHeN+|81O>*?ETj`8DYi6fESMN=W)*T?|0q-8$52AioHX9PMX|sNYXB}B&sG%Ee+xLBM>c?} z;$Pmo0rPVULRF)JL`&^rZZ$^bYf-Q%T#)W#WWIWwU-Ch$-Z%OE28eYXz=dKB-uAgO z)`D!E!K+701xb%+#F_>Zv;0_Fe=cM7uz*eBSiSf@aQD2p_`^u=j8=uY0r0}H?gUSj z)`D1Vi&bGZX~=vlb*uWH%Dn8Ls?2vGZ@x6`_~eEx+_N~i7Z%26w5nSFEc#HIyQxZ= zUjZ*H&7Rc%TbkL6lr)WFm@l+(onu(AJ`7cmvc4mq7{ls#76F?=(VE>-RmD2D@PlYw zZ;VnEYc1e~qV>*%0o;EJeELFFv6?qxo_O!1OCy%9&Hk8QZN#Fq4j=Qt#uyz-f1KI1 zF>7hka(ZR1jAg%@v?j+ghcscIn5vmhV!nylAL*5@9mmLm*&DRL6QO8&FJ7F$*aV$1 z4!2-xAu>(P*ea9%idGoX?bZpf9oVr1JNnQWZURV5&DtqUQ{Q3|mS%Udk(x@KYIo^n=dXfc`C#mly4<$;kVkdmqC#LqU;i4K(|eqo>d^aw);_AlZvqb7MJl1i-?Y` zc|d!1C}1oYfJl6jpDY*?9`B$~kEUjhzs#MV!!l7mf2 zHli)Pyzad=DRX}Z_PI%`)he@LC$_<)eUZrTe}uudO(HMTnfYnfL>|={YMeh4`1hSz zO>J2MKiZjns?|y04N}-(?NE!%pHkQnllCs2kLU_L&)RstzboskwU6gP-B|bFXU!;n z9Jk2ah*hDZ12%=Go_3qJ53*lqmN~B*!^yx7O?hAswqKjwgkSB!%4tKI@S;807Hvx$ z-_euBdku|~X*f9f66y#}OPRaJanD|Cu68Jv|I~|3(h_5Nh2HEJ_ru9l*{fw`w1@@O ztj7F$Z&o3=e`5;H4t}A6+guJ#zHBgR*_elY%qD0@8}VfyvvuyRlL|9%Z^V1{L1LH1 z@S}a$R`)>kTm|eeH{^5svaZ?}4f*}PY@>S~`nUq@zG(hSKa|7FXnwOF>!j6==D44? zipHY(fc|Wy*1G{OHGmz5GVAgHRzVvQ#fuJP-};{om&H1(5{mU>m?WV#4F*Vj%Qp>V zm9%f~^1lbNx?1=8+;b3XP~*#0)Hl8qqwuApZ?@gEQX|#%M}VMzNX~eldK}M>xzkd9 zG#@{RwP-f39`eI(oq~^;`HSi%ev>&D?)njc`Q1dE^r&tkxe&r!zaB3(n042R*URiT znB6R@MaJ=#pRs3JM9s|D|FMTfwU}y|HAk`TMSQPRwqUoWa-w~AD9S(4zBQ6`4_o<) z&smu106+9OYgDp2>>EaSQg79?Zl>25_Nu5UGqdDGw#;ODleu9M>*cETEY00#G2fC4 zrlD1(qwbocOY_>ZSotH?YGP$u6p9??S7)-1`BFP` z)%JSw4R#i3y2Y>BSw(GtCug%+>l7bPqtXe)g_tK*>{-;6+^BfPpel`n9b5AF+V+I3h*j(}{&*p?X$?m3NsFL7c+a;lV$Dq3 z`TIp|pm*Uhgveu+M*;cH36ZBMcK|ZYg^MNZ%PN`^L;5jC zS>zs!I_~EcLjgvktX2a`%EvY%_%};g_0sF|F&hm`sS*6_QZ}JTP+l?K;YZfY8U;n3 z;}Cjj*ab=xfWM%Zc5G97`IQN|PP)zO-cu~-*sajI1VY~TmyODiKKjStW(Hw26E>p%{cu^dF1#? zVHcuOQ1`gl8C0H~8=;URFp3lM3nx+?Q_2!n4wCsG={)>lY>1KGlfl&9u+kLApg zMwN@WhC6XE^LBB(mvboVUFZ=B>FPvMN94GskmsH&JYvGi|yN&-YGhc|dNcq8?=+CpeL0`RY(cgVtUKvX>JnkH*zjA)6cV zG;t#3u{a-vtY?I*Fw$8Pe#G}Reo(?;29`O4V#?y;paB`k(grdYB_oZ9=^V0SuR`WQ zhe~;N+=(ns$Ylz-|CvH=aw6$Cog*z>VShES-zuzS8%+LPaz8gCx)6`F1*~IA9W@YiThOE1&0oLCyz~=R zM4ICjPhZ9QXz8!GV-@Bu=2v|1YK*0;)?{X{W?!1L@-H%5{S2jL@a<>Bz(2vZlQu4B zt{p6{KcgITJ@Sk%U(0HHKN0A*uHo6Tgp4VY$ZnqUEHig4i#KTvpYmG2uvy+6r4I18 z_;oP4EL*Ek2(#b`Kl2MKtBrZWU;M(VxUT7k=>wimCu@mJUTr7Oko9bv*77kA+rVNq_LvXYz>>5bkNB1iY`s?e5%0H=ozS8la_c5`MEmqX zruHkdYue>pK6eYdqdmLJ$8BZrv=@$5V}4^M?U~3Nm&sa~w6%Bm{%!2@3hi&JJd48X zQ?AL2~ke~ZH!CuwzI%;o)Q}gAk3BQ-d@sMOOevSoHw`l!tE?T`|UR0 zww+DZ>fGiHcCatBO}F^&9jvxC;TC_ggN18tZ}E_wtd@SXdcDW>8zSN&EankvWEI>#7bjxXC9jk1=(kYw^my|Ae+_n8iHIx7_@=p zqbl4r{&O}<@+zSaIY1mxh%dRG!;;*$AC)1p6tGeo-am)M=>H=qqPfS{=CCquGry5U zyA;u{{6Y?^+HTOfVsxNgj#%KpU7I?`pFpudWuIFsLR3`{$VBuy_x`I9fSTaiIm4iFF@%) z((mVI53%s%dk2+wj-nur&r!38VqyWu;U>pz)w%95=zAA%>}zmLPUfu+vns)(4U&KY zmT3k{OERB(n0W;y7$kR-WID*!{XPa+iWMg`4r96Z?VtSb!z?WA9xYx0o}(zGe5VZg zB#3tLf)oB!6~sBwcp-jGKtBvYQ4-U6YJXU(V!A4MLT=b1|(q#`R)U-Xb* zX_;G1F~=h}_F%m5D=X+1*~CxW1{E<`aROcLNQmCbknw)2{M0;x(u2hQTY}uupODd^ zJ^06bPBbAB&zdWlB8c5J^5BL6yLCLM_7Xv2ky4GsVF{N)eb0*@WdUgkA9865d2-K_ z@%z-iuk!LHN8=@#u5duIg_v287_kMC{X{Jkpz4NXlMxRjdv>O98fo3u6Kvz87)B1( zHl$6HSs1FsMHcQrP(_C<)C;6N-%{2{%0ZsbU8ehM5K)rdPoY%=$u8ac4C)1vYrYI6 ztLpdJ3N%H6+|n0F>)zlFE%Uj_@>moPGBR2{gV`^!okjqelc6LU5hWIBB>pGCG9w!q z9DkJa<>1$+dFnA%u|pd}hNp?LAgQvYoKP}cm7r8t@Rycnj>XEtGsFmzCGl=k(-e5` zK@|))iX3AVlF$9FyyFWPUp=lsgB0W9va4`ZRKeF6^sN*Y$NGu9_iFzPSv!#K_=R@-MJDNO}; zkISCc5Zwv=Ij*bechWH>e0Lswf&~T|C-NEQe~nUd7tIQTpU%OQ)M*& zqo59Nt|a8bcWjlRuD5m|yM2NMyro8v}MW+~Q}x;1p}7*^g&lJH-Z@ zv~tII(iv<`zdD*Z`V9Nbq|G?OZ=Yr9MNLPFx$t&>GOrT7WoK%>ao7OxrGGL@(LeIN zd&s*g-+Sp#W>4!MMO7RY5%?Ceh=9*&dy!=8D|I0~8cBLM|h_FpuDg^2=%pocP zF3QnQF#Y(^j~9NB0a;YbJ!0UtVV`%10fc9pc#EQfHG~c#_%~vZdi)pjF*!Ip2Yptp zEMDy#Y-+Ca4(BjzXYtf?P_bp_@KxtnM7tP-CC*;NIpd17U*B4fY5OsspS$=%@}sDRQdEQTQA`YwsFxxOFqzJbmpvT)`K-TLv$R)rNTdVc zNIMVzz$cAc?xg;eY?~xaYu+QYq5k@n_S)r(Gx--U+0EfQKgg7wSlLODe^m~ZDL=uZ zQtmFkhg+TBPLB$PsFM+aGgl@_+)HsDFg)}8eopa_%$XOV{L^CU@qL%sA=3bE`-jyl z@n|JN-nZiC06+E*d%~hO!2PI>#(NFI)%uUFz{JSK%HLmMyESVazWXYp*Xb`_WocSv zEAM`dMX~5o_b zjZVK+`Byvr6{J6H=Qbz6TM=YA{buFQar&Q+mjYrMpai_7{O6tiKb8Ng)Bgwjcpq-S zoR+p-Z-o=O0no&}?8GT~B~x!F*m!rrSm1X6%0z#qc(lb7SNhY&^EAOK>$fr~Zu1_P zISQCJ9#h`Mnf#={!ZS9LFLki8yp03u`{TcnXM)yNek=3lciBu>O`N)mb-`19_bv<7 zY8~MvbJ>Jq9g$WXy$Zyiqx+xIe>{!JS(wYD_5H z?|uc`tu@vQ=EuMCKkj4g_V2I!@qHE+bg?43-v`u@$!9ciDbvmbveDDtff3vq~199l&GUmW`XhlBqI9O&vgTwG@hKE8<}x;Uyc z#QAj-fA@d|Rdp#xy-A*UQkI(R0rc;VPJ#YGug|)mzi7zZ|g+kII zHUGtTDU2_an#(H3w7?4d+(ULACQ%z7v6fncGW^{m*57o7_j$~EnMU&8AG5or8T^+g zY_Dl5ANrI9=z}304M-dKoTn^8TfZ(d>nX-K?foQv{u$J!pK-V6teqBAmiKwi>S>7~ ze8F>Uc&z@JAAHVgd4~ibc=|>3nW^+nFQyw;aH*l|3l`lxCy07qGB?Z?$Kl2!P{|ns zSgv3QN7Fa zzV2iV_g_Sxeh~(UHtRTY+hV-H9;vOP!cyv{9H`G@gLQvi?=AC6tEI?}-~+OIC_z+p zI{_(P^u}LE_mnZ{#=!OMK*@EXBHvAgZ_ss7bdwd`5k)sj(akGFS5whZ?+$)x3lw3J zA{<(XP*;T1?Ss&!=)x3Td?C7Pgu#OsimtDsdxsCw6I_UHyP~6E0{o&CUD{bi_?$}L zNXcA9NaF_xy%ga(MR%+a-C#vW;|l0<2gsb6s_1?yL>H&%Xt)90az)o&(WOl+MCh*w z(Ki#}0!3Iy5q2s>_n0tvB1F-RRCFa3UG+kAhZG%+Tj1A9(cQ!cIb&9IHDnq8s0e95 z1L52LG9|kd;bm(3M$U{Kz*GN)v3Mq1$@*p80!i_d+ySjp6kUx*8Oxs21_cc`QV$DCw&5 zk$KERYw5+O<+1Xf6%@5w`XE{g+svDn@|}5Xw0~0$E;#y#&kj4(-xzwDz%Aa+YpGY) zo-dg}d)(yi^1TehV5sIOZww;zqnu#YxF0ZfY?X@Pu`7BM3!3$%DHNAo8Z1+BP< zA4QoZE&xvbe{F=``yEYo??^00&O4%b&ilM1+t3;4fnlAvRD3ImA0(M@BY2)@3tysr zf?F6YW!{TL5{I6ecQosX+KC_d60`27ZT*37H|w8-1?0!MTC8)0EO*5Mgh;T-YXG3e z+$aX~gkpNNv_Zfo!YV&G>K2)w^oRM$O}D8TP&koDJrT$;0UkM{Ajl6S26gpLtC?s3 za&I}Ud6x=bFr?BOPVr^z&#g{BmGOg2TrFN>VUQaj1c<~9{J4sLBt5RiydxgZx9Ft< zb3pQ6gP2kCDc64IH7vS!+8Y(3K!gPVzyih|(|itaIM9Nc@4Frf{6TtjW;TxbJLIfuM&EBG@~~q^q`M-F_6=hrFX;$ z={!%xP7Prl7!kM_$!CxG(?%bw2R=J0sBAk1x42KC>@mM7^plxDS5)Zjgg(wcFR4c+ zt$>@VcVjS(dvP#p6dvZb&NqPJ0K&Zc#Q~E9ryF1|C%B{pk2JvV_|uYl?=*MA`%N*; zMP8ty4xJcpwOZdoklZVXOZni*nZ`w^DmrBgj2Z!7Gv*Gcd&KW(^_FM1}B~$*+{s z{TdcA1J+Byo@J;azfqLlQg^hL(3tXoLh5=mP)|O?J}-u=9Jqy7F<#qEU#Kmb!%w;C zRnwTF>o^^B`>~@5x_^l-5;4%tTGE*91|VQogNSx1qUvyq5+G9ACY!m^9E>Y!l$`_P zLK#?;L#2^V`Z^Sc`_sSy>m!OL7jB^)DymLe&2iU@YUjV=^WD)K{K}8G>s70rHX&N{ zbQlklGE%el=1>hO64UD|8o^StgiD@D4h85Qzv1OP^!gwFh7iHOk(kkw<;Enz z7}PONmSRi>#70W~OfN|8BudGqBIqSaHF7<{1Zz^Ythk5$c^cXhZ6WA_$zbz$zS9F; zLB?!;%R{f;sHNi8h`6m(sk%d{3II2mdDz~Hsi27biD-!;@`W2$z_j($EBQUQ%Y1X2 zO6NZkY;L$?0TXsy`Sz8kUay@y5+coYt3ZXA9e@c+;&USE%8XEWY(j&vY6<#O2A^5K((=B3YPX)W^ypt5ir+m1VUad%! zi^i9F>Hc-9xKNyQbP0-In|TG^ouaarVAtHu}n8c_|M$xS|b?SR$)np|4hn^lN~DRC_99qXq$ z)U4$(Z|$Rp1l@a$2#p12KhVb6d=hP~8J0S?Gx!W2Jy^@m;H!M}DAPiI%|~xkYWF)6 zs@qH9i;5yv^6I|&Y%TB|-|ef9HqGUA{Pgu@x`PA*N^C;LNJe(hoxY+!F|`ZvJaM!v#D@*>G*tumd*`|A<@jx?FYmnRnF;WU86c0S8r zuT;GXLJ{{VZy}B+eo!j&RFDh`V${CraN|7cRewDex1N>_&?n=)+hqZIz0&2ufQBW^ z@;(|NHTwI;^ZNn%{Nh6?YruN*Yd*KU?!}h|>Scm-q(ZKJ>5Zo}CSJcFBASzn9DXrS zzv5R45P6{#u$mM5K%C;IgY=w|5xC_6Ov$|9U%oO}ubtL6mD=x1u@%vwS{b%NerjYG zVj#bBA~EE_{2NgKM3e-tv_K=yPZrLE9Dg^VDr8>an?O4VCB+8At>LQa%6ao0I0p4B!&I0(n0bXW+i%xE3qRU_Y zFyJ#4oQ@<~GY!~i1xp)6#3m}qY)C=^HYN=Ea@0-if#E5NFsC8cWEu93$V2^rP_)nY zxdFN-L8&F!pzjRt-^d^%*|Q8#Ha`gpeYoLW%^#Atli|(arON3(-L?%=?1#wkGLO6Q3ecqqZ5Up;7|f~_#QyAa#nAsbPb;T;_q|~F-#n95mS@&c?ct8kbN!3vR zeSs)*D?+j?1sqFs{S+OvphUNaKwUs*P>I?=)58r3RsKc;r38CDrUc&+)j{PrFXxYt zvkZl?_5Tj(yS-Sq|N7L7VV^Io>(mq3)BeN;yUk9S+9hLcG4^B z<+Q|md`)FNFsT}XBuBl+S|{~Ul6~_Gguy%y1%!q63lVXbQj{m^(8lJ@=15fmazSEBb_7zM_gg+;oxqgzFJ$TW$e4<=`;m zbjfC_(l}s3$8BDUuN2QmRIUE!ko~a@02MU zNQHMr0c5g2BEU5Tka-ec2p}`8j^IC6)x+b%Zc+|sBZuW_rkyH>^G0o^;>@E^YX&&A z2P9ns#4Ux9l$3c6x~Q_69_=KAj}N2#S^4I!9IoZ1fV$tf^H;2AMd*y?{@$*gUVc) zVUXtP8$Pp!UfDE^Z?2&))-1Pqhe#+L2Jp`#^^g)Rnig}(Xc}rWH))!=G*X{nav$~? z?G|jt$uzWW>yXcQwOV?k($lU}>OW|I6ZxcCdPS}NHNLWzURJAeogb>D2PTvRWpdOV zWEnL(+1}8zZWN`js_Y8E_k(=A2s37B`%s)PMn*Qc%&XPL>=Ro^wbA67gQlR#Axhcg zj_`_g(BxKJ1oHpafQiwd&Q~+eyE(18sVRI?!R< zzpftCxLyzzW74wteC*U0xc5J`1&U}jHJpQ&DD$o9Y6eX!*^e$01Zl_w3RU4vARkp% zuafqtuLd1Hg*gJb55}LPabqJjkzPb7cOhT5IySZZ4^vU=PcK>gitkA_%dusAzbV z*RQ9CRciq6c4{|(Q8ntZACygE@zXK^KkeIM!CZ@i6oC$VMm@czws{cWSx>JL#Q;UR zKkbF-!#Q=>(+rSEkg1Px^pWUfLSIzqa6(tP%!BKrvuJUV&#kWyG9BV?>gypD*ZoaN zP0dl`k{=~Usk(FI30LIW6Vdz}Zxp2u^$EV9asWHL?O_BsDj9kF%K*MFN{=jg4g;OB z?pewIjnb>RFCmEC8u&5fL7@-fQ4RDU&36Fr+(5T#4bSu04fML2-vxfKfgTe03e<&F zx>@-{&=a25KrdVAq>TKE@PWX&MM;yc%3zyVhM>8wVlD{8EZ!no4{kc<|I2NJ!Dbu2 z=yaP=#{}u8P*{nY7t``PGKHPyf+`Ep`$XPW(<-(f#Ng4 z*qChlP5GubSfJMIDa)XGSCm01Wg1oyAfd}FiSbnEQiOI>Xm3K_<=tZR?D}1M$j}j8 zfIfxg67))K0TRW}QHI7uE3_M-PZvVx@~(~aZS{9_m!TV_0KHA2djli_{*s{=D0CS@ zZ!d&?%Ud?qhxk`k=!KnuexuNPx*;?kjBTuc?Ef3wDARI;zN6612>qV7h=mU6T`xX7 zRsw%p$$9-kSLD$fZhx`@#QI0Aq&hcw(_=ddTD>5!rbeGFotUHF5FN7OpMdZ_@1W} zM7>RLYlemXW?VNwy~UB9d~=*$VbDT%>NV;kN<0NjdxudJkxh|hJOSmne2NDsL{RvG zA{PgGU*?^h9PZ2j_Ao_o9J}{Zvvf!iz;$U4w$o7 zKoT(wIRt_$J@{8mbl+0Hf&e?@>Z-8|d`lC(np^3iq=Adr_xXGfruE{vlNhGHe)Jnj{a8sX?Jmj>1e{Nv_&os#vcQ631$CD5^X&zqedV22uX_e!-gGIQ@eQ=`H{q^ zr*2bmc?5wrIY|a6{~F~_^@PYYfxtQB;Pj8UN1|S>=~yGCb~2_>q~jTEfEp*TXcxbjs0XCo zG`!yz@HuOMrWNq1V}M2$@Y!U5dP`6$Ss{Om#8OpvxGNP(xtb~&F)CCHVxMM&3Rj@; zP{6LXQ)IphMi$R~4YH!g__rzaGt0a8m)t z0cadlK_46@F~}@@+IFfw@-3|z2K_0Cl;-zmTPXTH66BU1fk}C_}lQ3T$o@;6u?KNutOP@fgLiv3DAg6o&O(GP4vS=No*5=vsbi(IsW*=_yj*!ZhVgN& z^)A}??f9Rq^@REllfezIQlUh5+);DBPXQ8c2W97UQlU!|`l&)cZUyus-l>fq?$!g$ zGlILxC}OU*<+I!9Wwgd&d_x;OKwI9HA8DfpmY&uY0%cFWNp2=&%YfL0j37nH1AJIJJ)zGQ7otb$K}lwdS=pMH<)yZU7(Yw*#RQvoXt`o8 z#60*bNgNI4MHKUD`OMEuj+u-AE~%wz!Xj>w3+l1sHu_S$pBvB~n>9zv@&@hosJfl9 zmHx8nHh`fA&pr0cAfH|Zwz!uma$$U+FeIP9AJv!(SWtM}5 zytjpJKY%QDOytCD0I?jIAtv&k9rTY%SKCFC=>zq#s&yLSe9m(^=w*wy14dFl;7>c~ zUBc$?1-r7eI3m5;wuZEs{zmxV+00h<30AC)Zi|t8SVz67XAPok2XQyHlH#^2!~f{0 zH!OD%qo}yF2jg2_cEXhXp_s(iZx+3~B*Yu>JF=}+D_*{nUe0sX4#aGm5{hb~)*6C$ z?xa`HA4s^Xqa{!8q&M<^O??8A6^FWdTq#Fi00di(N-3-Gw7+$iEc!K#SOr{3bS zBw2sr(J^3R_8BGGS$}><08GqkUXgcGr#Jt_cnQ4>!`UrO>(RDHg4Y?+(j3>b$(CXukbG{S!lAii5|HrDXJg*1oLXe4&ZZ`F6r}(H|`e^OTSbndU?&q;F zQz|3SjzQP1Gsy>)BHbz2Bb2Fc<_>7@DRtVUGspn^UiDdVvSMA#{>G`DmbR)9a+ zKy4@xoF-a70W2948<$MJxVIisxel0+|MYLNjeiQwLjoEHrlvp?yv4GL-|VfIOY`xf z1<35s3@L6U%*h*${ehYkN<= zy1r7;B`W@a&c~cAAP1)eQ+Y_MMbGQsV@H+d*?!91@Q#T-=aa zpw+iAKi5}p5&QFcqKe5z);L+5LEeEya>gTYsiU~e5%Un;LGvQCY2xCmxXi)LI{kEC zQwKh}pPtmY7%|J(Z|gM6?YFIgvJZz^t=TE+%;C86i&J7#q3B{%bcky>?ZH1q(AY-#K~o9^?MK^KGOKcA zfkU@e1wjv*Ya%}>k!u^stO|?ahx_a0YFt`JsiUeMfJnsx1j2k2yS?#~si1b}qbEiH zJ%I-f&^u`Fqxi4^7~E_9!dDK^yPAIIrh$4mUfilSP!DN3X)Rhr#v`!&E*f#+bfi&? z+v_`JGt0UFec}M{KWP382_V@-eJR_=(cC@|hL82u@l^x$Hh2R4CHTf}{@LIga|~&4 zGEK~98bg(eHi8yIfP?0hC{1D-s+fM?fOi_C*C-?E(MtC>QURoz<7*zuE%aiQsPr>m zK1i>peO-@V8l(rCPVhH_kfM!isEjY5eYQksJmMe~2=we)-Uoq7Omq6SOt)Nu-}<5}z$r=hICV4UG8i$i%3- z+bUAncbJ^##q)ii=!u>C+@_8ca$)W`C8k3iM*18P(4vc4?F-G=H_>(x@;BgHVyYBVOC&VW<|lK}h5b=^aE~S9r(P z;?IZbHET4N;86sw3UEE?9pemCS$Zcp1NqnDoj%he{qEE(<}zh3Zaqf^x@Jr#2jV|f zlP~%Vd!xV9KN4qX zqsLgP^Zvv1PDOvM0k7LaOBH@+m|iWY3%tmZ(;D(%=t<;3EtuRZE9>LD#&A8r^gVAm zTyN=r5(S0w+EoE#QdxIu3$A|vgvaH68Lr0#j8^DAgbr6|Dq7KKIcFpE#%2E?JyKMB zXlhXQl*telX$Fh&*jBg3A`BjB9W+AE@t(YpWZzAzPVvYD<0M;fmFhfxq+TygTMU$a z_covs>|3*l!n(8&$l8>8(5p)%XrrPcV&SH!B*-S~jo6{6Rx2vP2uER%^=%F%rN9VBC$kbiy$Gf#xjZsf-uplJD)}k8$yZ0k2>d@Hxb6si>x> zrlB`XW7(@D=yO3ju8AAi=B=R_7FS_ zK|Y+MZE=vcq(xA4tqmy-n?c6??i01qpr@l?-VOvhd0O&Ksi*ytMw(c}G-Z-^5TQ?>f zd_HHNPgmON8+^p_rYk`e_k9G5ZE1K{0XwNTD46Ub4l+?Q}ajBTi*LpC_6cfX?TEZ^gN^(+MDpEO{ zk1Wiv9tegA_w@im_4Jil%|8CV%xZq{mNKgagV&>M>Irsmrcxezw_Tix3%$E6Z>BQV z{E%3pSLRSDGcrY|7zL`#P1{dCKrE)i%(_q=8_-g;xBRW$y8oJ^1YpWGaus; zApiL*R2B6~$Vme_x%d<*epB*uBtP}UC=k6wSL}%K6~k=fO6V4QWw)4F9^HZhhxQ+V z+gzubQFl8CU22}G1y7UM2eXyN$|7nXR>R#IY}srD?`vdGS2!m-TZllNR1v5LHB=hN z;{g(59T$X=-PvUM3088B(n~j-4V$A(@iVLesGIqMJ(!~;nx_E|*O}Igt}xtWwMCFq zRmn%2*w<1!FK0|7y3Z;>_rv)b=K{L%!`r|%h)R;Na}Ai{SS?82Qi?ddqLdOOt%ZIq zq|7Ji9u>fd#h*z{Nir5Auumi@ovgDRAP)bo5UDGXX1Eo(U8V)#F_{)N?2&0<^{13h zofGE8+M1OBpA5;OLikWN!mJF^mtW0t%t~c*U$R`ZM*)(D2%tIKfP!oeEnIw2;CabE z?BWa0g}$(TyX0rO`2SGRf-g$9Nd9zC<89+A2ODU?{YW+{MX4Xl(9IkUrJ`< z?VvXElyc@4f-kn_#OV?r^^=l!c&~RD#t}F(@PZX&&id(8kwQFJ0N>$ZQe0!iSswGd zQdA1$@~Tom!sl{7nN5`DU~^)bm)7U0gj9$BLEIBtP5Dt|htvhsg&@u_q^TJn3l_(xrQGM2|melr*U zBNv~X2** zNV!gu7_Ye?@gE_~uhP#&`pLmh-aJ6&&BhNuLbUz(k)C-`Z1+GB*LHardNI2ufmLSu zT}D3^{5Xpd{dB&N&gDOQFsP03(Nz9Tr>tl;J%ve5l4vRI3Xw$Ph$uu7?ZI575KS}z zRgm~A9QZT(c|t!A>E}24xr-m^m1q&Smcemhn4P$1@I%X?(8RAHNq6)r!2_vqnxeb| z3dAl7vRTp(6MdDS-*8-$w7o<#Yc$;UrdjX}qV2Ou{jXhiEGBXf4R*JSEY6-$AUp7+ zg46>ke04Rsg(Md)=zc`^*NW1L_a>T7qus#MhTqea+WPv-S($~(Zhc%JJF`%E(RzO% zIshidy>U5nSfuF=Wc@!3@;S=-Z=81^OP3Q@tu4qJag!Z4AeRNQmy493_lA>0sVCaN z3$LV-CTkj{Qkvlr3thiC(6dD-h@q1a)Y?kcq^xbMLDRuE@s+zGa(b){IQ z!=h}19=F)3m(MN_C$Ww4>O<(^uklze#x}Pgrs4fKTfHkd4LBOO^I4Q1=+5tBb(bjN z75`m~sRJ!Le2f`%8z0Osd^`S0qv9AgVTlrEKH$lJThh%BTY}b9aL!@(t(t;~3_zYu zN&2g;uV2w$QJ;gz)+wI4XFPQOfUl(cZFK5eT~Y%$@}a*NuHMT-Nar8Kczi3`a>p^* zRKgn7L$ZG3teRCRSMgJd6YfM|LLds+wMgdqOxDG!G$=Cwg&spUhLE&k7fZJ)ZQfs3 zj!MWBcezkeC9xyJtEzN|{I$cMmXSR%MXry4)k(jDQ#}BVrk*e%4;;f~610W-fg0!A z{w{W_R;v1z_Vuj36c)Qw3Hl%n72>>$cLWnYaTh<{ayQOu6V>NH)UpkUXpdBVKjNl4 zRo|2SsJ=e*-o3lbIw%v3VJthc6gRuZmGUg?R+?-p)(mQoCQG>xyqNwFadH;jP85wE zj{DBeqPvQs&jiS#TcT)M&)tKHK86>k9sZ88j?!q4)bDVk&$^Jx(0X?j5$c1%1S*EOcf$JBBh)iG5a^P8rE)^QdYnFH`aTZGC@BfjaA&Q zgcP+J=9FfCE>p(V_bV-0*uDFZ4fzs1rQvU~abI0Gb?Ca|6FatOoAM3ILH+iP8F)NfxrMrWOFk-o7n$X?H)~rzKc8I_k3l(vg|C^%2<528lol8$_ zA5hg3`5(zYjebDQlzVA;ZihT=O;2}?uQ7ZXv4#>|O89>D9&55vsn&5lB*4z$V+uj{ zkkC1Nq=cd_CZNYl`OCbC3;Hr|JCChfsZ>{X`v~ks_n3Vp-u0#p1F-cQ2B3Ok;f5iX zy@gnbI^NC@ZeVp*DM98>eckbU529jeY`g_8<=I3Slm!&RS*SG%;q#KzT;Fn^-DvOD z6Vy@B*6_i*LBdwiQ~5@imp1}!83v%&HVhO!XcGD<*2vw!vl#+L@+od*vYb_lPl>6; zomSF(NozRb>gcPW;KiP2C~;L5VLGO@>YgwbzFmk(E113+4*IEV zz#1hOw+GB?l)%>46zO|wux`pUO6+o5^Zn#8r|*pn!uNg@M&|?bJqo+NMyX-$2XXS* zx&uD{U>b^#Hx$jnGK|r4|Kl(gJj94%&L^WtpIDhjk*9u^Mf%hiMbdJy%|%6#-vUJz zc?D}cpx~8Urr~M4p>2(BW~vh%Ve-!6MNrzCIV@$367az_Zx5Gbnq1QG4JVJfT$1?x zT=-Z+vIh!Y$sGkgM%>bvbX577$cTKR5?ztlZTvOjU-qb0uxt}*PFjYE=<}j1{bQwaXm5z~Tc4xl z{=$>Ch&b*l3L~3;;)i~lTmx}kUzA<>SgG0I$EgS>o}XL*Z8XVf`}rOLU7P;zCK35M z@f=ojozg>p^hY*(9pW2p`G$WzN4-{#N{IzRZ z$X9u6q*TnUJ0!jq2G@KfFIEcFD>*{k-x2DD>mT4 zbEPC1dRoV|i)6Hz{5!=J`oECWLZ8Kc*`SycOF$4W5nDT&EPl{?LkqklHeOlR>MoYF z5Gidz##z~uu8^oEQB6uD-EoHe8bZF;i=F&LXkrcqTi=@J3vo$V(k&q)s%YXO{@h81nkE*1`vDUG}<;luKClXvazYXYyk$kims=H++eNjx{Dxcnq0P*B)Mr(`nud>?6$6iD`>!jP8xl4*VOT zEmpFQXvPts@kq%pC;4rNUsi@5n?rc<%9B{_&y=tR6?aom(RgT?prIs+q83q!p4j@l z-9o#SV?k0P?O{vjm>u8#y9=J>`4C~)feWI_PLHB6+AvqZp{t{)$&(hEZMEV%v zdRb7jQ{?(d+ld?+*C&D#v!EbpF3f%Qg3a8ngjYEK0?O{n!$d8q3fN!1V8^%PWw_aq0gO@cXC)%>GjU)^P0fn`fx$eY{zZ`8@h9yBH-a%wC-tV<#yp+q)K-7{x0P2|HYQt z%!ZTLVa+h$FXp#fsU1*sB;5fNr*libHL4Q$IR9s}Zo8Fm@5f(L?vJn8{N2h2{WEEu z75Zr#^wV`BP5rbCWW-c$9=-x9N5JQQh<+LbXjea-3}0fg$VP_z%jpKSCVWkThrL#ScMeY`N%9|JVnYc;ginNQiXJxUFI z!c$gZuVSn47)>6HZO89u`Cf9hr2{0ske%AAH1T}`puA@kJS;iDC5?Wc60VPa!s_i) zs+Rd^0QJg+y)hcd1)cZUfPG5!hIc?WS(CRxCzJ0P2)F!b^0##;$mp=2(2?Mp2rCUI z9lqywAA?vX7e~CKw#3;DiEKc_l#y!W$hreKtAf#_Jkd^#WF`3T{qB0s|i3F}T zXnPF0w!l4mdW+9ASm$MJmv4zl!DS=e*KP*_-OukXjt}?>p{#9lmYl*ZFnGb$WL$sa zVURtHE_Rwg6pLB?=)$uy($NLk?IRCR9ZXKaLmT$}mr6r(3z$j*jc^oSB-4BjrT~zK zhfq+QCEw9fUKFPDEg(29@C+dGbo@wq4qv7TR^l_MVA?7M4_C-cxwiGPqyqFZ-$oIA zz`G4*sb4AG+qFR!^wXH|(W`pji|h%y<3ZaA9Sua~@!Et$7o|6)a1Jjo#l$t{NmgM$ z-uzw8O#78;0r6C;makD$7-CHJGjJKV2M4f}{Yp92^pYHU%sUKZ8}=&|{Hu_sB>6k9 z2}D}JSY{x*uwM!4`V+CN2b#eU**e;h5#rTCNu-N<`f$aBeq@}u#YPoixpTBG*}R!7 zw9?D+rXn*qmUOl_pmeD2C+u!n$&~uwDW5<^YL}LlEG&vfD_hStA5h}MbNfRZss(ob zMsrABy3&Ulj)RHzK+~WVEbySxgWlOasMKm|N`&t)QF{~q0A1_4UPEk64&keaG`?1a zAkq|NJSF<(AYMc1D~iFMsje&JJf06R$1gPVqu^f`#Xfbk=4Xihp<|r$h7TF!7|eGP z8D^iQGC!ab-yx!y_(~kOjeZv6M{4Ep3lunXsJld~Oc%S-ptFRgEnO1U4q)a(IPEqA z{eO9Pgi2}v5XTu0Xr)k1xm>|;_?ta6y z8Vz@b9v>O1pgQRyIpptij>+8zY}sLDec1cGq>HbP z{fo(;q0xhPPV%%Ki`VY2V_j+$_h+MyD6PxvgO_;SEfselaP;%JhHsQzQ5rGF5v7#w zNA}MVCA^IB9hp9w%*dm#A^c~Sb`)pjbk_J7UP2$w`X9qNJFFL5cMP+1LofFAF{P94 zBJ(@0)C|nV=x4H|Wx@_>ea|n$PMcxSPptECym?l0$=-JALW z)-U;c^`lu+L9mQ;J9I=M&0febFB5!b!Y}QMxC4bW$0nEcuNJ z%f^O|eNQRP@Gad{r<4%=fxGO;DZJ#o)x zhfKoR+ucPvNmII`b4YNEpw9A^!r6ScTZ*rdX=vN|5=vVVH^MD?0>K8KR%-Yk?LkDk zjhW8lAb`C;B73m4r;Q-qmTn>2H_cQd}4?8CLwP^#x#)-nrm z=*gyJDMKs#(G~sGHme!fz6}|6{$f0dSXf7jxw4GN&+R;>Ok0w3>fyxWi^fr6w?Ik^()I-|N^VqX9 z%KOTsI4aUm^ENY`#r&aN7q;ju=F5kFW(UtI*UCHI2YRuBw9lw{SWj%D)>@=1+jLH; zQ)J^U1Kao3yEc1uPKhXXIv1ULKWp%{GFD%{6I=JS(!S2qPI5&31xK7H&`W8Y%|TA= z_Wy=Hb~zN?FBeaiOJpm<%GqEs1s{~t+5gfP_v&0`soBc=#Y67^-Z!3I%T@*#ooUi} zu?cqU$FiOEw_|6z{cHstCa`QeOlGg`%17pouw86EAcwDX-jtlV&qLud55^DZ81Ef} zB!3$(PyAloAX#VudwCHewda4~KvI@mhcEw4%UI>|uV=_&;1Um;PTzsi7>nuyq9iF; z<+H6TEcm?Ayw0?aG9IIGbbs)o3v%y%@M1h$a9*k1`s8)@m~=*_F(x(Jp^Zsf7gI~5 zA*s?;F(kD|xvn88^E!KU9(DO}JFEPS5~{Crora{!Y~VLaSLFmQOfeHXxt$&UMrowW zWR7pJ=zKS;^sQ2%K{H}D&!mg0q~e9HNw!b}sD%o|T&;|R_cJ0~@g;Xf zV|t7fS~@HpwoSA2`-M(RJ6;x+P6UR_(sMtstZ%VW(((iQ?OP?e@*31HCY>b+$J#EU zlf7s@Od(SK_iX9~rDo~NxXc5~WU{nFiB+<`XS*&aebglgB~GmQ{1NlJs08Zzvha(T zD+YhhI$uHV#U#QLh-zr87NN~ zYp)7NlkleV^gu6hPjihR(L}lA7Hy(DYXR9xw+fzI6ut+-J=a8e92C5KS(WdUkfwD= z*=jugIWBEmV?%z&rI>~)Q;LMi5-bJ54E)63cLKBQa)fI(cP19v5b}LI1Of$XUyD<1^WX?bxif@V+kW@4gX+zeE&VE&TmUti=_jz5X#? z9loNJ54rvwEU_I6#wp%p`zitlz6bK2r~U8Q(JQ#?KI1#~{E9N(PlZNyit{?~%|$l( zs?sj>(8d4Yq_-=Kv;S9`J~U=N-z(nXy&!K*rz>9T;-&N(6}^eB`C8*Qrr~Km?ME8t z4G|YvtM8TSbxJ}UQ;?XHzkExlj3Q&LQTZ33FfACZs<^dRNZQpIbVWCG<00$!STUww z^6jDfKPbLBeRK=<{2Jb2Z}tsqdL74?=h?LDc#-hO^Nd|r8v2E`q82r0FEwZbuh@#+ zxUPh^d=lkJy+=9p(OQt@14zLZ_2(rz80($J^lNH)Y;3h?06-jS|z(2JsKfEpJW6@o)jFums{F5!;hB_g_{vb2QzcEdk* zIEX)rPJNJYzX|6}-?U=5)}Uv zu|sHhzepN}=a$RtEcm7p-tW?T@*J3jqx&4V2|4FEFy1;slraa`;sYDM34Pja_pfr?}`gZo&O>DOGjg8IDRr($JQ)|_m8%@nHx0tD?7J`vr?$Uid~09%yS;C3{~`HBypX7UlYSoKr%%1y?{!`f=!(|c_b2YkhuDnZ|^JErNVz7hJ>lXfBAO9 zmmYfij71JypI=|l$De!qtde{Oi!)_myS3sGai!jL*<#MG(2yQwIa7 zT{Dzy=mQEJU)jl$(lM9$kqDW33K?XJHDD)ySJvumtoH*YLI2+0Y|jJ5ztr>;(kq>C z15*BH+0_S1Ba;a7}!_Gu5iX;tWjNk|PxEc^_UvIX#PsFWU_avM>N`j))Tbv~yDIvOHtl=Xiu;#~)1n@Ojf=Vu(aCe%| z2;W|zm1ha?;dd!MwX)cRM@qxsKW4fEv@J^u&@E^sQWy=_*y%?~xpH6Bk|8;bfepNv z9G()+);>{cmxgE`ie4hc6ZDO&#$)A(-un-B=P{n{Jahz+`IlJc6D3mjg#GYD>DjFM zAu%4;+=}sdx)@@8s)EGz(8F+DM6!z$J43R)i2duKAy1VFy5`?v0}nE%Y+ezssl(u) zY@j8Pr3U)NaT$%ne4MbObPQPJjXY!14&yA=OpXE4tC_F(z5iSUoe#TmQIVq*o^PA z`+Uoa}(X)K~ke^BrWaTckY*rvDGgo_pi z66+qj@DEJ4vxooSI%pFs`C6$Hv@2Ic^D)}OBUIw}jnfR&a5f@lRieLQ39prgflbGY zMvz5)=ge0qF%)qA5nr(luaz1;t3Uzg95f7e_T6ixa@jQ%WDD4VzyU2L1c?!#7^X((^EYLzQA70&H)fnrTj&!uX1r35)&BzJPA!8l zgf`ks;piPY6=89BQ|jCSqWF#92=*UH7;hc=MW>E5=o@Tf?-y5F>zjVhmKIl=RSZM} z^sRr^9&;MoZ?yKE?~S*0AaC&6%I*|bYlh`)0YB`rgLZhjN{mAwI7%YrzY8f)Pb2NK z_E24Kb)gOyN?$2z9o>AEtEf%&?=cpj!iyOU-y~4O^`$adiV8nEu3=kMI5Kt(`&Cu9 zRO&~V0V#9RbxEZW{hV!Z3~u-tYF=WWl~9fNG|EpU)Ec@yhu)S@D;3cVWEFkY4!S{X zu&+A3-rpNUT=97VY-}py8bHdoK>ypf{~3fJjn;_bXh0kDQ)}rKvHE^$gJxz(MElmy zw#Qr`?mTV0)Y|x=P>+?^Iax-YmmUP1W~U&MKBxdqqX2qp4fsH=>0%6Ul5- z_;;qP$NJQ_)IWZf>~tho$YiGPvYJ>Dh1u4|bhf*+Iz;!wq0(j4$vWM0wxFzP)}3HQ z%c&hBR^a^?-}+~ySqqSH1mK)G7718{g+%c`z+LnKKQC)zG^7vMta56M&mT+4oES1E zZz;Q2POYj>v9ibI)aLpaD+~8m+v^LKunGQZnEu)l*08)sYa(f!O6R zyTZs-k=RL+ja9N{;?E$yzvP!8euCt0k?MVkU0$+jy)JJm*^4AQlGp)~O^c^_fMk!7 z?8?OMBiZ$co&QF77Ax3gjsMOCf1TvhB5Qv0(6~S~wy2rWC9lPhDXx4C;WAfS=2pqv zYMBd_xtcP!T;|ecuA0nMmpQA&6p`OTL}3{V%vPSxQc43^X9nJ zSh`kfOeBp(n#MRyLlD1XudAp*BgdjlylyrQhoq|XLadLy*oYA$TH~UrE3s0ui<&IP z7$hC#>5|@-kxr0Z|AISKV?EH(@P!Rz@MuxIG?Z);yomt zh$O}T_){RB2b~b5cYO>74N>Mxr>%o5?A-w|+)JRP$!_V=#bDB6cNuf2j0(bPp;yji zIm6RrIYUS+(l~TJThS1fMZ2|{l2#dL(bJw7DP0{jDrc#PU?0PPWAdE2QCd?1Y&`3Q zH6`FRS@9=bZc9&pN7Csj$@4#-3Qy^3pD-7I){TOvYM|m~W~A{D_=Q2&yFpiafRa34 zFF|LLNThMrJn2>+w?=2uaJrQ!rCWeTCM*U5Supjebc^nz0;LAng)LCrcyCkOZi)M@ zip2aKbizz=x>a2R{X&9X0>3cm0Z_D9?eG94d4AxDh}B9Gi8QWCVVA0-wkEkXMtEq5 zmN`jkbOznC#+DqBuI-gpya#q+E2_9L%XnatJg*`#|1nF<1gbKMS5pmizXbgi{KBB; zLD5`0iKMd`kUT&CSh%)@L?Vq_&9cVQ+!`}U!&ze%DLn!-&l=l&Sh{ssTF?dT!WJ0a zc(n;n>%=6_jS{nrhUu!Y&Kl@(3Ho~OyP)?#(cHR@q!X0n`MpQNt&=1YX*@Mo*4TQt z#!3$jQDf_+#!S#XT|0P4x^`MxF%;~=uEn}BTX|rTJdc%_H8o5#`9?K1L<2o1L4E%N z`h1SdwTDR3nsFNRNS;6c!waK%4w7Otw;D&0aHMfG4z9sdW5v61+RibU5hHpUYs(;Q zz6(GYBx+0bI;8F%%j6g%MiIpg@^&`GSz?d`A1yI{2OTCnz`+AUHc)}_H3^vIP;}G- zghY6#1lbI_J?(_&Gki?nnfkRsSwnI47mqa3s{YblzLuLeDSkv4M zQr`nac(hi6l$Rg@g+V+#dOpi#$U`KZQ4tO$NQ|qX+r>-K!uUwD@jNK+dbG<8wAlmb zGI``9ftHX+q|r*+ZjTm2LUU-e8)JY621)Sq4@Fcu%tHMGq+?+a*(60&Lfs%0JwQl; zr%4b!==QW9K#?99U(NilL$^WGs^}_`&dB`m)(oI133QZXB8|sL-R%)ofacL!H^?#% z5K%?1@`OiICC0SE7#>xW;Kt}g7-p^IiaI(VK^lQ!7f&`dkHQJ=okr*52GSFdhI4}= zGhX66oFN=?;NanqM-`o)LGhO@_avm8Afk@KCCC;Dvb8XXM-`=!fW(;Tfg!5sH!KZ9 zcOC+|J?&?A74;!B;gA`#DymV zbmf7FK{m4Xw7c#YoB-k7O5FsS7RxnAIw}GujcB$E(&T9pYbHqcwEHfs7~>>R-UUk_ zMOiVOkQ89eiDt_nRo+~J)hAM;4pVOo(&wm%LJVe&>d{k0(bL z1#k?}Y#F4(V*}ERnsxkD;Pw$x~@bF4%=F z`5F|>lA}lpOSTiumO(mv`)?w28$dD>upAQl7|!gGE;NyEk;D^%{hi6jzfoHSVuFlt)@wyca)gVCQ3{?CkVHOX`m4j^fvf~LC=DsRr_HiouDMo z|IQU|Z6uLM5T+SU{0fq0=?x^EmXZRWB0*1sZcpnCitrA1=m^Gnj?p~+*o~9!fkRrn zj>MS`x~Ic&X3e_((z?L}Tgc;%ZqSw5lP8H)wwkP*UJ8ZwimwYoN|L z4%9%WOVE0SKnb&~8`IkZljQlQ67%V3Ox5javq924egMk59)Am(=J9DHoo$7*cs~iW z4Rp^sp8cutI3@*scr`(~+$GCqx&bGQru&j*#mJD5!hIxMUk%P#yY)4=1%T`DAGo@1 zxN1l`(H8JiH$>!0f@qIfta(_B^ga5mnMSdPqg*cLBI$&{j_(rU8xTGH+pt;sw-g2) z1h24v>)dE7JkXZ%1`=(yh9=zWgb}K%2A2-FQU8JK?1pPca3y85()qm~g@0R!kDJJ& z#A&ionp=D(iPNRuXe4NEwq9xsLDn8apW`6K{!G_WE!((_ThB;(_(Voj2+?c_t9dV} zSXn4$YKrBBV!eMxx`OuvNuhX$X!vq5FD(_XQyzCDM#@w90k_HyQXw1HB0)A@ye_<2 ziL7VHA8wQ(Uk8}E;1v$}Fi^BM+6PHtgNbOigmrwDL~MgRA*Q_TChrGlYLFWNSr^R0 zkYz|+p1z7BDIi~r5Re;rxP*L6`AFlF5z?)Tl$9!HkQ8q1LxSA;?V50F2eM>@^Qv2~ z+3*=))__;ohS{KKfRm6EHVh(~E#Wi1N+R|~o)8z%jXpZx?fpbXY8xQigIO4|HmOS+ zt0IZF$81rhh-ORJ#-jmgkNHBo%>HCJ$dSg4IJkO@##%rui82F8Vb~}$5p4-y@Yg>G zUk4#?kJ+oWmcGb&zzB&}q#<gAPsVmdB?e0|f# zSzs$Q6fat|YK4UnovyNp ztyE*z;#7de;7tdsVXe#whirx^AJgJJF{{HOPJ_&esi)1rJ`y~d^$JT)4H1@PvgfVT ztNN&k?0joA9v_ttXrp%2ZD9l3s7bn4?1wh$NptlK2p_5M5P(0zbIQgwQ$?1#RcxDHp&K>)}ht^_#Y0gyugnG)dhfyD*F zmVqp=ty-_i1h>jCQo(GV%ETAj&+yhz0esA7SgsVa5oN@DhNVBznQTT|)fl;jG;vSG=f~|Sz9iTJ(8mNPT1m6~a znd?Yx-Z?rkxi?p07uu;6^o#nl2kq2a`a6S|UwgH6>3SEuyr@DC;AJveg|V9rYOhu> zUjZ&08Hblt&?1P3T<4J@$3@Pkv*6jDQqGd*jh2daFDr(Th|Q037O~F}h;t<|LK00v z(lAyK#haM8kO0kxLSQrWB|6J*uLkK`4P${ZYBhcJVfe-xj>U$t0WoTr?pL-bMh(}U zX8Y){p52O3tNU5R8^3APK!Fn1oB4E5tNBiqR57`>8P>6=4r)~_1xxIpR;cu$;Kl64 zFeu8#2-FoeSPh8-*oqEnqtbnb2!C^69Jao(8HTeTI;gdLC-tFfFkG#RgUz2awWC@W zuQk1ggRdfZK4SzgYA3$O2Hqt0Nk_Gz@4DVX@83E?Zz#UUrqPfiRx7~yf9T~khr}~aInPq%9BWomk4-+Cozo1 zTM#Wh*>ACGXyg!2X5E1U2W|v2-c8JI6G7sUo|GQ=R%TRqPZrZjtz#}X2z7^#rJ<1? zjVlgpn@h7e`Vo$iiOY19?bl{8t8Ye2PqlL3tdA%0^9n&8g9N%)aKbqYV!fXW|Z^P zQQeYw*JO{O3L+Pe3AKc~yv)~Pw}P?A$5|$Z@iT9*72HcS>Nx8}ynk-|0F1+^6R*5q zsB6;u;4gU=Dg=-Fd2!gnTjA%OcdA&IIJNkwASwgzJf#kXWQ<*#k&(x*-(Vb1>#`v z>cLLNsg3n#yR*OJ)R6cLK~A{A|2QjN!-z*d&N4j=Z)CI;$*9ISU?ex(stKE#o_#|j zTgMOZQiK(kHwJ&VyxN@&i&ujar`suJK3N?xf=3{DnO#KtI3A1kak-e_Wq`ypA$KaL zD2(qQHTHCXrKwqXGt!$jGKUH4Q4T(uEneiHxcJv(_u|!-9ly_(5v?E;qqgC-V7M4U zB}^|aN>~RH_mXUjWE+S*L$VhW`yLzCS*`C|eJM?9Yj-b)C2{`#OWCH*YUNTdtwih- z^9<)N!*2F%XEj1OB51u0$*gD>wZ1xE%eETCT6R$r^fjz(RTs5~a&ZZ%_BL49Q-~>R zwCo40R#!D<_^__1IsDo2g*BPNYaQ=7lQu&blJs_mJT#&Mz8t#&d#2c`EZFEnF?aH?J9)sJ z{L-D=<4mIbFPv$+VY@rI#hJ841*~&A5-qK|+Sz5*C3o_IJ9*xn%yuWwx|64!$q2jQ zgfne79CarTxs&_d$$g#KiVsk0Uz`!)sp2%!Zm8%^2Dp>u+{x1JWGQ#Dggfc&P8!@v zy*pXpvcqh6<4ofm_OUZO0n#dyTP4PwZ0k<8b|+i9lg-`9rtV~8cQV42tYE0;N*ijs zleO?6!R~4WcaD6i+;>(YAZg^7tX8hiWWReh03uOxBpo zoop?e;dr={tw}SU!Fu$l_A-fCEtXz<>* zwr>^I$+{nYa20Mdjvyp&9MbA!Oj z?H4^DG4ItOX)XyoLOPC4^!z3y_z(1R2|t=U-wF0h9wv&#HXyUS`B6>fXR(O}zGsz~ zcMKYGOAT!I7~O-l@2}Rav3Wni;3^0!ec$r=$cfka9wc9nx6T!>_zhsI`m0UCVh@rH z1-v_sChM#)`A%qAU{WO85f!ynZu^pS_yd;QhsHGcZi)41^Nt zHKuOMeNaoawdA9O3OxRFR8RCDD8#(T;66VA~4QYlQ(z2D6Pyl zpcg7_EL%8G4XWHyAiZOEE74~Tu+sz8a*1Q!)hq!5?ED>d$M1>i6kCAbMHNuhv;dpB zmA1j>-)J6T{4AI-OMd-3?>w~VU3%XGtl=QFb=_<-!qq6YAm!D_w0ebNm6l8&k8ywUQb z2PWoy#Ci=@JCvwJs@MR)&KcV_SgqP(xF$Y99PPXz9kEHk?|9_S^F@uH^NuaJm~_qY zNNjP#D?tKVAzfvhO9n@Bb7D>pBPjFt2C2MNC2KE zK-3rSM>d`2We6~sO&YA$En~-hLWxQSV?U>6Y|mgdu*GxeldlEhXy;jUbXvO74g+Lgh)7e9QWd)(=OIUsR#8269=}&Ymi%9kbtL33?9XB9@e%>Mosf&z;o)j< znI3zj+XKYW&V%=`hr`v1Rc`D8J@srG@;KXBKd(vq1pks%8=>AT`Hjot7Q0!ok!pD9 zf1yN%6^9)8dY-izsg|pa>f5N{d9Ne13yM^3{G%vZ0 zEgYvdtms2}7*oaV01Iv6gEtAS$%pwfc4eGev&Nrr6}b6mjrp+!7m_(`5=PZ{gBY|+ z6~@i+YDBTU)c`%5bseup>L+bwe)CoTswY1cR@2Ub;z>E-mV%yaw{uz6c#JoT*iYlt ziuL*DR3TrZLLMVzWiU#~@0;F-Jg)>Rj#ZOR#Svl6`cR#r`-nwOP&?~R zu_Y7K%DUcc>jX6-tn_Cv6*CLkUB@8P!Pc0RXiEpY)gQk&*3vnU&cF{@!2~q`Uylr! zh_~BnZDP?A(F}iM!zQYY!@5yu1jbt)Bh&9<(2l&d1xEQYBq2^p&ysgx_Z#tO_Bs1w zqMBUwWAN}jAaQ%l+8kqjp;mjIrA$&Q>MpR4C!ugFJ2^?MX?(L@1frR2p6*yb+YHa^ z2t2W$p2_NNqBmHD$*Qp_Z6iyKvkv{#boAj|<7_z=y4$!c)J zmabyaMq9b!-v^o$|5=1&i{fR_kbc_)OOCT^lhuhn?;{;I_Mc{S=G@6Xn4(rs{0>O5 zmJVpO=K|%42Ade2jkUItQ<+3)&~W{!INJHgI2JzT{~c?2i&jh_F1nG5e<|;WP4VVa z*_A13i0%@5F-09vY3auldF>>Qd6bxZh?DqyHX%v%Z*dgrqHN4G#ayEw2sno~#ep}J zOzJNYpGcdQfF!qhW~GLxEzA7%k+VOv00)&kQJxxf$dO(N0b%DCKau13XOM?M6KU*q zof>um9H$11m&t1F!Gn>*oOMb3T`_u*mo$K-!xd~U=n*qM5|R|wNjRdecw-!Cc7?Mr zzT<+7<7WWzFiQQ%b2q@HO#HC)N{T-#JyjiqFioDSR<9Jd763S1#y0;s`AMub$ThOH z+rair#f!o9*RrQm)pAvT&h!v;bT^It3!66LA_KcseZ%oB#TwJp*1F%>&}nLI-9EN# zn!30A4%m$gogD3wNM7)~Vm+p-jdVlUqUq{qx-3?Ah8pzV<~1bQ4};iO!Qvir_jC&3 zb|Tvj(3%y>PNxWp3xfknHbVO#|P8Yp!y-8V@R9$?vPd#2JlGy zcn)b#n0}UO49{8(tPl8Vil=)>YtB<8fj`iW+u5L5YPos~Ada|C6vMuINV7nSy3|fh z`coYFQ?_ZAT6Um`XsP*kF$GErwidyt6-*5TGbjWljQ`1;&I}eHPw5f{z01xki99wT zK^5{wgn};CQXJ266-UQoo|Vb&eWaFUHD{}3P>s!Jt6g+!m}Ry)$kdK%9vbmD(u@wX z9i-6Es1r6Nw|_=I;zf!5BR>W+WS7M@=oXLeip#h;cy;?5Hgb;IxAGi_5ooeFio2fP zs3go%U#^jsJe{M~E&0s~(8TmJkg>42YG~ zdb-uneUq z%%M&?D?U%{+xV7{!sdCCVR_y}9eHKv_EplmQ{pIh*XsrW?5@{l6?-^Ct;)Wdr&cc( z>Vr1^6T3A}ZH(uBzG&jj8URphgZeo{k?xh2{rAjtY(>$7DW7lK4IC zcvCxm%sxz2Lptw(!(?zTBx?wSGKYb7UDtJ_iXk;HLco%r6|D` zSGf&FdiZ{=7)nW>Gj8$KxPOLhH%*3a7X{@uhe)rVBf$dv_Q~xG*VG)q~ zuosKe`jLmNUS6GS?HdE_EdKMGCFglewsxaj{u_Q=sO|~CN;Yt@YHZSqxH$KR(N7Ib z0P0BsUxA~T0APw*1En}RKmsx6ZI`l~#cJi|`AbC6bzDVjlYn2L#mhpQVrwKcTh!(^ zFys@U@Tpd>j4nzbGg{O{eWN98wMFfr!v_>B>Lq=Ig&kd@4p1&HCYy3GVW?)sRCIe0 zwCVw@msM?1d8`FiSzC$NT*m1TW42gAi1XoQw#%w^4nMyLwu;8xoMcnKZWC*rsx@we zfuw0>UpI|~ELDS>WI{aF*1ig=+ia2YJ&U*jj; zY_$_FB?)}8c5JekU0zUchd{$&I)|qM zgCa9BjgHxII&o%?1r=8{5TNakOA8rsGq0pzk`+G!~3;Q_C^h&`^L1h7*! zwUoX~8oOjuL(Se46-zOilf~-J#apnYlx@xpQNRmcuz=!GleS-`lXLjrFq|F~^&ua0 zaA$zHoAVH2n4I_(E*)1IM28Q*!wPXzQJfJ@wLx#^>10`IKAW;ktvVN|k&0wn@oKWsbFKqhbc?OGnx3 zl|cSx-v35!mdfH*srAcvQ^{G6^e*J#Z0Rbsj{ar}0sFJN%hWRX`i|ON-vyhoFtxhA z&V1Hqwfdp%7|U6W2puwu0{&Nos?TFXGSs#KcTz-zu!)IBgx)i=V;P9h_fKH#<+=FT z${uE@Q}xwT*pM}9^Wp>ZK`S?xy;-S-vv1d^J{3YB^}nLDlKr_xZCrK$S>ox+u{q4P zih?x9!XIBoG{r~zkpXt`3 z;#GIu=)FDOM%+i|Fg1>>RJ6-+b!H|jxl@nf}NnFUm+XN$VW{MMn~ z6Q+~3Wm(sC>eM3Fr+KkW_r1%qpVz6?1MWK=+3iNpQ@GgpH5;EKT|8Ro8(6`oY6IPOtiflvYFox8e5NMi+J=na<=;uUy5wr6e`9xXPb5!@d8F% z_^$-h-uK}6^!E4;B7g=9AiN|r4n^n&U=E$Q7r{gmjYr_YnD^)|J7#BlNwb&HP&xKF;es2?6vvBn!X95t7xw9j>{| z9A^GPtz@407v$1VZ?BUBAwL6L@c@+9gH6EcEBQ&2uw-e#A;L8J;!xnPP{wNf$3~}d(}%eYnNKLY_f|VF8PE0WJhnE^X(;K`QLwpgR{yn zQB8;{H zmH|ZV9};0obp)Cst907H>VBm*=o{Xddb|0e$(9t-fMD_m;#7B1=u%mO2TAk;gXu`m zllc&le8M;5D3?~Cl9UaIhFn!~7I#Ms)i$sLU#a!=f39c0eWecYS+U;5&)dW%?pMpz z^`Oi{Dd}=&VdwtBhHf7G!7P10Zs_CF>ig9i#U`&S>Xlr9?e!~Kj#WN@y#xou2`7Fc zCupl2UJu?nz9lEH%;PLyL(YTzkaanr)(z?geN*bAKw)oWy@wxDMHWvEAdx5iS?Pmn z`7+nu7mz<|kWct$EbD+;JMZTn++P-hS=d1}plqUq_`wBHH<7hCs0IbC zE{x&hS^mmIS$=+}cgnZ34F}cQK_P{8*MH<;dv#5B@w>Xcgl^6KB?IJ{ijezhEqioWt!#eJWE~rf{1<}js<%*4Vv*$P6#E?jq~lU5nVv5sq8ZxT zMPQila-70E%#RzS5m#LG@dJ8_;|#t|rbEZV^KcO zP?HGK8JB&}`oO-g-1Z%YkKBSE&AvEmKx~njHwr>`vZA1&bh38?GXFIE_CUHNfx29C{MRSHLN2smqP5g?ww98GJxK!uc_Lbi+K0V8s#a@0yo0dM z-6WdyF5D#YVJ|`*v1-zUlRlttzz>osO91;-LWd)|Mf~C)|KCt&fz4+YaywbkxQ2FF9h!i zKjW6E*Bznx3DF>h>1_E)?0tH6CEIyYt??Klp3V?AQNY;=eLr*J*5UJyCEBA4ZXC2RXweS1iQ-6fbJJDb4JVki_0^RqXG68z$K5cya2(KbB~YSco6CT?%yLF; zGvGD6CC`J25yAh^vJsSR!*6TZHk9qkztgh9tU>&g$j%O#ZnE?bnMf#u_)el>zBLG) zzRNOJ>#REB|9JZru&Ao;?=vGHGr*YvRIUnm#Y+la(KJC((Y)ZLGP3|-lraPZyk>!> zhPT2}7n&8875ZACuPK=+mL-}MT5pk=Vb{TK*lWFLrr&Sxz0L^y|9#K*eb4v&$H((K z>$lckXYaMIXP+~Jc~CMv@s4$zxzl9&;vF=nA2!f`-oYmSClqzu+BWD2GA0(@yFEl! z-mF0MWBSJmfj`B}f zm(|((M7%dlL?v8aN58*oJ&tD;hu*VpDeON)UhD}6zLFqvET`?8Sh3Vkl?+<0Kb!pc zXA_h2!sK(wz~q;~%FC-wP2LbD+YOWVrAdkHiQJ|(rp;7y0#8DZU``g>E~N^^Vr{QH z{lOK6VMVlz*a;#WxL5)CS6kZ@4j1kp!wW=`oXk1MkOY?_T$oG&Beq?(Vug!keet(q z(TwkS9qwF*h`3^K8MolmM8JKkD?;YsS66q*KNRdL8VT!l580De(avgX`@%k0agJZN zvvV0-jeYc|;IV^$vaQ13{Dn)=P_JY4B=VjNGhBP~H=4Evdq;;m+hO8*78AW&Skgog zm)-!m-YK39i89|8CjyMKLg6P)_+5v;hvkZw z6rsMn2<3KI<`ii+oDqcoRYRuZ3KQezb0AXXyyV zG$S`0V)01RwVRVjVO0}Bq~8j;ex@vE+gSH?MWc7+v8fa$G!aC)G?8u;>K1?OE2OjC z)q9X?g5nFCGC*P$y-Hc9tm6}}V|uR-k&0#LiD+Dxz^Fc4$7k`bPjU9+hcetx50v39 ze9IkbCtW{fZ6EQgXb9_Cp-%B0Q@-wC4kBd-Kze z()%A^v-UFierR2aKREZ;ht^GacoO~*2B&vu^hegn=xPLsZ_OV4$=mf5W;IN5nA1`NkMOuVT_)w~h5Sql`TiENe+#*_lCcq@LQwx5 zmTm4LBLm%AB>1w0{__^{M=j)co6CHgfeQb@bpR^-#VTJ!n|}D*8c_J0Bo=&=q;CJS zl=z9TU5sDO&X7@((7MhZ+rhqy7Z+r#k_n3-FX2UCD|h=_kuOa{B5=3_a-R* z*#wIJ%DU8iE}jm5WsPY2YrNU7Xm-Ytih& z<7sJ~^<0~U!^QlGja#|1l8C`_#c;~JXw9(Op-C8g0dL9M7kuVAbjQK-7x7E@=5L15 z@84J(&2OBgU%$n}r@07668^?D)(HdFou$a{tRbyeoW&S+Z6L*eXKgdJ79)z>o5Fg^ zwsMOWKtWvdFLl~{#s@Qb^GVNImK_&Ofvu1lHHO99%|FQe*-s&wl!K+dq z{z#mAzFXjdZ!_c3DgHjzoH%OlvUa-7LnME6S?6f2z$zR^yEIH^XrQUsv3_TEiM*KF zobA@1<{?R+ZAW0RW*IiGU~d>?o27}JQPYU)`yy7jv>gHe=|ei|vPN{P5S*CV zG7XKUGWBKttxOdTBF}njr#9{S31iHQoGEWt@E{smZ;k3b9~x{qJ{o&6_AD9&zR&a_ zE_k#r?8W9HvSM3#XDdjksu%jwx_YbwGH?Y~E8!C4O*Iv*`jzVsJC<$V`Bl|ljO!cW z2&IZS;vouhPKpJHw%}Zwn~Qfh6ESfO2T}BAt2OW1R*pv=R(_FS+jM*=b-RRC8Pk{I zFInR{_HCBP^+l|>pm7EGI37#eE}?TBjHRQOtc3v+uc?+WP@(61%L-T2O!KuWH`t6SG&=@HCE z=3VpXnHp=bd2cKotg&`A&zVPG)!>=y%vkb0gBzf$S@iS2ti4S!wCoISy?15Nuut*G zgGFZ+eR&36vRLk(MeWZ*?iE8r&*F>rKRie&XEF7ke306IhGcz|mo*R4{Y}KN57IMD zM9YKpWfSq_TxyTm($t&N&ZYa6c>ZHtVa!~*`LVU*(EEEgAMU3%myg&GxcW$tYRdc1 zi6X4k@JI!JNW@9^e}X*!bQ>4ZNzVv*56ey`eJSKDmb*Kt{l7#RZ=<3ALP+W}>HdFN zZ!@3orP6QBq*sJ~doQKGHQKE>VC6Fuqk zPw~B_F>_Q1yU(GAWoX&1em1=&P->32Kto{I)KJd9A*63;WA0?Hks7D*S)f zoo;Wier)L#jn(#sVYVX_blE!5yeOLHT((Z=SJw?;*^rDa-OrNNOv8-s-$ld;XBf=n zH2#bmOQTi0|q~KU}uPncwP0QNLN+^=>5`V0FE&Gde&gH2Nmv*s-+c!Q7ih zj^C`w@qIcX4VJ@?HkjVQ7I~4V8~5nwi9g0`R6%SNe!s-FxoXWA?{|1PjSiJczyD^P z8eY~3W7W$z{P@$YP2V>KxxN!+{M$OkCrexhUuLWK??lJ`ZA~y2bvfv9#aeDQFN>g8 z|FDiX$4AihKk#LaRuKmW{|AftP<0i~Op{(bc7_^>#&@9cU*PZdT)kKI>g(-Q_ju!8 z)vFJ)yyRZhtCz7n_+HhkhqhO}_A0*WBYO2u;oQBZ%U<1fPq-Y{H&==H*bY>Q_{7uE zM*M}N6V&&rwb*<|2pzs^4a4gWwO6e#m`&mI@HOjXb4)PRT*E5~^YK@^kiUd58uq94 z4)Ys#(Zhe@ZS7;UUdqwrXbOP{bA&!6uQYY+32Po$)^S$JedT zcQ`ure`9l;p85-4_*gQP-uesgUlvmBU-;ulLxQNo4QsOby(yG?!`k1pk6yfC9cvyv zg&H85`?jO5H}Mk1RhoU%IwZONqjt?YQT>lW&1KKv=JKB{^p~5rO$W*xbhEJF&{a~ocXK*XiMVjW}GorBF z*Z;YB)C<#p*+3-hoHV(cv zDa(fyEE3>~inw~SXxKKKd)Zc2BaOIi7TIzwM{JPi3bT>tPvifDY4R+(;OWz`$NeI? zZPjBaj_p9(J0KMe3liLh5jPj~80u|XT_(J(^Zb)~c=;5xxdV~Gl4j*QU{yDCt)qQj zK9fujk-4=`r_?(zFdXgYi#&;MuDb@dM%RCoEuXFvi3>WrRx~z>ld$(Ze@Ml(etgwc zq*NByXFmv=1>04CyX)}b@tvej;`QdM-yU4m+UJ6~aQ9L%{@|{Cr$uCM*s;-&H?*Dg z#}!dS>!EcmwIc01xIUihn^An%L3Mt^g!32G`8Ax2PW(>O#Zd_=e1_paQTb0({>{q& z&_Z#6P+S0)9xhd$F;97*#=7DtKFG^wzauYeJfp!NOBMkqJat2V!ZXY`UE}F1l*mT? z30w_XktMUJEM|)A{)HwFUm4>3N1PWTi-(pV(HhTt1y?3&dEKzLMkznI`V4XP4FfjA zs6J15ZBj-gUnY`^2xh3X%WtLapwi9|X$Kg=l%~A&@SZ4GC*39%stq1M-+EkF(@4Lmp?#W2QXLmB%c3%$CPodCZf? z0(o30kBjATX`zH=^7x267RloZd0Z)vtL3p+9@om_dUkGth@k37CAj}`K`4@a@GY4053p(RYc+2!6L2mOM5 zJT`j_>>V3J_rKy3`PhJ0eDL4*;DA^B?wo7FBbOlk33H7P1qAD(O~rI?u>OQ8iRy#( zF?h);HblS2XXd4pfxAuB(IE?C`N8r1=2_5u7=E@PY zw1eKk+&F@^b%1s;o$R2`!97rH1Tt1e#{?A7a&sLSVyF^)8tNoc)F-yerNbW?zfQjc!>OQuLV(+ys8DJ>dMLYJeZ z*R9aIF+Je^b?|TV)P$Spfjyj}6dM%r*a@jsp>NZ(1@Cd0R}DK`3SQ1===9)_C_gS;{kUdXA;)7v*iS@^=0~ z-9qyQ>mw~S|Bd2HD9m-gP{$$qKvOW?I|S`2n05`(QvjCRrF8G@xMUpF3n-=~LjlkY z!xdv_?=TdjoURUo4E&Um!oB-$De zGMAzfKtf8;@9AE(2-{}(1hql{n|u!ATtvKTsnUl~T9JMmjUJ&t5AXFO(3pa$`$)Z; zxo!nb8L4+g+g~zLC$u~NL|iPM5&-6^t(2FjXCtx~6OmhVw^8~eOWiZdX%?kL*aAI2 z!GbA*_h z6%WYfw(9{5Ahqw)+7x8Cm_kx9P`W;*A*rZ_GP;9mf z0jcq4vd%(6Z`wKwCB+z)u78C_Y{@{;&?z(Y=OA6pK=e>C4io}|y8{(d( zj}fN*Y`vekrZr8OEeF)Cv(d%MC~6L%oOaEDYs4TEp{9iyZLmjF*OMgv-Yo6Z6(lAx!qmg3g zVgTDd1zQV#+4{tGZ+)X_cDrwD?-GoMozZT@|9G08t#`9bzgs2hO$j-Am}dbrsrXd@ zy&JCKpQCs3JPro)Uk;cgN)rseGK|zX?S_&_2ET-f{<;^8C7AtnqaC@JIsVMi+ajC( zxuTihMMHA*j((ldyBqFOmkprU7xgf&@t|CH(Y9Ru4oft9Siv50_4by<&AHp>gR5@N zJp>NX+BzS#Tzrr6{u;Xw=8es{+w;I>PH$%S5IA$~blR4OY$V;Qbf3|VJUxoc`FcCE z>s|`Y$28t}A1%$-@3vHwDVqu6IuX>EuSZ%cnY&AI@dbLMxu%R}6`(a_(yjtjaWT~w zV4T9JvjF2R+S>xurADTO=u)oPv~8jOVPN?Z<+>Itb@B0j&5s^hq&q~NFVY8D%9ksB zw5Ycrs(D8D@VbKabj5NSu~K#rxDSfgpzQvGN^ z+%{#^1?@sweMCzilC#jshtQ`GhGpnQSmrE4v#5EV%nzem+wq-e{RvCWE|vCe)rt?l zEL!m+`hDicf6&}V^xo#iJ+w_=?JIQZ5mZLyKI&1ZKOnksAv!}e4K9LnbgCkBD$Gnp z`eRWwN0ig&#hrMQPdLs+bHr~rEk~kc+AF|LXO^RTqefN$F#4=O05Adw!0JVjdsko# zs{4R`TYE2M*3uR>zS=Z|wAa-Amfj^m+uSa7=Sl44RufB^y zH=ydK)7TAA7SWCk`e&$!C6DXB!p8Xo!Z)3^J|P?XnI}*nj3gx@yGv2HR3Pxe+NB(^Vm1LGvUoi6t(;T)mG%pTf9kryWm8e;1!Z`BKTW2{wz# zxd|zf=#5PXB}VYg2!HjLbZ9dsDJ*rj=n+ThRHO5xtt+J7ZY}31%s~YLtHoc=4 z{uriK-AHQ%SJz1U1U5^M~ES}MIpMg|PpFe|2w^P)!h*2)h ze^y33W9Q(tXqXr)T) zNtXxP!aY53?N-#|MO1G?H=6RIen(&tTa55yXI(BTXFo?^GWq^ReMDfKT{#(ne2S0v zvWs;4OZqA_jbkri1<=@)#=WeM4or?#c5fMj>6|+1IY%Es-@S|xcQJJ@$EX>{@wcdg zCQq;-lGuDfB+IcFjHV^K^m_v9bmgieukF2aF3`GOD5B?XeQIE_ztT=YI+>xt7wEy= z$WT08-mNbRjP9fKnMUeK7wDmX=&J&?zDhB{COjm_8okI2kifQm(kQ&Y3D@TUC0&?l{UJMtdGflG$YA>KTI(x?$-}uD5~ENH<(vm$FN&FiN?K- zVHZQi>qudzy|0UVs42?bB8m#Mb@CjKKtxmQ0VFG;v;(j!rLB<6pU$8j2lcMha6tDq zyJpa}1DL?B+$YGSICBs_=aQ)su$W>iQIs|_Rk9@%S-rAp)fs9I{@aIIdtv~ ztXwW+D!cV`u}beH?E2E!LvXO~L8W+16?i7vik0>rLaxy2VSxCAya~vp$!~&8qU8b> z(q2K%rTRB93}ShG7_J&Blq)XwN>S`)l;?g>adhS|Jm9zV-jWxI70`~-1hk=z0L!