TerrainEngine.cs 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. *
  27. */
  28. using System;
  29. using System.Collections.Generic;
  30. using System.Drawing;
  31. using System.Drawing.Imaging;
  32. using System.Globalization;
  33. using System.IO;
  34. using System.Threading;
  35. using libTerrain;
  36. using OpenJPEGNet;
  37. using OpenSim.Framework;
  38. namespace OpenSim.Region.Terrain
  39. {
  40. public class TerrainCommand
  41. {
  42. public virtual bool run(string[] cmdargs, ref string output)
  43. {
  44. return false;
  45. }
  46. public string args;
  47. public string help;
  48. }
  49. public class TerrainEngine
  50. {
  51. public static Mutex fileIOLock = new Mutex();
  52. /// <summary>
  53. /// Plugin library for scripts
  54. /// </summary>
  55. public FilterHost customFilters = new FilterHost();
  56. /// <summary>
  57. /// A [normally] 256x256 heightmap
  58. /// </summary>
  59. public Channel heightmap;
  60. /// <summary>
  61. /// A copy of heightmap at the last save point (for reverting)
  62. /// </summary>
  63. public Channel revertmap;
  64. /// <summary>
  65. /// Water heightmap (needs clientside mods to work)
  66. /// </summary>
  67. public Channel watermap;
  68. /// <summary>
  69. /// Max amount the terrain can be raised from the revert parameters
  70. /// </summary>
  71. public double maxRaise = 500.0;
  72. /// <summary>
  73. /// Min amount the terrain can be lowered from the revert parameters
  74. /// </summary>
  75. public double minLower = 500.0;
  76. /// <summary>
  77. /// The last time the terrain was edited
  78. /// </summary>
  79. public DateTime lastEdit = DateTime.Now;
  80. /// <summary>
  81. /// Whether or not the terrain has been modified since it was last saved and sent to the Physics engine.
  82. /// Counts the number of modifications since the last save. (0 = Untainted)
  83. /// </summary>
  84. public int tainted;
  85. private int w, h;
  86. /// <summary>
  87. /// Used to determine what offset to use when loading singular heightmaps across multiple sims
  88. /// </summary>
  89. private int offsetX;
  90. private int offsetY;
  91. /// <summary>
  92. /// Generate a new TerrainEngine instance and creates a new heightmap
  93. /// </summary>
  94. public TerrainEngine(int X, int Y)
  95. {
  96. w = 256;
  97. h = 256;
  98. heightmap = new Channel(w, h);
  99. revertmap = new Channel(w, h);
  100. watermap = new Channel(w, h);
  101. watermap.Fill(20);
  102. offsetX = X;
  103. offsetY = Y;
  104. tainted++;
  105. }
  106. public bool Tainted()
  107. {
  108. return (tainted != 0);
  109. }
  110. public bool StillEditing()
  111. {
  112. TimeSpan gap = DateTime.Now - lastEdit;
  113. if (gap.TotalSeconds <= 4.0)
  114. return true;
  115. return false;
  116. }
  117. public bool Tainted(int x, int y)
  118. {
  119. return (heightmap.diff[x/16, y/16] != 0);
  120. }
  121. public void ResetTaint()
  122. {
  123. tainted = 0;
  124. heightmap.diff = new int[w/16,h/16];
  125. }
  126. //Testing to see if moving the TerraForming packet handling code into here works well
  127. /// <summary>
  128. /// Modifies terrain using the specified information
  129. /// </summary>
  130. /// <param name="height">The height at which the user started modifying the terrain</param>
  131. /// <param name="seconds">The number of seconds the modify button was pressed</param>
  132. /// <param name="brushsize">The size of the brush used</param>
  133. /// <param name="action">The action to be performed</param>
  134. /// <param name="north">Distance from the north border where the cursor is located</param>
  135. /// <param name="west">Distance from the west border where the cursor is located</param>
  136. public void ModifyTerrain(float height, float seconds, byte brushsize, byte action, float north, float west,
  137. float south, float east,
  138. IClientAPI remoteUser)
  139. {
  140. // Shiny.
  141. double size = (double) (1 << brushsize);
  142. /* Okay, so here's the deal
  143. * This has to handle both when a user draws on the terrain *and* when a user selects
  144. * a selection of AABB on terrain and applies whatever routine the client requests
  145. * There's something currently wrong with the brushsize --> size conversion.. however
  146. * it's workable.. just unpredictable.
  147. *
  148. * North is always higher and East is always higher
  149. * in the AABB representation
  150. *
  151. * Therefore what we're doing is looping from south to north and west to east
  152. * and applying the associated algorithm with the brush.
  153. *
  154. * This works good on the fast ones, but things like smooth take 12 seconds a single click..
  155. * for now, smooth won't be 'selectionated'
  156. *
  157. * If the user draws instead of selects, north will = south, and east will = west.
  158. * if the user selects, then the selection is inclusive
  159. * it'll always affect at least one point on the heightmap.
  160. *
  161. * that means we use the <= operator
  162. *
  163. * Again, libTerrain is yx instead of xy.. so, it's reflected in the function calls
  164. *
  165. */
  166. switch (action)
  167. {
  168. case 0:
  169. // flatten terrain
  170. for (float x = south; x <= north; x++)
  171. {
  172. for (float y = west; y <= east; y++)
  173. {
  174. FlattenTerrain(y, x, size, (double) seconds/5.0);
  175. lastEdit = DateTime.Now;
  176. }
  177. }
  178. break;
  179. case 1:
  180. // raise terrain
  181. for (float x = south; x <= north; x++)
  182. {
  183. for (float y = west; y <= east; y++)
  184. {
  185. RaiseTerrain(y, x, size, (double) seconds/5.0);
  186. lastEdit = DateTime.Now;
  187. }
  188. }
  189. break;
  190. case 2:
  191. //lower terrain
  192. for (float x = south; x <= north; x++)
  193. {
  194. for (float y = west; y <= east; y++)
  195. {
  196. LowerTerrain(y, x, size, (double) seconds/5.0);
  197. lastEdit = DateTime.Now;
  198. }
  199. }
  200. break;
  201. case 3:
  202. // smooth terrain
  203. //
  204. // We're leaving this out of the parcel calculations for now
  205. // because just a single one of these will stall your sim for
  206. // 12 seconds. Looping over the parcel on this one is just stupid
  207. //
  208. //for (float x = south; x <= north; x++)
  209. //{
  210. //for (float y = west; y <= east; y++)
  211. //{
  212. //SmoothTerrain(y, x , size, (double)seconds / 5.0);
  213. //}
  214. //}
  215. SmoothTerrain(west, north, size, (double) seconds/5.0);
  216. break;
  217. case 4:
  218. // noise
  219. for (float x = south; x <= north; x++)
  220. {
  221. for (float y = west; y <= east; y++)
  222. {
  223. NoiseTerrain(y, x, size, (double) seconds/5.0);
  224. lastEdit = DateTime.Now;
  225. }
  226. }
  227. break;
  228. case 5:
  229. // revert
  230. for (float x = south; x <= north; x++)
  231. {
  232. for (float y = west; y <= east; y++)
  233. {
  234. RevertTerrain(y, x, size, (double) seconds/5.0);
  235. lastEdit = DateTime.Now;
  236. }
  237. }
  238. break;
  239. // CLIENT EXTENSIONS GO HERE
  240. case 128:
  241. // erode-thermal
  242. break;
  243. case 129:
  244. // erode-aerobic
  245. break;
  246. case 130:
  247. // erode-hydraulic
  248. break;
  249. }
  250. for (int x = 0; x < 16; x++)
  251. {
  252. for (int y = 0; y < 16; y++)
  253. {
  254. if (Tainted(x*16, y*16))
  255. {
  256. remoteUser.SendLayerData(x, y, GetHeights1D());
  257. }
  258. }
  259. }
  260. lastEdit = DateTime.Now;
  261. return;
  262. }
  263. /// <summary>
  264. /// Checks to make sure the terrain is within baked values +/- maxRaise/minLower
  265. /// </summary>
  266. public void CheckHeightValues()
  267. {
  268. int x, y;
  269. for (x = 0; x < w; x++)
  270. {
  271. for (y = 0; y < h; y++)
  272. {
  273. if ((heightmap.Get(x, y) > revertmap.Get(x, y) + maxRaise))
  274. {
  275. heightmap.map[x, y] = revertmap.Get(x, y) + maxRaise;
  276. }
  277. if ((heightmap.Get(x, y) > revertmap.Get(x, y) - minLower))
  278. {
  279. heightmap.map[x, y] = revertmap.Get(x, y) - minLower;
  280. }
  281. }
  282. }
  283. }
  284. /// <summary>
  285. /// Converts the heightmap to a 65536 value 1D floating point array
  286. /// </summary>
  287. /// <returns>A float[65536] array containing the heightmap</returns>
  288. public float[] GetHeights1D()
  289. {
  290. float[] heights = new float[w*h];
  291. int i;
  292. for (i = 0; i < w*h; i++)
  293. {
  294. heights[i] = (float) heightmap.map[i%w, i/w];
  295. }
  296. return heights;
  297. }
  298. /// <summary>
  299. /// Converts the heightmap to a 256x256 value 2D floating point array.
  300. /// </summary>
  301. /// <returns>An array of 256,256 values containing the heightmap</returns>
  302. public float[,] GetHeights2D()
  303. {
  304. float[,] heights = new float[w,h];
  305. int x, y;
  306. for (x = 0; x < w; x++)
  307. {
  308. for (y = 0; y < h; y++)
  309. {
  310. heights[x, y] = (float) heightmap.map[x, y];
  311. }
  312. }
  313. return heights;
  314. }
  315. /// <summary>
  316. /// Converts the heightmap to a 256x256 value 2D floating point array. Double precision version.
  317. /// </summary>
  318. /// <returns>An array of 256,256 values containing the heightmap</returns>
  319. public double[,] GetHeights2DD()
  320. {
  321. return heightmap.map;
  322. }
  323. /// <summary>
  324. /// Imports a 1D floating point array into the 2D heightmap array
  325. /// </summary>
  326. /// <param name="heights">The array to import (must have 65536 members)</param>
  327. public void GetHeights1D(float[] heights)
  328. {
  329. int i;
  330. for (i = 0; i < w*h; i++)
  331. {
  332. heightmap.map[i%w, i/w] = heights[i];
  333. }
  334. tainted++;
  335. }
  336. /// <summary>
  337. /// Loads a 2D array of values into the heightmap
  338. /// </summary>
  339. /// <param name="heights">An array of 256,256 float values</param>
  340. public void SetHeights2D(float[,] heights)
  341. {
  342. int x, y;
  343. for (x = 0; x < w; x++)
  344. {
  345. for (y = 0; y < h; y++)
  346. {
  347. heightmap.Set(x, y, (double) heights[x, y]);
  348. }
  349. }
  350. SaveRevertMap();
  351. tainted++;
  352. }
  353. /// <summary>
  354. /// Loads a 2D array of values into the heightmap (Double Precision Version)
  355. /// </summary>
  356. /// <param name="heights">An array of 256,256 float values</param>
  357. public void SetHeights2D(double[,] heights)
  358. {
  359. int x, y;
  360. for (x = 0; x < w; x++)
  361. {
  362. for (y = 0; y < h; y++)
  363. {
  364. heightmap.Set(x, y, heights[x, y]);
  365. }
  366. }
  367. SaveRevertMap();
  368. tainted++;
  369. }
  370. /// <summary>
  371. /// Swaps the two heightmap buffers (the 'revert map' and the heightmap)
  372. /// </summary>
  373. public void SwapRevertMaps()
  374. {
  375. Channel backup = heightmap.Copy();
  376. heightmap = revertmap;
  377. revertmap = backup;
  378. }
  379. /// <summary>
  380. /// Saves the current heightmap into the revertmap
  381. /// </summary>
  382. public void SaveRevertMap()
  383. {
  384. revertmap = heightmap.Copy();
  385. }
  386. /// <summary>
  387. /// Processes a terrain-specific command
  388. /// </summary>
  389. /// <param name="args">Commandline arguments (space seperated)</param>
  390. /// <param name="resultText">Reference that returns error or help text if returning false</param>
  391. /// <returns>If the operation was successful (if not, the error is placed into resultText)</returns>
  392. public bool RunTerrainCmd(string[] args, ref string resultText, string simName)
  393. {
  394. string command;
  395. if (args.Length > 0)
  396. {
  397. command = args[0];
  398. }
  399. else
  400. {
  401. command = "help";
  402. }
  403. try
  404. {
  405. switch (command)
  406. {
  407. case "help":
  408. resultText += "terrain regenerate - rebuilds the sims terrain using a default algorithm\n";
  409. resultText +=
  410. "terrain hills <type> <number of hills> <min height> <max height> <island t/f> <additive t/f> <noisy t/f>\n";
  411. resultText += " type should be spheres, blocks, cones, or squared\n";
  412. resultText +=
  413. "terrain voronoi <points> <blocksize> - generates a worley fractal with X points per block";
  414. resultText += "terrain seed <seed> - sets the random seed value to <seed>\n";
  415. resultText +=
  416. "terrain load <type> <filename> - loads a terrain from disk, type can be 'F32', 'F64', 'RAW' or 'IMG'\n";
  417. resultText +=
  418. "terrain save <type> <filename> - saves a terrain to disk, type can be 'F32', 'F64', 'PNG', 'RAW' or 'HIRAW'\n";
  419. resultText +=
  420. "terrain save grdmap <filename> <gradient map> - creates a PNG snapshot of the region using a named gradient map\n";
  421. resultText +=
  422. "terrain rescale <min> <max> - rescales a terrain to be between <min> and <max> meters high\n";
  423. resultText += "terrain fill <val> - fills a terrain at the specified height\n";
  424. resultText +=
  425. "terrain erode aerobic <windspeed> <pickupmin> <dropmin> <carry> <rounds> <lowest t/f> <fluid dynamics t/f>\n";
  426. resultText += "terrain erode thermal <talus> <rounds> <carry>\n";
  427. resultText += "terrain erode hydraulic <rain> <evaporation> <solubility> <frequency> <rounds>\n";
  428. resultText += "terrain multiply <val> - multiplies a terrain by <val>\n";
  429. resultText += "terrain revert - reverts the terrain to the stored original\n";
  430. resultText += "terrain bake - saves the current terrain into the revert map\n";
  431. resultText +=
  432. "terrain csfilter <filename.cs> - loads a new filter from the specified .cs file\n";
  433. resultText +=
  434. "terrain jsfilter <filename.js> - loads a new filter from the specified .js file\n";
  435. foreach (KeyValuePair<string, ITerrainFilter> filter in customFilters.filters)
  436. {
  437. resultText += filter.Value.Help();
  438. }
  439. return false;
  440. case "revert":
  441. SwapRevertMaps();
  442. SaveRevertMap();
  443. break;
  444. case "bake":
  445. SaveRevertMap();
  446. break;
  447. case "seed":
  448. SetSeed(Convert.ToInt32(args[1]));
  449. break;
  450. case "erode":
  451. return ConsoleErosion(args, ref resultText);
  452. case "voronoi":
  453. double[] c = new double[2];
  454. c[0] = -1;
  455. c[1] = 1;
  456. heightmap.VoronoiDiagram(Convert.ToInt32(args[1]), Convert.ToInt32(args[2]), c);
  457. break;
  458. case "hills":
  459. return ConsoleHills(args, ref resultText);
  460. case "regenerate":
  461. HillsGenerator();
  462. break;
  463. case "rescale":
  464. SetRange(Convert.ToSingle(args[1]), Convert.ToSingle(args[2]));
  465. break;
  466. case "fill":
  467. heightmap.Fill(Convert.ToDouble(args[1]));
  468. tainted++;
  469. break;
  470. case "clip":
  471. heightmap.Clip(Convert.ToDouble(args[1]), Convert.ToDouble(args[2]));
  472. tainted++;
  473. break;
  474. case "smooth":
  475. heightmap.Smooth(Convert.ToDouble(args[1]));
  476. tainted++;
  477. break;
  478. case "add":
  479. heightmap += Convert.ToDouble(args[1]);
  480. tainted++;
  481. break;
  482. case "multiply":
  483. heightmap *= Convert.ToDouble(args[1]);
  484. tainted++;
  485. break;
  486. case "load":
  487. string filenameL = args[2].Replace("%name%", simName);
  488. filenameL = filenameL.Replace("%x%", offsetX.ToString());
  489. filenameL = filenameL.Replace("%y%", offsetY.ToString());
  490. switch (args[1].ToLower())
  491. {
  492. case "f32":
  493. LoadFromFileF32(filenameL);
  494. break;
  495. case "f64":
  496. LoadFromFileF64(filenameL);
  497. break;
  498. case "raw":
  499. LoadFromFileSLRAW(filenameL);
  500. break;
  501. case "img":
  502. heightmap = heightmap.LoadImage(filenameL);
  503. tainted++;
  504. break;
  505. default:
  506. resultText = "Unknown image or data format";
  507. return false;
  508. }
  509. break;
  510. case "load-tile":
  511. switch (args[1].ToLower())
  512. {
  513. case "f32":
  514. LoadFromFileF32(args[2], Convert.ToInt32(args[3]), Convert.ToInt32(args[4]),
  515. Convert.ToInt32(args[5]), Convert.ToInt32(args[6]));
  516. break;
  517. case "img":
  518. LoadFromFileIMG(args[2], Convert.ToInt32(args[3]), Convert.ToInt32(args[4]),
  519. Convert.ToInt32(args[5]), Convert.ToInt32(args[6]));
  520. break;
  521. default:
  522. resultText = "Unknown or unsupported image or data format";
  523. return false;
  524. }
  525. break;
  526. case "save":
  527. string filename = args[2].Replace("%name%", simName);
  528. filename = filename.Replace("%x%", offsetX.ToString());
  529. filename = filename.Replace("%y%", offsetY.ToString());
  530. switch (args[1].ToLower())
  531. {
  532. case "f32":
  533. WriteToFileF32(filename);
  534. break;
  535. case "f64":
  536. WriteToFileF64(filename);
  537. break;
  538. case "grdmap":
  539. if (args.Length >= 4)
  540. ExportImage(filename, args[3]);
  541. else
  542. ExportImage(filename, "defaultstripe.png");
  543. break;
  544. case "png":
  545. heightmap.SaveImage(filename);
  546. break;
  547. case "raw":
  548. WriteToFileRAW(filename);
  549. break;
  550. case "hiraw":
  551. WriteToFileHiRAW(filename);
  552. break;
  553. default:
  554. resultText = "Unknown image or data format";
  555. return false;
  556. }
  557. break;
  558. case "csfilter":
  559. customFilters.LoadFilterCSharp(args[1]);
  560. break;
  561. case "jsfilter":
  562. customFilters.LoadFilterJScript(args[1]);
  563. break;
  564. default:
  565. // Run any custom registered filters
  566. if (customFilters.filters.ContainsKey(command))
  567. {
  568. customFilters.filters[command].Filter(heightmap, args);
  569. break;
  570. }
  571. else
  572. {
  573. resultText = "Unknown terrain command";
  574. return false;
  575. }
  576. }
  577. return true;
  578. }
  579. catch (Exception e)
  580. {
  581. resultText = "Error running terrain command: " + e.ToString();
  582. return false;
  583. }
  584. }
  585. private bool ConsoleErosion(string[] args, ref string resultText)
  586. {
  587. double min = heightmap.FindMin();
  588. double max = heightmap.FindMax();
  589. switch (args[1].ToLower())
  590. {
  591. case "aerobic":
  592. // WindSpeed, PickupMinimum,DropMinimum,Carry,Rounds,Lowest
  593. heightmap.AerobicErosion(Convert.ToDouble(args[2]), Convert.ToDouble(args[3]),
  594. Convert.ToDouble(args[4]), Convert.ToDouble(args[5]),
  595. Convert.ToInt32(args[6]), Convert.ToBoolean(args[7]),
  596. Convert.ToBoolean(args[8]));
  597. break;
  598. case "thermal":
  599. heightmap.ThermalWeathering(Convert.ToDouble(args[2]), Convert.ToInt32(args[3]),
  600. Convert.ToDouble(args[4]));
  601. break;
  602. case "hydraulic":
  603. Channel rainMap = new Channel(w, h);
  604. rainMap.Fill(Convert.ToDouble(args[2]));
  605. heightmap.HydraulicErosion(rainMap, Convert.ToDouble(args[3]), Convert.ToDouble(args[4]),
  606. Convert.ToInt32(args[5]), Convert.ToInt32(args[6]));
  607. break;
  608. default:
  609. resultText = "Unknown erosion type";
  610. return false;
  611. }
  612. heightmap.Normalise(min, max);
  613. tainted++;
  614. return true;
  615. }
  616. private bool ConsoleHills(string[] args, ref string resultText)
  617. {
  618. Random RandomClass = new Random();
  619. SetSeed(RandomClass.Next());
  620. int count;
  621. double sizeMin;
  622. double sizeRange;
  623. bool island;
  624. bool additive;
  625. bool noisy;
  626. if (args.GetLength(0) > 2)
  627. {
  628. int.TryParse(args[2].ToString(), out count);
  629. double.TryParse(args[3].ToString(), NumberStyles.AllowDecimalPoint, Culture.NumberFormatInfo,
  630. out sizeMin);
  631. double.TryParse(args[4].ToString(), NumberStyles.AllowDecimalPoint, Culture.NumberFormatInfo,
  632. out sizeRange);
  633. bool.TryParse(args[5].ToString(), out island);
  634. bool.TryParse(args[6].ToString(), out additive);
  635. bool.TryParse(args[7].ToString(), out noisy);
  636. }
  637. else
  638. {
  639. count = 200;
  640. sizeMin = 20;
  641. sizeRange = 40;
  642. island = true;
  643. additive = true;
  644. noisy = false;
  645. }
  646. switch (args[1].ToLower())
  647. {
  648. case "blocks":
  649. heightmap.HillsBlocks(count, sizeMin, sizeRange, island, additive, noisy);
  650. break;
  651. case "cones":
  652. heightmap.HillsCones(count, sizeMin, sizeRange, island, additive, noisy);
  653. break;
  654. case "spheres":
  655. heightmap.HillsSpheres(count, sizeMin, sizeRange, island, additive, noisy);
  656. break;
  657. case "squared":
  658. heightmap.HillsSquared(count, sizeMin, sizeRange, island, additive, noisy);
  659. break;
  660. default:
  661. resultText = "Unknown hills type";
  662. return false;
  663. }
  664. tainted++;
  665. return true;
  666. }
  667. /// <summary>
  668. /// Renormalises the array between min and max
  669. /// </summary>
  670. /// <param name="min">Minimum value of the new array</param>
  671. /// <param name="max">Maximum value of the new array</param>
  672. public void SetRange(float min, float max)
  673. {
  674. heightmap.Normalise((double) min, (double) max);
  675. tainted++;
  676. }
  677. /// <summary>
  678. /// Loads a file consisting of 256x256 doubles and imports it as an array into the map.
  679. /// </summary>
  680. /// <remarks>TODO: Move this to libTerrain itself</remarks>
  681. /// <param name="filename">The filename of the double array to import</param>
  682. public void LoadFromFileF64(string filename)
  683. {
  684. FileInfo file = new FileInfo(filename);
  685. FileStream s = file.Open(FileMode.Open, FileAccess.Read);
  686. BinaryReader bs = new BinaryReader(s);
  687. int x, y;
  688. for (y = 0; y < h; y++)
  689. {
  690. for (x = 0; x < h; x++)
  691. {
  692. heightmap.map[x, y] = bs.ReadDouble();
  693. }
  694. }
  695. bs.Close();
  696. s.Close();
  697. tainted++;
  698. }
  699. /// <summary>
  700. /// Loads a file consisting of 256x256 floats and imports it as an array into the map.
  701. /// </summary>
  702. /// <remarks>TODO: Move this to libTerrain itself</remarks>
  703. /// <param name="filename">The filename of the float array to import</param>
  704. public void LoadFromFileF32(string filename)
  705. {
  706. FileInfo file = new FileInfo(filename);
  707. FileStream s = file.Open(FileMode.Open, FileAccess.Read);
  708. BinaryReader bs = new BinaryReader(s);
  709. int x, y;
  710. for (y = 0; y < h; y++)
  711. {
  712. for (x = 0; x < w; x++)
  713. {
  714. heightmap.map[x, y] = (double) bs.ReadSingle();
  715. }
  716. }
  717. bs.Close();
  718. s.Close();
  719. tainted++;
  720. }
  721. /// <summary>
  722. /// Loads a section of a larger heightmap (F32)
  723. /// </summary>
  724. /// <param name="filename">File to load</param>
  725. /// <param name="dimensionX">Size of the file</param>
  726. /// <param name="dimensionY">Size of the file</param>
  727. /// <param name="lowerboundX">Where do the region coords start for this terrain?</param>
  728. /// <param name="lowerboundY">Where do the region coords start for this terrain?</param>
  729. public void LoadFromFileF32(string filename, int dimensionX, int dimensionY, int lowerboundX, int lowerboundY)
  730. {
  731. fileIOLock.WaitOne();
  732. try
  733. {
  734. int sectionToLoadX = ((offsetX - lowerboundX)*w);
  735. int sectionToLoadY = ((offsetY - lowerboundY)*h);
  736. double[,] tempMap = new double[dimensionX,dimensionY];
  737. FileInfo file = new FileInfo(filename);
  738. FileStream s = file.Open(FileMode.Open, FileAccess.Read);
  739. BinaryReader bs = new BinaryReader(s);
  740. int x, y;
  741. for (x = 0; x < dimensionX; x++)
  742. {
  743. for (y = 0; y < dimensionY; y++)
  744. {
  745. tempMap[x, y] = (double) bs.ReadSingle();
  746. }
  747. }
  748. for (y = 0; y < h; y++)
  749. {
  750. for (x = 0; x < w; x++)
  751. {
  752. heightmap.Set(x, y, tempMap[x + sectionToLoadX, y + sectionToLoadY]);
  753. }
  754. }
  755. bs.Close();
  756. s.Close();
  757. tainted++;
  758. }
  759. finally
  760. {
  761. fileIOLock.ReleaseMutex();
  762. }
  763. }
  764. /// <summary>
  765. /// Loads a larger tiled image across a terrain
  766. /// </summary>
  767. /// <param name="filename">Filename to load from (any generic image format should work)</param>
  768. /// <param name="dimensionX">The dimensions of the image</param>
  769. /// <param name="dimensionY">The dimensions of the image</param>
  770. /// <param name="lowerboundX">Where sim coords begin for this patch</param>
  771. /// <param name="lowerboundY">Where sim coords begin for this patch</param>
  772. public void LoadFromFileIMG(string filename, int dimensionX, int dimensionY, int lowerboundX, int lowerboundY)
  773. {
  774. int sectionToLoadX = ((offsetX - lowerboundX)*w);
  775. int sectionToLoadY = ((offsetY - lowerboundY)*h);
  776. double[,] tempMap = new double[dimensionX,dimensionY];
  777. Bitmap lgrBmp = new Bitmap(filename);
  778. int x, y;
  779. for (x = 0; x < dimensionX; x++)
  780. {
  781. for (y = 0; y < dimensionY; y++)
  782. {
  783. tempMap[x, y] = (float) lgrBmp.GetPixel(x, y).GetBrightness();
  784. }
  785. }
  786. for (y = 0; y < h; y++)
  787. {
  788. for (x = 0; x < w; x++)
  789. {
  790. heightmap.Set(x, y, tempMap[x + sectionToLoadX, y + sectionToLoadY]);
  791. }
  792. }
  793. tainted++;
  794. }
  795. /// <summary>
  796. /// Loads a file formatted in the SL .RAW Format used on the main grid
  797. /// </summary>
  798. /// <remarks>This file format stinks and is best avoided.</remarks>
  799. /// <param name="filename">A path to the .RAW format</param>
  800. public void LoadFromFileSLRAW(string filename)
  801. {
  802. FileInfo file = new FileInfo(filename);
  803. FileStream s = file.Open(FileMode.Open, FileAccess.Read);
  804. BinaryReader bs = new BinaryReader(s);
  805. int x, y;
  806. for (y = 0; y < h; y++)
  807. {
  808. for (x = 0; x < w; x++)
  809. {
  810. heightmap.map[x, y] = (double) bs.ReadByte()*((double) bs.ReadByte()/127.0);
  811. bs.ReadBytes(11); // Advance the stream to next bytes.
  812. }
  813. }
  814. bs.Close();
  815. s.Close();
  816. tainted++;
  817. }
  818. /// <summary>
  819. /// Writes the current terrain heightmap to disk, in the format of a 65536 entry double[] array.
  820. /// </summary>
  821. /// <param name="filename">The desired output filename</param>
  822. public void WriteToFileF64(string filename)
  823. {
  824. FileInfo file = new FileInfo(filename);
  825. FileStream s = file.Open(FileMode.CreateNew, FileAccess.Write);
  826. BinaryWriter bs = new BinaryWriter(s);
  827. int x, y;
  828. for (y = 0; y < h; y++)
  829. {
  830. for (x = 0; x < w; x++)
  831. {
  832. bs.Write(heightmap.Get(x, y));
  833. }
  834. }
  835. bs.Close();
  836. s.Close();
  837. }
  838. /// <summary>
  839. /// Writes the current terrain heightmap to disk, in the format of a 65536 entry float[] array
  840. /// </summary>
  841. /// <param name="filename">The desired output filename</param>
  842. public void WriteToFileF32(string filename)
  843. {
  844. FileInfo file = new FileInfo(filename);
  845. FileStream s = file.Open(FileMode.CreateNew, FileAccess.Write);
  846. BinaryWriter bs = new BinaryWriter(s);
  847. int x, y;
  848. for (y = 0; y < h; y++)
  849. {
  850. for (x = 0; x < w; x++)
  851. {
  852. bs.Write((float) heightmap.Get(x, y));
  853. }
  854. }
  855. bs.Close();
  856. s.Close();
  857. }
  858. /// <summary>
  859. /// A very fast LL-RAW file output mechanism - lower precision mechanism but wont take 5 minutes to run either.
  860. /// (is also editable in an image application)
  861. /// </summary>
  862. /// <param name="filename">Filename to write to</param>
  863. public void WriteToFileRAW(string filename)
  864. {
  865. FileInfo file = new FileInfo(filename);
  866. FileStream s = file.Open(FileMode.CreateNew, FileAccess.Write);
  867. BinaryWriter binStream = new BinaryWriter(s);
  868. int x, y;
  869. // Used for the 'green' channel.
  870. byte avgMultiplier = (byte) heightmap.Avg();
  871. byte backupMultiplier = (byte) revertmap.Avg();
  872. // Limit the multiplier so it can represent points >64m.
  873. if (avgMultiplier > 196)
  874. avgMultiplier = 196;
  875. if (backupMultiplier > 196)
  876. backupMultiplier = 196;
  877. // Make sure it's at least one to prevent a div by zero
  878. if (avgMultiplier < 1)
  879. avgMultiplier = 1;
  880. if (backupMultiplier < 1)
  881. backupMultiplier = 1;
  882. for (y = 0; y < h; y++)
  883. {
  884. for (x = 0; x < h; x++)
  885. {
  886. byte red = (byte) (heightmap.Get(x, y)/((double) avgMultiplier/128.0));
  887. byte green = avgMultiplier;
  888. byte blue = (byte) watermap.Get(x, y);
  889. byte alpha1 = 0; // Land Parcels
  890. byte alpha2 = 0; // For Sale Land
  891. byte alpha3 = 0; // Public Edit Object
  892. byte alpha4 = 0; // Public Edit Land
  893. byte alpha5 = 255; // Safe Land
  894. byte alpha6 = 255; // Flying Allowed
  895. byte alpha7 = 255; // Create Landmark
  896. byte alpha8 = 255; // Outside Scripts
  897. byte alpha9 = (byte) (revertmap.Get(x, y)/((double) backupMultiplier/128.0));
  898. byte alpha10 = backupMultiplier;
  899. binStream.Write(red);
  900. binStream.Write(green);
  901. binStream.Write(blue);
  902. binStream.Write(alpha1);
  903. binStream.Write(alpha2);
  904. binStream.Write(alpha3);
  905. binStream.Write(alpha4);
  906. binStream.Write(alpha5);
  907. binStream.Write(alpha6);
  908. binStream.Write(alpha7);
  909. binStream.Write(alpha8);
  910. binStream.Write(alpha9);
  911. binStream.Write(alpha10);
  912. }
  913. }
  914. binStream.Close();
  915. s.Close();
  916. }
  917. /// <summary>
  918. /// Outputs to a LL compatible RAW in the most efficient manner possible
  919. /// </summary>
  920. /// <remarks>Does not calculate the revert map</remarks>
  921. /// <param name="filename">The filename to output to</param>
  922. public void WriteToFileHiRAW(string filename)
  923. {
  924. FileInfo file = new FileInfo(filename);
  925. FileStream s = file.Open(FileMode.CreateNew, FileAccess.Write);
  926. BinaryWriter binStream = new BinaryWriter(s);
  927. // Generate a smegging big lookup table to speed the operation up (it needs it)
  928. double[] lookupHeightTable = new double[65536];
  929. int i, j, x, y;
  930. for (i = 0; i < 256; i++)
  931. {
  932. for (j = 0; j < 256; j++)
  933. {
  934. lookupHeightTable[i + (j*256)] = ((double) i*((double) j/127.0));
  935. }
  936. }
  937. // Output the calculated raw
  938. for (y = 0; y < h; y++)
  939. {
  940. for (x = 0; x < w; x++)
  941. {
  942. double t = heightmap.Get(x, y);
  943. double min = double.MaxValue;
  944. int index = 0;
  945. for (i = 0; i < 65536; i++)
  946. {
  947. if (Math.Abs(t - lookupHeightTable[i]) < min)
  948. {
  949. min = Math.Abs(t - lookupHeightTable[i]);
  950. index = i;
  951. }
  952. }
  953. byte red = (byte) (index & 0xFF);
  954. byte green = (byte) ((index >> 8) & 0xFF);
  955. byte blue = (byte) watermap.Get(x, y);
  956. byte alpha1 = 0; // Land Parcels
  957. byte alpha2 = 0; // For Sale Land
  958. byte alpha3 = 0; // Public Edit Object
  959. byte alpha4 = 0; // Public Edit Land
  960. byte alpha5 = 255; // Safe Land
  961. byte alpha6 = 255; // Flying Allowed
  962. byte alpha7 = 255; // Create Landmark
  963. byte alpha8 = 255; // Outside Scripts
  964. byte alpha9 = red;
  965. byte alpha10 = green;
  966. binStream.Write(red);
  967. binStream.Write(green);
  968. binStream.Write(blue);
  969. binStream.Write(alpha1);
  970. binStream.Write(alpha2);
  971. binStream.Write(alpha3);
  972. binStream.Write(alpha4);
  973. binStream.Write(alpha5);
  974. binStream.Write(alpha6);
  975. binStream.Write(alpha7);
  976. binStream.Write(alpha8);
  977. binStream.Write(alpha9);
  978. binStream.Write(alpha10);
  979. }
  980. }
  981. binStream.Close();
  982. s.Close();
  983. }
  984. /// <summary>
  985. /// Sets the random seed to be used by procedural functions which involve random numbers.
  986. /// </summary>
  987. /// <param name="val">The desired seed</param>
  988. public void SetSeed(int val)
  989. {
  990. heightmap.seed = val;
  991. }
  992. /// <summary>
  993. /// Sets a particular heightmap point to a specified value
  994. /// </summary>
  995. /// <param name="x">X Coordinate</param>
  996. /// <param name="y">Y Coordinate</param>
  997. /// <param name="val">Value</param>
  998. public void Set(int x, int y, double val)
  999. {
  1000. lock (heightmap)
  1001. {
  1002. heightmap.Set(x, y, val);
  1003. }
  1004. tainted++;
  1005. }
  1006. /// <summary>
  1007. /// Raises land in a sphere around the specified coordinates
  1008. /// </summary>
  1009. /// <param name="rx">Center of the sphere on the X axis</param>
  1010. /// <param name="ry">Center of the sphere on the Y axis</param>
  1011. /// <param name="size">The radius of the sphere</param>
  1012. /// <param name="amount">Scale the height of the sphere by this amount (recommended 0..2)</param>
  1013. public void RaiseTerrain(double rx, double ry, double size, double amount)
  1014. {
  1015. lock (heightmap)
  1016. {
  1017. heightmap.Raise(rx, ry, size, amount);
  1018. }
  1019. tainted++;
  1020. }
  1021. /// <summary>
  1022. /// Lowers the land in a sphere around the specified coordinates
  1023. /// </summary>
  1024. /// <param name="rx">The center of the sphere at the X axis</param>
  1025. /// <param name="ry">The center of the sphere at the Y axis</param>
  1026. /// <param name="size">The radius of the sphere in meters</param>
  1027. /// <param name="amount">Scale the height of the sphere by this amount (recommended 0..2)</param>
  1028. public void LowerTerrain(double rx, double ry, double size, double amount)
  1029. {
  1030. lock (heightmap)
  1031. {
  1032. heightmap.Lower(rx, ry, size, amount);
  1033. }
  1034. tainted++;
  1035. }
  1036. /// <summary>
  1037. /// Flattens the land under the brush of specified coordinates (spherical mask)
  1038. /// </summary>
  1039. /// <param name="rx">Center of sphere</param>
  1040. /// <param name="ry">Center of sphere</param>
  1041. /// <param name="size">Radius of the sphere</param>
  1042. /// <param name="amount">Thickness of the mask (0..2 recommended)</param>
  1043. public void FlattenTerrain(double rx, double ry, double size, double amount)
  1044. {
  1045. lock (heightmap)
  1046. {
  1047. heightmap.Flatten(rx, ry, size, amount);
  1048. }
  1049. tainted++;
  1050. }
  1051. /// <summary>
  1052. /// Creates noise within the specified bounds
  1053. /// </summary>
  1054. /// <param name="rx">Center of the bounding sphere</param>
  1055. /// <param name="ry">Center of the bounding sphere</param>
  1056. /// <param name="size">The radius of the sphere</param>
  1057. /// <param name="amount">Strength of the mask (0..2) recommended</param>
  1058. public void NoiseTerrain(double rx, double ry, double size, double amount)
  1059. {
  1060. lock (heightmap)
  1061. {
  1062. Channel smoothed = new Channel();
  1063. smoothed.Noise();
  1064. Channel mask = new Channel();
  1065. mask.Raise(rx, ry, size, amount);
  1066. heightmap.Blend(smoothed, mask);
  1067. }
  1068. tainted++;
  1069. }
  1070. /// <summary>
  1071. /// Reverts land within the specified bounds
  1072. /// </summary>
  1073. /// <param name="rx">Center of the bounding sphere</param>
  1074. /// <param name="ry">Center of the bounding sphere</param>
  1075. /// <param name="size">The radius of the sphere</param>
  1076. /// <param name="amount">Strength of the mask (0..2) recommended</param>
  1077. public void RevertTerrain(double rx, double ry, double size, double amount)
  1078. {
  1079. lock (heightmap)
  1080. {
  1081. Channel mask = new Channel();
  1082. mask.Raise(rx, ry, size, amount);
  1083. heightmap.Blend(revertmap, mask);
  1084. }
  1085. tainted++;
  1086. }
  1087. /// <summary>
  1088. /// Smooths land under the brush of specified coordinates (spherical mask)
  1089. /// </summary>
  1090. /// <param name="rx">Center of the sphere</param>
  1091. /// <param name="ry">Center of the sphere</param>
  1092. /// <param name="size">Radius of the sphere</param>
  1093. /// <param name="amount">Thickness of the mask (0..2 recommended)</param>
  1094. public void SmoothTerrain(double rx, double ry, double size, double amount)
  1095. {
  1096. lock (heightmap)
  1097. {
  1098. Channel smoothed = heightmap.Copy();
  1099. smoothed.Smooth(amount);
  1100. Channel mask = new Channel();
  1101. mask.Raise(rx, ry, size, amount);
  1102. heightmap.Blend(smoothed, mask);
  1103. }
  1104. tainted++;
  1105. }
  1106. /// <summary>
  1107. /// Generates a simple set of hills in the shape of an island
  1108. /// </summary>
  1109. public void HillsGenerator()
  1110. {
  1111. lock (heightmap)
  1112. {
  1113. heightmap.HillsSpheres(200, 20, 40, true, true, false);
  1114. heightmap.Normalise();
  1115. heightmap *= 60.0; // Raise to 60m
  1116. heightmap.Clip(0.0, 25.0);
  1117. heightmap.Pertubation(2.5);
  1118. heightmap.Smooth(35.0);
  1119. heightmap.Normalise(0.0, 21.0);
  1120. }
  1121. tainted++;
  1122. }
  1123. /// <summary>
  1124. /// Wrapper to heightmap.get()
  1125. /// </summary>
  1126. /// <param name="x">X coord</param>
  1127. /// <param name="y">Y coord</param>
  1128. /// <returns>Height at specified coordinates</returns>
  1129. public double GetHeight(int x, int y)
  1130. {
  1131. return heightmap.Get(x, y);
  1132. }
  1133. /// <summary>
  1134. /// Multiplies the heightfield by val
  1135. /// </summary>
  1136. /// <param name="meep">The heightfield</param>
  1137. /// <param name="val">The multiplier</param>
  1138. /// <returns></returns>
  1139. public static TerrainEngine operator *(TerrainEngine terrain, Double val)
  1140. {
  1141. terrain.heightmap *= val;
  1142. terrain.tainted++;
  1143. return terrain;
  1144. }
  1145. /// <summary>
  1146. /// Exports the current heightmap to a PNG file
  1147. /// </summary>
  1148. /// <param name="filename">The destination filename for the image</param>
  1149. /// <param name="gradientmap">A 1x*height* image which contains the colour gradient to export with. Must be at least 1x2 pixels, 1x256 or more is ideal.</param>
  1150. public void ExportImage(string filename, string gradientmap)
  1151. {
  1152. try
  1153. {
  1154. Bitmap bmp = TerrainToBitmap(gradientmap);
  1155. bmp.Save(filename, ImageFormat.Png);
  1156. }
  1157. catch (Exception e)
  1158. {
  1159. Console.WriteLine("Failed generating terrain map: " + e.ToString());
  1160. }
  1161. }
  1162. /// <summary>
  1163. /// Exports the current heightmap in Jpeg2000 format to a byte[]
  1164. /// </summary>
  1165. /// <param name="gradientmap">A 1x*height* image which contains the colour gradient to export with. Must be at least 1x2 pixels, 1x256 or more is ideal.</param>
  1166. public byte[] ExportJpegImage(string gradientmap)
  1167. {
  1168. byte[] imageData = null;
  1169. try
  1170. {
  1171. Bitmap bmp = TerrainToBitmap(gradientmap);
  1172. imageData = OpenJPEG.EncodeFromImage(bmp, true);
  1173. }
  1174. catch (Exception e)
  1175. {
  1176. Console.WriteLine("Failed generating terrain map: " + e.ToString());
  1177. }
  1178. return imageData;
  1179. }
  1180. private Bitmap TerrainToBitmap(string gradientmap)
  1181. {
  1182. Bitmap gradientmapLd = new Bitmap(gradientmap);
  1183. int pallete = gradientmapLd.Height;
  1184. Bitmap bmp = new Bitmap(heightmap.w, heightmap.h);
  1185. Color[] colours = new Color[pallete];
  1186. for (int i = 0; i < pallete; i++)
  1187. {
  1188. colours[i] = gradientmapLd.GetPixel(0, i);
  1189. }
  1190. Channel copy = heightmap.Copy();
  1191. for (int y = 0; y < copy.h; y++)
  1192. {
  1193. for (int x = 0; x < copy.w; x++)
  1194. {
  1195. // 512 is the largest possible height before colours clamp
  1196. int colorindex = (int) (Math.Max(Math.Min(1.0, copy.Get(x, y)/512.0), 0.0)*(pallete - 1));
  1197. bmp.SetPixel(x, copy.h - y - 1, colours[colorindex]);
  1198. }
  1199. }
  1200. return bmp;
  1201. }
  1202. }
  1203. }