clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 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 OpenSimulator 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.IO;
31 using System.Reflection;
32 using System.Net;
33  
34 using log4net;
35 using Nini.Config;
36  
37 using OpenMetaverse;
38 using Mono.Addins;
39  
40 using OpenSim.Data;
41 using OpenSim.Framework;
42 using OpenSim.Framework.Console;
43 using OpenSim.Region.CoreModules.Framework.InterfaceCommander;
44 using OpenSim.Region.CoreModules.World.Terrain.FileLoaders;
45 using OpenSim.Region.CoreModules.World.Terrain.FloodBrushes;
46 using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes;
47 using OpenSim.Region.Framework.Interfaces;
48 using OpenSim.Region.Framework.Scenes;
49  
50 namespace OpenSim.Region.CoreModules.World.Terrain
51 {
52 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "TerrainModule")]
53 public class TerrainModule : INonSharedRegionModule, ICommandableModule, ITerrainModule
54 {
55 #region StandardTerrainEffects enum
56  
57 /// <summary>
58 /// A standard set of terrain brushes and effects recognised by viewers
59 /// </summary>
60 public enum StandardTerrainEffects : byte
61 {
62 Flatten = 0,
63 Raise = 1,
64 Lower = 2,
65 Smooth = 3,
66 Noise = 4,
67 Revert = 5,
68  
69 // Extended brushes
70 Erode = 255,
71 Weather = 254,
72 Olsen = 253
73 }
74  
75 #endregion
76  
77 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
78  
79 #pragma warning disable 414
80 private static readonly string LogHeader = "[TERRAIN MODULE]";
81 #pragma warning restore 414
82  
83 private readonly Commander m_commander = new Commander("terrain");
84  
85 private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects =
86 new Dictionary<StandardTerrainEffects, ITerrainFloodEffect>();
87  
88 private readonly Dictionary<string, ITerrainLoader> m_loaders = new Dictionary<string, ITerrainLoader>();
89  
90 private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects =
91 new Dictionary<StandardTerrainEffects, ITerrainPaintableEffect>();
92  
93 private ITerrainChannel m_channel;
94 private Dictionary<string, ITerrainEffect> m_plugineffects;
95 private ITerrainChannel m_revert;
96 private Scene m_scene;
97 private volatile bool m_tainted;
98 private readonly Stack<LandUndoState> m_undo = new Stack<LandUndoState>(5);
99  
100 private String m_InitialTerrain = "pinhead-island";
101  
102 // If true, send terrain patch updates to clients based on their view distance
103 private bool m_sendTerrainUpdatesByViewDistance = true;
104  
105 // Class to keep the per client collection of terrain patches that must be sent.
106 // A patch is set to 'true' meaning it should be sent to the client. Once the
107 // patch packet is queued to the client, the bit for that patch is set to 'false'.
108 private class PatchUpdates
109 {
110 private bool[,] updated; // for each patch, whether it needs to be sent to this client
111 private int updateCount; // number of patches that need to be sent
112 public ScenePresence Presence; // a reference to the client to send to
113 public PatchUpdates(TerrainData terrData, ScenePresence pPresence)
114 {
115 updated = new bool[terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize];
116 updateCount = 0;
117 Presence = pPresence;
118 // Initially, send all patches to the client
119 SetAll(true);
120 }
121 // Returns 'true' if there are any patches marked for sending
122 public bool HasUpdates()
123 {
124 return (updateCount > 0);
125 }
126 public void SetByXY(int x, int y, bool state)
127 {
128 this.SetByPatch(x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, state);
129 }
130 public bool GetByPatch(int patchX, int patchY)
131 {
132 return updated[patchX, patchY];
133 }
134 public void SetByPatch(int patchX, int patchY, bool state)
135 {
136 bool prevState = updated[patchX, patchY];
137 if (!prevState && state)
138 updateCount++;
139 if (prevState && !state)
140 updateCount--;
141 updated[patchX, patchY] = state;
142 }
143 public void SetAll(bool state)
144 {
145 updateCount = 0;
146 for (int xx = 0; xx < updated.GetLength(0); xx++)
147 for (int yy = 0; yy < updated.GetLength(1); yy++)
148 updated[xx, yy] = state;
149 if (state)
150 updateCount = updated.GetLength(0) * updated.GetLength(1);
151 }
152 // Logically OR's the terrain data's patch taint map into this client's update map.
153 public void SetAll(TerrainData terrData)
154 {
155 if (updated.GetLength(0) != (terrData.SizeX / Constants.TerrainPatchSize)
156 || updated.GetLength(1) != (terrData.SizeY / Constants.TerrainPatchSize))
157 {
158 throw new Exception(
159 String.Format("{0} PatchUpdates.SetAll: patch array not same size as terrain. arr=<{1},{2}>, terr=<{3},{4}>",
160 LogHeader, updated.GetLength(0), updated.GetLength(1),
161 terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize)
162 );
163 }
164 for (int xx = 0; xx < terrData.SizeX; xx += Constants.TerrainPatchSize)
165 {
166 for (int yy = 0; yy < terrData.SizeY; yy += Constants.TerrainPatchSize)
167 {
168 // Only set tainted. The patch bit may be set if the patch was to be sent later.
169 if (terrData.IsTaintedAt(xx, yy, false))
170 {
171 this.SetByXY(xx, yy, true);
172 }
173 }
174 }
175 }
176 }
177  
178 // The flags of which terrain patches to send for each of the ScenePresence's
179 private Dictionary<UUID, PatchUpdates> m_perClientPatchUpdates = new Dictionary<UUID, PatchUpdates>();
180  
181 /// <summary>
182 /// Human readable list of terrain file extensions that are supported.
183 /// </summary>
184 private string m_supportedFileExtensions = "";
185  
186 //For terrain save-tile file extensions
187 private string m_supportFileExtensionsForTileSave = "";
188  
189 #region ICommandableModule Members
190  
191 public ICommander CommandInterface
192 {
193 get { return m_commander; }
194 }
195  
196 #endregion
197  
198 #region INonSharedRegionModule Members
199  
200 /// <summary>
201 /// Creates and initialises a terrain module for a region
202 /// </summary>
203 /// <param name="scene">Region initialising</param>
204 /// <param name="config">Config for the region</param>
205 public void Initialise(IConfigSource config)
206 {
207 IConfig terrainConfig = config.Configs["Terrain"];
208 if (terrainConfig != null)
209 {
210 m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain);
211 m_sendTerrainUpdatesByViewDistance = terrainConfig.GetBoolean("SendTerrainUpdatesByViewDistance", m_sendTerrainUpdatesByViewDistance);
212 }
213 }
214  
215 public void AddRegion(Scene scene)
216 {
217 m_scene = scene;
218  
219 // Install terrain module in the simulator
220 lock (m_scene)
221 {
222 if (m_scene.Heightmap == null)
223 {
224 m_channel = new TerrainChannel(m_InitialTerrain, (int)m_scene.RegionInfo.RegionSizeX,
225 (int)m_scene.RegionInfo.RegionSizeY,
226 (int)m_scene.RegionInfo.RegionSizeZ);
227 m_scene.Heightmap = m_channel;
228 UpdateRevertMap();
229 }
230 else
231 {
232 m_channel = m_scene.Heightmap;
233 UpdateRevertMap();
234 }
235  
236 m_scene.RegisterModuleInterface<ITerrainModule>(this);
237 m_scene.EventManager.OnNewClient += EventManager_OnNewClient;
238 m_scene.EventManager.OnClientClosed += EventManager_OnClientClosed;
239 m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole;
240 m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick;
241 m_scene.EventManager.OnFrame += EventManager_OnFrame;
242 }
243  
244 InstallDefaultEffects();
245 LoadPlugins();
246  
247 // Generate user-readable extensions list
248 string supportedFilesSeparator = "";
249 string supportedFilesSeparatorForTileSave = "";
250  
251 m_supportFileExtensionsForTileSave = "";
252 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
253 {
254 m_supportedFileExtensions += supportedFilesSeparator + loader.Key + " (" + loader.Value + ")";
255 supportedFilesSeparator = ", ";
256  
257 //For terrain save-tile file extensions
258 if (loader.Value.SupportsTileSave() == true)
259 {
260 m_supportFileExtensionsForTileSave += supportedFilesSeparatorForTileSave + loader.Key + " (" + loader.Value + ")";
261 supportedFilesSeparatorForTileSave = ", ";
262 }
263 }
264 }
265  
266 public void RegionLoaded(Scene scene)
267 {
268 //Do this here to give file loaders time to initialize and
269 //register their supported file extensions and file formats.
270 InstallInterfaces();
271 }
272  
273 public void RemoveRegion(Scene scene)
274 {
275 lock (m_scene)
276 {
277 // remove the commands
278 m_scene.UnregisterModuleCommander(m_commander.Name);
279 // remove the event-handlers
280 m_scene.EventManager.OnFrame -= EventManager_OnFrame;
281 m_scene.EventManager.OnTerrainTick -= EventManager_OnTerrainTick;
282 m_scene.EventManager.OnPluginConsole -= EventManager_OnPluginConsole;
283 m_scene.EventManager.OnClientClosed -= EventManager_OnClientClosed;
284 m_scene.EventManager.OnNewClient -= EventManager_OnNewClient;
285 // remove the interface
286 m_scene.UnregisterModuleInterface<ITerrainModule>(this);
287 }
288 }
289  
290 public void Close()
291 {
292 }
293  
294 public Type ReplaceableInterface
295 {
296 get { return null; }
297 }
298  
299 public string Name
300 {
301 get { return "TerrainModule"; }
302 }
303  
304 #endregion
305  
306 #region ITerrainModule Members
307  
308 public void UndoTerrain(ITerrainChannel channel)
309 {
310 m_channel = channel;
311 }
312  
313 /// <summary>
314 /// Loads a terrain file from disk and installs it in the scene.
315 /// </summary>
316 /// <param name="filename">Filename to terrain file. Type is determined by extension.</param>
317 public void LoadFromFile(string filename)
318 {
319 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
320 {
321 if (filename.EndsWith(loader.Key))
322 {
323 lock (m_scene)
324 {
325 try
326 {
327 ITerrainChannel channel = loader.Value.LoadFile(filename);
328 if (channel.Width != m_scene.RegionInfo.RegionSizeX || channel.Height != m_scene.RegionInfo.RegionSizeY)
329 {
330 // TerrainChannel expects a RegionSize x RegionSize map, currently
331 throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}",
332 m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY));
333 }
334 m_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height);
335 m_scene.Heightmap = channel;
336 m_channel = channel;
337 UpdateRevertMap();
338 }
339 catch (NotImplementedException)
340 {
341 m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
342 " parser does not support file loading. (May be save only)");
343 throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
344 }
345 catch (FileNotFoundException)
346 {
347 m_log.Error(
348 "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)");
349 throw new TerrainException(
350 String.Format("unable to load heightmap: file {0} not found (or permissions do not allow access", filename));
351 }
352 catch (ArgumentException e)
353 {
354 m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message);
355 throw new TerrainException(
356 String.Format("Unable to load heightmap: {0}", e.Message));
357 }
358 }
359 m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
360 return;
361 }
362 }
363  
364 m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
365 throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
366 }
367  
368 /// <summary>
369 /// Saves the current heightmap to a specified file.
370 /// </summary>
371 /// <param name="filename">The destination filename</param>
372 public void SaveToFile(string filename)
373 {
374 try
375 {
376 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
377 {
378 if (filename.EndsWith(loader.Key))
379 {
380 loader.Value.SaveFile(filename, m_channel);
381 m_log.InfoFormat("[TERRAIN]: Saved terrain from {0} to {1}", m_scene.RegionInfo.RegionName, filename);
382 return;
383 }
384 }
385 }
386 catch (IOException ioe)
387 {
388 m_log.Error(String.Format("[TERRAIN]: Unable to save to {0}, {1}", filename, ioe.Message));
389 }
390  
391 m_log.ErrorFormat(
392 "[TERRAIN]: Could not save terrain from {0} to {1}. Valid file extensions are {2}",
393 m_scene.RegionInfo.RegionName, filename, m_supportedFileExtensions);
394 }
395  
396 /// <summary>
397 /// Loads a terrain file from the specified URI
398 /// </summary>
399 /// <param name="filename">The name of the terrain to load</param>
400 /// <param name="pathToTerrainHeightmap">The URI to the terrain height map</param>
401 public void LoadFromStream(string filename, Uri pathToTerrainHeightmap)
402 {
403 LoadFromStream(filename, URIFetch(pathToTerrainHeightmap));
404 }
405  
406 public void LoadFromStream(string filename, Stream stream)
407 {
408 LoadFromStream(filename, Vector3.Zero, 0f, Vector2.Zero, stream);
409 }
410  
411 /// <summary>
412 /// Loads a terrain file from a stream and installs it in the scene.
413 /// </summary>
414 /// <param name="filename">Filename to terrain file. Type is determined by extension.</param>
415 /// <param name="stream"></param>
416 public void LoadFromStream(string filename, Vector3 displacement,
417 float radianRotation, Vector2 rotationDisplacement, Stream stream)
418 {
419 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
420 {
421 if (filename.EndsWith(loader.Key))
422 {
423 lock (m_scene)
424 {
425 try
426 {
427 ITerrainChannel channel = loader.Value.LoadStream(stream);
428 m_channel.Merge(channel, displacement, radianRotation, rotationDisplacement);
429 UpdateRevertMap();
430 }
431 catch (NotImplementedException)
432 {
433 m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
434 " parser does not support file loading. (May be save only)");
435 throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
436 }
437 }
438  
439 m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
440 return;
441 }
442 }
443 m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
444 throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
445 }
446  
447 private static Stream URIFetch(Uri uri)
448 {
449 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
450  
451 // request.Credentials = credentials;
452  
453 request.ContentLength = 0;
454 request.KeepAlive = false;
455  
456 WebResponse response = request.GetResponse();
457 Stream file = response.GetResponseStream();
458  
459 if (response.ContentLength == 0)
460 throw new Exception(String.Format("{0} returned an empty file", uri.ToString()));
461  
462 // return new BufferedStream(file, (int) response.ContentLength);
463 return new BufferedStream(file, 1000000);
464 }
465  
466 /// <summary>
467 /// Modify Land
468 /// </summary>
469 /// <param name="pos">Land-position (X,Y,0)</param>
470 /// <param name="size">The size of the brush (0=small, 1=medium, 2=large)</param>
471 /// <param name="action">0=LAND_LEVEL, 1=LAND_RAISE, 2=LAND_LOWER, 3=LAND_SMOOTH, 4=LAND_NOISE, 5=LAND_REVERT</param>
472 /// <param name="agentId">UUID of script-owner</param>
473 public void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId)
474 {
475 float duration = 0.25f;
476 if (action == 0)
477 duration = 4.0f;
478  
479 client_OnModifyTerrain(user, (float)pos.Z, duration, size, action, pos.Y, pos.X, pos.Y, pos.X, agentId);
480 }
481  
482 /// <summary>
483 /// Saves the current heightmap to a specified stream.
484 /// </summary>
485 /// <param name="filename">The destination filename. Used here only to identify the image type</param>
486 /// <param name="stream"></param>
487 public void SaveToStream(string filename, Stream stream)
488 {
489 try
490 {
491 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
492 {
493 if (filename.EndsWith(loader.Key))
494 {
495 loader.Value.SaveStream(stream, m_channel);
496 return;
497 }
498 }
499 }
500 catch (NotImplementedException)
501 {
502 m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented.");
503 throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented"));
504 }
505 }
506  
507 // Someone diddled terrain outside the normal code paths. Set the taintedness for all clients.
508 // ITerrainModule.TaintTerrain()
509 public void TaintTerrain ()
510 {
511 lock (m_perClientPatchUpdates)
512 {
513 // Set the flags for all clients so the tainted patches will be sent out
514 foreach (PatchUpdates pups in m_perClientPatchUpdates.Values)
515 {
516 pups.SetAll(m_scene.Heightmap.GetTerrainData());
517 }
518 }
519 }
520  
521 // ITerrainModule.PushTerrain()
522 public void PushTerrain(IClientAPI pClient)
523 {
524 if (m_sendTerrainUpdatesByViewDistance)
525 {
526 ScenePresence presence = m_scene.GetScenePresence(pClient.AgentId);
527 if (presence != null)
528 {
529 lock (m_perClientPatchUpdates)
530 {
531 PatchUpdates pups;
532 if (!m_perClientPatchUpdates.TryGetValue(pClient.AgentId, out pups))
533 {
534 // There is a ScenePresence without a send patch map. Create one.
535 pups = new PatchUpdates(m_scene.Heightmap.GetTerrainData(), presence);
536 m_perClientPatchUpdates.Add(presence.UUID, pups);
537 }
538 pups.SetAll(true);
539 }
540 }
541 }
542 else
543 {
544 // The traditional way is to call into the protocol stack to send them all.
545 pClient.SendLayerData(new float[10]);
546 }
547 }
548  
549 #region Plugin Loading Methods
550  
551 private void LoadPlugins()
552 {
553 m_plugineffects = new Dictionary<string, ITerrainEffect>();
554 LoadPlugins(Assembly.GetCallingAssembly());
555 string plugineffectsPath = "Terrain";
556  
557 // Load the files in the Terrain/ dir
558 if (!Directory.Exists(plugineffectsPath))
559 return;
560  
561 string[] files = Directory.GetFiles(plugineffectsPath);
562 foreach (string file in files)
563 {
564 m_log.Info("Loading effects in " + file);
565 try
566 {
567 Assembly library = Assembly.LoadFrom(file);
568 LoadPlugins(library);
569 }
570 catch (BadImageFormatException)
571 {
572 }
573 }
574 }
575  
576 private void LoadPlugins(Assembly library)
577 {
578 foreach (Type pluginType in library.GetTypes())
579 {
580 try
581 {
582 if (pluginType.IsAbstract || pluginType.IsNotPublic)
583 continue;
584  
585 string typeName = pluginType.Name;
586  
587 if (pluginType.GetInterface("ITerrainEffect", false) != null)
588 {
589 ITerrainEffect terEffect = (ITerrainEffect)Activator.CreateInstance(library.GetType(pluginType.ToString()));
590  
591 InstallPlugin(typeName, terEffect);
592 }
593 else if (pluginType.GetInterface("ITerrainLoader", false) != null)
594 {
595 ITerrainLoader terLoader = (ITerrainLoader)Activator.CreateInstance(library.GetType(pluginType.ToString()));
596 m_loaders[terLoader.FileExtension] = terLoader;
597 m_log.Info("L ... " + typeName);
598 }
599 }
600 catch (AmbiguousMatchException)
601 {
602 }
603 }
604 }
605  
606 public void InstallPlugin(string pluginName, ITerrainEffect effect)
607 {
608 lock (m_plugineffects)
609 {
610 if (!m_plugineffects.ContainsKey(pluginName))
611 {
612 m_plugineffects.Add(pluginName, effect);
613 m_log.Info("E ... " + pluginName);
614 }
615 else
616 {
617 m_plugineffects[pluginName] = effect;
618 m_log.Info("E ... " + pluginName + " (Replaced)");
619 }
620 }
621 }
622  
623 #endregion
624  
625 #endregion
626  
627 /// <summary>
628 /// Installs into terrain module the standard suite of brushes
629 /// </summary>
630 private void InstallDefaultEffects()
631 {
632 // Draggable Paint Brush Effects
633 m_painteffects[StandardTerrainEffects.Raise] = new RaiseSphere();
634 m_painteffects[StandardTerrainEffects.Lower] = new LowerSphere();
635 m_painteffects[StandardTerrainEffects.Smooth] = new SmoothSphere();
636 m_painteffects[StandardTerrainEffects.Noise] = new NoiseSphere();
637 m_painteffects[StandardTerrainEffects.Flatten] = new FlattenSphere();
638 m_painteffects[StandardTerrainEffects.Revert] = new RevertSphere(m_revert);
639 m_painteffects[StandardTerrainEffects.Erode] = new ErodeSphere();
640 m_painteffects[StandardTerrainEffects.Weather] = new WeatherSphere();
641 m_painteffects[StandardTerrainEffects.Olsen] = new OlsenSphere();
642  
643 // Area of effect selection effects
644 m_floodeffects[StandardTerrainEffects.Raise] = new RaiseArea();
645 m_floodeffects[StandardTerrainEffects.Lower] = new LowerArea();
646 m_floodeffects[StandardTerrainEffects.Smooth] = new SmoothArea();
647 m_floodeffects[StandardTerrainEffects.Noise] = new NoiseArea();
648 m_floodeffects[StandardTerrainEffects.Flatten] = new FlattenArea();
649 m_floodeffects[StandardTerrainEffects.Revert] = new RevertArea(m_revert);
650  
651 // Filesystem load/save loaders
652 m_loaders[".r32"] = new RAW32();
653 m_loaders[".f32"] = m_loaders[".r32"];
654 m_loaders[".ter"] = new Terragen();
655 m_loaders[".raw"] = new LLRAW();
656 m_loaders[".jpg"] = new JPEG();
657 m_loaders[".jpeg"] = m_loaders[".jpg"];
658 m_loaders[".bmp"] = new BMP();
659 m_loaders[".png"] = new PNG();
660 m_loaders[".gif"] = new GIF();
661 m_loaders[".tif"] = new TIFF();
662 m_loaders[".tiff"] = m_loaders[".tif"];
663 }
664  
665 /// <summary>
666 /// Saves the current state of the region into the revert map buffer.
667 /// </summary>
668 public void UpdateRevertMap()
669 {
670 /*
671 int x;
672 for (x = 0; x < m_channel.Width; x++)
673 {
674 int y;
675 for (y = 0; y < m_channel.Height; y++)
676 {
677 m_revert[x, y] = m_channel[x, y];
678 }
679 }
680 */
681 m_revert = m_channel.MakeCopy();
682 }
683  
684 /// <summary>
685 /// Loads a tile from a larger terrain file and installs it into the region.
686 /// </summary>
687 /// <param name="filename">The terrain file to load</param>
688 /// <param name="fileWidth">The width of the file in units</param>
689 /// <param name="fileHeight">The height of the file in units</param>
690 /// <param name="fileStartX">Where to begin our slice</param>
691 /// <param name="fileStartY">Where to begin our slice</param>
692 public void LoadFromFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
693 {
694 int offsetX = (int) m_scene.RegionInfo.RegionLocX - fileStartX;
695 int offsetY = (int) m_scene.RegionInfo.RegionLocY - fileStartY;
696  
697 if (offsetX >= 0 && offsetX < fileWidth && offsetY >= 0 && offsetY < fileHeight)
698 {
699 // this region is included in the tile request
700 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
701 {
702 if (filename.EndsWith(loader.Key))
703 {
704 lock (m_scene)
705 {
706 ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY,
707 fileWidth, fileHeight,
708 (int) m_scene.RegionInfo.RegionSizeX,
709 (int) m_scene.RegionInfo.RegionSizeY);
710 m_scene.Heightmap = channel;
711 m_channel = channel;
712 UpdateRevertMap();
713 }
714  
715 return;
716 }
717 }
718 }
719 }
720  
721 /// <summary>
722 /// Save a number of map tiles to a single big image file.
723 /// </summary>
724 /// <remarks>
725 /// If the image file already exists then the tiles saved will replace those already in the file - other tiles
726 /// will be untouched.
727 /// </remarks>
728 /// <param name="filename">The terrain file to save</param>
729 /// <param name="fileWidth">The number of tiles to save along the X axis.</param>
730 /// <param name="fileHeight">The number of tiles to save along the Y axis.</param>
731 /// <param name="fileStartX">The map x co-ordinate at which to begin the save.</param>
732 /// <param name="fileStartY">The may y co-ordinate at which to begin the save.</param>
733 public void SaveToFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
734 {
735 int offsetX = (int)m_scene.RegionInfo.RegionLocX - fileStartX;
736 int offsetY = (int)m_scene.RegionInfo.RegionLocY - fileStartY;
737  
738 if (offsetX < 0 || offsetX >= fileWidth || offsetY < 0 || offsetY >= fileHeight)
739 {
740 MainConsole.Instance.OutputFormat(
741 "ERROR: file width + minimum X tile and file height + minimum Y tile must incorporate the current region at ({0},{1}). File width {2} from {3} and file height {4} from {5} does not.",
742 m_scene.RegionInfo.RegionLocX, m_scene.RegionInfo.RegionLocY, fileWidth, fileStartX, fileHeight, fileStartY);
743  
744 return;
745 }
746  
747 // this region is included in the tile request
748 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
749 {
750 if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave())
751 {
752 lock (m_scene)
753 {
754 loader.Value.SaveFile(m_channel, filename, offsetX, offsetY,
755 fileWidth, fileHeight,
756 (int)m_scene.RegionInfo.RegionSizeX,
757 (int)m_scene.RegionInfo.RegionSizeY);
758  
759 MainConsole.Instance.OutputFormat(
760 "Saved terrain from ({0},{1}) to ({2},{3}) from {4} to {5}",
761 fileStartX, fileStartY, fileStartX + fileWidth - 1, fileStartY + fileHeight - 1,
762 m_scene.RegionInfo.RegionName, filename);
763 }
764  
765 return;
766 }
767 }
768  
769 MainConsole.Instance.OutputFormat(
770 "ERROR: Could not save terrain from {0} to {1}. Valid file extensions are {2}",
771 m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave);
772 }
773  
774 /// <summary>
775 /// Called before processing of every simulation frame.
776 /// This is used to check to see of any of the terrain is tainted and, if so, schedule
777 /// updates for all the presences.
778 /// This also checks to see if there are updates that need to be sent for each presence.
779 /// This is where the logic is to send terrain updates to clients.
780 /// </summary>
781 private void EventManager_OnFrame()
782 {
783 TerrainData terrData = m_channel.GetTerrainData();
784  
785 bool shouldTaint = false;
786 for (int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize)
787 {
788 for (int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize)
789 {
790 if (terrData.IsTaintedAt(x, y))
791 {
792 // Found a patch that was modified. Push this flag into the clients.
793 SendToClients(terrData, x, y);
794 shouldTaint = true;
795 }
796 }
797 }
798  
799 // This event also causes changes to be sent to the clients
800 CheckSendingPatchesToClients();
801  
802 // If things changes, generate some events
803 if (shouldTaint)
804 {
805 m_scene.EventManager.TriggerTerrainTainted();
806 m_tainted = true;
807 }
808 }
809  
810 /// <summary>
811 /// Performs updates to the region periodically, synchronising physics and other heightmap aware sections
812 /// Called infrequently (like every 5 seconds or so). Best used for storing terrain.
813 /// </summary>
814 private void EventManager_OnTerrainTick()
815 {
816 if (m_tainted)
817 {
818 m_tainted = false;
819 m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised());
820 m_scene.SaveTerrain();
821  
822 // Clients who look at the map will never see changes after they looked at the map, so i've commented this out.
823 //m_scene.CreateTerrainTexture(true);
824 }
825 }
826  
827 /// <summary>
828 /// Processes commandline input. Do not call directly.
829 /// </summary>
830 /// <param name="args">Commandline arguments</param>
831 private void EventManager_OnPluginConsole(string[] args)
832 {
833 if (args[0] == "terrain")
834 {
835 if (args.Length == 1)
836 {
837 m_commander.ProcessConsoleCommand("help", new string[0]);
838 return;
839 }
840  
841 string[] tmpArgs = new string[args.Length - 2];
842 int i;
843 for (i = 2; i < args.Length; i++)
844 tmpArgs[i - 2] = args[i];
845  
846 m_commander.ProcessConsoleCommand(args[1], tmpArgs);
847 }
848 }
849  
850 /// <summary>
851 /// Installs terrain brush hook to IClientAPI
852 /// </summary>
853 /// <param name="client"></param>
854 private void EventManager_OnNewClient(IClientAPI client)
855 {
856 client.OnModifyTerrain += client_OnModifyTerrain;
857 client.OnBakeTerrain += client_OnBakeTerrain;
858 client.OnLandUndo += client_OnLandUndo;
859 client.OnUnackedTerrain += client_OnUnackedTerrain;
860 }
861  
862 /// <summary>
863 /// Installs terrain brush hook to IClientAPI
864 /// </summary>
865 /// <param name="client"></param>
866 private void EventManager_OnClientClosed(UUID client, Scene scene)
867 {
868 ScenePresence presence = scene.GetScenePresence(client);
869 if (presence != null)
870 {
871 presence.ControllingClient.OnModifyTerrain -= client_OnModifyTerrain;
872 presence.ControllingClient.OnBakeTerrain -= client_OnBakeTerrain;
873 presence.ControllingClient.OnLandUndo -= client_OnLandUndo;
874 presence.ControllingClient.OnUnackedTerrain -= client_OnUnackedTerrain;
875 }
876  
877 lock (m_perClientPatchUpdates)
878 m_perClientPatchUpdates.Remove(client);
879 }
880  
881 /// <summary>
882 /// Scan over changes in the terrain and limit height changes. This enforces the
883 /// non-estate owner limits on rate of terrain editting.
884 /// Returns 'true' if any heights were limited.
885 /// </summary>
886 private bool EnforceEstateLimits()
887 {
888 TerrainData terrData = m_channel.GetTerrainData();
889  
890 bool wasLimited = false;
891 for (int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize)
892 {
893 for (int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize)
894 {
895 if (terrData.IsTaintedAt(x, y, false /* clearOnTest */))
896 {
897 // If we should respect the estate settings then
898 // fixup and height deltas that don't respect them.
899 // Note that LimitChannelChanges() modifies the TerrainChannel with the limited height values.
900 wasLimited |= LimitChannelChanges(terrData, x, y);
901 }
902 }
903 }
904 return wasLimited;
905 }
906  
907 /// <summary>
908 /// Checks to see height deltas in the tainted terrain patch at xStart ,yStart
909 /// are all within the current estate limits
910 /// <returns>true if changes were limited, false otherwise</returns>
911 /// </summary>
912 private bool LimitChannelChanges(TerrainData terrData, int xStart, int yStart)
913 {
914 bool changesLimited = false;
915 float minDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit;
916 float maxDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit;
917  
918 // loop through the height map for this patch and compare it against
919 // the revert map
920 for (int x = xStart; x < xStart + Constants.TerrainPatchSize; x++)
921 {
922 for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++)
923 {
924 float requestedHeight = terrData[x, y];
925 float bakedHeight = (float)m_revert[x, y];
926 float requestedDelta = requestedHeight - bakedHeight;
927  
928 if (requestedDelta > maxDelta)
929 {
930 terrData[x, y] = bakedHeight + maxDelta;
931 changesLimited = true;
932 }
933 else if (requestedDelta < minDelta)
934 {
935 terrData[x, y] = bakedHeight + minDelta; //as lower is a -ve delta
936 changesLimited = true;
937 }
938 }
939 }
940  
941 return changesLimited;
942 }
943  
944 private void client_OnLandUndo(IClientAPI client)
945 {
946 lock (m_undo)
947 {
948 if (m_undo.Count > 0)
949 {
950 LandUndoState goback = m_undo.Pop();
951 if (goback != null)
952 goback.PlaybackState();
953 }
954 }
955 }
956  
957 /// <summary>
958 /// Sends a copy of the current terrain to the scenes clients
959 /// </summary>
960 /// <param name="serialised">A copy of the terrain as a 1D float array of size w*h</param>
961 /// <param name="x">The patch corner to send</param>
962 /// <param name="y">The patch corner to send</param>
963 private void SendToClients(TerrainData terrData, int x, int y)
964 {
965 if (m_sendTerrainUpdatesByViewDistance)
966 {
967 // Add that this patch needs to be sent to the accounting for each client.
968 lock (m_perClientPatchUpdates)
969 {
970 m_scene.ForEachScenePresence(presence =>
971 {
972 PatchUpdates thisClientUpdates;
973 if (!m_perClientPatchUpdates.TryGetValue(presence.UUID, out thisClientUpdates))
974 {
975 // There is a ScenePresence without a send patch map. Create one.
976 thisClientUpdates = new PatchUpdates(terrData, presence);
977 m_perClientPatchUpdates.Add(presence.UUID, thisClientUpdates);
978 }
979 thisClientUpdates.SetByXY(x, y, true);
980 }
981 );
982 }
983 }
984 else
985 {
986 // Legacy update sending where the update is sent out as soon as noticed
987 // We know the actual terrain data passed is ignored. This kludge saves changing IClientAPI.
988 //float[] heightMap = terrData.GetFloatsSerialized();
989 float[] heightMap = new float[10];
990 m_scene.ForEachClient(
991 delegate(IClientAPI controller)
992 {
993 controller.SendLayerData(x / Constants.TerrainPatchSize,
994 y / Constants.TerrainPatchSize,
995 heightMap);
996 }
997 );
998 }
999 }
1000  
1001 private class PatchesToSend : IComparable<PatchesToSend>
1002 {
1003 public int PatchX;
1004 public int PatchY;
1005 public float Dist;
1006 public PatchesToSend(int pX, int pY, float pDist)
1007 {
1008 PatchX = pX;
1009 PatchY = pY;
1010 Dist = pDist;
1011 }
1012 public int CompareTo(PatchesToSend other)
1013 {
1014 return Dist.CompareTo(other.Dist);
1015 }
1016 }
1017  
1018 // Called each frame time to see if there are any patches to send to any of the
1019 // ScenePresences.
1020 // Loop through all the per-client info and send any patches necessary.
1021 private void CheckSendingPatchesToClients()
1022 {
1023 lock (m_perClientPatchUpdates)
1024 {
1025 foreach (PatchUpdates pups in m_perClientPatchUpdates.Values)
1026 {
1027 if (pups.HasUpdates())
1028 {
1029 // There is something that could be sent to this client.
1030 List<PatchesToSend> toSend = GetModifiedPatchesInViewDistance(pups);
1031 if (toSend.Count > 0)
1032 {
1033 // m_log.DebugFormat("{0} CheckSendingPatchesToClient: sending {1} patches to {2} in region {3}",
1034 // LogHeader, toSend.Count, pups.Presence.Name, m_scene.RegionInfo.RegionName);
1035 // Sort the patches to send by the distance from the presence
1036 toSend.Sort();
1037 /*
1038 foreach (PatchesToSend pts in toSend)
1039 {
1040 pups.Presence.ControllingClient.SendLayerData(pts.PatchX, pts.PatchY, null);
1041 // presence.ControllingClient.SendLayerData(xs.ToArray(), ys.ToArray(), null, TerrainPatch.LayerType.Land);
1042 }
1043 */
1044  
1045 int[] xPieces = new int[toSend.Count];
1046 int[] yPieces = new int[toSend.Count];
1047 float[] patchPieces = new float[toSend.Count * 2];
1048 int pieceIndex = 0;
1049 foreach (PatchesToSend pts in toSend)
1050 {
1051 patchPieces[pieceIndex++] = pts.PatchX;
1052 patchPieces[pieceIndex++] = pts.PatchY;
1053 }
1054 pups.Presence.ControllingClient.SendLayerData(-toSend.Count, 0, patchPieces);
1055 }
1056 }
1057 }
1058 }
1059 }
1060  
1061 private List<PatchesToSend> GetModifiedPatchesInViewDistance(PatchUpdates pups)
1062 {
1063 List<PatchesToSend> ret = new List<PatchesToSend>();
1064  
1065 ScenePresence presence = pups.Presence;
1066 if (presence == null)
1067 return ret;
1068  
1069 // Compute the area of patches within our draw distance
1070 int startX = (((int) (presence.AbsolutePosition.X - presence.DrawDistance))/Constants.TerrainPatchSize) - 2;
1071 startX = Math.Max(startX, 0);
1072 startX = Math.Min(startX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize);
1073 int startY = (((int) (presence.AbsolutePosition.Y - presence.DrawDistance))/Constants.TerrainPatchSize) - 2;
1074 startY = Math.Max(startY, 0);
1075 startY = Math.Min(startY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize);
1076 int endX = (((int) (presence.AbsolutePosition.X + presence.DrawDistance))/Constants.TerrainPatchSize) + 2;
1077 endX = Math.Max(endX, 0);
1078 endX = Math.Min(endX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize);
1079 int endY = (((int) (presence.AbsolutePosition.Y + presence.DrawDistance))/Constants.TerrainPatchSize) + 2;
1080 endY = Math.Max(endY, 0);
1081 endY = Math.Min(endY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize);
1082 // m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. rName={1}, ddist={2}, apos={3}, start=<{4},{5}>, end=<{6},{7}>",
1083 // LogHeader, m_scene.RegionInfo.RegionName,
1084 // presence.DrawDistance, presence.AbsolutePosition,
1085 // startX, startY, endX, endY);
1086 for (int x = startX; x < endX; x++)
1087 {
1088 for (int y = startY; y < endY; y++)
1089 {
1090 //Need to make sure we don't send the same ones over and over
1091 Vector3 presencePos = presence.AbsolutePosition;
1092 Vector3 patchPos = new Vector3(x * Constants.TerrainPatchSize, y * Constants.TerrainPatchSize, presencePos.Z);
1093 if (pups.GetByPatch(x, y))
1094 {
1095 //Check which has less distance, camera or avatar position, both have to be done.
1096 //Its not a radius, its a diameter and we add 50 so that it doesn't look like it cuts off
1097 if (Util.DistanceLessThan(presencePos, patchPos, presence.DrawDistance + 50)
1098 || Util.DistanceLessThan(presence.CameraPosition, patchPos, presence.DrawDistance + 50))
1099 {
1100 //They can see it, send it to them
1101 pups.SetByPatch(x, y, false);
1102 float dist = Vector3.DistanceSquared(presencePos, patchPos);
1103 ret.Add(new PatchesToSend(x, y, dist));
1104 //Wait and send them all at once
1105 // pups.client.SendLayerData(x, y, null);
1106 }
1107 }
1108 }
1109 }
1110 return ret;
1111 }
1112  
1113 private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action,
1114 float north, float west, float south, float east, UUID agentId)
1115 {
1116 bool god = m_scene.Permissions.IsGod(user);
1117 bool allowed = false;
1118 if (north == south && east == west)
1119 {
1120 if (m_painteffects.ContainsKey((StandardTerrainEffects) action))
1121 {
1122 bool[,] allowMask = new bool[m_channel.Width,m_channel.Height];
1123 allowMask.Initialize();
1124 int n = size + 1;
1125 if (n > 2)
1126 n = 4;
1127  
1128 int zx = (int) (west + 0.5);
1129 int zy = (int) (north + 0.5);
1130  
1131 int dx;
1132 for (dx=-n; dx<=n; dx++)
1133 {
1134 int dy;
1135 for (dy=-n; dy<=n; dy++)
1136 {
1137 int x = zx + dx;
1138 int y = zy + dy;
1139 if (x>=0 && y>=0 && x<m_channel.Width && y<m_channel.Height)
1140 {
1141 if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0)))
1142 {
1143 allowMask[x, y] = true;
1144 allowed = true;
1145 }
1146 }
1147 }
1148 }
1149 if (allowed)
1150 {
1151 StoreUndoState();
1152 m_painteffects[(StandardTerrainEffects) action].PaintEffect(
1153 m_channel, allowMask, west, south, height, size, seconds);
1154  
1155 //revert changes outside estate limits
1156 if (!god)
1157 EnforceEstateLimits();
1158 }
1159 }
1160 else
1161 {
1162 m_log.Debug("Unknown terrain brush type " + action);
1163 }
1164 }
1165 else
1166 {
1167 if (m_floodeffects.ContainsKey((StandardTerrainEffects) action))
1168 {
1169 bool[,] fillArea = new bool[m_channel.Width,m_channel.Height];
1170 fillArea.Initialize();
1171  
1172 int x;
1173 for (x = 0; x < m_channel.Width; x++)
1174 {
1175 int y;
1176 for (y = 0; y < m_channel.Height; y++)
1177 {
1178 if (x < east && x > west)
1179 {
1180 if (y < north && y > south)
1181 {
1182 if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0)))
1183 {
1184 fillArea[x, y] = true;
1185 allowed = true;
1186 }
1187 }
1188 }
1189 }
1190 }
1191  
1192 if (allowed)
1193 {
1194 StoreUndoState();
1195 m_floodeffects[(StandardTerrainEffects) action].FloodEffect(m_channel, fillArea, size);
1196  
1197 //revert changes outside estate limits
1198 if (!god)
1199 EnforceEstateLimits();
1200 }
1201 }
1202 else
1203 {
1204 m_log.Debug("Unknown terrain flood type " + action);
1205 }
1206 }
1207 }
1208  
1209 private void client_OnBakeTerrain(IClientAPI remoteClient)
1210 {
1211 // Not a good permissions check (see client_OnModifyTerrain above), need to check the entire area.
1212 // for now check a point in the centre of the region
1213  
1214 if (m_scene.Permissions.CanIssueEstateCommand(remoteClient.AgentId, true))
1215 {
1216 InterfaceBakeTerrain(null); //bake terrain does not use the passed in parameter
1217 }
1218 }
1219  
1220 protected void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY)
1221 {
1222 //m_log.Debug("Terrain packet unacked, resending patch: " + patchX + " , " + patchY);
1223 // SendLayerData does not use the heightmap parameter. This kludge is so as to not change IClientAPI.
1224 float[] heightMap = new float[10];
1225 client.SendLayerData(patchX, patchY, heightMap);
1226 }
1227  
1228 private void StoreUndoState()
1229 {
1230 lock (m_undo)
1231 {
1232 if (m_undo.Count > 0)
1233 {
1234 LandUndoState last = m_undo.Peek();
1235 if (last != null)
1236 {
1237 if (last.Compare(m_channel))
1238 return;
1239 }
1240 }
1241  
1242 LandUndoState nUndo = new LandUndoState(this, m_channel);
1243 m_undo.Push(nUndo);
1244 }
1245 }
1246  
1247 #region Console Commands
1248  
1249 private void InterfaceLoadFile(Object[] args)
1250 {
1251 LoadFromFile((string) args[0]);
1252 }
1253  
1254 private void InterfaceLoadTileFile(Object[] args)
1255 {
1256 LoadFromFile((string) args[0],
1257 (int) args[1],
1258 (int) args[2],
1259 (int) args[3],
1260 (int) args[4]);
1261 }
1262  
1263 private void InterfaceSaveFile(Object[] args)
1264 {
1265 SaveToFile((string) args[0]);
1266 }
1267  
1268 private void InterfaceSaveTileFile(Object[] args)
1269 {
1270 SaveToFile((string)args[0],
1271 (int)args[1],
1272 (int)args[2],
1273 (int)args[3],
1274 (int)args[4]);
1275 }
1276  
1277 private void InterfaceBakeTerrain(Object[] args)
1278 {
1279 UpdateRevertMap();
1280 }
1281  
1282 private void InterfaceRevertTerrain(Object[] args)
1283 {
1284 int x, y;
1285 for (x = 0; x < m_channel.Width; x++)
1286 for (y = 0; y < m_channel.Height; y++)
1287 m_channel[x, y] = m_revert[x, y];
1288  
1289 }
1290  
1291 private void InterfaceFlipTerrain(Object[] args)
1292 {
1293 String direction = (String)args[0];
1294  
1295 if (direction.ToLower().StartsWith("y"))
1296 {
1297 for (int x = 0; x < m_channel.Width; x++)
1298 {
1299 for (int y = 0; y < m_channel.Height / 2; y++)
1300 {
1301 double height = m_channel[x, y];
1302 double flippedHeight = m_channel[x, (int)m_channel.Height - 1 - y];
1303 m_channel[x, y] = flippedHeight;
1304 m_channel[x, (int)m_channel.Height - 1 - y] = height;
1305  
1306 }
1307 }
1308 }
1309 else if (direction.ToLower().StartsWith("x"))
1310 {
1311 for (int y = 0; y < m_channel.Height; y++)
1312 {
1313 for (int x = 0; x < m_channel.Width / 2; x++)
1314 {
1315 double height = m_channel[x, y];
1316 double flippedHeight = m_channel[(int)m_channel.Width - 1 - x, y];
1317 m_channel[x, y] = flippedHeight;
1318 m_channel[(int)m_channel.Width - 1 - x, y] = height;
1319  
1320 }
1321 }
1322 }
1323 else
1324 {
1325 MainConsole.Instance.OutputFormat("ERROR: Unrecognised direction {0} - need x or y", direction);
1326 }
1327 }
1328  
1329 private void InterfaceRescaleTerrain(Object[] args)
1330 {
1331 double desiredMin = (double)args[0];
1332 double desiredMax = (double)args[1];
1333  
1334 // determine desired scaling factor
1335 double desiredRange = desiredMax - desiredMin;
1336 //m_log.InfoFormat("Desired {0}, {1} = {2}", new Object[] { desiredMin, desiredMax, desiredRange });
1337  
1338 if (desiredRange == 0d)
1339 {
1340 // delta is zero so flatten at requested height
1341 InterfaceFillTerrain(new Object[] { args[1] });
1342 }
1343 else
1344 {
1345 //work out current heightmap range
1346 double currMin = double.MaxValue;
1347 double currMax = double.MinValue;
1348  
1349 int width = m_channel.Width;
1350 int height = m_channel.Height;
1351  
1352 for (int x = 0; x < width; x++)
1353 {
1354 for (int y = 0; y < height; y++)
1355 {
1356 double currHeight = m_channel[x, y];
1357 if (currHeight < currMin)
1358 {
1359 currMin = currHeight;
1360 }
1361 else if (currHeight > currMax)
1362 {
1363 currMax = currHeight;
1364 }
1365 }
1366 }
1367  
1368 double currRange = currMax - currMin;
1369 double scale = desiredRange / currRange;
1370  
1371 //m_log.InfoFormat("Current {0}, {1} = {2}", new Object[] { currMin, currMax, currRange });
1372 //m_log.InfoFormat("Scale = {0}", scale);
1373  
1374 // scale the heightmap accordingly
1375 for (int x = 0; x < width; x++)
1376 {
1377 for (int y = 0; y < height; y++)
1378 {
1379 double currHeight = m_channel[x, y] - currMin;
1380 m_channel[x, y] = desiredMin + (currHeight * scale);
1381 }
1382 }
1383  
1384 }
1385  
1386 }
1387  
1388 private void InterfaceElevateTerrain(Object[] args)
1389 {
1390 int x, y;
1391 for (x = 0; x < m_channel.Width; x++)
1392 for (y = 0; y < m_channel.Height; y++)
1393 m_channel[x, y] += (double) args[0];
1394 }
1395  
1396 private void InterfaceMultiplyTerrain(Object[] args)
1397 {
1398 int x, y;
1399 for (x = 0; x < m_channel.Width; x++)
1400 for (y = 0; y < m_channel.Height; y++)
1401 m_channel[x, y] *= (double) args[0];
1402 }
1403  
1404 private void InterfaceLowerTerrain(Object[] args)
1405 {
1406 int x, y;
1407 for (x = 0; x < m_channel.Width; x++)
1408 for (y = 0; y < m_channel.Height; y++)
1409 m_channel[x, y] -= (double) args[0];
1410 }
1411  
1412 public void InterfaceFillTerrain(Object[] args)
1413 {
1414 int x, y;
1415  
1416 for (x = 0; x < m_channel.Width; x++)
1417 for (y = 0; y < m_channel.Height; y++)
1418 m_channel[x, y] = (double) args[0];
1419 }
1420  
1421 private void InterfaceMinTerrain(Object[] args)
1422 {
1423 int x, y;
1424 for (x = 0; x < m_channel.Width; x++)
1425 {
1426 for (y = 0; y < m_channel.Height; y++)
1427 {
1428 m_channel[x, y] = Math.Max((double)args[0], m_channel[x, y]);
1429 }
1430 }
1431 }
1432  
1433 private void InterfaceMaxTerrain(Object[] args)
1434 {
1435 int x, y;
1436 for (x = 0; x < m_channel.Width; x++)
1437 {
1438 for (y = 0; y < m_channel.Height; y++)
1439 {
1440 m_channel[x, y] = Math.Min((double)args[0], m_channel[x, y]);
1441 }
1442 }
1443 }
1444  
1445 private void InterfaceShow(Object[] args)
1446 {
1447 Vector2 point;
1448  
1449 if (!ConsoleUtil.TryParseConsole2DVector((string)args[0], null, out point))
1450 {
1451 Console.WriteLine("ERROR: {0} is not a valid vector", args[0]);
1452 return;
1453 }
1454  
1455 double height = m_channel[(int)point.X, (int)point.Y];
1456  
1457 Console.WriteLine("Terrain height at {0} is {1}", point, height);
1458 }
1459  
1460 private void InterfaceShowDebugStats(Object[] args)
1461 {
1462 double max = Double.MinValue;
1463 double min = double.MaxValue;
1464 double sum = 0;
1465  
1466 int x;
1467 for (x = 0; x < m_channel.Width; x++)
1468 {
1469 int y;
1470 for (y = 0; y < m_channel.Height; y++)
1471 {
1472 sum += m_channel[x, y];
1473 if (max < m_channel[x, y])
1474 max = m_channel[x, y];
1475 if (min > m_channel[x, y])
1476 min = m_channel[x, y];
1477 }
1478 }
1479  
1480 double avg = sum / (m_channel.Height * m_channel.Width);
1481  
1482 MainConsole.Instance.OutputFormat("Channel {0}x{1}", m_channel.Width, m_channel.Height);
1483 MainConsole.Instance.OutputFormat("max/min/avg/sum: {0}/{1}/{2}/{3}", max, min, avg, sum);
1484 }
1485  
1486 private void InterfaceEnableExperimentalBrushes(Object[] args)
1487 {
1488 if ((bool) args[0])
1489 {
1490 m_painteffects[StandardTerrainEffects.Revert] = new WeatherSphere();
1491 m_painteffects[StandardTerrainEffects.Flatten] = new OlsenSphere();
1492 m_painteffects[StandardTerrainEffects.Smooth] = new ErodeSphere();
1493 }
1494 else
1495 {
1496 InstallDefaultEffects();
1497 }
1498 }
1499  
1500 private void InterfaceRunPluginEffect(Object[] args)
1501 {
1502 string firstArg = (string)args[0];
1503  
1504 if (firstArg == "list")
1505 {
1506 MainConsole.Instance.Output("List of loaded plugins");
1507 foreach (KeyValuePair<string, ITerrainEffect> kvp in m_plugineffects)
1508 {
1509 MainConsole.Instance.Output(kvp.Key);
1510 }
1511 return;
1512 }
1513  
1514 if (firstArg == "reload")
1515 {
1516 LoadPlugins();
1517 return;
1518 }
1519  
1520 if (m_plugineffects.ContainsKey(firstArg))
1521 {
1522 m_plugineffects[firstArg].RunEffect(m_channel);
1523 }
1524 else
1525 {
1526 MainConsole.Instance.Output("WARNING: No such plugin effect {0} loaded.", firstArg);
1527 }
1528 }
1529  
1530 private void InstallInterfaces()
1531 {
1532 Command loadFromFileCommand =
1533 new Command("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file.");
1534 loadFromFileCommand.AddArgument("filename",
1535 "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
1536 m_supportedFileExtensions, "String");
1537  
1538 Command saveToFileCommand =
1539 new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file.");
1540 saveToFileCommand.AddArgument("filename",
1541 "The destination filename for your heightmap, the file extension determines the format to save in. Supported extensions include: " +
1542 m_supportedFileExtensions, "String");
1543  
1544 Command loadFromTileCommand =
1545 new Command("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file.");
1546 loadFromTileCommand.AddArgument("filename",
1547 "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
1548 m_supportedFileExtensions, "String");
1549 loadFromTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
1550 loadFromTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
1551 loadFromTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
1552 "Integer");
1553 loadFromTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first section on the file",
1554 "Integer");
1555  
1556 Command saveToTileCommand =
1557 new Command("save-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceSaveTileFile, "Saves the current heightmap to the larger file.");
1558 saveToTileCommand.AddArgument("filename",
1559 "The file you wish to save to, the file extension determines the loader to be used. Supported extensions include: " +
1560 m_supportFileExtensionsForTileSave, "String");
1561 saveToTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
1562 saveToTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
1563 saveToTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
1564 "Integer");
1565 saveToTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first tile on the file\n"
1566 + "= Example =\n"
1567 + "To save a PNG file for a set of map tiles 2 regions wide and 3 regions high from map co-ordinate (9910,10234)\n"
1568 + " # terrain save-tile ST06.png 2 3 9910 10234\n",
1569 "Integer");
1570  
1571 // Terrain adjustments
1572 Command fillRegionCommand =
1573 new Command("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value.");
1574 fillRegionCommand.AddArgument("value", "The numeric value of the height you wish to set your region to.",
1575 "Double");
1576  
1577 Command elevateCommand =
1578 new Command("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount.");
1579 elevateCommand.AddArgument("amount", "The amount of height to add to the terrain in meters.", "Double");
1580  
1581 Command lowerCommand =
1582 new Command("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount.");
1583 lowerCommand.AddArgument("amount", "The amount of height to remove from the terrain in meters.", "Double");
1584  
1585 Command multiplyCommand =
1586 new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified.");
1587 multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Double");
1588  
1589 Command bakeRegionCommand =
1590 new Command("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions revert map.");
1591 Command revertRegionCommand =
1592 new Command("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the revert map terrain into the regions heightmap.");
1593  
1594 Command flipCommand =
1595 new Command("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis");
1596 flipCommand.AddArgument("direction", "[x|y] the direction to flip the terrain in", "String");
1597  
1598 Command rescaleCommand =
1599 new Command("rescale", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRescaleTerrain, "Rescales the current terrain to fit between the given min and max heights");
1600 rescaleCommand.AddArgument("min", "min terrain height after rescaling", "Double");
1601 rescaleCommand.AddArgument("max", "max terrain height after rescaling", "Double");
1602  
1603 Command minCommand = new Command("min", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMinTerrain, "Sets the minimum terrain height to the specified value.");
1604 minCommand.AddArgument("min", "terrain height to use as minimum", "Double");
1605  
1606 Command maxCommand = new Command("max", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMaxTerrain, "Sets the maximum terrain height to the specified value.");
1607 maxCommand.AddArgument("min", "terrain height to use as maximum", "Double");
1608  
1609  
1610 // Debug
1611 Command showDebugStatsCommand =
1612 new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats,
1613 "Shows some information about the regions heightmap for debugging purposes.");
1614  
1615 Command showCommand =
1616 new Command("show", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceShow,
1617 "Shows terrain height at a given co-ordinate.");
1618 showCommand.AddArgument("point", "point in <x>,<y> format with no spaces (e.g. 45,45)", "String");
1619  
1620 Command experimentalBrushesCommand =
1621 new Command("newbrushes", CommandIntentions.COMMAND_HAZARDOUS, InterfaceEnableExperimentalBrushes,
1622 "Enables experimental brushes which replace the standard terrain brushes. WARNING: This is a debug setting and may be removed at any time.");
1623 experimentalBrushesCommand.AddArgument("Enabled?", "true / false - Enable new brushes", "Boolean");
1624  
1625 //Plugins
1626 Command pluginRunCommand =
1627 new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect");
1628 pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String");
1629  
1630 m_commander.RegisterCommand("load", loadFromFileCommand);
1631 m_commander.RegisterCommand("load-tile", loadFromTileCommand);
1632 m_commander.RegisterCommand("save", saveToFileCommand);
1633 m_commander.RegisterCommand("save-tile", saveToTileCommand);
1634 m_commander.RegisterCommand("fill", fillRegionCommand);
1635 m_commander.RegisterCommand("elevate", elevateCommand);
1636 m_commander.RegisterCommand("lower", lowerCommand);
1637 m_commander.RegisterCommand("multiply", multiplyCommand);
1638 m_commander.RegisterCommand("bake", bakeRegionCommand);
1639 m_commander.RegisterCommand("revert", revertRegionCommand);
1640 m_commander.RegisterCommand("newbrushes", experimentalBrushesCommand);
1641 m_commander.RegisterCommand("show", showCommand);
1642 m_commander.RegisterCommand("stats", showDebugStatsCommand);
1643 m_commander.RegisterCommand("effect", pluginRunCommand);
1644 m_commander.RegisterCommand("flip", flipCommand);
1645 m_commander.RegisterCommand("rescale", rescaleCommand);
1646 m_commander.RegisterCommand("min", minCommand);
1647 m_commander.RegisterCommand("max", maxCommand);
1648  
1649 // Add this to our scene so scripts can call these functions
1650 m_scene.RegisterModuleCommander(m_commander);
1651 }
1652  
1653  
1654 #endregion
1655  
1656 }
1657 }