From d833ef33adbe7452dfe15a2531843f7aec7cefde Mon Sep 17 00:00:00 2001 From: Dylan Hemsworth Date: Tue, 6 Sep 2022 18:47:13 -0700 Subject: [PATCH] Add erosion option --- Bin/AdornedControl.dll | Bin 14336 -> 14336 bytes Bin/LibNoise.dll | Bin 75776 -> 100864 bytes Bin/LibnoiseDesigner.exe | Bin 225792 -> 226304 bytes Bin/NetworkUI.dll | Bin 42496 -> 42496 bytes Bin/Utils.dll | Bin 20992 -> 20992 bytes Bin/ZoomAndPan.dll | Bin 20992 -> 20992 bytes LibNoise/BrownianMotionNoise.cs | 217 ++++++++++ LibNoise/DropletErosion.cs | 294 ++++++++++--- LibNoise/FeatureTracer.cs | 391 ++++++++++++++++++ LibNoise/LibNoise.csproj | 2 + LibNoise/Operator/Final.cs | 2 +- .../Viewer/ValidateDiagram.cs | 26 +- LibnoiseDesigner/PreviewWindow.xaml | 2 + LibnoiseDesigner/PreviewWindow.xaml.cs | 19 +- 14 files changed, 891 insertions(+), 62 deletions(-) create mode 100644 LibNoise/BrownianMotionNoise.cs create mode 100644 LibNoise/FeatureTracer.cs diff --git a/Bin/AdornedControl.dll b/Bin/AdornedControl.dll index 77d1ce44db3c0b3a1d62727d9ace8c188c59c970..1917f8635f538d36657ac3df006e88047f0d1e43 100644 GIT binary patch delta 64 zcmZoDXegM_!8Ggp#;!VDf%7_(ScRRxyROsQen{G$_5EfEJq2c<sF|2Dh{IeJ?AOiy3+><&n8W002 W+=E6bzeB0u-@-!XMzFJFF`NdZG#q&V diff --git a/Bin/LibNoise.dll b/Bin/LibNoise.dll index 94dfc5aa94e2e93caed48f7d070dd7868f26536f..bb904e3f1abf4e2135b5c2d54056ecb508d85228 100644 GIT binary patch literal 100864 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�#SuZ@EQ1 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≫;-<0P`<5S6z%RyBa{4sVKMzK z4Jg*R&JUgkRQhj2Rx=ycr(Kscc|7c=jdVa-yR^?wA5rJu`lHpwSN4hY7(D%mXq!u8 z``H#BYb)%t_|Eo_!Q7@vbH17~DB(D1(yC@n4_I&F{JD29e6nNeZW`u&Yiy)n9$kE3 z$)HXl5${p&^tNfrp`)(lB(g${~-CFpED)96rpe8(@>b7Ld!e8&w+V#(M z8T9a!I^Oa0($J!1@6s;wUR7J?nMM42o~$*u>lPLNI<0LFj77jMuayB(l`F zgvSGgeTHR+EXkU6pI-mDJFNU%CT%C(Ju!dpVRd|5=~Ml!YMmxgRfjG)TPBNcx%Pcl z3nKD^Kid9iTWVr9J-=v%SGms)lP(^<_w#~otK)C?X?DNX?mY4+`>U(NlJ}7sAz|aU z))ec@>r$V7zr%Am*|{vU8qC%IVTH}-O|ZUh(o;9RxO;oSo^knbaPmU>9t;d^Iz?+DlU+HaHh3ii){ zEBqf{?2{`8Y^0(5)(D}^@DBL!T}C$b8i5cc#se5q5!X|5{~kMR@?{=52+Pf zP%0gy%{JrU2P`_ki5m(WeHGui+OR)Kd^f5ru!eNrZP04W!+i>OJHk=P%?G$`_ znC8GO`w(BwEBuC=K<4L-7VicMe|rK9eq?WihT{BVao$D>U(Lr@a@Uf#pD1J60{?M4 T!C;UT+qU&x<8Z$Jl-K_Q<^*UX delta 29302 zcmcJ234B!5_5ZnV-n`i-^JYR4AV2~HCJP7x3X1HA5m3>p0YspJ5JjVa3&!w*3m9?3 zv0~i{Qmcsms-;%c)hcYg(8YsXv^`oK`M7trvpU3@IDrI0u|@ z(72)}TGm9{xIUQf1u%#L6UDN)5B94?5;U$4mqAl^w9cPD9l4 zT@Pui4Wf1(WLS0vu6Cw+-T>7n^ZpqdOu&^gT|N)lv#G zZ7fSxx}SuS`!*Buq0GE9t5hnp`pl!eFzZYo4q4xvbQQ$HmLt&Ca88z24PK7JV2)pU z5~-(v>B*5u@(A>;m}XM!N?3Q|!M3a98fd$kz428vMeK^Es1pdT=#LQ!x2?^LonO^^ z0E(A6t=t9yPlequLjJjgJaftX!9&o~aN8B&mZc)~$CE!5Elb7f)8w>rr5kZ$ZqyB| zKscS2n#`}}kMX#^We}RB2ZN-7Zg52!g_?M%r50Dh{5g)35$=c6@V$PJ)4CRIQi*zU z6P#UsYCya&x-R)75GF=;BnvqbQ44+1(TLSkNT%CSibhe4gVAV$aSb$cFgoNKXuB5I z;$T2k+xpCo1-V#olWwv2WWgXuVzDmMZ()zrI><=p18y|m`BB~jSTC4&umGgdt8gv` z$9IjvdNj+%K!Sj!DVw4gCH-UT4x2=@>3axZTmn_m6t*jzric?*(LbZUSzRV0YjH$Kc}8;OU~Qda^LYRcn;a9z78E8rRdQS+NY$FSc+8G;Jp12co zv#>}s>edz5h-22p-Jmw;V%oTvOvo7@-HdA@^U>n(C^xg=?8z#a`NP=*R5J6gvm4aV z%%CM>Ko=~jaC)59`HG>`Xko6IO4 zbZjz5%GIA9!&liEu(?tXcC9}O1!Tq15D_zVR(U$a@>F+1u4tvvAE(U6+l6?6Rg%hB z&+<;p1?}HFUT%0Z(-F~4Zbu*Eddo-;$D}njDzY|~TiIgHhAx!L-jp|%nRQONcL>d< zMS-m*W2a}<&aGCq*xin8`3(Lr|FX!*ZsD8I(%iD z8}e;@My;*Y>28-H=zZAbVkU20Ccv4GmX&*0Q8CBTr@#*@Jqz@Jg#%4rQx&yzrO^@8 z26(_AS(fc8$~d};4nCqaU@TZ)%C7I5Q=0O-erB)%dCR(+fg^aRndU=8{8UrLgp17(=by<3h!1?>$~%r4WdTd=N$%V(z#L-S~$ z{9x6wKu*@n3#V+@$T14){n%`THWN#@`jAe{N#c_ZNb9VvsgpV;&m_c{srZyAUklb zG~N}=Gn}7{r-hXfzwx@TGOFv!ZpR6f_leBu7v|K;;Vv?IzU1iSiI`=H=nJ8?J6^1IchhDdN*?kajd`*vo-*Q*jMe#2WrTA;= z^~N7U@hUg(u|xHT?s0d2%_*li0(HqkeOMB71V?1ECV#ry{ect#1Cq?GJEBYVy zZ5gG@x+V$cksQV}TY`m1;G8vcWmTeEW!u3jA6i*8KPPF^pV1s=p{}=G1@~YPe*uUV z-j2m=!@(&&oWdCqE21+eU7cyT=(e6Z^M!s5+cOFN0XtKf4YVXE7I51=Z#Nmg)#=LI=ib#%+uN%+j!wO!o^9@ ztF*AQL*P*HInYYq3B_};=G@I!EHj(Pv=nkHG`%$XBcrv?MAVRE)9nKwzt@Kv*)Jor%nR#iw3T0MZIx_2RZi(x*_}ATvJG;0)?xY`K zKj26kHeB1X+pxLXxcmE&bPsVl^m$hMEJ-d)O6wRjl~pLyuqud~ol&c%fwrxxN*HZC zCVEmlw5m$|DD&G@eU2$GRCpeKx*z}!J&)rA&Y{`kL`8P_&!jIKQM}YrLorICl#p3F`%{kqG9g)?D~0Kc$sgv4jj;RF~ngI#|y5_ zY0d0wEuE*QT|P9M5-TQRK}bK1`a>Kt3;1_; zowTwMKFe*bLOq-a`l*^vj}e>owM>E~{VXnKr9E9Y8FiSw3|8NWbdfXX4JaINPU17? z6!2@>nbR_rMdi%N(k;gdWJB|im3T;jb+O`r9S!1P$}fB3VafmqTxO+z#qOajKNEb* zOfl2eR<54Qe5-B1u{zdEdSzqH=hX-jtSX;iMJIkauZj=TU?LT$uS^Eg$7K$*_4XQ3 zG2|7TL<0vPl|PBfkyDyXfJE*jilOgX84HHYgT91jhFXqiE0OxMH4bWwm5G}guHSL( z*)AVSaf(DzK9kwJdW5RY{9*OU*?}IQBk$He+Q?V5R~8Z70dMteC6dD%b324{UD#c z`B>}+7s;DKuC+}^mYL!VUCbiJR5jbwqI@}?F8XVOr3QekkxO*l`koNVaIE=M{$qN)R3^y)Sd(6~lAobyp0pH1*)lY053=*`vveS4`5t?Z_oqM61xIV@lFd)_&-dC-P(sz2kJk0}-a(@|35C?rB@211OTMz`=xcc@`qIb? z2d6o{@HtMdkoy=o*mO>h^XI4d!KmwZ5y1CQnI6QF0nk3j(H?xg6Qc%xae>x%Ukb-&w z;zTE!x}=~pG~a7KJ(WvAUS9O@>k<#YE+GZ$&pUMf@q*AT|FDZ@^G~N3XILm_LYLTt zF8Kmrmt=0ZW*m-*zqzKD8jyMCnyPF-7j_S5b;raZxRFt9-vlo|(z(=5SHKqUU6pc8 z56IluQ9tb;)M1&y*VgpWCy3_5 z3uD?(;d5`!q|CBw`exQ$J7OXyd&+O7u4ASyCKV*HeK%Eo@x>S3{B!V;9Of<_2;pH+ zZ*cVd#fL=c;hFzjTcw6&D%ZDnPxry>W^uafs;;y2_R)DSeR7+Z&G$i2p~qgAcwlsi zGvBu7H0842FX^ADx~^1`en6(_x~biLI*ffPIA{zJAAg%4C|_EO*lA&5^B*ov5cUyL zFcn>r_oK1-E;tmmh(9lOd-7wWp=hT2piA6`p_x(F-#HowVJAHVL(qiBhoNQoEGfhx z=IZh$!-%@UB1Q!mna~Xz)!~`VHw;xXGJ9?q;x(X!YwIP<^_#0%-^xVMoTgMSJmc+E zH+01q2)8#QJL~$Fs5G|KaNRglz~V@Bz(Q6?P926TmTGs!cr@V`BJgO4Up`ol3zb=| z=P^9sOz^;#w;S|Z4RzET33)6I6N5R~tX6I&Z%_EFR&JZdLW~cvrQ9&h*U+a>qy?AR-qP*8o}eh75}u-p~b#575mb&Kr8ys zePOKy_G4Yt75Pza)aj0ucULBil&v)zc8eGp`elB4zN}`XjRfsrjvAKke#(C^BSqet z{uMWK4%W;&#F zI2o?gv@<7-g`z&~14)J8T+6BKba?b%P@N2?Ph*6S6CyH8CBKJ89Rm+o^{R}Y_K*3# z2XV^Q&rZeMm(az1@ZNj z4J$EFhq&J7%Z>&Te>U3GoZ!Yht+DB}(F0+kKy8Iqi%I&7G)XHnfDR={0RDpt? zT0zoH7W-A?)Zw15j2+8Q)~sVWwbvJ`?WNVG+*HFcg?@cWcXOT1>BKr}T=gV|y|vO( zx3pMkv9Cq0c31jWj7>rRE49uZZja*r7dz^e&n_I8rtWHQELPh`t1WZO8U`2kKR4yO z2C#lsfm3~T>vFfe;V5bCC@s*_y`r{^Gtjw<{7i!bnmB7C5%B>#k> zyJJpzE~K$LO{~P@K(rr%ne&l_`PUbl4VuaipN0-YDa||AWV3Uv#!SG@)rJf2T==Af zcdjW1+qm`yiE6p+RBnr>L~U5#Z7h7;xUC(eN2EuzELN7nD^6HOSHr$7Z{RttBH79^ zdKq$7;LMHZF}U-A9K#>`5)vJdfs*M=-C1P)Aq4zl(U*f#k zpbq9dSQz!}`6CrOn7QuWo1PbMeDQ;|{jz~KT0EoXs|LQ~`t#nI@a342bg!{T) ztf*r3xftYvJ@@GRp35BBb5}1bmKn=3XJQ;{+L@EKbNe!S)Up5!ysnzUJ~ZYdtT}jp zieGW9Po-pHWBl#bOF;_fjRdbLh3z1jlKuP0`hq}qlQg_l(4u#CtodVlvn?%(S;2E9 z3T3ismnX~3rd{TGWerF3Tfppx>L&zx;f9em@^+p-5g9mIr8CJPf4R}7U z=eS-TrGZH&sHt|V8+v3VbXG}+>j!8V%hQdSrFHgq`!@t&m=hA6n~CX@a?A!dB7&6Q zggT)306V}P&|sCvc>7y}%9A1U_*e)Q+9a_ zHA^XLKffhv12O|m$5ydHymGEmei)M@@gR3lLv~7bo4m8x^a-Z%WoX=l!+8B+RF0#y z%&x)~9(Tu_Z@M%IvB|;i;G^n4`{E0{V>M)`8{k)l!u`U-vkPl0ch5h6NW!#bjAr$! zoCB10(+vywe84YGg;TZlqm#Aiv$hD*?J$WL+B zsy$Ga%Ad8UP9^wAwKh2QS3x;QUEvdTmMkhG54&a3k(=BgO{7g6ZE%=#sYKb3%N%ykka5A$ zcrb^QS&8ds2>F0hRxWQoe+f>?M9XFn?U{nQQ%|9>8}K47>IO_ZwGmY`1NfT3ncMFf zv#P5yx#Yd2I8-DRW!4s;+?_&nTUFwP7*`ERb%`e`Xop(hwcf1dUPdU=%_GL9Em~u2|ZluUkkn-hp5oArDkSm zRK%eR62qbnJ)xMc3$lUhEiT__O|l(&TTQZ0r@083L(MSk(9=S{6J?p7ik{yFhDF2V z^$WcnjngUN-$vpT4l#W|=&ysng_%8Sxa-g@P#ve2Q18&s#bh+dijGe)eKT?q%s-l7 z+B?QIM+!a0$K|slOlL|>GS1qbEn&$&iROUkGW*Z)#i4ggxF7pbPSVcppTHNg8e!p{FC=(XsG;KUsIj;oPPc!W<^1V#iG+ka+AXgd=#=}}xngXvBPy1E~dNi^sJ`%TI zmkBy7$-FdOE@-Dg?Si&PHSa1xSEV$%M$jbbM4GN6JYGytdr&iP66RxKek9!~=m8Nu zlI|09NrENE(8Ge32s#FzC$N@Y((V}gnV{*Sb3FY{BjHWLJ54Nkck*m#Ig#EW94`{I zKgRG>`b?ND!aSA!BWPa<^G?Hfa>FO1j83P3MiEBys9eyAB6$`K60}ZuiwI9|kqHUp z%tTsBqm{2ZK|?TKCnCB)%1h96q|-z?kB$LM&_sw&q~$bT&;sGDL@o196J8rl7IZ1@ zk|*N+XsRGAZa`PiG(kTSw2m4J3|>dm3(yU8g2Knp9>t;NTXYl6g5R&$DZio*Kpm$X z^tAR{lVzZ@qf8e@%D_Fgq!;L2c0d2D_UnBtv&PpS^dh162;B&(=n8!Npy*pd|Hog0 z@^hf4w7+ICy(yZ42JdmGnAQFlp+1NKGK6`Q#q2;C_4y4CZZH2zR%TpDXdd!^>zLMsYXza=%c z)btlxs9K5Y!yGo&F6D*@mp4eOpGCR+w3MehBT=sNd)$MrDrU7$q8dbEs?g(vj#gZA zw9s)vhailz+J^}p0a{H*Cyxf5D)dB9MHS-8{X+K&{kg+~c$PR<>Q8w9e|34BbXc_L zmo&)9YZI_0^faN5 zTBrLRIyZteq<;1y(EE`M4t*Nv5Be)Q5A^jE*Lz8~S5>Lkji5gXF!#4eH+Cok`ej`1(w*qcX4>rM z@&hPeMBn!xSA|(gx6;ee#}J!e3-WHIUqqkw+qAlxnQJT7W3vp?I*o!4`St?cXwY__ z0=mth=fR86CWBrEFG5>2f|j%KrrXnkt^!inTRs%@Y^ZPaMSlq$htnI;vtDR$^w0hT z-6n8t@Tijg{vDy=d4#R&y(DiA0>fw6aE{6TwT< zg9hCgsd5_V(?OcI0lZOEF<7IgV7$&!w5SHh6DxFJ20L-k@zBKDM4ruNXKo zwU?&Q8wMSP^>GTlXVAuQqcfF0HRyZcnLwW#^eA}8k{?eN*~HV}9ZPYIWI(4;MGl?p zOrt4+GBnTU&2gqv1t0DxLoYPDU4O;Ssz#TN7?l(_8Igs&_Wu9GdJs;;Paes(gK5inONtXO-~EDm9CE6=sD-mI|dF@w>sxh0mcs6`NmbPLcW25n2;2h?uR$kZ0+0=mYaqf++)Z7}Ff z@VpD@+XlW1=7mLJTTb_A9)kFwvz(q5bZhYP*mmb4dN;>&FunefLp#8W9>$$`mN;cE zrhx`=%3e$}1?ib~nMX}I_>^-QU0Traa=NLY;pOy;oTNkT^n4CI>$KBn8U;IJdz>pN zIgHJ(4Sp8e0aPVOJ98C{Hi(_MioC*JTvODGYiLtJ!w%Y^QIO|v2mRh4p1#-8n*~a* zqxr+N`AegX&P}w$psS)Yfi5s;0eBneQqRC;U~Zr_8cELHOb_PJ4(DdtmqUMWGGrev zCg|n(tIn-dAxQV|HX3FS_wY8_p(TTUOuRek4+f1-yy4tQuO5!42pp(ciT42C(o8Xb z7k!>XJDj_yYJ@g8&wkapn>HBqAdcsoXpuF3O?*b-qXUn|2TR_BapF&kWk`+X1x4 zpy$EcmdlHm!P`dffd~6*gZ~8cL2{1Jo_-GIgH$R=_vsg&JXCd9I^u2>A#FTGf5t$(j)UG9;KBA@dSFD)(g^8 z=JBG5_jpmtK2En3w0nXcEYSHx(ZqYo%sY>jKIJ?`FBUZ1N$(Xj+*#D{>7ohyG~pSZ zM1~!GmZliQaeB6>-E&3lo};U@q$f+nuj$T$hQBUqxTmP$9(u9B;PdoeLA&Q^@ECSR z$LXb_$h<_~$nipfKhjE#f*$APD|DTKoR_cAcMamS+)F<;h|_W}y=c%s!F!e7Gw5^h zUM2e|Hou03;f3J8(0hV(2JI*NXy)k*+E1$ltqnfpLCs%jje%_buXL?JZ2m2}*&sIm z7TsYGn}3_O7{un^rUx~W)O&~C$)O$2J9Ok&HqVg>1v+VwAa>1r+y6dYZXl1^`}C+5 z4c-=SbUvb=7_>P)6X;okwgwuVkLkAtZ4b-@`lCi-{uBB*hjuuhP}wnA^M7zYrLlq} zGM@jR^bG^q{6A@JPSn9Y;KMoetn(lGB8Peg4iJAgiKj2G`L-%t^KCW7@Jb_%j$fT& zP*r3m(02{u<=J!8j}7GY*-r&NW(D=llK&jeb(5%EzpxX_)52w2x>K=oB zWcQ#RYMVwf6lLmXIkdwmQ?KXHk%4mMMJMS#&9i4ZJ=J1^cwO(MRvNSbZy)wj`vhsf zd#le3Vz+y%swtuc=jBGHpGq6V>D*5ZH;B`@N{umy)457b@(kpBu2#nz#Q9vUPSHqu z(O=z^Lpz-Q>a`pS1qP_-)O<(>tAPfwYlGE1L3&N7S9j*n4yRtdHx=u@o)%W1K?RP@ zqH%$tYOF@VlTwY&2sPWFGgC8xt~6*Q=Ez93-k@=qBO}!<2HA(^XFw@7Ym0$>13O( zmKwy8)78(kWRSPt6V-DD@%DRSk(QIx3z`R=Gx7bBcZ?R^s%9y9?n9n7KTB0i&jzd_*7M^k+^o6nvz2^ozqm4AnoYs>H>q<(bLr*wWJK%T=kkktaGkggO>6u3a8Ph|6JD4oE~idrd%2Mzn*Md~^OdDJdakDa7F<$1qK{nQ}t(<=2#jl{%d z>Wv(l>0G8h6QnbtRV8Mz79FQnb+w?i!Iu-*a@6$(btYzdfVUdR2HVx$2C>0*b)P}p z-!W2%pzG9mhWC`!9=N`!-3_YU@OtBs(+x$u8$I=7 z!@L5_8;h7XsfM}i%#~z=cT*8>gIa2M=YqGPhLi&$>2na}h72uFCT0ra(q{ zg+07Ql`hr|p9k|6wMCGQ#kY#od`mrSBp;wV0^d?E6-aI@lH91?G?E@|32apS(M3O- zKM(vApJw?yda@*6Rh{I&Wc7Z8KT`T%P(R(C;LmWh%&TFR)O3%JRr~`~QE#+Tv^z&F zBU6`Rnb$#m^eL!~=V&Z>YLM%7*((HoVVnh%J~}^^Z=mbXg+o3(+2i^(;#@1JLubKo zfX+-N5 zL(dY4Y@fQjxE7B3>H8AD_2^81j*PO4|0-b@+0if@#r~HKX<>MExiEkaAow=@Qih<=_REU&1)js4G}dH{SC1=OYz1ja(o-I8b@fchvUUbmvz z!3^keJrC(@(S?%K-JR$vgZvLjnrM@UYM@Pi{R}VcfNsk@wxq{CDy;E12#4;CR-_v+ zwP?-Pkk7ZWWFR7FuHOt(cf-1-d#gj`L9qr||Ch;P`6o~xU5wGSX)$__6&q1;%&LAU z1Ba?FH1j1god0b8&?8!?=uqYV_YUON|F5H(qk_F3QOWLgU(&O$*D=p*KZWLXZ~iUE z5H@DhB-xw}W$aGbEek0xw>qLqmfmdnZjro8v}x)UPMB=jkGBCr#Gl%^pbp;qSsxGM zz4i6+1PvEDM(9MLjY4M$oh@`BXb-$#!15OgT_tn_s6)Fzd(rQuX1^y@odP}*`Y)kM zv5KCcRWw{(7#V?&6W7Pb(HM0?dn zffrg>vRSwTMSdX7vtOctbT96%E>zx>;67RfwgWm2Z!R@Jh|SE7d`=zeV|RwCq$?7a zsfFr=*dw5~Cm&JYRufV?QGPsr2i}TdMI%M^Ncus^Cu*b^7%O^4(h*5zjU;}aG?JR) z1FR`j}UgzANAd-M@CLzK=Cr87k73{mbpwRT_w(4XY`Z>{q{2H zz*copvafxMsqc{-VP7TnSBd^RS-)r9DeAsUAE*)5K=J*%w9R_W+Mt%lmfAtQ^>Q(2 zDYb%DP&?>Al%sg-=W5Ufx)yX8-3U68Zn0x{LG9a+nL+n}o<#S7euEwaZK6j&&!8WH zE}$oE3vZpce*8VeZ;OGupwyz@pel&mWRv?rExdT+`YrN<#we(EqCTW9hMB0^4VqBT zgO;jSKr7Unpq1)<&?@!6paazxplKEKS5qtWSL0Uh=fR=)ocynWp_HNz0z*CgnYb9L z22dGjJq-jMA@pdWlcjz-)uH|rshKPEESiLxb7>jq3R;C1wAjEzG0-mM4!VP03U<&I zdK+{beG0lAZ8r&b3$3SL2e*>PLR%qJ9@+|_I#3qcDMGtN#cpt$L%YFU1N`>YILh0|`wcFvDCA?qA>S6NiiUjKXe21t*GhdY>eoeUQGW+0*H83GzWc~%pG7Cj!p~y6gOfzJr$C@EC7ZfsHxLt(S7|QB8M6?5<55zhk`UEJe z+axkug}W8pP<$)6JwchfQ@Fc@yBpl4@!jBF0?OQd!tE4pC%DhVJHdT1?sW$u~e*a)HP! zG?XQqJrQk&=nJ7{h>l7$+dO`2L}-mI@er8~$TTK8ATt+~)om7;%_6f^WVS-)5_c

+T7i_m@%>V(iU;Z6uelARD52+9tH{1O{KCsfG4 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 diff --git a/Bin/NetworkUI.dll b/Bin/NetworkUI.dll index 388bd0d7649bfb3f9e90cec06a9bdf54ff5807f2..aaedd55143c755f7635826dbaa3af77b91bb2713 100644 GIT binary patch delta 109 zcmV-z0FwWJ%mRSS0+5IUn)es6i&=#b>$;MzCrUg;wc2QdI|OLkv-E|*4+8-J0+YCv zHU|L!0ssR51he&(BMJkW_ZO2Jpcxz0E}P>G-Q7Ww>EyJXmz-Mx0000(I$UyPaBs72 Ppr8ShPyrva4x;h{yRs}X delta 136 zcmZoT!_;twX+j5c<#Yax-I1*VtW#MJz1KP6vm?Wli_xbmfAg2tgZ#`)42+X^Of_Q% z(hMvN%$vVXRptU|nJm3P((22m+Esh@+qkTIBxMj^lD~+Nfq}uPt+vA|4k6aH#*YXhd`e0-5)d-b)$~ YfV=Exk1vu>x?VpqG2_yKvj|N11t8lW761SM delta 66 zcmZoz!q~8caY6^v-8&n*&N&N|S + /// Implementation of Brownian Motion Noise + /// + public class BrownianMotionNoise + { + // static Random random = new Random(DateTime.Now.Millisecond + DateTime.Now.Second + DateTime.Now.Minute + DateTime.Now.Hour); + + ///

+ /// Create noise tiled left-to-right + /// + /// + /// + public static void TileNoiseLeftRight(float[,] noise, int buffer) + { + int width = noise.GetLength(0); + int height = noise.GetLength(1); + + for (int x = 0; x < buffer; x++) + { + for (int y = 0; y < height; y++) + { + noise[(width - 1) - x, y] = noise[x, y]; + } + } + } + + /// + /// Merge noise layer into white noise, by the provided percent + /// + /// + /// + /// + public static void AddNoise(float[,] noise, double percent, int seed) + { + int width = noise.GetLength(0); + int height = noise.GetLength(1); + + MergeNoise(noise, GenerateWhiteNoise(width, height, seed), percent); + } + + /// + /// Merge noise layers together, by the provided percent + /// + /// + /// + /// + public static void MergeNoise(float[,] noise, float[,] mergeValues, double percent) + { + int width = noise.GetLength(0); + int height = noise.GetLength(1); + + Parallel.For(0, width, i => + { + for (int j = 0; j < height; j++) + { + noise[i, j] += mergeValues[i, j] * Convert.ToSingle(percent / 100.0); + if (noise[i, j] > 1) noise[i, j] = 1; + } + }); + } + + /// + /// Interpolate noise value + /// + /// + /// + /// + /// + public static float Interpolate(float x0, float x1, float alpha) + { + return x0 * (1 - alpha) + alpha * x1; + } + + /// + /// Generate a white noise base set + /// + /// + /// + /// + /// + public static float[,] GenerateWhiteNoise(int width, int height, int seed) + { + Random random = new Random(seed); + float[,] noise = new float[width, height]; + + Parallel.For(0, width, i => + { + for (int j = 0; j < height; j++) + { + noise[i, j] = (float)random.NextDouble() % 1; + } + }); + + return noise; + } + + /// + /// Create smoothed noise from a base noise layer (usually white noise), with the provided octave + /// + /// + /// + /// + public static float[,] GenerateSmoothNoise(float[,] baseNoise, int octave) + { + int width = baseNoise.GetLength(0); + int height = baseNoise.GetLength(1); + + float[,] smoothNoise = new float[width, height]; + + int samplePeriod = 1 << octave; // calculates 2 ^ k + float sampleFrequency = 1.0f / samplePeriod; + + for (int i = 0; i < width; i++) + { + // calculate the horizontal sampling indices + int sample_i0 = (i / samplePeriod) * samplePeriod; + int sample_i1 = (sample_i0 + samplePeriod) % width; // wrap around + float horizontal_blend = (i - sample_i0) * sampleFrequency; + + for (int j = 0; j < height; j++) + { + // calculate the vertical sampling indices + int sample_j0 = (j / samplePeriod) * samplePeriod; + int sample_j1 = (sample_j0 + samplePeriod) % height; // wrap around + float vertical_blend = (j - sample_j0) * sampleFrequency; + + // blend the top two corners + float top = Interpolate(baseNoise[sample_i0, sample_j0], + baseNoise[sample_i1, sample_j0], horizontal_blend); + + // blend the bottom two corners + float bottom = Interpolate(baseNoise[sample_i0, sample_j1], + baseNoise[sample_i1, sample_j1], horizontal_blend); + + // final blend + smoothNoise[i, j] = Interpolate(top, bottom, vertical_blend); + } + } + + return smoothNoise; + } + + /// + /// Generate Perlin noise + /// + /// + /// + /// + public static float[,] GeneratePerlinNoise(float[,] baseNoise, int octaveCount) + { + int width = baseNoise.GetLength(0); + int height = baseNoise.GetLength(1); + + List smoothNoise = new List(octaveCount); + + float persistance = 0.7f; + + // generate smooth noise + for (int i = 0; i < octaveCount; i++) + { + smoothNoise.Add(GenerateSmoothNoise(baseNoise, i)); + } + + float[,] bmNoise = new float[width, height]; + + float amplitude = 1.0f; + float totalAmplitude = 0.0f; + + // blend noise together + for (int octave = octaveCount - 1; octave >= 0; octave--) + { + amplitude *= persistance; + totalAmplitude += amplitude; + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + bmNoise[i, j] += smoothNoise[octave][i, j] * amplitude; + } + } + } + + // normalisation + Parallel.For(0, width, i => + { + for (int j = 0; j < height; j++) + { + bmNoise[i, j] /= totalAmplitude; + } + }); + + return bmNoise; + } + + /// + /// Create a noise layer of brownian motion noise, with the provided width, height, octave and seed + /// + /// + /// + /// + /// + /// + public static float[,] GenerateBrownianMotionNoise(int width, int height, int octaveCount, int seed) + { + float[,] baseNoise = GenerateWhiteNoise(width, height, seed); + + return GeneratePerlinNoise(baseNoise, octaveCount); + } + } +} diff --git a/LibNoise/DropletErosion.cs b/LibNoise/DropletErosion.cs index 6afcc40..6e002c3 100644 --- a/LibNoise/DropletErosion.cs +++ b/LibNoise/DropletErosion.cs @@ -6,14 +6,23 @@ namespace LibNoise { + /// + /// Simple Erosion simulation for adjusting noise. Taken predominantly + /// from my existing Java project JavaLibNoise + /// public class Erosion { - // Thermal erosion "collapses" cliffs and evens out heights based - // from the difference in height between a point and its neighbours - // This difference is called the "Talus Angle". The lower the value of - // the talus angle, the more erosion will occur. Higher values will - // collapse extreme cliffs, but keep the terrain more jagged - public static void ThermalErosion(float[,] data, float talusAngle, int iterations) + /// + /// Thermal erosion "collapses" cliffs and evens out heights based + /// from the difference in height between a point and its neighbours + /// This difference is called the "Talus Angle". The lower the value of + /// the talus angle, the more erosion will occur. Higher values will + /// collapse extreme cliffs, but keep the terrain more jagged + /// + /// + /// + /// + public static void ThermalErosion(double[,] data, float talusAngle, int iterations) { int width = data.GetLength(0); int height = data.GetLength(1); @@ -26,39 +35,39 @@ public static void ThermalErosion(float[,] data, float talusAngle, int iteration //for (int x = 0; x < width; x++) { // this pixel height value - float heightValue = data[x, y]; + double heightValue = data[x, y]; // neighbouring height values // if we're on an e/w edge, loop around the map. North and south do not loop. - float nw = y == 0 ? -1 : x == 0 ? data[width - 1, y - 1] : data[x - 1, y - 1]; - float n = y == 0 ? -1 : data[x, y - 1]; - float ne = y == 0 ? -1 : x == width - 1 ? data[0, y - 1] : data[x + 1, y - 1]; - float e = x == width - 1 ? data[0, y] : data[x + 1, y]; - float se = y == height - 1 ? -1 : x == width - 1 ? data[0, y + 1] : data[x + 1, y + 1]; - float s = y == height - 1 ? -1 : data[x, y + 1]; - float sw = y == height - 1 ? -1 : x == 0 ? data[width - 1, y + 1] : data[x - 1, y + 1]; - float w = x == 0 ? data[width - 1, y] : data[x - 1, y]; - - List> flows = new List>(); - - flows.Add(new KeyValuePair(1, nw)); - flows.Add(new KeyValuePair(2, n)); - flows.Add(new KeyValuePair(3, ne)); - flows.Add(new KeyValuePair(4, w)); - flows.Add(new KeyValuePair(5, e)); - flows.Add(new KeyValuePair(6, sw)); - flows.Add(new KeyValuePair(7, s)); - flows.Add(new KeyValuePair(8, se)); + double nw = y == 0 ? -1 : x == 0 ? data[width - 1, y - 1] : data[x - 1, y - 1]; + double n = y == 0 ? -1 : data[x, y - 1]; + double ne = y == 0 ? -1 : x == width - 1 ? data[0, y - 1] : data[x + 1, y - 1]; + double e = x == width - 1 ? data[0, y] : data[x + 1, y]; + double se = y == height - 1 ? -1 : x == width - 1 ? data[0, y + 1] : data[x + 1, y + 1]; + double s = y == height - 1 ? -1 : data[x, y + 1]; + double sw = y == height - 1 ? -1 : x == 0 ? data[width - 1, y + 1] : data[x - 1, y + 1]; + double w = x == 0 ? data[width - 1, y] : data[x - 1, y]; + + List> flows = new List>(); + + flows.Add(new KeyValuePair(1, nw)); + flows.Add(new KeyValuePair(2, n)); + flows.Add(new KeyValuePair(3, ne)); + flows.Add(new KeyValuePair(4, w)); + flows.Add(new KeyValuePair(5, e)); + flows.Add(new KeyValuePair(6, sw)); + flows.Add(new KeyValuePair(7, s)); + flows.Add(new KeyValuePair(8, se)); // order slopes by highest to lowest flows = flows.OrderBy(kvp => kvp.Value).ToList(); - foreach (KeyValuePair slope in flows) + foreach (KeyValuePair slope in flows) { if (slope.Value != -1 && heightValue > slope.Value) { - float difference = heightValue - slope.Value; + double difference = heightValue - slope.Value; if (difference >= talusAngle) { @@ -83,9 +92,17 @@ public static void ThermalErosion(float[,] data, float talusAngle, int iteration } } - // Hydraulic erosion without sediment deposition - // set mustHitSeaLevel to TRUE for coastal erosion - public static void HydraulicErosion(float[,] data, float erosionAmount, float seaLevel, bool mustHitSealevel, float minimumHeight, int iterations) + /// + /// Hydraulic erosion without sediment deposition + /// set mustHitSeaLevel to TRUE for coastal erosion + /// + /// + /// + /// + /// + /// + /// + public static void HydraulicErosion(double[,] data, float erosionAmount, float seaLevel, bool mustHitSealevel, float minimumHeight, int iterations) { int width = data.GetLength(0); int height = data.GetLength(1); @@ -108,7 +125,7 @@ public static void HydraulicErosion(float[,] data, float erosionAmount, float se bool stuck = false; bool isLoop = false; bool hitTheSea = false; - float thisHeight = data[newX, newY]; + double thisHeight = data[newX, newY]; List points = new List(); @@ -118,34 +135,34 @@ public static void HydraulicErosion(float[,] data, float erosionAmount, float se { points.Add(new Point(newX, newY)); - float nw = newY == 0 ? -1 : newX == 0 ? data[width - 1, newY - 1] : data[newX - 1, newY - 1]; - float n = newY == 0 ? -1 : data[newX, newY - 1]; - float ne = newY == 0 ? -1 : newX == width - 1 ? data[0, newY - 1] : data[newX + 1, newY - 1]; - float e = newX == width - 1 ? data[0, newY] : data[newX + 1, newY]; - float se = newY == height - 1 ? -1 : newX == width - 1 ? data[0, newY + 1] : data[newX + 1, newY + 1]; - float s = newY == height - 1 ? -1 : data[newX, newY + 1]; - float sw = newY == height - 1 ? -1 : newX == 0 ? data[width - 1, newY + 1] : data[newX - 1, newY + 1]; - float w = newX == 0 ? data[width - 1, newY] : data[newX - 1, newY]; - - List> flows = new List>(); - - flows.Add(new KeyValuePair(1, nw)); - flows.Add(new KeyValuePair(2, n)); - flows.Add(new KeyValuePair(3, ne)); - flows.Add(new KeyValuePair(4, w)); - flows.Add(new KeyValuePair(5, e)); - flows.Add(new KeyValuePair(6, sw)); - flows.Add(new KeyValuePair(7, s)); - flows.Add(new KeyValuePair(8, se)); + double nw = newY == 0 ? -1 : newX == 0 ? data[width - 1, newY - 1] : data[newX - 1, newY - 1]; + double n = newY == 0 ? -1 : data[newX, newY - 1]; + double ne = newY == 0 ? -1 : newX == width - 1 ? data[0, newY - 1] : data[newX + 1, newY - 1]; + double e = newX == width - 1 ? data[0, newY] : data[newX + 1, newY]; + double se = newY == height - 1 ? -1 : newX == width - 1 ? data[0, newY + 1] : data[newX + 1, newY + 1]; + double s = newY == height - 1 ? -1 : data[newX, newY + 1]; + double sw = newY == height - 1 ? -1 : newX == 0 ? data[width - 1, newY + 1] : data[newX - 1, newY + 1]; + double w = newX == 0 ? data[width - 1, newY] : data[newX - 1, newY]; + + List> flows = new List>(); + + flows.Add(new KeyValuePair(1, nw)); + flows.Add(new KeyValuePair(2, n)); + flows.Add(new KeyValuePair(3, ne)); + flows.Add(new KeyValuePair(4, w)); + flows.Add(new KeyValuePair(5, e)); + flows.Add(new KeyValuePair(6, sw)); + flows.Add(new KeyValuePair(7, s)); + flows.Add(new KeyValuePair(8, se)); // order slopes by lowest to highest flows = flows.OrderBy(kvp => kvp.Value).Where(kvp => kvp.Value > 0).ToList(); if (flows.Count > 0) { - KeyValuePair lowestHeight = flows.First(); + KeyValuePair lowestHeight = flows.First(); - float heightPlusWater = thisHeight + waterBuildup[newX, newY]; + double heightPlusWater = thisHeight + waterBuildup[newX, newY]; // get the next direction if (heightPlusWater >= lowestHeight.Value && data[newX, newY] > seaLevel) @@ -207,6 +224,177 @@ public static void HydraulicErosion(float[,] data, float erosionAmount, float se }); } } + + /// + /// This is used to clear out any single pixel dangles and define the coastline areas a little clearer + /// It helps clear up the terrain for a nicer map + /// + /// + /// + /// + /// + public static void Normalize(double[,] noise, int Width, int Height, float SeaLevel) + { + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + double val = noise[x, y]; + + // get the values N,S,E,W + double n = y > 0 ? noise[x, y - 1] : -99.0; + double s = y < Height - 1 ? noise[x, y + 1] : -99.0; + double e = x < Width - 1 ? noise[x + 1, y] : noise[0, y]; // wrap to the other edge + double w = x > 0 ? noise[x - 1, y] : noise[Width - 1, y]; // wrap to the other edge + + int waterNeighbours = 0; + int landNeighbours = 0; + + if (n <= SeaLevel) waterNeighbours++; + else landNeighbours++; + if (e <= SeaLevel) waterNeighbours++; + else landNeighbours++; + if (s <= SeaLevel) waterNeighbours++; + else landNeighbours++; + if (w <= SeaLevel) waterNeighbours++; + else landNeighbours++; + + // If this is a dangle (single pixel surrounded by water or land) then fill it in or sink it down + if (val <= SeaLevel && waterNeighbours < 2) noise[x, y] = SeaLevel + 0.05; + else if (val > SeaLevel && landNeighbours < 2) noise[x, y] = SeaLevel - 0.05; + } + } + } + + /// + /// Detects any low basin areas within a tolerance. These are areas that appear like + /// big hollow lakes or blobs in the middle of larger more continental terrain. This + /// helper method will return the points that are considered within a basin. If you + /// set "fillBasins" to true, it will automatically collapse them to the defined + /// "Sea Level". keepSmallLakes will ignore smaller sections that you might want to + /// keep as lakes + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static int[,] DetectBasins(double[,] noise, int Width, int Height, int tolerance, float SeaLevel, bool fillBasins, bool keepSmallLakes) + { + int[,] setPoints = new int[Width, Height]; + + // create a temporary array for water levels and land + // 1 = land, 2 = lake, 3 = large waterbody (ocean/sea), 4 = Unfilled lake, 0 is unassigned + // unfilled lakes are small lakes that are underneath the tolerance for a basin fill + // these lakes will remain as water after the basin fill process. + // + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + // only process unassigned points + if (setPoints[x, y] == 0) + { + double val = noise[x, y]; + + if (val <= SeaLevel) + { + // check if neighbouring an "ocean" pixel first, before walking points + int n = y > 0 ? setPoints[x, y - 1] : -1; + int s = y < Height - 1 ? setPoints[x, y + 1] : -1; + int e = x < Width - 1 ? setPoints[x + 1, y] : setPoints[0, y]; // wrap to the other edge + int w = x > 0 ? setPoints[x - 1, y] : setPoints[Width - 1, y]; // wrap to the other edge + + int nw = x > 0 && y > 0 ? setPoints[x - 1, y - 1] : -1; + int ne = x < Width - 1 && y > 0 ? setPoints[x + 1, y - 1] : -1; + int sw = x > 0 && y < Height - 1 ? setPoints[x - 1, y + 1] : -1; + int se = x < Width - 1 && y < Height - 1 ? setPoints[x + 1, y + 1] : -1; + + // neighbour is an ocean, and this is water, so it too must be part of the ocean. + // otherwise, this is unassigned and next to land or a small waterbody. We need to scan the points + if (n == 3 || s == 3 || e == 3 || w == 3 || nw == 3 || ne == 3 || sw == 3 || se == 3) setPoints[x, y] = 3; + else if (n == 2 || s == 2 || e == 2 || w == 2 || nw == 2 || ne == 2 || sw == 2 || se == 2) + { + setPoints[x, y] = 2; + } + else if (n == 4 || s == 4 || e == 4 || w == 4 || nw == 4 || ne == 4 || sw == 4 || se == 4) + { + setPoints[x, y] = 4; + } + else + { + List scannedPoints = FeatureTracer.TraceEqualOrBelowValue(noise, x, y, Width, Height, SeaLevel, true); + foreach (Point p in scannedPoints) + { + setPoints[p.X, p.Y] = scannedPoints.Count() <= tolerance ? 2 : 3; + if (keepSmallLakes && scannedPoints.Count() < tolerance / 4) setPoints[p.X, p.Y] = 4; + } + } + } + else setPoints[x, y] = 1; // set as "land" + } + } + } + + //second pass, sw to ne this time, to clean up any missed internal areas + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + double val = noise[x, y]; + + if (val <= SeaLevel) + { + int n = y > 0 ? setPoints[x, y - 1] : -1; + int s = y < Height - 1 ? setPoints[x, y + 1] : -1; + int e = x < Width - 1 ? setPoints[x + 1, y] : setPoints[0, y]; // wrap to the other edge + int w = x > 0 ? setPoints[x - 1, y] : setPoints[Width - 1, y]; // wrap to the other edge + + int nw = x > 0 && y > 0 ? setPoints[x - 1, y - 1] : -1; + int ne = x < Width - 1 && y > 0 ? setPoints[x + 1, y - 1] : -1; + int sw = x > 0 && y < Height - 1 ? setPoints[x - 1, y + 1] : -1; + int se = x < Width - 1 && y < Height - 1 ? setPoints[x + 1, y + 1] : -1; + + // neighbour is an ocean, and this is water, so it too must be part of the ocean. + // otherwise, this is unassigned and next to land or a small waterbody. We need to scan the points + if (n == 3 || s == 3 || e == 3 || w == 3 || nw == 3 || ne == 3 || sw == 3 || se == 3) setPoints[x, y] = 3; + } + } + } + + //final pass, there shouldn't be any corrections needed + //but we'll ensure the ocean/lake values are set, just in case + //at this point, we'll also fill in the basins, if required. + for (int x = Width - 1; x >= 0; x--) + { + for (int y = Height - 1; y >= 0; y--) + { + double val = noise[x, y]; + + if (val <= SeaLevel) + { + int n = y > 0 ? setPoints[x, y - 1] : -1; + int s = y < Height - 1 ? setPoints[x, y + 1] : -1; + int e = x < Width - 1 ? setPoints[x + 1, y] : setPoints[0, y]; // wrap to the other edge + int w = x > 0 ? setPoints[x - 1, y] : setPoints[Width - 1, y]; // wrap to the other edge + + int nw = x > 0 && y > 0 ? setPoints[x - 1, y - 1] : -1; + int ne = x < Width - 1 && y > 0 ? setPoints[x + 1, y - 1] : -1; + int sw = x > 0 && y < Height - 1 ? setPoints[x - 1, y + 1] : -1; + int se = x < Width - 1 && y < Height - 1 ? setPoints[x + 1, y + 1] : -1; + + // neighbour is an ocean, and this is water, so it too must be part of the ocean. + // otherwise, this is unassigned and next to land or a small waterbody. We need to scan the points + if (n == 3 || s == 3 || e == 3 || w == 3 || nw == 3 || ne == 3 || sw == 3 || se == 3) setPoints[x, y] = 3; + else if (setPoints[x, y] == 2 && fillBasins) noise[x, y] = SeaLevel + 0.005f; + } + } + } + return setPoints; + } } public class Point diff --git a/LibNoise/FeatureTracer.cs b/LibNoise/FeatureTracer.cs new file mode 100644 index 0000000..f340b9b --- /dev/null +++ b/LibNoise/FeatureTracer.cs @@ -0,0 +1,391 @@ +using System; +using System.Collections.Generic; + +namespace LibNoise +{ + /// + /// Traces features from noise based on neighbouring values. Taken from https://github.com/dhlevi/JavaLibNoise/blob/master/src/main/java/ca/dhlevi/libnoise/FeatureTracer.java + /// Pretty gnarly implementation, and should be revisted and reworked. + /// + public class FeatureTracer + { + private enum Direction { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest }; + public static List TraceEqualOrBelowValue(double[,] noise, int x, int y, int width, int height, float value, bool wrap) + { + List points = new List(); + + try + { + bool traceComplete = false; + bool firstRun = true; + Point firstPoint = new Point(x, y); + Point lastPoint = firstPoint; + Direction lastDirection = Direction.East; + + while (!traceComplete) + { + if (!firstRun && lastPoint.X == x && lastPoint.Y == y) + { + traceComplete = true; + } + else + { + if (firstRun) firstRun = false; + points.Add(lastPoint); + switch (lastDirection) + { + case Direction.NorthWest: + // check SW W NW N NE E SE S + // first hit that is <= value is the direction to move + if (lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] <= value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] <= value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] <= value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] <= value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else traceComplete = true; + break; + case Direction.North: + // check W NW N NE E SE S SW + // first hit that is <= value is the direction to move + if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] <= value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] <= value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] <= value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] <= value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else traceComplete = true; + break; + case Direction.NorthEast: + // check NW N NE E SE S SW W + // first hit that is <= value is the direction to move + if (lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] <= value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] <= value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] <= value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] <= value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else traceComplete = true; + break; + case Direction.East: + // check N NE E SE S SW W NW + // first hit that is <= value is the direction to move + if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] <= value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] <= value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] <= value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] <= value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else traceComplete = true; + break; + case Direction.SouthEast: + // check NE E SE S SW W NW N + // first hit that is <= value is the direction to move + if (lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] <= value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] <= value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] <= value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] <= value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else traceComplete = true; + break; + case Direction.South: + // check E SE S SW W NW N NE + // first hit that is <= value is the direction to move + if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] <= value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] <= value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] <= value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] <= value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else traceComplete = true; + break; + case Direction.SouthWest: + // check SE S SW W NW N NE E + // first hit that is <= value is the direction to move + if (lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] <= value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] <= value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] <= value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] <= value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else traceComplete = true; + break; + case Direction.West: + // check S SW W NW N NE E SE + // first hit that is <= value is the direction to move + if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] <= value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] <= value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] <= value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] <= value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] <= value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else traceComplete = true; + break; + } + } + } + } + catch (Exception e) + { + points = new List(); + } + + return points; + } + + public static List TraceEqualValue(double[,] noise, int x, int y, int width, int height, float value, bool wrap) + { + bool traceAngles = false; + + List points = new List(); + + try + { + bool traceComplete = false; + bool firstRun = true; + Point firstPoint = new Point(x, y); + Point lastPoint = firstPoint; + Direction lastDirection = Direction.East; + + while (!traceComplete) + { + if (!firstRun && lastPoint.X == x && lastPoint.Y == y) + { + traceComplete = true; + } + else + { + if (firstRun) firstRun = false; + points.Add(lastPoint); + switch (lastDirection) + { + case Direction.NorthWest: + // check SW W NW N NE E SE S + // first hit that is <= value is the direction to move + if (traceAngles && lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] == value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] == value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] == value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] == value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else traceComplete = true; + break; + case Direction.North: + // check W NW N NE E SE S SW + // first hit that is <= value is the direction to move + if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] == value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] == value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] == value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] == value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else traceComplete = true; + break; + case Direction.NorthEast: + // check NW N NE E SE S SW W + // first hit that is <= value is the direction to move + if (traceAngles && lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] == value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] == value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] == value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] == value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else traceComplete = true; + break; + case Direction.East: + // check N NE E SE S SW W NW + // first hit that is <= value is the direction to move + if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] == value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] == value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] == value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] == value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else traceComplete = true; + break; + case Direction.SouthEast: + // check NE E SE S SW W NW N + // first hit that is <= value is the direction to move + if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] == value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] == value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] == value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] == value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else traceComplete = true; + break; + case Direction.South: + // check E SE S SW W NW N NE + // first hit that is <= value is the direction to move + if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] == value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] == value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] == value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] == value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else traceComplete = true; + break; + case Direction.SouthWest: + // check SE S SW W NW N NE E + // first hit that is <= value is the direction to move + if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] == value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] == value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] == value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] == value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else traceComplete = true; + break; + case Direction.West: + // check S SW W NW N NE E SE + // first hit that is <= value is the direction to move + if (lastPoint.Y < height - 1 && noise[lastPoint.X, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y + 1); lastDirection = Direction.South; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y < height - 1 && noise[lastPoint.X - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y < height - 1 && noise[width - 1, lastPoint.Y + 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y + 1); lastDirection = Direction.SouthWest; } + else if (lastPoint.X > 0 && noise[lastPoint.X - 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (wrap && lastPoint.X == 0 && noise[width - 1, lastPoint.Y] == value) { lastPoint = new Point(width - 1, lastPoint.Y); lastDirection = Direction.West; } + else if (traceAngles && lastPoint.X > 0 && lastPoint.Y > 0 && noise[lastPoint.X - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (traceAngles && wrap && lastPoint.X == 0 && lastPoint.Y > 0 && noise[width - 1, lastPoint.Y - 1] == value) { lastPoint = new Point(width - 1, lastPoint.Y - 1); lastDirection = Direction.NorthWest; } + else if (lastPoint.Y > 0 && noise[lastPoint.X, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X, lastPoint.Y - 1); lastDirection = Direction.North; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y > 0 && noise[lastPoint.X + 1, lastPoint.Y - 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y > 0 && noise[0, lastPoint.Y - 1] == value) { lastPoint = new Point(0, lastPoint.Y - 1); lastDirection = Direction.NorthEast; } + else if (lastPoint.X < width - 1 && noise[lastPoint.X + 1, lastPoint.Y] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y); lastDirection = Direction.East; } + else if (wrap && lastPoint.X == width - 1 && noise[0, lastPoint.Y] == value) { lastPoint = new Point(0, lastPoint.Y); lastDirection = Direction.East; } + else if (traceAngles && lastPoint.X < width - 1 && lastPoint.Y < height - 1 && noise[lastPoint.X + 1, lastPoint.Y + 1] == value) { lastPoint = new Point(lastPoint.X + 1, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else if (traceAngles && wrap && lastPoint.X == width - 1 && lastPoint.Y < height - 1 && noise[0, lastPoint.Y + 1] == value) { lastPoint = new Point(0, lastPoint.Y + 1); lastDirection = Direction.SouthEast; } + else traceComplete = true; + break; + } + } + } + } + catch (Exception e) + { + points = new List(); + } + + return points; + } + } +} diff --git a/LibNoise/LibNoise.csproj b/LibNoise/LibNoise.csproj index 774e259..1ea0684 100644 --- a/LibNoise/LibNoise.csproj +++ b/LibNoise/LibNoise.csproj @@ -41,6 +41,8 @@ + + diff --git a/LibNoise/Operator/Final.cs b/LibNoise/Operator/Final.cs index 9b31b13..4b33c90 100644 --- a/LibNoise/Operator/Final.cs +++ b/LibNoise/Operator/Final.cs @@ -6,7 +6,7 @@ public class Final : Cache { public override string GetDescription() { - return "Functionally, the Final Component is a Cache. This components only exists for the designer, and acts as the final node for generating the LibNoise values. You cannot delete the Final node, and only values which are attached to this node will be processed by World Forge, or saved when a LibNoise document is written to disk."; + return "Functionally, the Final Component is a Cache. This components only exists for the designer, and acts as the final node for generating the LibNoise values. You cannot delete the Final node, and only values which are attached to this node will be processed by Libnoise Designer, or saved when a LibNoise document is written to disk."; } } } diff --git a/LibnoiseDesigner/LibnoiseDesigner/Viewer/ValidateDiagram.cs b/LibnoiseDesigner/LibnoiseDesigner/Viewer/ValidateDiagram.cs index 5d9a540..3bb2cae 100644 --- a/LibnoiseDesigner/LibnoiseDesigner/Viewer/ValidateDiagram.cs +++ b/LibnoiseDesigner/LibnoiseDesigner/Viewer/ValidateDiagram.cs @@ -11,16 +11,23 @@ namespace WorldForge.LibnoiseDesigner.Viewer { public class ValidateDiagram { - public static bool Validate(List nodes) - { - bool result = true; + /// + /// Validate a set of Libnoise nodes to ensure they can generate without error, and + /// that their names are unique + /// + /// + /// - result = ValidateNoiseGeneration(nodes); - if(result) result = ValidateUniqueNames(nodes); - - return result; + public static bool Validate(List nodes) + { + return ValidateNoiseGeneration(nodes) && ValidateUniqueNames(nodes); } + /// + /// Validate the LibNoise nodes will generate a result + /// + /// + /// public static bool ValidateNoiseGeneration(List nodes) { bool result = true; @@ -40,6 +47,11 @@ public static bool ValidateNoiseGeneration(List nodes) return result; } + /// + /// Validate that the libnoise nodes contain only unique names + /// + /// + /// public static bool ValidateUniqueNames(List nodes) { bool result = true; diff --git a/LibnoiseDesigner/PreviewWindow.xaml b/LibnoiseDesigner/PreviewWindow.xaml index ca0dc56..d6ec0d7 100644 --- a/LibnoiseDesigner/PreviewWindow.xaml +++ b/LibnoiseDesigner/PreviewWindow.xaml @@ -33,6 +33,8 @@ +