From 8662919a2bc72aa6d67d93429b22d08caca115f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 13 Mar 2018 14:57:47 +0100 Subject: [PATCH] Primitives: new solid and wireframe cone primitive. --- doc/changelog.dox | 3 +- doc/generated/primitives.cpp | 13 ++ doc/primitives-conesolid.png | Bin 0 -> 18533 bytes doc/primitives-conewireframe.png | Bin 0 -> 3448 bytes src/Magnum/Primitives/CMakeLists.txt | 2 + src/Magnum/Primitives/Capsule.cpp | 2 +- src/Magnum/Primitives/Cone.cpp | 73 +++++++ src/Magnum/Primitives/Cone.h | 100 +++++++++ src/Magnum/Primitives/Cylinder.cpp | 2 +- src/Magnum/Primitives/Cylinder.h | 6 +- .../Primitives/Implementation/Spheroid.cpp | 11 +- .../Primitives/Implementation/Spheroid.h | 2 +- src/Magnum/Primitives/Test/CMakeLists.txt | 2 + src/Magnum/Primitives/Test/ConeTest.cpp | 200 ++++++++++++++++++ src/Magnum/Primitives/Test/CylinderTest.cpp | 3 +- 15 files changed, 407 insertions(+), 12 deletions(-) create mode 100644 doc/primitives-conesolid.png create mode 100644 doc/primitives-conewireframe.png create mode 100644 src/Magnum/Primitives/Cone.cpp create mode 100644 src/Magnum/Primitives/Cone.h create mode 100644 src/Magnum/Primitives/Test/ConeTest.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index 98d0fb02c..da66e21a0 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -53,7 +53,8 @@ See also: @subsubsection changelog-latest-new-primitives Primitives library -- New @ref Primitives::circle3DSolid() and @ref Primitives::circle3DWireframe() +- New @ref Primitives::circle3DSolid(), @ref Primitives::circle3DWireframe(), + @ref Primitives::coneSolid() and @ref Primitives::coneWireframe() primitives @subsection changelog-latest-deprecated Deprecated APIs diff --git a/doc/generated/primitives.cpp b/doc/generated/primitives.cpp index 0d16ac2b7..0b1fee72c 100644 --- a/doc/generated/primitives.cpp +++ b/doc/generated/primitives.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,7 @@ struct PrimitiveVisualizer: Platform::WindowlessApplication { std::pair capsule3DWireframe(); std::pair circle3DWireframe(); std::pair crosshair3D(); + std::pair coneWireframe(); std::pair cubeWireframe(); std::pair cylinderWireframe(); std::pair line3D(); @@ -103,6 +105,7 @@ struct PrimitiveVisualizer: Platform::WindowlessApplication { std::pair capsule3DSolid(); std::pair circle3DSolid(); + std::pair coneSolid(); std::pair cubeSolid(); std::pair cylinderSolid(); std::pair icosphereSolid(); @@ -251,6 +254,7 @@ int PrimitiveVisualizer::exec() { for(auto fun: {&PrimitiveVisualizer::capsule3DWireframe, &PrimitiveVisualizer::circle3DWireframe, &PrimitiveVisualizer::crosshair3D, + &PrimitiveVisualizer::coneWireframe, &PrimitiveVisualizer::cubeWireframe, &PrimitiveVisualizer::cylinderWireframe, &PrimitiveVisualizer::line3D, @@ -330,6 +334,7 @@ int PrimitiveVisualizer::exec() { for(auto fun: {&PrimitiveVisualizer::capsule3DSolid, &PrimitiveVisualizer::circle3DSolid, + &PrimitiveVisualizer::coneSolid, &PrimitiveVisualizer::cubeSolid, &PrimitiveVisualizer::cylinderSolid, &PrimitiveVisualizer::icosphereSolid, @@ -404,6 +409,10 @@ std::pair PrimitiveVisualizer::crosshair3D() { return {Primitives::crosshair3D(), "crosshair3d.png"}; } +std::pair PrimitiveVisualizer::coneWireframe() { + return {Primitives::coneWireframe(32, 1.25f), "conewireframe.png"}; +} + std::pair PrimitiveVisualizer::cubeWireframe() { return {Primitives::cubeWireframe(), "cubewireframe.png"}; } @@ -444,6 +453,10 @@ std::pair PrimitiveVisualizer::circle3DSolid() { return {Primitives::circle3DSolid(16), "circle3dsolid.png"}; } +std::pair PrimitiveVisualizer::coneSolid() { + return {Primitives::coneSolid(1, 12, 1.25f, Primitives::ConeFlag::CapEnd), "conesolid.png"}; +} + std::pair PrimitiveVisualizer::cubeSolid() { return {Primitives::cubeSolid(), "cubesolid.png"}; } diff --git a/doc/primitives-conesolid.png b/doc/primitives-conesolid.png new file mode 100644 index 0000000000000000000000000000000000000000..41a979f1ec03d0055254b4d828990c4c728fa237 GIT binary patch literal 18533 zcmdRVg;$i{_cb#xFbw6;-Q5Zj0@B?eAreD~NO#8!jkHKfDlOe944{P6C`e04gM^Y2 z@_YDv*ZW7jEY_M?i^aY7x%ZxZ_TJ|_ae6wcL@-(y78Vwfx|)&!78VHj3c`Zo0w3m{ z<@Q)uYW(U-@=yHd_w%+)Cg(1@dv1Gw-?-&T)=ZY}j!M|NnSyqd_3N2^2g5(czp|my zuS04WzJ>b22`GO-?kN>R^l(7(fz0?bho8CR6iC(dwj@NZ%tm}H88g!s0}gH$ua<7l z!@wvx=6g__{~tau@E@Iihl2koaKd}I<$)hp;OD260`mny!^q%Z8F3cBk{t8JUj6*! zWFuoaHYYy(9`v6Xzvb4>Q)m~qV;>Y4El(Zb~8v-fjd)Gehxj#2EO2SR# zXi`Ay1b6>Idmf~@_GLIt6BH%(#IeTeWF$6Z&aZskGmjA>HQ3n#iHmn zToy36x8QY{_u-OEdmCzPPJ5lVSDeX)kA+}1g(VNNSRPSER5v|2X?Puvj}yk);j=fr zuOm-ZA2nvqgL3kzI{|g!hHF?KYjT6%Yp^H_-Ys*|L!dIh^U!)`P2&%$eI`9JQ#}Lm zH3Z6yvO&vI<%{?7{x@q({KQCrV9fKaks-Vmdohti6|%o_N)WB~;-5AQc07n@4X;I` z?txG%x)IAe&lMq{Bnv3Jj=UH(T@Ru=8eHWRofP)h* z`#5G|DWfDb6ylA)QQ?`qrYv`h zwr%{Y=WpYwPn18iyb3>m8;sCyfL)O>9SOae4(Bs`>yhAUSF6<_v7WXx6dgol_psmK z?kSiu_f?C?(k#>6nVVr-*t+VKF&NT1so$uVR2$MJ3c)gMx%$F_Evd02-$1J$p7}Tj zW8{2A_?MXP=2UFpy72y4aV4p={?NMphLY>}t$!&puKsj3?*g4qzhe^CPf(ZqW{tA; zje>$_mRBi#?I6**Xhh82t|SjO2z1NijZ1G_N>Mkii8+yJaN|h7ep^RWY4>LiH(#{Z zMN&(;7X7#Z6?Oi^4*Z)5VPgtk(%o3EKzt4SZ|$$XFWf}5L@%v8C6f3RH1vt>r5zu- z=bm#vZ#_}vr>o~8&(YKTb;+;S?s2?0*4znRamXhfM4S7T4yE!~uRi*3jOZvQzl$o8 zHl+?y^JM8k&ntm(ii)-$SZnW{fI;kRoEg-Kj>_>ltqg3Kcm^d)Q<0=Qn}suJxW9-$ z+8iFM*uhNOjAM~38OCT zvmrE#Mp8&I{md~;a9X~Fl+wKB(;0%ZTZnep1P#7hQ|82INq&C3uWE{c!Njhn8%_6b z*a6Gj&|7O&%4+b6=ZB<9pOg=gFYz^g6|K*ziZw+?9(?azKVk)z)F;~*cg=Y>3RUtC z9P!qGB^K!+0o$4WbHZinoypMB&y(bR$Ok?0cjraaD$V&vC9m#t64Fmq`WxBKWh3tZwctJJ)y68hTX#dI^Ds6 z6+QOo=&12UzKzp15uaVzn)G<|R@3y|EA#(0dr(e9r13Onyusg%e1fj`^C5n}%o@#(PhMe_ zNpf%(U2^179baD|oiAorw~-$+pzDVTI#cl>~eD7Q!BgLX>f57XH0!lB{fL(uub zv!p>0pM4esIz-@+W^|CH{lx%ZRog{|0%Gol(^+w;^%YXl{~^m^@m^p2fw&VC>8rM# zr4_ZmO3xoWN#iG7V91d&3GKPV-EP8vUq1$&TX2!p=kz4L`NNqpK=EMom=xH&-VFpS zJMrET?k|O9Cv8w;t=Mbq+<#bWHWX&8g*2q|f3(J_d$jIGWHMJXr?LCraR0#{dM7Bs z*J@(zTBBttaIlUjllp)rx&2&Qhl;m-87la-*~}1mGt8URmHG%fQ#QR6OPdcE(agVu zb_AQo^rBRRr74Ag;>h6c(m%iFzRII$Vd5@u^HB9$CCx!V#dloi4j!YZ8(+mF7q#|j za^ahXTq@qSCwPJ$Fa|h=5MMivUq4=swuZ}+%nBkzYrW)ncMi92FqJ!m^sQMdblJJ% zCc=To6wwtB#O`mcKTqn!@zk+IOR+iuI7?SA`Zq7?9_u*O05)TJXW%ZKk-YJb3Ru_u zzw2~0#)=*0cMFsKyk!?&)pM-itGvWfL0Hwwr6Go}l{S_-bU$VIWD?=!pFU15eA<<3 z2U&DL#%;ZM#9Bx}8d!n8)HAMxY9(^B*5iKLjRNl5t;n^mMdqoW<&Z{?Nbxi(Z9*p6 zrHy+Isg3_D;1VT`xGV`gCC6}reXY}fjfNW_yzCdq3tvLae-@`;oDfC^*tXXXo++bc zE(QD?SxRs>VZh0ThMh&{YcsC@>`l;CReu>-0jeN;7FPcqca(U7jcGQwrcbO*s~@HY zx1MPWWE@%zWz*^|pW?fF;>N@Jlc2aLGaUO*Tlkqzd$ZWJxgQSHrO&wd=a~MX3#MIjghR^Y&|Dvwd zj8&NLjDci#MZM$>p8cMm*Oz~?PRkIP50RNzdeX?UD$qZjXR?6wR|&LWV=l@kq|kb7 zSSAIDG!}kbn)E4fN7=g7zr+&mfR9gsJxK7jo*>0KpimoUGnZ$>LqY5HzkJ_BcRv>0CkIlpozq7^3vG|yH>hJ~o#fr6Fz)W+higAtva0a(I zKN`!4us1zyhgU|-nXw)%{PoO#h`xD{^R`q`)XGsy~0bP0xVxx0%YnqJO<~ zx5&p*$0jbn>vI}Y3#wnN(Vb^Gx%Jv;hKoYVUC`Ur;;|__{@-xcbiX6W%s=?SOwaU& zpRK6HZITxTF=A**Kg3jgeQw5z*vUy!4d!^6=-z00cPF2E@VL-q;b(q2Be-q2#c4&zn+J9Hyx%E7 zi9Cp}43Jt>$^-XTFCRESL=_dfvtiCj>()D&6_hA%{`0c>pDIK&u(nHD`#S>nSf*vI zv_sosM^Tr+Z$RjLUFv%n{jk;cnuBAbX7Gn@x?vjyW>H#=3`MyjQxKMCI61y%uV48E(Yi1@|i zqz$dDy$feaalX+O?EbT$((kxU#o*TES~=X&SGE0|+j%IS!ti$Sw<#d~bYU^$ukR>; zOD5wM`E*NGcoUJ41g+Bi!1WBW^@n`a@;|0&m(bACTF}Fh!nipp>}An=lWXA1@zIa~ zIgeW|1-6fT&W7D3@}wG!5re#OvY>iw%%2|3AFx=(hJ~xB@KE@lU0f2>l)=Au;OcZ; zRMA|At?@3(3Znl3i7exQ@Lfzh-Ayi~=VpCEVdi&2D?c3628f@8P?lnNj%+^6gSMG5PR&uaEnN7I)sxWY|_X;_)hWiJZRrMc_Ekhu) zv*&1+)dI70OEUNAiLEp?Y~H>8N++ZNF0{s0aqfNAC-M}Ms*v|+&hk%3BOT6I_k92!99ZK^{E7cGyvZvg-^bRuCa1g2(^UkHQRbNVrV5A@pW}vYY%uQex?XhJoboQdh zZsJ%4_LfDoSpx_ijM2a8>SwJX@R^IReTn&$2U<_PQleX5d(s5g8PF}W(Q zDTIGN@xe1hvV+sc#g5wG2cqG7kP>~?I^L3O1?Ei|i5PN`&B9Rlk+xg097KMCH@jWo zZg>vj!KKv~BQH;yvZD9hme?~NHn2a+d{=4v&{^&oulx568yNC)D_xzW<@@`P?PRE1 z$eb3C`E#WD4O8&liSB zS2c$*Y)Rvok9U-*x?_FQE07&l!Bsavxmx1#8E zDpL5-yAlj^h}Dp^wi(`oss7m`jYJ-_yM#l(7sVk`=I<^K8p;O%m1FoERV9rVw}}9_ zw(cZo&P>2QOd`HW5@x6Ee&kvK94eapx^L}yh|7y_{lUCF^J8q=X1MnQh!V`hj2l2s zJ}$_aea}BANfXCBVI#G&6`@mf|WDv+^i5WPa$ z%8AK8;0Q=`U-8AKyMuv&`YThdKUgtyC)6_O1y%M9B0q`!G!O|h9MLt#3rOG0h{f>Y zFGqRVyj95`)0N~3C@@)I|XQEBBwwW$nP|QTZ8@PwQnf-q5-)2IRe~v=N;~xiEkLPLN^>qvGpjX&No(K}&wf zXf#T}AJuq5V-1;CP(X2fdslew1rY;59G7d0do*x1$l~sLvc!u!lhEF`hRY_rRnm=r zDIqYg?iuy^uQd&!MSzA2_W1TJvOV>|AJBxIat@Y5HN>Z1J_-I%DLsNP%dcz>iM4G8 zs!q|v)=I3#P=FF0?`IZ*Z8TYmh|kQ)Sefrp+kGyzkf%(`{L??Aeo~h;;BbB_9b)nM zG9E%`K~r5%JaquAI}bn?@y7Cl779t(O0m>&LObV_Sls`}NE8@TyFi*06ew2cFKZOx zS@#TvqRvyy2s%lZ8svf17mm1%$b?jtRj({4Pp26(XvH=BP;>B6*Fh3Yd*Gk_*LSJ<1*Cvhr%C%{Q)`h2OqC zy;Ct_r4v@GI}gIhfNA8CzZ(9X3#7c#kzL=VnA$S846K;I{!Ws7+Gu65nx*(5+P~l#;Zt}?3#dNO8BHN(E~Jcm?b}SbSLAl0 z1-k5)_vPZ9%j8N&R%w<^@)X_#Annz-+o1H}@lq71ow8T$CIo)V;`OKD2|mEqh54u? z)wdS3_GTW&w3nNp4%+ySS6}*wG$uW^9r~lDj&(AJO#H3&_%hs_;)q zGNLp$^SBD1bVK-e*Eb2iLY6zIb$JQUmhOWmIQDshtJVyQf-ax-%xms}daZIoBgRH4 z-6TiP`v;n9zydE^+1uNa(#yPP8l>MV(h1vN3H*%DQ7;D%Vhm2n_3X?gxT{|8*8(~$ zM?^at41kDsHOsrt;v1$Aa&KO+KyoT)#?V}_SWNTXGZ^6#-WPe+yeF}oljvjtA@;H8 z{qf81mm0qT0g{EzJ$p5$rta99BLIEk^_^1}M7FluE);{OE&TWOH@iFi6o|oceet;I z6*`L&el$aY3fTJMKaDl>iguPdQ$R`XXlA|g;74cBf$T8Jf6KpmB*1>A&p0^AEBp*z zF^KC>27Zx0B#-MaV`9T_F(1hzM~c$k;r)Wl3)nBkVygyDVA~s#AfsPO3CadCT)GmX z_w#x25KDI*GHWe8hnhhCJf!mZ@Z*vwPD z*FWY=dx!@xjeiQ)K%#aHikO(Ry=VjOQ*wsLEWLL+UJ0Ukhh8zGc@|0ikpaXe#^uia zF(Z>Vi~jhXrXl*R9R~;c-wf?cls5St5ttv;Hf0K_761o+_{<+t&?vf!y`$~US(fBe zX4$ZbzJK>P#CU9^7ao!E)4cAhHBbYm-T+mwVn$T!92PbMDBi}34cmCh3H_+9E{#V{ zoL*F{BP0I%(R&(ua}ys{tmp}BGereh7MWFyPhlx^ot7L$F;h9Lhu$%1u8JE+G~eB* zC|eKlzX9w1-Ao64Bt@K)lYM!^l{ab?5iju$@B8nlH)AO|`QcPK?Lpn=Z?m?13_M~^ zzs^{^1!p|gGr*ze(eDv-j)XQ9fPtyT4AdNKyrUT5L#|xlm!_Ac^_TVnwQugF>-X;t zd3W{e3;jI|>W zLQ7AD22k~$+zkWBspd@z34fPmypi0ye>{b0%`4#xFiY%r8w0wa%POQ-LRYmqw?*83 zed|1{`%ntBcrBndf6*SQQ*HD7!V5SxS#c?n7_M^$k?g~a2Y^9-YCT)S0>^zbrCw|Hu>?<+ZET2 z;+eK4R_?r=(W`uHQ(y!dx4g2CzvajJFFFcwCp1s7QV>j-g>`(4r1mH|l4md&TUX$!^6x!xxUh9cRBar?= z;pJ{b_@9mYW{=K+D2PZ;WvyGw&Q(Ak%1df076AJ6`tOnH8inZSOU`8?pKoRm!E+gH zPl{Q$cJvB|HKD5o9&HUQhw}PWm;G!;&6{pSp7vcVi~QW^a*pXrZ&JSuYKldtUK3Y-cD@#w&0JDrsoT=IA0JV0FW3+`Fx|c`4yRy#i>>1y z_waVi>H;aY+uZRcmiV&$u6ZJ;V#;Sws|>z43A5gifmSe4I180;^23Y7n zdadkc%;dPs{biz-88b-I+m@ukC?->?%zozNd$>aQlR6OEa##*kdNQHuxt?H1{bL*0 zY`Q@8A2EAGF&1?T_$>95zk}-`zC|TuCX3|TGDqD<==wGVs8dl$^7=y@n8M#mz;3l( z6^H9FB`aXpuRZpw-87Jb-B( zP0-FSafw{JOOsE2<#KgsKkJHf!<+l_LvIVn029U{zvBYr6s*~bj8z{v)TiDVaU()q z%DDVGRiAfbtA3Cp?37Fyh~K(*BrOmfDS-?Hc{;hl^Tve_*>U*WCu2|RTWMJ_0hK9$ zc(8_Oc;+DX=bS?cx`coWI%roM)qInizVO$#FmZra&k<@Fv@M-H(mNoMB+uCU?NJ>E z#jLz4GX7TN0o`g!70n*o19coQtb-nIS@7XK&Me>FPx3-l8~~y{vwXX!?cnZ@-)des zQAj4%@rtDdfX*d+0TRAKqSAv>9HVrd<-?cjG+J@4WzL^Ljg0%_2H@<}gYE~qApcI& zeIn;p{{Y!au>r)tB93!{#65Z{3(a5wX%kdX_->jh=xQj?n4?lrfeB%*1XyL;ZRW=i=}bIJpW2FD&uJa%$2Ns+iPz>(t7cGA8W70sPM8Lr}#s5cFe&8hp!&Z zL=x|M*a|I-W(RUg)0aN92f6GmK2MX&SZzBSOm}KqRvL?rTnQe}R1B2im;Re88IvXvxStB#$jk|o+wG;05tLpx9YWwa zXmp?jhE9naQUAhgt*QWupk$bI!#3Wjg*DE5d&pl4(2TqTCqYP>Bj z{COnu953zetU*z6`J}PmHvO#bdIiwp2ltzhBk4z7#GwS*)@Sdea4ac+pzHe!P?I0N z3~}pM^3#th(cMV|5;ww`w!x>_gc>Ob zu(nvgq*}6d*j|z&hG4U*4v~?&=u>YWJl4WmGk&7@kP59w`f3Uk#Z!HYg)iCuIc<9 z3mQf;eqXy_6egkM2oNsxj*_wz95cf^#R?WOQ$~Clo&m%{BW;$?2~Q*O5+^gDvQ`K< zng5W=#LTwTfWxRCcc0c{v9aq8o+@H3hLUgsTqV^Kgv5)LA8=YM}C<3n?fduW$a3C z1(M9+z_EensHj=?9*)W%@?v~8K>1Jlr$T0E+L-lRuoV)vp(!8wq<;bq!EE3b-^ z@RH)ex%zY8Us8-S{dpP`X=hyb`wux&N$xh5S?u6M^Otxa|MCe_Q6r3AEP&(u_{*HP zU<~RQThv+3VT%DoCwM<_JD_w3zaA{7dIo>lnJnMapyWiyC_?EHs)^s+YV-K6AQAxZ4SP4pAuH-msY zvwg-yurKQi&oqEZ_}9kD6M$*jR?0Jogo7o^t9hMW6ylmXI*@YS$lLt0aP*6s902)>GBo3XLq>Q$);w2D#lgb+7R010~{5X7q9Ty2W zC&Bpq=G~7S%A5I?Pa3D5LjKuE#T1!*iyN={8P0KHF=&H0 zBO_X#iy6Y4$CaGAZl4$za13Pui|Mu68G{gqw+kQ(&Tc6!z@` zsmIE^q`bYW4yZG7%tmY-JDALNuZ{0c)9t)(&}2m{$K83NH8UtDXYER;DN~S>L1hXs zcZOvJ`$v4B&iU!(*2BefE>ECRwh}g+t2`1in)l;xX>I$G(TYKVp>!qIvk61O+_2&? z+$;>dZhtAr6Lda%o*lcPLK$A* zxxlyU^oo^nii5B-t_+@rR=>)GNf^y8-{;(YAZ7Lu<~?Nw z(Rmh@RrX!jV~Yb2^rkM@Nq!i3p%Zp#j9uyiVNSs?&00apeMoSy#L&hQftlUaY|_#J zzWQ5gxUewfKlQgSWHcLG+BRPoZ;xm`^Eb+^kiV43|DnxU`P5nBP_$id^W^6k* zUIPi`7MD+otM#`C<*`qJe(>b7#tRCGZ!QAKFutdBdA-Ph7$AO9{v{!eI_~r*$4aJ| zVkxOP+meq9Y?~R-haaK|?(|`)m0@#ecBdeiC@(s4QAjNK4K!h{AbQJU=l-|4(MMI{ zR-as!efkc7GTwF4?4u;FoCj_kR_BZreiRaz=l5qUyLz_*LD*XiHfT3hfL0Co;f%_r z>oV5;@g>UmS36}W*MbjUO_~YUJmmAorsv7fP)wi#wSP61gW*?ja?YH;f%FvRgOrZApq^zuVb3c#**Vi@>U_y(PA(N;#=+Hw%bPN$ZVtpKCWO zfAbp_f@E)3Z0|?l;t#@uAtdTGs1=2}M_DD5DCZmas$gV9F6#%$Q$|1~iW_~)G*!h~ zXL5ni7p-5ED=Pf^yW;*{AsI?-#ck6pn^>J(GN_hZ`rW4eH@)Gx1=|zgDxVj+-OAe^ zn*{z|wu)(e2Km55(EN6IX*0k&EnrG~fdyPqnVG4V}S14_0^PM1~1kZXDvWDMbA$ zzhMXfQ=xd5MD)%r-u*i2SZ2m3#by&!B}x8<{!0;KC!Gg~CHWgML^di3i-@l7&-*7d z-OSwvm!u9lAPXYAm!_bYG$Ww9hNW($M&t6E3Jn|BGp!VhhrQT=2!E_|DbmF~8_t^I zRA)4FDfg(;aJ>unI^Wo~`yZiUa1E*F7ElrG-C8@ex+%JPTYYtLIkOu4W5)cD*L`}^ zmffUxR=|TWYp=Eigi`UvX)=mRl+DtzXQLl7XaUkLs&L0p?y7ku*C}xwte?I@KRFu* z9g78%(A(i4GHCamJ4JJ`u-xIZ(0wbhe8#xsN-;Zq&-s1`w(cuOQ^w24e_q$r!iUV* zVN&nCB+HMJkR#6BMk`_ zGy7ltyzFo&@4n}|*y=Fcm82bvW^T(n9?ox^Fov%c*kY#vC zRYtl{d2P#X$>k6U?|P>A8f7i~&Rj1?Jo`WoG%3&;%9-@ZU(SU4T2uVW?0tqX(r6r@ zIzNs@?zIn0r(j+O!FCS6Lg1J#X*<7d4$xB=40I^9kzh*Jzb*q%w*pi@OW8&Dp<2Wp zM?8*eFX;aoqfGdAO9)@Cl1O42kP|gTbpS1OI$Uw)nPnU|)&*r3NYjg+24nNU+RRAS zCPaOn;t&!b&jQfD=rnlPr=SO!>$_usv=?sC`?TuLIp{?}E*3E}W}s5__;axgK0Osc zbl&(M%JA>ag^AwY_(TGtu=peF8gQ9IUlh0tR=&0-NN$=#5)0w&v;z1wuy%Zc{S@%e z^uPTj$CSA1Nw&PB`1R$r#$y>P%t8EBg;JpltS-6S>CQ<&ja1MQ!To186ySqwwa_sW zRzuWQvrTj@F!v7=311OKAg(9t1`o4-m7z9wjS7`J2Nk#`C%-XF6lJ!XOhtbF!whH+ zhYlh=lc=${AvP-0pjH)9Bz6JdIyF*yCswXmv#}5No_4(-VMVxoLKTveIs&2fcO#;I zm;|tb7Ecu5UIpa2;0B4O<*X+7dq3l;TX<1f_3gh0T4oU9DGSK> zGUGra%zgHr`t95mP-LpVV?EYATZ|;K=(rig#;w5wIYwV;wyC*rhG3_o&W|#%yUXGp zO6N{^a4YkqXHt`+&cvZa@#xtiNhOMkA$|02e3TE0+kO0O4&NDHpHqpP z@?y+@RN)aNl+0`WrH0_7V571H6r=SEDzR)v!+6nQ`dJS~H2$_{(-8l7L3&unY z3{H7gf*AP_UpSOAmC;K$d@H_BJqC5+zLB-o>`-!UPBZNLYLut>iQf z6=Z5m*KcD|vl~MdGXLhG^v)KL3L(29YF+L5I9hD9f_2KPw^pxLIhPY~S%Zo~sh<=y z>(1bAn-f1Urw|)GexNzt%|dddR{qK6mf)29=uetb z$Ok#~vid-aLqqZ|sW7kr15*~056sn|P1Wc1+}9k>z>&c0sHex2e-j!QCc;7)M`v)2 zQ9ZMxoWhS2%TElVM~{I94SIn54#j?`Bbr%CB^Z5dm=xvKk@`Qj;v~N{de&KP}a&X(!;o#ovX%vq7|Bv#HNQwi}UPNBv`(Gk$W>6!=)qA)5$B>IVY zna(-=nrPhZwW!*fS=|}$(ct~b-^3q#$HE_}1mbuju)~S`aGEM%IpirSEu_9B!zDPm zo8*}9v32iwf2W-Lv-9{9(Ffrr>3iN77v@1{{n)X9k{RHk7pb`SES6KEJh54i2Dbl; zzr{W)uS(qf@4bJ#l$FZ9*!?6E5RR)jXq4GEFa2FmfV*1*j_Ev#xHLO@{ZU_9z-hqV z2zq0Gi3vLkC89$(s{(LK-MG)0xh&?@PVY?e@pa8W%PZu4_^R(}p)Es91bu%cR7f$f ze7!~E^7UJ8@R>!{U0ub)Xob{pp@T;F0QcRhAjqr0Zn0>|y_GV5sxLq0)Zafa@Re>e z+F`(Sp))b>LC)>9gq|7XjG_9*v>fpMS`L_X`4HC`)R$wkmCvzpeM@+qZKb7{o}^3? zx~6{K7gQL7`N%imlI;{ zl0$l`a#D1{Z2a-&=;Kf#%By@jS%~+2db_aquh%{U^0}?n@#=MG0(6@K%Ytvk(@e*% zgq(C9(57Nl2FcJj-W7VE7P+$L%Hjje@y;$D9g>ggHcgeUBgVobPgy2Y~tUM&4?Vl2&sK3*$E6dO5aOnF@3ElPqscLiz|~M%1_q zS4W?40tMdLVe+GweeytdrRRSW8fPMHRMC^m!~;$ph)2Kg+)dGA(0cp~kVje59EgLo z(d*;qKsB5wTML*oZ?g7QP9!-!|GHDv>LtZ&pn&OBJ;rK(lhl(f4a22On7gmoLlbmT z20pbUey~X0C0vT6N6a;A08Yei9=L;%_*)pW#J&yoON};jJi+=z7uI`!>sbNg!acTX zkgq74l(~s8m(8h33BAdtjPB!#Ql=VFPm8ch8N!7=T0IQA{c!b#Xz9A_MGvtFN;WaO z_n@4e)RUJ+Y&kh}@|?O3RO*8xu^*)7D1fucQ-2atlMnv(SX*gUF1^jd0TSd@T)foL zv^g!br5cCCH$-}QKT~rUthE27YrF$T5!b-|^tf$mC{dq(QxuE$*;zwy7+`3?=sIZk zA-Uc8Y9fHuKQBYM`)&SS*Ox;=lK7lDb6U0KoqZQH%j6^Qlo;a8j8UH_ZGb6(4oY&~ z5Um;$L0AjnDyZI4Cu0XpZIMJExNVgSd%5B6GIw@*pw+v_Pu~I(f;qNq4 zS7^C=*cG?1z3H)_bI`vlTiH`y_pBu&1kgM%y6FAp1;t9&KlYFxeibvQOJ}qP+avHi zZG?%HASX$dUkf3Sso6@A$xytP^w7W_=Th>cz&JnzXG4{Itzj58aAR$2RAi}@t0NgI z<-DL!Q_hj%QJU4$xx32W&on{dp(Sio@Ud;!zvDHm)8-ZZeMW+GW7Iffr~ip%)YZZ9 ziO(`mHTFXnq<&_Ew+lK_k*CWMTyUb&jO8r~v7^ZXdtYHoN!`o!@5t!%^v6Kc#>GRr zph|DWAhW7d^}E@R*q;!s+3o_c+8Kn+>5b3dVcu)T#KiO)pUOKn48gtpZ*lpE*L__n zFssx0pgVui?ZeZ+6XKAveT*rd@7MpTJzZ|@o6WDB4toz)H2V#aI!d!s?kNXmnB(?W ze&})MrtYksOsIs)H1MRb-uqppUU1>}n&>Zw94t{|gbzhT$O_!$W1Qz!*{-^0(7u)E z`+%R=yO!z6cPy~vA+#;!(iu%8yy=;;IC77dScB==-3)?A!|Hk#pzlOFc z&A2xczouC6t*CHfz4tO4Ya0&AoFoFO(5Dr=Ky#M^7x;Op5B^1uje!z=ObsXJE8W&Y zL*IQV?!CWzh<`tdtYQGjAnt*Oi07cCASVUghW)s~!K&z{LM_j?y?fR~n?Y#{fN7!> zsZETR-gsH@+6EB@l5oIB#<3P25BdY7MZHL!TOVa%S^}_^oZ!USy{BxAGSbqY%AO%U z;na!$Eqko^tPyLUiNFQ0XPjlyusxC`bM(KYNCqv;VX+C~(Nw`YV^7By7Wt!QTM^B& zL;$+KgbB0K+kOAuTxf=q%T_4~wD~>}W|K#S_*Vl2#g`pS#0dpDIiR)Azgj$ZO2e#s z5jn!kx7U||HZuwOFzqIZYclGQOYNn&o)e~lp0Mr73<$;y(jr6I!LmZ?N3ciTJY>}X zn$)fb0b1UkHZvkPtmeu%R=7lixud)T^U%uqY z8JD0OB8MR8iYzs9U3u$gRpyVxE$K*Aerx7h*-n_RFH`N3EJf9ngAS|4wgeX)DoR z15Hq58Ha1u0U~LwDwBgQbikFpL*P9zwDqA(zOlS}H%zm@4(R2Yimm&-$~=bM3lY!Z zesX>xsq#H65=wnswp4g$;mYXQ`2CFd%DO^pF+A4rMdNn`C3WK9HzWB7)r3{6<=gpQ zUcDdpq@BogHFmQPGvx{@mQM0o6dOSMLU=Tja7+!a^Li;x(hISpCdORsMxg2AWkHK< zT^@zIgueQ=6gST1^4BbIpSC3VuMg0vmvMKryIVa-1k+2@cdARlPgcSVHq3hHhnj#A zNc}tgA>;^h=z-BMbihQtEOZQXc=F-8+af-d^SHk*MeoRc{k1u~9UXrBJnJyv110K> zpi1O|IG#^8Wp0R(4)HxBkj~f3Qse|P7=EcN$H(s7LEKpf_=@+davLd_iEnW@S04qu zEsIJNxxK8(45X0ww8C3gFbN))P4`%FB)(bf;-FjUZj2a_r)2;!7)Pm-#I#+=a3~Vv zJ)m8{k=v51i-%jYO^2UZQ{a&I(jkViIeb?i762OV8E9}Q@XRqflGX9_mD~tX-<-)V zz9xBR7%EOHPi%5>zyo}HefA>JXax$mi6s4lleSajfpepqLnTjG)8eT`ZDRl+Xki0t zCzx(}QNX#?q2O*a!uQ3q$`XcnlM+8zm2$n)yctKOE|(6fM68q&$%{ z`pOAQH#o;Kd0HSQ3gU0oB#i_i6QML+5MvobYrh8Es|jB4DZ?w|0tduzJ*6f*K%8P% zQGUN^B{Ivbri&~lEL;#_yR{MSil=fZDH@#YFNTcH0AAQ>5sT2$lg5bSZ{ipouee8#d3v z(hl_N23OhD15xfBwaTWlpqtZ-3=2JGyS?mgo#s7yCR2&#ak>sLAQq%k*b(_$`5o7L z-!sM4_9$y#T%Fr>(;VOQs)U zUJ$b0vz~>u<-4rg0HX69X(yS{uP>;8pFfieE3a=3t$eK7$2A48K8n^rTC2eOO~xqf z0YTQ)0G7^)kA)NKW4m5hhq{yS!WNKkXIh&Hh>PHf=|M=}_K#$mk6Upb@Ks%EV%o3| z(K)9_ZTOr8Zr2P-g=;*(1DI1ITudMl)W1Jn;*IKr!C!;{zkM`C6|{~iCB*yTrV@?sQTP+UxA?fiPx)}LSd++Z5Zzfrw7JpVWaWYS9}g72n~8tHo@5#py=LbIxe>N)e%C>1{(z)#IR zmu0OcCVnnv-My#Z$+Plw<`TQJjne#|oP6q|)EDlsiz?Pa8Hm>7<)Jv3(t*{{%Oeu# zY7S$MMkSgluvLa~UiH|_eSz0~U_g%Xqtk%(bLNj0;*PUOhCmmzT-t}eVjjfQCXUYL zLyvRp*{iZ09T2B-WFisXj_f^eSS@P{>4O|=dxbPJtjA?g>U^-hrJ-j#@#d=t+1@gn z5n`q}KiRXFtuW_v({kWazx=Ah*k$;0lR??dM0Xh1+ZRY)n!Ff)x;nA$EP8~oH@wL2 zv7Rm{Ijk(U?hLi^enGnnHz~c`#J#5xDxoyJ=3%8g#g|>Rr7L^G{}j&) z4N)x^&j}DHowaMLd%PL+U46A=;tmT~=ej^4p#?-nQg);P>U`})(|mkaI4OyBA6#XQ zaOz!oK(8Ch+(r0D|NJbkd{+)<_mLXGbPgw0>Zydv@Y8~6t>AqBW8EW3PUL=2TG+Ku zVZKq}fbBaVVgOp@zP9nKMMd`A`6r^2{HD~$z{?`7FBw3%jKuXv-MxQVDKeK)r`<0A z#4C`c7d`CA4PLRMkx-uY;RnMDRB#6JGa9)_h(CY+9OqN8(pVRo=4Vset;i0#XfeU7 zlvBqFT$o_v<%wsT@!FOOC<4(HX2V3M;23qItgYuQKLmhx3k(&WC(H*Fe3fFNr6T?& z4)MMyRNKP4OBnP*(KG1x>^!l@lFEpm?VL%Oy#^k$To<|jaf%?c@V~!46D>}?3^J6C z41_oH9_sug%AF|>k}3(7+9?C zmFCe$G|nblaaGNj;?a6Z4#}w^8R>)p*bL?Q-sOOkiOfux_t15MZA-H{D@hD}YF=p~ zAd)ueE1{+K`%vS>hj;^>TBH6kv^!-#zV)jAaqpp;rVyeMt4S1RuL``-fVGzS*S)7W zJ1)QX`jIt5Pe&lFhh%w(s$A^B2D6M9BJ>HPB)UPfBw;?Z7HISn0cHt9P)&#XzT5aU z9BqTJc=f>rb^%xcNvVx8bmJUcT=7CZD}E&)^ z_^%d&F72v{jFyDOtv;*pKP|SH#w(%j!t+nfz>Zlg zJgU>ojCkZo*gf=$0HCdlS>lc$pqdx_&0TL0FPuzw=JgcTIbH9=8p~%mI=+nLZ_~=z z&s?6Dq)YrFyB4Z%F**l-fE2n=NX!D9D)W?|*h-CMS08l46$CUrGguHmc+Xp9VOtk} zYiKDJuB#zX)WY>r3zVl68A7BDDzF#W&i>%+vQtlRh0(!jf)py_(6jEEo|!5;w*V6a zgZ^7S(q8dMct3h&+Ne;-iYF4(26`zIiMLl^XBPKlj(#4}SywpJP`2sI5{?1PBhtqjU2 znW77Fw(-?$x|#-z^@;;rVmJ> z2hE8cO(;sOu?L)chMP%aLyG!5XF{rZR@PYi2BIRor^9LS!ZiB^fM*I z00kt|@xM7<*$Wk(@z%-S?tFKpaF}j@<$gID=4Vz_P&^MswOz1d362DQ(e&kB8 zq^`q-XsRvc@{CsQ0kss6Efr~j8M-Em>w34LVGOrz7uc#{?Dqr4QDcv@6^ZK}{9|DI z=vG;cJRlb~157>d)k2_9dQ3*ggL7B&Rw8mTd}B^oZ`sR)nzlrrRiX=kbQD&fH}<K|DP)Ld3S%-)sK{fmqns1?(0HYhZw~BAuyL2hNUVzg_IlXj0!D(2hv5D zd4h5ttZ2qN*Db8c`^Ae3ftoJ=k(G-4*F@Akn{0p{Y5=*UucBytq$@#OJ?p-Pa#XB+ zp|y{oc-bwK^h_50rQQ^O(WG~yv3;E;n02MNNb5E#)ZF8BZ+9a+hkc-aIFe&&YtqyR z%F*71Vya<4V^hYbkDPQ_VV6W~txGn==leRPShj3^rO^?u>PZA_FF!vzv+Ez$%64|s z(@UZL3kj1D4FW9~qvn&NLW{QQ_0EY@{f~@MH#Sy*i54}3xZd7(n1}@GVDAQ2>aUnm zElKe5XC8dH%J48m^1xShq_CHMl0F^&f~T#N-uL^{CnXbcaq-_gq}Lu?Sc#T$)kcNW zw(mMBcpegX;gS%`)<5?CS0|>dR2ZEPx|ch*O8YrJr1*2V~O9EYUoJ77h(2g=^XiGO zvCK>2&7GZ&n*N1EV6mb*nF+qQ}O-iGbg zbf|Z^@ykKVIN8hM!=}y|(~V~CZf|b~x}L-8;MLq^$NxRJ;k8`y^h?V#k=ddzHl^@? zzqew>hF=2q)_Qr->}3y&0@`I6-%2&NSH~T*-#Y7xRl&FFV?eh93%gvwhZlFt8R;oZ zHZ|Ao)Mi}2V8H^#w6wHG`&T(6JW_nP>~8zxYwx*}J1)-wRyI$2cx|eJPv3d_@XwnE z3$vNO00*5vG-NCfO}u56@K94Svcdh1v|B~&^;t}7zS$+Er6|_vKbdoG!pS+HDYNx^ z9X?F_B4=>pjQN|UoKv4-|0LNq1LvoUtarD3bzi@N)nPvGi&IY*oLhZJruNi4UJHunp3>uw>PSh_EVh%6Rl$Nl{H9nk{mwe>T-JW^-`MLS) z*Gy+k*lxvf{Hwa;_Malc+vlc!dgtwPyS#TM`+I};FPz0%muxUmY7RBDTw|@CTwJ4g zJ5OYz^-7le?x`|9D-{eDcRdI%opWYl@je$oxSD7#hW*6-!7k%mS%S3%$b}Oz%8@DKFXKi ma^54g)f61W^Fryr{7!Qv#Y>_K)PTnaF?hQAxvX$(q4NZ?e+Xfl!pe+YSA3%ihZ}Bu~ppW0I5mOPL6(pIhT~eY(%A}5a;RJV~Rd&%UG8>Yrh_&#w9}{)qs(i`) zytpWg?}04&6B1JFg+e`mRV9XGMZb=*++EQujE!ACFHbntU4*Ctz}X}~Tc_g&FhlPVlf2^gEXsIxg>}=?04uQY-SVVXz=IO<9%OZ{j*L0xE;Ixd>ITTcwOBDTvM11`)*x7DN~r9|%{Ej0-VsSG_<#byIN)$6VeA`_ z1N3y8gQ-bKA#};!lu_8lxPyjf$N?;9pxXPT{C3pH0V4sQ#c~*PRYd{d84*UQdhy%G zpBEmb%`2?FF*LB1`s^JY*IEaaDKI-%~x$gO5m(E(6dHH)mJ## z<^g&?>XKb7XJ;y-DS;pQukF#)jMq&VQ5WCpfau5hy(J1dnGj zwTn#1DeH=@)BY2<7BH@nXJV9`5V47Nm3x&_p`)jTBKvoXL)A$6>P|bzlT7AnR;yBa z*_V-QRI;Y8+TD$xh4@(+=ue+I+*CQW(0cazv#Jw-d~&fgW*m8V_blB_A331=gJ*UX zz8BwKCVbra@-L{gs&e*TbH5A?rZ(xubGIy$iGS+<)R(|-EbD78a2=SH4-v>|nN5Wy4|U&uThx=! zVi0&hGBETi<|ZC%+a|km@M>euB<#G`Trk!VxKTnUJ>W?pT>4y^h1o^<8=Zffx?JMt+&6AM78TqpM52YD^ zE0-j&&PNkm&sbdHHt$StFv-UlTck3VU?|}lyip3;bR9t&hfp|{@v&HH#v<+dmyd|Um^h2bR0^~$bJO} ze(CHdW{-M&ExeK?n*dS9(oPQx(${g}4LvdS-5G7elxmbf6oD<<6_5p0I?+M~>U3v-_@+1fl3$ z$N&G6c_{H3X@WiI26?FF6{>jBu56yB!|oEwfSx9j;Vvj>Onf)I)_V1`nxJ zgX$;P$DA+wf(K^Z&9#Wc=M8By&i1aL$jV@9G^LOr4PU|mnskw#mxXmC!c#9ecDVo1x;NrNos~Zti;+KA8Y1dWb&qD-h9*$8J|9 zLX_p=pW2<R{BaGn>WVPKqB{h*_|fh zBl2P^oTRcZ<52I5@V!*nHJqv3Jrn|<;WbupSogl$skpx!=BI9Lw!I!Z7sL_JEl!#D z!w#p%*D%1zNVnkP-I`tpW?9Olu`-fS12_mu{0fc}&^@ow1WAIVmk0b)TFU$E>8x+2 zIOSAH^=|UuWQK~(8ox;z)!wE=Py4CrvD$e_E)H3klaiOO!1sPET@6!`cuegqFgA_L z+806fs%2r6;l^5{9VJX%?3KdliqI~?mdZF0^ebmvv zdR>P8Ywxe%b-q|R`_Q?dApFo2g#%;WFX*6xclH;Tj!nnH!ajPN6^})awlCe?K0=q` zFczZE)@ExxB>=BB+vuhqE_eBIbUnwA*UgU>uC6w&FO#j~nP0?MH*zzOsL3NEf%WL3 z8LWKs*3brb+Wpd2xM{lyAsbjd$xQry2EYIu+jl}Ix6V3N-fB`7t6;-xHRtgwa=!n z#Jsrb6-N$xUN#WKZ2IT^?HfBmeRro=Aj$Wuyh5?}IdF>aU(bayp;^K3QjzF^VDqkw zv=h!QIfm&IKC3>Pn30V;xvIQFPc_SEB74W|MG;s*ZFxnuoCCL>Z%Mi@Y>+4TSk_;V zR>ckTg>iSxJK^=l)SJ?VP6UJ72(QHe2YSohjMDu0;z4TvMifA2168dkgWM=&EN7p5;Ksxq~A~O-*UXc6`Qe zusC&|VE!I{>LUsb2p_O{_QK4lEOvKNy=f$Ytoh@6_V1X(w^2XG7FJZG+eR&8da5*S zB=>49zp1y2!2@2SgBRl(EsV-MC-)QRYN1E5gGzPloxXYda$g6x9)ciF>(4Aao}JSB zI;jiX>9-f|T#?T7i>+?&GjwbEo4oy&z3>wFVHk(X3BdPn)$_s=E%fnY3#4_~&0a&KqorP`7CvH;7(Xo9yIZE!N6JG1AlcMIs66y?9mw#kUcXq7~vb zs%QB})VAW148UAd3Dwxt*DEV{7MBL5tgW9h9o~bICzJEgwA5JDp z`fArhYqbFbr-}XT*J7#Or|KD^b>|GOb3tE1*PPZ+t-@y~7*+0IO4FBHd)9wV`>p7H zkH7y#6F$*X{MqxJ)JVu9CoN)gc%{j8Cuzz2HG9oC2WI)Qjoz*+6eD8v#_rx$j#Nj? ze`mO;;{IQ{r{)s5RuQHW1Zm*ZTJwo}s9?Aa_v($7n9Rw({8uL8ZvtYAgey83!TgYM zpOZ5}^c~@y|JInHvwl%jL_w?DWMNZ*0l9oLqwv$styi#Z;{ryO?ycotCgj1U>44ba zk%hz86owvjXwS9v0k?xaPy1%65|1qpNWFjTE(7dypBqc|H~wV1(>fX6O0?Ri00)I_AO$jMJ;vO!JN;?#qAhKotLPa zVTFs;KrCD_L#L?X6NT<3e-b>r@a-N$FXc2tC?LFUM)H**nfifofWBc2Nf=HCRcbBM z?<@1=sj=H10bVphd?O%g@0~XzAG~dEf==nxuR1VyGKsyLk`6*Y9)*?4x*C)JsrBL? z_zy7*2_2(un>9`%RTzI~h8$Hc5w!(B9i2F~*|u$0@556`Y&i)3I`389YD(KU{py>M zqSetHV&3m4>n+a)4=-F}W86X6lS8c2lrY$nSvrTdUu2{0tw;0MkjHq9oS5C&h?kv# z9Lsss^%aT2^}+4#P(hcmh#^l6uqJfCbF{#=*vP5*Sp=E+_XYi;&ok{;><>y0{$ek~ zcV|lj@F=Q7KT^A6j@D0c-U + + 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: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + 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. +*/ + +#include "Cone.h" + +#include "Magnum/Mesh.h" +#include "Magnum/Math/Color.h" +#include "Magnum/Primitives/Implementation/Spheroid.h" +#include "Magnum/Primitives/Implementation/WireframeSpheroid.h" +#include "Magnum/Trade/MeshData3D.h" + +namespace Magnum { namespace Primitives { + +Trade::MeshData3D coneSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const ConeFlags flags) { + CORRADE_ASSERT(rings >= 1 && segments >= 3, + "Primitives::coneSolid(): at least one ring and three segments expected", + (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + + Implementation::Spheroid cone{segments, flags & ConeFlag::GenerateTextureCoords ? Implementation::Spheroid::TextureCoords::Generate : Implementation::Spheroid::TextureCoords::DontGenerate}; + + const Float length = 2.0f*halfLength; + const Float textureCoordsV = flags & ConeFlag::CapEnd ? 1.0f/(length + 1.0f) : 0.0f; + + /* Bottom cap */ + if(flags & ConeFlag::CapEnd) { + cone.capVertex(-halfLength, -1.0f, 0.0f); + cone.capVertexRing(-halfLength, textureCoordsV, Vector3::yAxis(-1.0f)); + } + + /* Vertex rings */ + cone.cylinderVertexRings(rings+1, -halfLength, {-1.0f/rings, length/rings}, textureCoordsV, length/(rings*(flags & ConeFlag::CapEnd ? length + 1.0f : length))); + + /* Faces */ + if(flags & ConeFlag::CapEnd) cone.bottomFaceRing(); + cone.faceRings(rings, flags & ConeFlag::CapEnd ? (1 + segments) : 0); + + return cone.finalize(); +} + +Trade::MeshData3D coneWireframe(const UnsignedInt segments, const Float halfLength) { + CORRADE_ASSERT(segments >= 4 && segments%4 == 0, + "Primitives::cylinderWireframe(): multiples of 4 segments expected", + (Trade::MeshData3D{MeshPrimitive::Lines, {}, {}, {}, {}, {}, nullptr})); + + Implementation::WireframeSpheroid cone{segments/4}; + cone.ring(-halfLength); + cone.topHemisphere(halfLength - 1.0f, 1); + return cone.finalize(); +} + +}} diff --git a/src/Magnum/Primitives/Cone.h b/src/Magnum/Primitives/Cone.h new file mode 100644 index 000000000..e25a79e6e --- /dev/null +++ b/src/Magnum/Primitives/Cone.h @@ -0,0 +1,100 @@ +#ifndef Magnum_Primitives_Cone_h +#define Magnum_Primitives_Cone_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + 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: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + 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. +*/ + +/** @file + * @brief Function @ref Magnum::Primitives::coneSolid(), @ref Magnum::Primitives::coneWireframe() + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Primitives/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace Primitives { + +/** +@brief Cone flag + +@see @ref ConeFlags, @ref coneSolid() +*/ +enum class ConeFlag { + GenerateTextureCoords = 1 << 0, /**< Generate texture coordinates */ + CapEnd = 1 << 1 /**< Cap end */ +}; + +/** +@brief Cone flags + +@see @ref coneSolid() +*/ +typedef Containers::EnumSet ConeFlags; + +CORRADE_ENUMSET_OPERATORS(ConeFlags) + +/** +@brief Solid 3D cone +@param rings Number of (face) rings. Must be larger or equal to + @cpp 1 @ce. +@param segments Number of (face) segments. Must be larger or equal to + @cpp 3 @ce. +@param halfLength Half the cone length +@param flags Flags + +Cone along Y axis of radius @cpp 1.0f @ce. Indexed +@ref MeshPrimitive::Triangles. Note that in order to have properly smooth +normals over the whole area, the tip consists of @cpp segments*2 @ce vertices +instead of just one. + +@image html primitives-conesolid.png + +The cone is by default created with radius set to @f$ 1.0 @f$. In order to get +radius @f$ r @f$, length @f$ l @f$ and preserve correct normals, set +@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all +@ref Trade::MeshData3D::positions() by @f$ r @f$, for example using +@ref MeshTools::transformPointsInPlace(). +@see @ref coneWireframe(), @ref cylinderSolid() +*/ +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D coneSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, ConeFlags flags = {}); + +/** +@brief Wireframe 3D cone +@param segments Number of (line) segments. Must be larger or equal to + @cpp 4 @ce and multiple of @cpp 4 @ce. +@param halfLength Half the cone length + +Cone along Y axis of radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Lines. + +@image html primitives-conewireframe.png + +@see @ref coneSolid(), @ref cylinderWireframe() +*/ +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D coneWireframe(UnsignedInt segments, Float halfLength); + +}} + +#endif diff --git a/src/Magnum/Primitives/Cylinder.cpp b/src/Magnum/Primitives/Cylinder.cpp index a7781e47a..09c3ea894 100644 --- a/src/Magnum/Primitives/Cylinder.cpp +++ b/src/Magnum/Primitives/Cylinder.cpp @@ -50,7 +50,7 @@ Trade::MeshData3D cylinderSolid(const UnsignedInt rings, const UnsignedInt segme } /* Vertex rings */ - cylinder.cylinderVertexRings(rings+1, -halfLength, length/rings, textureCoordsV, length/(rings*(flags & CylinderFlag::CapEnds ? length + 2.0f : length))); + cylinder.cylinderVertexRings(rings+1, -halfLength, {0.0f, length/rings}, textureCoordsV, length/(rings*(flags & CylinderFlag::CapEnds ? length + 2.0f : length))); /* Top cap */ if(flags & CylinderFlag::CapEnds) { diff --git a/src/Magnum/Primitives/Cylinder.h b/src/Magnum/Primitives/Cylinder.h index b466125e7..e57ae14d0 100644 --- a/src/Magnum/Primitives/Cylinder.h +++ b/src/Magnum/Primitives/Cylinder.h @@ -44,7 +44,7 @@ namespace Magnum { namespace Primitives { */ enum class CylinderFlag { GenerateTextureCoords = 1 << 0, /**< Generate texture coordinates */ - CapEnds = 1 << 1 /**< Cap ends */ + CapEnds = 1 << 1 /**< Cap ends */ }; /** @@ -77,7 +77,7 @@ get radius @f$ r @f$, length @f$ l @f$ and preserve correct normals, set @p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all @ref Trade::MeshData3D::positions() by @f$ r @f$, for example using @ref MeshTools::transformPointsInPlace(). -@see @ref cylinderWireframe() +@see @ref cylinderWireframe(), @ref coneSolid() */ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cylinderSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, CylinderFlags flags = {}); @@ -94,7 +94,7 @@ Cylinder along Y axis of radius @cpp 1.0f @ce. Indexed @image html primitives-cylinderwireframe.png -@see @ref cylinderSolid() +@see @ref cylinderSolid(), @ref coneWireframe() */ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cylinderWireframe(UnsignedInt rings, UnsignedInt segments, Float halfLength); diff --git a/src/Magnum/Primitives/Implementation/Spheroid.cpp b/src/Magnum/Primitives/Implementation/Spheroid.cpp index 026f59f13..acfe9ae02 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.cpp +++ b/src/Magnum/Primitives/Implementation/Spheroid.cpp @@ -68,13 +68,16 @@ void Spheroid::hemisphereVertexRings(UnsignedInt count, Float centerY, Rad start } } -void Spheroid::cylinderVertexRings(UnsignedInt count, Float startY, Float yIncrement, Float startTextureCoordsV, Float textureCoordsVIncrement) { +void Spheroid::cylinderVertexRings(const UnsignedInt count, const Float startY, const Vector2& increment, const Float startTextureCoordsV, const Float textureCoordsVIncrement) { + const Vector2 baseNormal = -increment.perpendicular().normalized(); + Vector2 base = {1.0f, startY}; + Rad segmentAngleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != count; ++i) { for(UnsignedInt j = 0; j != segments; ++j) { Rad segmentAngle = Float(j)*segmentAngleIncrement; - positions.emplace_back(Math::sin(segmentAngle), startY, Math::cos(segmentAngle)); - normals.emplace_back(Math::sin(segmentAngle), 0.0f, Math::cos(segmentAngle)); + positions.emplace_back(base.x()*Math::sin(segmentAngle), base.y(), base.x()*Math::cos(segmentAngle)); + normals.emplace_back(baseNormal.x()*Math::sin(segmentAngle), baseNormal.y(), baseNormal.x()*Math::cos(segmentAngle)); if(textureCoords == TextureCoords::Generate) textureCoords2D.emplace_back(j*1.0f/segments, startTextureCoordsV + i*textureCoordsVIncrement); @@ -87,7 +90,7 @@ void Spheroid::cylinderVertexRings(UnsignedInt count, Float startY, Float yIncre textureCoords2D.emplace_back(1.0f, startTextureCoordsV + i*textureCoordsVIncrement); } - startY += yIncrement; + base += increment; } } diff --git a/src/Magnum/Primitives/Implementation/Spheroid.h b/src/Magnum/Primitives/Implementation/Spheroid.h index ad6e3f244..dbabebbca 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.h +++ b/src/Magnum/Primitives/Implementation/Spheroid.h @@ -43,7 +43,7 @@ class Spheroid { void capVertex(Float y, Float normalY, Float textureCoordsV); void hemisphereVertexRings(UnsignedInt count, Float centerY, Rad startRingAngle, Rad ringAngleIncrement, Float startTextureCoordsV, Float textureCoordsVIncrement); - void cylinderVertexRings(UnsignedInt count, Float startY, Float yIncrement, Float startTextureCoordsV, Float textureCoordsVIncrement); + void cylinderVertexRings(UnsignedInt count, Float startY, const Vector2& increment, Float startTextureCoordsV, Float textureCoordsVIncrement); void bottomFaceRing(); void faceRings(UnsignedInt count, UnsignedInt offset = 1); void topFaceRing(); diff --git a/src/Magnum/Primitives/Test/CMakeLists.txt b/src/Magnum/Primitives/Test/CMakeLists.txt index b7bc7476c..28eb3a469 100644 --- a/src/Magnum/Primitives/Test/CMakeLists.txt +++ b/src/Magnum/Primitives/Test/CMakeLists.txt @@ -28,6 +28,7 @@ corrade_add_test(PrimitivesCapsuleTest CapsuleTest.cpp LIBRARIES MagnumPrimitive corrade_add_test(PrimitivesCircleTest CircleTest.cpp LIBRARIES MagnumPrimitives) corrade_add_test(PrimitivesCrosshairTest CrosshairTest.cpp LIBRARIES MagnumPrimitives) corrade_add_test(PrimitivesCubeTest CubeTest.cpp LIBRARIES MagnumPrimitives) +corrade_add_test(PrimitivesConeTest ConeTest.cpp LIBRARIES MagnumPrimitives) corrade_add_test(PrimitivesCylinderTest CylinderTest.cpp LIBRARIES MagnumPrimitives) corrade_add_test(PrimitivesIcosphereTest IcosphereTest.cpp LIBRARIES MagnumPrimitives) corrade_add_test(PrimitivesLineTest LineTest.cpp LIBRARIES MagnumPrimitives) @@ -41,6 +42,7 @@ set_target_properties( PrimitivesCircleTest PrimitivesCrosshairTest PrimitivesCubeTest + PrimitivesConeTest PrimitivesCylinderTest PrimitivesIcosphereTest PrimitivesLineTest diff --git a/src/Magnum/Primitives/Test/ConeTest.cpp b/src/Magnum/Primitives/Test/ConeTest.cpp new file mode 100644 index 000000000..bf55db3a2 --- /dev/null +++ b/src/Magnum/Primitives/Test/ConeTest.cpp @@ -0,0 +1,200 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 + Vladimír Vondruš + + 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: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + 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. +*/ + +#include +#include + +#include "Magnum/Math/Vector3.h" +#include "Magnum/Primitives/Cone.h" +#include "Magnum/Trade/MeshData3D.h" + +namespace Magnum { namespace Primitives { namespace Test { + +struct ConeTest: TestSuite::Tester { + explicit ConeTest(); + + void solidWithoutAnything(); + void solidWithTextureCoordsAndCaps(); + void wireframe(); +}; + +ConeTest::ConeTest() { + addTests({&ConeTest::solidWithoutAnything, + &ConeTest::solidWithTextureCoordsAndCaps, + &ConeTest::wireframe}); +} + +void ConeTest::solidWithoutAnything() { + Trade::MeshData3D cone = coneSolid(2, 3, 1.0f); + + CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + {0.0f, -1.0f, 1.0f}, + {0.866025f, -1.0f, -0.5f}, + {-0.866025f, -1.0f, -0.5f}, + + {0.0f, 0.0f, 0.5f}, + {0.433013f, 0.0f, -0.25f}, + {-0.433013f, 0.0f, -0.25f}, + + {0.0f, 1.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 1.0f, 0.0f} + }), TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + {0.0f, 0.447214f, 0.894427f}, + {0.774597f, 0.447214f, -0.447214f}, + {-0.774597f, 0.447214f, -0.447214f}, + + {0.0f, 0.447214f, 0.894427f}, + {0.774597f, 0.447214f, -0.447214f}, + {-0.774597f, 0.447214f, -0.447214f}, + + {0.0f, 0.447214f, 0.894427f}, + {0.774597f, 0.447214f, -0.447214f}, + {-0.774597f, 0.447214f, -0.447214f} + }), TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 2, 0, 3, 2, 3, 5, + 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7, 5, 3, 6, 5, 6, 8 + }), TestSuite::Compare::Container); +} + +void ConeTest::solidWithTextureCoordsAndCaps() { + Trade::MeshData3D cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords|ConeFlag::CapEnd); + + /* Bottom ring duplicated because it has different normals, first vertex of + each ring duplicated because it has different texture coordinates */ + CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + {0.0f, -1.0f, 0.0f}, + + {0.0f, -1.0f, 1.0f}, + {0.866025f, -1.0f, -0.5f}, + {-0.866025f, -1.0f, -0.5f}, + {0.0f, -1.0f, 1.0f}, + + {0.0f, -1.0f, 1.0f}, + {0.866025f, -1.0f, -0.5f}, + {-0.866025f, -1.0f, -0.5f}, + {0.0f, -1.0f, 1.0f}, + + {0.0f, 0.0f, 0.5f}, + {0.433013f, 0.0f, -0.25f}, + {-0.433013f, 0.0f, -0.25f}, + {0.0f, 0.0f, 0.5f}, + + {0.0f, 1.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 1.0f, 0.0f} + }), TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + {0.0f, -1.0f, 0.0f}, + + {0.0f, -1.0f, 0.0f}, + {0.0f, -1.0f, 0.0f}, + {0.0f, -1.0f, 0.0f}, + {0.0f, -1.0f, 0.0f}, + + {0.0f, 0.447214f, 0.894427f}, + {0.774597f, 0.447214f, -0.447214f}, + {-0.774597f, 0.447214f, -0.447214f}, + {0.0f, 0.447214f, 0.894427f}, + + {0.0f, 0.447214f, 0.894427f}, + {0.774597f, 0.447214f, -0.447214f}, + {-0.774597f, 0.447214f, -0.447214f}, + {0.0f, 0.447214f, 0.894427f}, + + {0.0f, 0.447214f, 0.894427f}, + {0.774597f, 0.447214f, -0.447214f}, + {-0.774597f, 0.447214f, -0.447214f}, + {0.0f, 0.447214f, 0.894427f} + }), TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(cone.textureCoords2D(0), (std::vector{ + {0.5f, 0.0f}, + + {0.0f, 0.333333f}, + {0.333333f, 0.333333f}, + {0.666667f, 0.333333f}, + {1.0f, 0.333333f}, + + {0.0f, 0.333333f}, + {0.333333f, 0.333333f}, + {0.666667f, 0.333333f}, + {1.0f, 0.333333f}, + + {0.0f, 0.666667f}, + {0.333333f, 0.666667f}, + {0.666667f, 0.666667f}, + {1.0f, 0.666667f}, + + {0.0f, 1.0f}, + {0.333333f, 1.0f}, + {0.666667f, 1.0f}, + {1.0f, 1.0f}, + }), TestSuite::Compare::Container); + + /* Faces of the caps and sides do not share any vertices due to different + normals */ + CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + 0, 2, 1, 0, 3, 2, 0, 4, 3, + 4, 5, 9, 4, 9, 8, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10, + 8, 9, 13, 8, 13, 12, 9, 10, 14, 9, 14, 13, 10, 11, 15, 10, 15, 14 + }), TestSuite::Compare::Container); +} + +void ConeTest::wireframe() { + Trade::MeshData3D cone = coneWireframe(8, 1.5f); + + CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + {0.0f, -1.5f, 1.0f}, + {1.0f, -1.5f, 0.0f}, + {0.0f, -1.5f, -1.0f}, + {-1.0f, -1.5f, 0.0f}, + {0.707107f, -1.5f, 0.707107f}, + {0.707107f, -1.5f, -0.707107f}, + {-0.707107f, -1.5f, -0.707107f}, + {-0.707107f, -1.5f, 0.707107f}, + + {0.0f, 1.5f, 0.0f} + }), TestSuite::Compare::Container); + + CORRADE_COMPARE(cone.normalArrayCount(), 0); + + CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + 0, 4, 1, 5, 2, 6, 3, 7, + 4, 1, 5, 2, 6, 3, 7, 0, + + 0, 8, 1, 8, 2, 8, 3, 8 + }), TestSuite::Compare::Container); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Primitives::Test::ConeTest) diff --git a/src/Magnum/Primitives/Test/CylinderTest.cpp b/src/Magnum/Primitives/Test/CylinderTest.cpp index 959e4181d..cc7ea4fe4 100644 --- a/src/Magnum/Primitives/Test/CylinderTest.cpp +++ b/src/Magnum/Primitives/Test/CylinderTest.cpp @@ -86,7 +86,8 @@ void CylinderTest::solidWithoutAnything() { void CylinderTest::solidWithTextureCoordsAndCaps() { Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords|CylinderFlag::CapEnds); - /* First and last ring are duplicated because they have different normals */ + /* Bottom ring duplicated because it has different normals, first vertex of + each ring duplicated because it has different texture coordinates */ CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ {0.0f, -1.5f, 0.0f},