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