OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
TerrainModule.cs
Go to the documentation of this file.
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 using System;
28 using System.Collections.Generic;
29 using System.IO;
30 using System.Reflection;
31 using System.Net;
32 using System.Threading;
33 
34 using log4net;
35 using Nini.Config;
36 
37 using OpenMetaverse;
38 using Mono.Addins;
39 
40 using OpenSim.Framework;
41 using OpenSim.Framework.Console;
42 using OpenSim.Region.CoreModules.Framework.InterfaceCommander;
43 using OpenSim.Region.CoreModules.World.Terrain.FileLoaders;
44 using OpenSim.Region.CoreModules.World.Terrain.Modifiers;
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")]
54  {
55  #region StandardTerrainEffects enum
56 
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  private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects =
85  new Dictionary<StandardTerrainEffects, ITerrainFloodEffect>();
86  private readonly Dictionary<string, ITerrainLoader> m_loaders = new Dictionary<string, ITerrainLoader>();
87  private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects =
88  new Dictionary<StandardTerrainEffects, ITerrainPaintableEffect>();
89  private Dictionary<string, ITerrainModifier> m_modifyOperations =
90  new Dictionary<string, ITerrainModifier>();
91  private Dictionary<string, ITerrainEffect> m_plugineffects;
92  private ITerrainChannel m_channel;
93  private ITerrainChannel m_baked;
94  private Scene m_scene;
95  private volatile bool m_tainted;
96 
97  private String m_InitialTerrain = "pinhead-island";
98 
99  // If true, send terrain patch updates to clients based on their view distance
100  private bool m_sendTerrainUpdatesByViewDistance = true;
101 
102  // Class to keep the per client collection of terrain patches that must be sent.
103  // A patch is set to 'true' meaning it should be sent to the client. Once the
104  // patch packet is queued to the client, the bit for that patch is set to 'false'.
105  private class PatchUpdates
106  {
107  private bool[,] updated; // for each patch, whether it needs to be sent to this client
108  private int updateCount; // number of patches that need to be sent
109  public ScenePresence Presence; // a reference to the client to send to
110  public bool sendAll;
111  public int sendAllcurrentX;
112  public int sendAllcurrentY;
113 
114 
115  public PatchUpdates(TerrainData terrData, ScenePresence pPresence)
116  {
117  updated = new bool[terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize];
118  updateCount = 0;
119  Presence = pPresence;
120  // Initially, send all patches to the client
121  SetAll(true);
122  }
123  // Returns 'true' if there are any patches marked for sending
124  public bool HasUpdates()
125  {
126  return (updateCount > 0);
127  }
128 
129  public void SetByXY(int x, int y, bool state)
130  {
131  this.SetByPatch(x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, state);
132  }
133 
134  public bool GetByPatch(int patchX, int patchY)
135  {
136  return updated[patchX, patchY];
137  }
138 
139  public void SetByPatch(int patchX, int patchY, bool state)
140  {
141  bool prevState = updated[patchX, patchY];
142  if (!prevState && state)
143  updateCount++;
144  if (prevState && !state)
145  updateCount--;
146  updated[patchX, patchY] = state;
147  }
148 
149  public void SetAll(bool state)
150  {
151  updateCount = 0;
152  for (int xx = 0; xx < updated.GetLength(0); xx++)
153  for (int yy = 0; yy < updated.GetLength(1); yy++)
154  updated[xx, yy] = state;
155  if (state)
156  updateCount = updated.GetLength(0) * updated.GetLength(1);
157  sendAllcurrentX = 0;
158  sendAllcurrentY = 0;
159  sendAll = true;
160  }
161 
162  // Logically OR's the terrain data's patch taint map into this client's update map.
163  public void SetAll(TerrainData terrData)
164  {
165  if (updated.GetLength(0) != (terrData.SizeX / Constants.TerrainPatchSize)
166  || updated.GetLength(1) != (terrData.SizeY / Constants.TerrainPatchSize))
167  {
168  throw new Exception(
169  String.Format("{0} PatchUpdates.SetAll: patch array not same size as terrain. arr=<{1},{2}>, terr=<{3},{4}>",
170  LogHeader, updated.GetLength(0), updated.GetLength(1),
172  );
173  }
174 
175  for (int xx = 0; xx < terrData.SizeX; xx += Constants.TerrainPatchSize)
176  {
177  for (int yy = 0; yy < terrData.SizeY; yy += Constants.TerrainPatchSize)
178  {
179  // Only set tainted. The patch bit may be set if the patch was to be sent later.
180  if (terrData.IsTaintedAt(xx, yy, false))
181  {
182  this.SetByXY(xx, yy, true);
183  }
184  }
185  }
186  }
187  }
188 
189  // The flags of which terrain patches to send for each of the ScenePresence's
190  private Dictionary<UUID, PatchUpdates> m_perClientPatchUpdates = new Dictionary<UUID, PatchUpdates>();
191 
195  private string m_supportedFileExtensions = "";
196 
197  //For terrain save-tile file extensions
198  private string m_supportFileExtensionsForTileSave = "";
199 
200  #region ICommandableModule Members
201 
202  public ICommander CommandInterface {
203  get { return m_commander; }
204  }
205 
206  #endregion
207 
208  #region INonSharedRegionModule Members
209 
215  public void Initialise(IConfigSource config)
216  {
217  IConfig terrainConfig = config.Configs["Terrain"];
218  if (terrainConfig != null)
219  {
220  m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain);
221  m_sendTerrainUpdatesByViewDistance =
222  terrainConfig.GetBoolean(
223  "SendTerrainUpdatesByViewDistance",m_sendTerrainUpdatesByViewDistance);
224  }
225  }
226 
227  public void AddRegion(Scene scene)
228  {
229  m_scene = scene;
230 
231  // Install terrain module in the simulator
232  lock(m_scene)
233  {
234  if (m_scene.Heightmap == null)
235  {
236  m_channel = new TerrainChannel(m_InitialTerrain, (int)m_scene.RegionInfo.RegionSizeX,
237  (int)m_scene.RegionInfo.RegionSizeY,
238  (int)m_scene.RegionInfo.RegionSizeZ);
239  m_scene.Heightmap = m_channel;
240 
241  UpdateBakedMap();
242  }
243  else
244  {
245  m_channel = m_scene.Heightmap;
246  UpdateBakedMap();
247  }
248 
249  m_scene.RegisterModuleInterface<ITerrainModule>(this);
250  m_scene.EventManager.OnNewClient += EventManager_OnNewClient;
251  m_scene.EventManager.OnClientClosed += EventManager_OnClientClosed;
252  m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole;
253  m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick;
254  m_scene.EventManager.OnTerrainCheckUpdates += EventManager_TerrainCheckUpdates;
255  }
256 
257  InstallDefaultEffects();
258  LoadPlugins();
259 
260  // Generate user-readable extensions list
261  string supportedFilesSeparator = "";
262  string supportedFilesSeparatorForTileSave = "";
263 
264  m_supportFileExtensionsForTileSave = "";
265  foreach(KeyValuePair<string, ITerrainLoader> loader in m_loaders)
266  {
267  m_supportedFileExtensions += supportedFilesSeparator + loader.Key + " (" + loader.Value + ")";
268  supportedFilesSeparator = ", ";
269 
270  //For terrain save-tile file extensions
271  if (loader.Value.SupportsTileSave() == true)
272  {
273  m_supportFileExtensionsForTileSave += supportedFilesSeparatorForTileSave + loader.Key + " (" + loader.Value + ")";
274  supportedFilesSeparatorForTileSave = ", ";
275  }
276  }
277  }
278 
279  public void RegionLoaded(Scene scene)
280  {
281  //Do this here to give file loaders time to initialize and
282  //register their supported file extensions and file formats.
283  InstallInterfaces();
284  }
285 
286  public void RemoveRegion(Scene scene)
287  {
288  lock(m_scene)
289  {
290  // remove the commands
291  m_scene.UnregisterModuleCommander(m_commander.Name);
292  // remove the event-handlers
293 
294  m_scene.EventManager.OnTerrainCheckUpdates -= EventManager_TerrainCheckUpdates;
295  m_scene.EventManager.OnTerrainTick -= EventManager_OnTerrainTick;
296  m_scene.EventManager.OnPluginConsole -= EventManager_OnPluginConsole;
297  m_scene.EventManager.OnClientClosed -= EventManager_OnClientClosed;
298  m_scene.EventManager.OnNewClient -= EventManager_OnNewClient;
299  // remove the interface
300  m_scene.UnregisterModuleInterface<ITerrainModule>(this);
301  }
302  }
303 
304  public void Close()
305  {
306  }
307 
308  public Type ReplaceableInterface {
309  get { return null; }
310  }
311 
312  public string Name {
313  get { return "TerrainModule"; }
314  }
315 
316  #endregion
317 
318  #region ITerrainModule Members
319 
320  public void UndoTerrain(ITerrainChannel channel)
321  {
322  m_channel = channel;
323  }
324 
329  public void LoadFromFile(string filename)
330  {
331  foreach(KeyValuePair<string, ITerrainLoader> loader in m_loaders)
332  {
333  if (filename.EndsWith(loader.Key))
334  {
335  lock(m_scene)
336  {
337  try
338  {
339  ITerrainChannel channel = loader.Value.LoadFile(filename);
340  if (channel.Width != m_scene.RegionInfo.RegionSizeX || channel.Height != m_scene.RegionInfo.RegionSizeY)
341  {
342  // TerrainChannel expects a RegionSize x RegionSize map, currently
343  throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}",
344  m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY));
345  }
346  m_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height);
347  m_scene.Heightmap = channel;
348  m_channel = channel;
349  UpdateBakedMap();
350  }
351  catch(NotImplementedException)
352  {
353  m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
354  " parser does not support file loading. (May be save only)");
355  throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
356  }
357  catch(FileNotFoundException)
358  {
359  m_log.Error(
360  "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)");
361  throw new TerrainException(
362  String.Format("unable to load heightmap: file {0} not found (or permissions do not allow access", filename));
363  }
364  catch(ArgumentException e)
365  {
366  m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message);
367  throw new TerrainException(
368  String.Format("Unable to load heightmap: {0}", e.Message));
369  }
370  }
371  m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
372  return;
373  }
374  }
375 
376  m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
377  throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
378  }
379 
384  public void SaveToFile(string filename)
385  {
386  try
387  {
388  foreach(KeyValuePair<string, ITerrainLoader> loader in m_loaders)
389  {
390  if (filename.EndsWith(loader.Key))
391  {
392  loader.Value.SaveFile(filename, m_channel);
393  m_log.InfoFormat("[TERRAIN]: Saved terrain from {0} to {1}", m_scene.RegionInfo.RegionName, filename);
394  return;
395  }
396  }
397  }
398  catch(IOException ioe)
399  {
400  m_log.Error(String.Format("[TERRAIN]: Unable to save to {0}, {1}", filename, ioe.Message));
401  }
402 
403  m_log.ErrorFormat(
404  "[TERRAIN]: Could not save terrain from {0} to {1}. Valid file extensions are {2}",
405  m_scene.RegionInfo.RegionName, filename, m_supportedFileExtensions);
406  }
407 
413  public void LoadFromStream(string filename, Uri pathToTerrainHeightmap)
414  {
415  LoadFromStream(filename, URIFetch(pathToTerrainHeightmap));
416  }
417 
418  public void LoadFromStream(string filename, Stream stream)
419  {
420  LoadFromStream(filename, Vector3.Zero, 0f, Vector2.Zero, stream);
421  }
422 
428  public void LoadFromStream(string filename, Vector3 displacement,
429  float radianRotation, Vector2 rotationDisplacement, Stream stream)
430  {
431  foreach(KeyValuePair<string, ITerrainLoader> loader in m_loaders)
432  {
433  if (filename.EndsWith(loader.Key))
434  {
435  lock(m_scene)
436  {
437  try
438  {
439  ITerrainChannel channel = loader.Value.LoadStream(stream);
440  m_channel.Merge(channel, displacement, radianRotation, rotationDisplacement);
441  UpdateBakedMap();
442  }
443  catch(NotImplementedException)
444  {
445  m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
446  " parser does not support file loading. (May be save only)");
447  throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
448  }
449  }
450 
451  m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
452  return;
453  }
454  }
455  m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
456  throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
457  }
458 
459  public void LoadFromStream(string filename, Vector3 displacement,
460  float rotationDegrees, Vector2 boundingOrigin, Vector2 boundingSize, Stream stream)
461  {
462  foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
463  {
464  if (filename.EndsWith(loader.Key))
465  {
466  lock (m_scene)
467  {
468  try
469  {
470  ITerrainChannel channel = loader.Value.LoadStream(stream);
471  m_channel.MergeWithBounding(channel, displacement, rotationDegrees, boundingOrigin, boundingSize);
472  UpdateBakedMap();
473  }
474  catch (NotImplementedException)
475  {
476  m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
477  " parser does not support file loading. (May be save only)");
478  throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
479  }
480  }
481 
482  m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
483  return;
484  }
485  }
486  m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
487  throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
488  }
489 
490  private static Stream URIFetch(Uri uri)
491  {
492  HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
493 
494  // request.Credentials = credentials;
495 
496  request.ContentLength = 0;
497  request.KeepAlive = false;
498 
499  WebResponse response = request.GetResponse();
500  Stream file = response.GetResponseStream();
501 
502  if (response.ContentLength == 0)
503  throw new Exception(String.Format("{0} returned an empty file", uri.ToString()));
504 
505  // return new BufferedStream(file, (int) response.ContentLength);
506  return new BufferedStream(file, 1000000);
507  }
508 
516  public void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId)
517  {
518  float duration = 0.25f;
519  if (action == 0)
520  duration = 4.0f;
521 
522  client_OnModifyTerrain(user, (float)pos.Z, duration, size, action, pos.Y, pos.X, pos.Y, pos.X, agentId);
523  }
524 
530  public void SaveToStream(string filename, Stream stream)
531  {
532  try
533  {
534  foreach(KeyValuePair<string, ITerrainLoader> loader in m_loaders)
535  {
536  if (filename.EndsWith(loader.Key))
537  {
538  loader.Value.SaveStream(stream, m_channel);
539  return;
540  }
541  }
542  }
543  catch(NotImplementedException)
544  {
545  m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented.");
546  throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented"));
547  }
548  }
549 
550  // Someone diddled terrain outside the normal code paths. Set the taintedness for all clients.
551  // ITerrainModule.TaintTerrain()
552  public void TaintTerrain ()
553  {
554  lock (m_perClientPatchUpdates)
555  {
556  // Set the flags for all clients so the tainted patches will be sent out
557  foreach (PatchUpdates pups in m_perClientPatchUpdates.Values)
558  {
559  pups.SetAll(m_scene.Heightmap.GetTerrainData());
560  }
561  }
562  }
563 
564  // ITerrainModule.PushTerrain()
565  public void PushTerrain(IClientAPI pClient)
566  {
567  if (m_sendTerrainUpdatesByViewDistance)
568  {
569  ScenePresence presence = m_scene.GetScenePresence(pClient.AgentId);
570  if (presence != null)
571  {
572  lock (m_perClientPatchUpdates)
573  {
574  PatchUpdates pups;
575  if (!m_perClientPatchUpdates.TryGetValue(pClient.AgentId, out pups))
576  {
577  // There is a ScenePresence without a send patch map. Create one.
578  pups = new PatchUpdates(m_scene.Heightmap.GetTerrainData(), presence);
579  m_perClientPatchUpdates.Add(presence.UUID, pups);
580  }
581  pups.SetAll(true);
582  }
583  }
584  }
585  else
586  {
587  // The traditional way is to call into the protocol stack to send them all.
588  pClient.SendLayerData(new float[10]);
589  }
590  }
591 
592  #region Plugin Loading Methods
593 
594  private void LoadPlugins()
595  {
596  m_plugineffects = new Dictionary<string, ITerrainEffect>();
597  LoadPlugins(Assembly.GetCallingAssembly());
598  string plugineffectsPath = "Terrain";
599 
600  // Load the files in the Terrain/ dir
601  if (!Directory.Exists(plugineffectsPath))
602  return;
603 
604  string[] files = Directory.GetFiles(plugineffectsPath);
605  foreach(string file in files)
606  {
607  m_log.Info("Loading effects in " + file);
608  try
609  {
610  Assembly library = Assembly.LoadFrom(file);
611  LoadPlugins(library);
612  }
613  catch(BadImageFormatException)
614  {
615  }
616  }
617  }
618 
619  private void LoadPlugins(Assembly library)
620  {
621  foreach(Type pluginType in library.GetTypes())
622  {
623  try
624  {
625  if (pluginType.IsAbstract || pluginType.IsNotPublic)
626  continue;
627 
628  string typeName = pluginType.Name;
629 
630  if (pluginType.GetInterface("ITerrainEffect", false) != null)
631  {
632  ITerrainEffect terEffect = (ITerrainEffect)Activator.CreateInstance(library.GetType(pluginType.ToString()));
633 
634  InstallPlugin(typeName, terEffect);
635  }
636  else if (pluginType.GetInterface("ITerrainLoader", false) != null)
637  {
638  ITerrainLoader terLoader = (ITerrainLoader)Activator.CreateInstance(library.GetType(pluginType.ToString()));
639  m_loaders[terLoader.FileExtension] = terLoader;
640  m_log.Info("L ... " + typeName);
641  }
642  }
643  catch(AmbiguousMatchException)
644  {
645  }
646  }
647  }
648 
649  public void InstallPlugin(string pluginName, ITerrainEffect effect)
650  {
651  lock(m_plugineffects)
652  {
653  if (!m_plugineffects.ContainsKey(pluginName))
654  {
655  m_plugineffects.Add(pluginName, effect);
656  m_log.Info("E ... " + pluginName);
657  }
658  else
659  {
660  m_plugineffects[pluginName] = effect;
661  m_log.Info("E ... " + pluginName + " (Replaced)");
662  }
663  }
664  }
665 
666  #endregion
667 
668  #endregion
669 
673  private void InstallDefaultEffects()
674  {
675  // Draggable Paint Brush Effects
676  m_painteffects[StandardTerrainEffects.Raise] = new RaiseSphere();
677  m_painteffects[StandardTerrainEffects.Lower] = new LowerSphere();
678  m_painteffects[StandardTerrainEffects.Smooth] = new SmoothSphere();
679  m_painteffects[StandardTerrainEffects.Noise] = new NoiseSphere();
680  m_painteffects[StandardTerrainEffects.Flatten] = new FlattenSphere();
681  m_painteffects[StandardTerrainEffects.Revert] = new RevertSphere(m_baked);
682  m_painteffects[StandardTerrainEffects.Erode] = new ErodeSphere();
683  m_painteffects[StandardTerrainEffects.Weather] = new WeatherSphere();
684  m_painteffects[StandardTerrainEffects.Olsen] = new OlsenSphere();
685 
686  // Area of effect selection effects
687  m_floodeffects[StandardTerrainEffects.Raise] = new RaiseArea();
688  m_floodeffects[StandardTerrainEffects.Lower] = new LowerArea();
689  m_floodeffects[StandardTerrainEffects.Smooth] = new SmoothArea();
690  m_floodeffects[StandardTerrainEffects.Noise] = new NoiseArea();
691  m_floodeffects[StandardTerrainEffects.Flatten] = new FlattenArea();
692  m_floodeffects[StandardTerrainEffects.Revert] = new RevertArea(m_baked);
693 
694  // Terrain Modifier operations
695 
696  m_modifyOperations["min"] = new MinModifier(this);
697  m_modifyOperations["max"] = new MaxModifier(this);
698  m_modifyOperations["raise"] = new RaiseModifier(this);
699  m_modifyOperations["lower"] = new LowerModifier(this);
700  m_modifyOperations["fill"] = new FillModifier(this);
701  m_modifyOperations["smooth"] = new SmoothModifier(this);
702  m_modifyOperations["noise"] = new NoiseModifier(this);
703 
704  // Filesystem load/save loaders
705  m_loaders[".r32"] = new RAW32();
706  m_loaders[".f32"] = m_loaders[".r32"];
707  m_loaders[".ter"] = new Terragen();
708  m_loaders[".raw"] = new LLRAW();
709  m_loaders[".jpg"] = new JPEG();
710  m_loaders[".jpeg"] = m_loaders[".jpg"];
711  m_loaders[".bmp"] = new BMP();
712  m_loaders[".png"] = new PNG();
713  m_loaders[".gif"] = new GIF();
714  m_loaders[".tif"] = new TIFF();
715  m_loaders[".tiff"] = m_loaders[".tif"];
716  }
717 
720 
722  public void UpdateBakedMap()
723  {
724  m_baked = m_channel.MakeCopy();
725  m_painteffects[StandardTerrainEffects.Revert] = new RevertSphere(m_baked);
726  m_floodeffects[StandardTerrainEffects.Revert] = new RevertArea(m_baked);
727  }
728 
737  public void LoadFromFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
738  {
739  int offsetX = (int)m_scene.RegionInfo.RegionLocX - fileStartX;
740  int offsetY = (int)m_scene.RegionInfo.RegionLocY - fileStartY;
741 
742  if (offsetX >= 0 && offsetX < fileWidth && offsetY >= 0 && offsetY < fileHeight)
743  {
744  // this region is included in the tile request
745  foreach(KeyValuePair<string, ITerrainLoader> loader in m_loaders)
746  {
747  if (filename.EndsWith(loader.Key))
748  {
749  lock(m_scene)
750  {
751  ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY,
752  fileWidth, fileHeight,
753  (int) m_scene.RegionInfo.RegionSizeX,
754  (int) m_scene.RegionInfo.RegionSizeY);
755  m_scene.Heightmap = channel;
756  m_channel = channel;
757  UpdateBakedMap();
758  }
759 
760  return;
761  }
762  }
763  }
764  }
765 
778  public void SaveToFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
779  {
780  int offsetX = (int)m_scene.RegionInfo.RegionLocX - fileStartX;
781  int offsetY = (int)m_scene.RegionInfo.RegionLocY - fileStartY;
782 
783  if (offsetX < 0 || offsetX >= fileWidth || offsetY < 0 || offsetY >= fileHeight)
784  {
785  MainConsole.Instance.OutputFormat(
786  "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.",
787  m_scene.RegionInfo.RegionLocX, m_scene.RegionInfo.RegionLocY, fileWidth, fileStartX, fileHeight, fileStartY);
788 
789  return;
790  }
791 
792  // this region is included in the tile request
793  foreach(KeyValuePair<string, ITerrainLoader> loader in m_loaders)
794  {
795  if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave())
796  {
797  lock(m_scene)
798  {
799  loader.Value.SaveFile(m_channel, filename, offsetX, offsetY,
800  fileWidth, fileHeight,
801  (int)m_scene.RegionInfo.RegionSizeX,
802  (int)m_scene.RegionInfo.RegionSizeY);
803 
804  MainConsole.Instance.OutputFormat(
805  "Saved terrain from ({0},{1}) to ({2},{3}) from {4} to {5}",
806  fileStartX, fileStartY, fileStartX + fileWidth - 1, fileStartY + fileHeight - 1,
807  m_scene.RegionInfo.RegionName, filename);
808  }
809 
810  return;
811  }
812  }
813 
814  MainConsole.Instance.OutputFormat(
815  "ERROR: Could not save terrain from {0} to {1}. Valid file extensions are {2}",
816  m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave);
817  }
818 
819 
827  private void EventManager_TerrainCheckUpdates()
828  {
829  Util.FireAndForget(
830  EventManager_TerrainCheckUpdatesAsync);
831  }
832 
833  object TerrainCheckUpdatesLock = new object();
834 
835  private void EventManager_TerrainCheckUpdatesAsync(object o)
836  {
837  // dont overlap execution
838  if(Monitor.TryEnter(TerrainCheckUpdatesLock))
839  {
840  // this needs fixing
841  TerrainData terrData = m_channel.GetTerrainData();
842 
843  bool shouldTaint = false;
844  for (int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize)
845  {
846  for (int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize)
847  {
848  if (terrData.IsTaintedAt(x, y,true))
849  {
850  // Found a patch that was modified. Push this flag into the clients.
851  SendToClients(terrData, x, y);
852  shouldTaint = true;
853  }
854  }
855  }
856 
857  // This event also causes changes to be sent to the clients
858  CheckSendingPatchesToClients();
859 
860  // If things changes, generate some events
861  if (shouldTaint)
862  {
863  m_scene.EventManager.TriggerTerrainTainted();
864  m_tainted = true;
865  }
866  Monitor.Exit(TerrainCheckUpdatesLock);
867  }
868  }
869 
874  private void EventManager_OnTerrainTick()
875  {
876  if (m_tainted)
877  {
878  m_tainted = false;
879  m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised());
880  m_scene.SaveTerrain();
881 
882  // Clients who look at the map will never see changes after they looked at the map, so i've commented this out.
883  //m_scene.CreateTerrainTexture(true);
884  }
885  }
886 
891  private void EventManager_OnPluginConsole(string[] args)
892  {
893  if (args[0] == "terrain")
894  {
895  if (args.Length == 1)
896  {
897  m_commander.ProcessConsoleCommand("help", new string[0]);
898  return;
899  }
900 
901  string[] tmpArgs = new string[args.Length - 2];
902  int i;
903  for(i = 2; i < args.Length; i++)
904  tmpArgs[i - 2] = args[i];
905 
906  m_commander.ProcessConsoleCommand(args[1], tmpArgs);
907  }
908  }
909 
914  private void EventManager_OnNewClient(IClientAPI client)
915  {
916  client.OnModifyTerrain += client_OnModifyTerrain;
917  client.OnBakeTerrain += client_OnBakeTerrain;
918  client.OnLandUndo += client_OnLandUndo;
919  client.OnUnackedTerrain += client_OnUnackedTerrain;
920  }
921 
926  private void EventManager_OnClientClosed(UUID client, Scene scene)
927  {
928  ScenePresence presence = scene.GetScenePresence(client);
929  if (presence != null)
930  {
931  presence.ControllingClient.OnModifyTerrain -= client_OnModifyTerrain;
932  presence.ControllingClient.OnBakeTerrain -= client_OnBakeTerrain;
933  presence.ControllingClient.OnLandUndo -= client_OnLandUndo;
934  presence.ControllingClient.OnUnackedTerrain -= client_OnUnackedTerrain;
935  }
936  lock (m_perClientPatchUpdates)
937  m_perClientPatchUpdates.Remove(client);
938  }
939 
945  private bool EnforceEstateLimits()
946  {
947  TerrainData terrData = m_channel.GetTerrainData();
948 
949  bool wasLimited = false;
950  for (int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize)
951  {
952  for (int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize)
953  {
954  if (terrData.IsTaintedAt(x, y, false /* clearOnTest */))
955  {
956  // If we should respect the estate settings then
957  // fixup and height deltas that don't respect them.
958  // Note that LimitChannelChanges() modifies the TerrainChannel with the limited height values.
959  wasLimited |= LimitChannelChanges(terrData, x, y);
960  }
961  }
962  }
963  return wasLimited;
964  }
965 
971  private bool LimitChannelChanges(TerrainData terrData, int xStart, int yStart)
972  {
973  bool changesLimited = false;
974  float minDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit;
975  float maxDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit;
976 
977  // loop through the height map for this patch and compare it against
978  // the baked map
979  for (int x = xStart; x < xStart + Constants.TerrainPatchSize; x++)
980  {
981  for(int y = yStart; y < yStart + Constants.TerrainPatchSize; y++)
982  {
983  float requestedHeight = terrData[x, y];
984  float bakedHeight = (float)m_baked[x, y];
985  float requestedDelta = requestedHeight - bakedHeight;
986 
987  if (requestedDelta > maxDelta)
988  {
989  terrData[x, y] = bakedHeight + maxDelta;
990  changesLimited = true;
991  }
992  else if (requestedDelta < minDelta)
993  {
994  terrData[x, y] = bakedHeight + minDelta; //as lower is a -ve delta
995  changesLimited = true;
996  }
997  }
998  }
999 
1000  return changesLimited;
1001  }
1002 
1003  private void client_OnLandUndo(IClientAPI client)
1004  {
1005  }
1006 
1013  private void SendToClients(TerrainData terrData, int x, int y)
1014  {
1015  if (m_sendTerrainUpdatesByViewDistance)
1016  {
1017  // Add that this patch needs to be sent to the accounting for each client.
1018  lock (m_perClientPatchUpdates)
1019  {
1020  m_scene.ForEachScenePresence(presence =>
1021  {
1022  PatchUpdates thisClientUpdates;
1023  if (!m_perClientPatchUpdates.TryGetValue(presence.UUID, out thisClientUpdates))
1024  {
1025  // There is a ScenePresence without a send patch map. Create one.
1026  thisClientUpdates = new PatchUpdates(terrData, presence);
1027  m_perClientPatchUpdates.Add(presence.UUID, thisClientUpdates);
1028  }
1029  thisClientUpdates.SetByXY(x, y, true);
1030  }
1031  );
1032  }
1033  }
1034  else
1035  {
1036  // Legacy update sending where the update is sent out as soon as noticed
1037  // We know the actual terrain data that is passed is ignored so this passes a dummy heightmap.
1038  //float[] heightMap = terrData.GetFloatsSerialized();
1039  float[] heightMap = new float[10];
1040  m_scene.ForEachClient(
1041  delegate (IClientAPI controller)
1042  {
1043  controller.SendLayerData(x / Constants.TerrainPatchSize,
1044  y / Constants.TerrainPatchSize,
1045  heightMap);
1046  }
1047  );
1048  }
1049  }
1050 
1051  private class PatchesToSend : IComparable<PatchesToSend>
1052  {
1053  public int PatchX;
1054  public int PatchY;
1055  public float Dist;
1056  public PatchesToSend(int pX, int pY, float pDist)
1057  {
1058  PatchX = pX;
1059  PatchY = pY;
1060  Dist = pDist;
1061  }
1062  public int CompareTo(PatchesToSend other)
1063  {
1064  return Dist.CompareTo(other.Dist);
1065  }
1066  }
1067 
1068  // Called each frame time to see if there are any patches to send to any of the
1069  // ScenePresences.
1070  // Loop through all the per-client info and send any patches necessary.
1071  private void CheckSendingPatchesToClients()
1072  {
1073  lock (m_perClientPatchUpdates)
1074  {
1075  foreach (PatchUpdates pups in m_perClientPatchUpdates.Values)
1076  {
1077  if(pups.Presence.IsDeleted)
1078  continue;
1079 
1080  // limit rate acording to udp land queue state
1081  if (!pups.Presence.ControllingClient.CanSendLayerData())
1082  continue;
1083 
1084  if (pups.HasUpdates())
1085  {
1086  if (m_sendTerrainUpdatesByViewDistance)
1087  {
1088  // There is something that could be sent to this client.
1089  List<PatchesToSend> toSend = GetModifiedPatchesInViewDistance(pups);
1090  if (toSend.Count > 0)
1091  {
1092  // m_log.DebugFormat("{0} CheckSendingPatchesToClient: sending {1} patches to {2} in region {3}",
1093  // LogHeader, toSend.Count, pups.Presence.Name, m_scene.RegionInfo.RegionName);
1094  // Sort the patches to send by the distance from the presence
1095  toSend.Sort();
1096  /*
1097  foreach (PatchesToSend pts in toSend)
1098  {
1099  pups.Presence.ControllingClient.SendLayerData(pts.PatchX, pts.PatchY, null);
1100  // presence.ControllingClient.SendLayerData(xs.ToArray(), ys.ToArray(), null, TerrainPatch.LayerType.Land);
1101  }
1102  */
1103 
1104  int[] xPieces = new int[toSend.Count];
1105  int[] yPieces = new int[toSend.Count];
1106  float[] patchPieces = new float[toSend.Count * 2];
1107  int pieceIndex = 0;
1108  foreach (PatchesToSend pts in toSend)
1109  {
1110  patchPieces[pieceIndex++] = pts.PatchX;
1111  patchPieces[pieceIndex++] = pts.PatchY;
1112  }
1113  pups.Presence.ControllingClient.SendLayerData(-toSend.Count, 0, patchPieces);
1114  }
1115  if (pups.sendAll && toSend.Count < 1024)
1116  SendAllModifiedPatchs(pups);
1117  }
1118  else
1119  SendAllModifiedPatchs(pups);
1120  }
1121  }
1122  }
1123  }
1124  private void SendAllModifiedPatchs(PatchUpdates pups)
1125  {
1126  if (!pups.sendAll) // sanity
1127  return;
1128 
1129  int limitX = (int)m_scene.RegionInfo.RegionSizeX / Constants.TerrainPatchSize;
1130  int limitY = (int)m_scene.RegionInfo.RegionSizeY / Constants.TerrainPatchSize;
1131 
1132  if (pups.sendAllcurrentX >= limitX && pups.sendAllcurrentY >= limitY)
1133  {
1134  pups.sendAll = false;
1135  pups.sendAllcurrentX = 0;
1136  pups.sendAllcurrentY = 0;
1137  return;
1138  }
1139 
1140  int npatchs = 0;
1141  List<PatchesToSend> patchs = new List<PatchesToSend>();
1142  int x = pups.sendAllcurrentX;
1143  int y = pups.sendAllcurrentY;
1144  // send it in the order viewer draws it
1145  // even if not best for memory scan
1146  for (; y < limitY; y++)
1147  {
1148  for (; x < limitX; x++)
1149  {
1150  if (pups.GetByPatch(x, y))
1151  {
1152  pups.SetByPatch(x, y, false);
1153  patchs.Add(new PatchesToSend(x, y, 0));
1154  if (++npatchs >= 128)
1155  {
1156  x++;
1157  break;
1158  }
1159  }
1160  }
1161  if (npatchs >= 128)
1162  break;
1163  x = 0;
1164  }
1165 
1166  if (x >= limitX && y >= limitY)
1167  {
1168  pups.sendAll = false;
1169  pups.sendAllcurrentX = 0;
1170  pups.sendAllcurrentY = 0;
1171  }
1172  else
1173  {
1174  pups.sendAllcurrentX = x;
1175  pups.sendAllcurrentY = y;
1176  }
1177 
1178  npatchs = patchs.Count;
1179  if (npatchs > 0)
1180  {
1181  int[] xPieces = new int[npatchs];
1182  int[] yPieces = new int[npatchs];
1183  float[] patchPieces = new float[npatchs * 2];
1184  int pieceIndex = 0;
1185  foreach (PatchesToSend pts in patchs)
1186  {
1187  patchPieces[pieceIndex++] = pts.PatchX;
1188  patchPieces[pieceIndex++] = pts.PatchY;
1189  }
1190  pups.Presence.ControllingClient.SendLayerData(-npatchs, 0, patchPieces);
1191  }
1192  }
1193 
1194  private List<PatchesToSend> GetModifiedPatchesInViewDistance(PatchUpdates pups)
1195  {
1196  List<PatchesToSend> ret = new List<PatchesToSend>();
1197 
1198  int npatchs = 0;
1199 
1200  ScenePresence presence = pups.Presence;
1201  if (presence == null)
1202  return ret;
1203 
1204  float minz = presence.AbsolutePosition.Z;
1205  if (presence.CameraPosition.Z < minz)
1206  minz = presence.CameraPosition.Z;
1207 
1208  // this limit should be max terrainheight + max draw
1209  if (minz > 1500f)
1210  return ret;
1211 
1212  int DrawDistance = (int)presence.DrawDistance;
1213 
1214  DrawDistance = DrawDistance / Constants.TerrainPatchSize;
1215 
1216  int testposX;
1217  int testposY;
1218 
1219  if (Math.Abs(presence.AbsolutePosition.X - presence.CameraPosition.X) > 30
1220  || Math.Abs(presence.AbsolutePosition.Y - presence.CameraPosition.Y) > 30)
1221  {
1222  testposX = (int)presence.CameraPosition.X / Constants.TerrainPatchSize;
1223  testposY = (int)presence.CameraPosition.Y / Constants.TerrainPatchSize;
1224  }
1225  else
1226  {
1227  testposX = (int)presence.AbsolutePosition.X / Constants.TerrainPatchSize;
1228  testposY = (int)presence.AbsolutePosition.Y / Constants.TerrainPatchSize;
1229  }
1230  int limitX = (int)m_scene.RegionInfo.RegionSizeX / Constants.TerrainPatchSize;
1231  int limitY = (int)m_scene.RegionInfo.RegionSizeY / Constants.TerrainPatchSize;
1232 
1233  // Compute the area of patches within our draw distance
1234  int startX = testposX - DrawDistance;
1235  if (startX < 0)
1236  startX = 0;
1237  else if (startX >= limitX)
1238  startX = limitX - 1;
1239 
1240  int startY = testposY - DrawDistance;
1241  if (startY < 0)
1242  startY = 0;
1243  else if (startY >= limitY)
1244  startY = limitY - 1;
1245 
1246  int endX = testposX + DrawDistance;
1247  if (endX < 0)
1248  endX = 0;
1249  else if (endX > limitX)
1250  endX = limitX;
1251 
1252  int endY = testposY + DrawDistance;
1253  if (endY < 0)
1254  endY = 0;
1255  else if (endY > limitY)
1256  endY = limitY;
1257 
1258  int distx;
1259  int disty;
1260  int distsq;
1261 
1262  DrawDistance *= DrawDistance;
1263 
1264  for (int x = startX; x < endX; x++)
1265  {
1266  for (int y = startY; y < endY; y++)
1267  {
1268  if (pups.GetByPatch(x, y))
1269  {
1270  distx = x - testposX;
1271  disty = y - testposY;
1272  distsq = distx * distx + disty * disty;
1273  if (distsq < DrawDistance)
1274  {
1275  pups.SetByPatch(x, y, false);
1276  ret.Add(new PatchesToSend(x, y, (float)distsq));
1277  if (npatchs++ > 1024)
1278  {
1279  y = endY;
1280  x = endX;
1281  }
1282  }
1283  }
1284  }
1285  }
1286  return ret;
1287  }
1288 
1289  private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action,
1290  float north, float west, float south, float east, UUID agentId)
1291  {
1292  bool god = m_scene.Permissions.IsGod(user);
1293  bool allowed = false;
1294  if (north == south && east == west)
1295  {
1296  if (m_painteffects.ContainsKey((StandardTerrainEffects)action))
1297  {
1298  bool[,] allowMask = new bool[m_channel.Width, m_channel.Height];
1299  allowMask.Initialize();
1300  int n = size + 1;
1301  if (n > 2)
1302  n = 4;
1303 
1304  int zx = (int)(west + 0.5);
1305  int zy = (int)(north + 0.5);
1306 
1307  int startX = zx - n;
1308  if (startX < 0)
1309  startX = 0;
1310 
1311  int startY = zy - n;
1312  if (startY < 0)
1313  startY = 0;
1314 
1315  int endX = zx + n;
1316  if (endX >= m_channel.Width)
1317  endX = m_channel.Width - 1;
1318  int endY = zy + n;
1319  if (endY >= m_channel.Height)
1320  endY = m_channel.Height - 1;
1321 
1322  int x, y;
1323 
1324  for (x = startX; x <= endX; x++)
1325  {
1326  for (y = startY; y <= endY; y++)
1327  {
1328  if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x, y, 0)))
1329  {
1330  allowMask[x, y] = true;
1331  allowed = true;
1332  }
1333  }
1334  }
1335  if (allowed)
1336  {
1337  StoreUndoState();
1338  m_painteffects[(StandardTerrainEffects) action].PaintEffect(
1339  m_channel, allowMask, west, south, height, size, seconds,
1340  startX, endX, startY, endY);
1341 
1342  //block changes outside estate limits
1343  if (!god)
1344  EnforceEstateLimits();
1345  }
1346  }
1347  else
1348  {
1349  m_log.Debug("Unknown terrain brush type " + action);
1350  }
1351  }
1352  else
1353  {
1354  if (m_floodeffects.ContainsKey((StandardTerrainEffects)action))
1355  {
1356  bool[,] fillArea = new bool[m_channel.Width, m_channel.Height];
1357  fillArea.Initialize();
1358 
1359  int startX = (int)west;
1360  int startY = (int)south;
1361  int endX = (int)east;
1362  int endY = (int)north;
1363 
1364  if (startX < 0)
1365  startX = 0;
1366  else if (startX >= m_channel.Width)
1367  startX = m_channel.Width - 1;
1368 
1369  if (endX < 0)
1370  endX = 0;
1371  else if (endX >= m_channel.Width)
1372  endX = m_channel.Width - 1;
1373 
1374  if (startY < 0)
1375  startY = 0;
1376  else if (startY >= m_channel.Height)
1377  startY = m_channel.Height - 1;
1378 
1379  if (endY < 0)
1380  endY = 0;
1381  else if (endY >= m_channel.Height)
1382  endY = m_channel.Height - 1;
1383 
1384 
1385  int x, y;
1386 
1387  for (x = startX; x <= endX; x++)
1388  {
1389  for (y = startY; y <= endY; y++)
1390  {
1391  if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x, y, 0)))
1392  {
1393  fillArea[x, y] = true;
1394  allowed = true;
1395  }
1396  }
1397  }
1398 
1399  if (allowed)
1400  {
1401  StoreUndoState();
1402  m_floodeffects[(StandardTerrainEffects)action].FloodEffect(m_channel, fillArea, size,
1403  startX, endX, startY, endY);
1404 
1405  //block changes outside estate limits
1406  if (!god)
1407  EnforceEstateLimits();
1408  }
1409  }
1410  else
1411  {
1412  m_log.Debug("Unknown terrain flood type " + action);
1413  }
1414  }
1415  }
1416 
1417  private void client_OnBakeTerrain(IClientAPI remoteClient)
1418  {
1419  // Not a good permissions check (see client_OnModifyTerrain above), need to check the entire area.
1420  // for now check a point in the centre of the region
1421 
1422  if (m_scene.Permissions.CanIssueEstateCommand(remoteClient.AgentId, true))
1423  {
1424  InterfaceBakeTerrain(null); //bake terrain does not use the passed in parameter
1425  }
1426  }
1427 
1428  protected void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY)
1429  {
1430  //m_log.Debug("Terrain packet unacked, resending patch: " + patchX + " , " + patchY);
1431  // SendLayerData does not use the heightmap parameter. This kludge is so as to not change IClientAPI.
1432  float[] heightMap = new float[10];
1433  client.SendLayerData(patchX, patchY, heightMap);
1434  }
1435 
1436  private void StoreUndoState()
1437  {
1438  }
1439 
1440  #region Console Commands
1441 
1442  private void InterfaceLoadFile(Object[] args)
1443  {
1444  LoadFromFile((string) args[0]);
1445  }
1446 
1447  private void InterfaceLoadTileFile(Object[] args)
1448  {
1449  LoadFromFile((string) args[0],
1450  (int) args[1],
1451  (int) args[2],
1452  (int) args[3],
1453  (int) args[4]);
1454  }
1455 
1456  private void InterfaceSaveFile(Object[] args)
1457  {
1458  SaveToFile((string)args[0]);
1459  }
1460 
1461  private void InterfaceSaveTileFile(Object[] args)
1462  {
1463  SaveToFile((string)args[0],
1464  (int)args[1],
1465  (int)args[2],
1466  (int)args[3],
1467  (int)args[4]);
1468  }
1469 
1470  private void InterfaceBakeTerrain(Object[] args)
1471  {
1472  UpdateBakedMap();
1473  }
1474 
1475  private void InterfaceRevertTerrain(Object[] args)
1476  {
1477  int x, y;
1478  for (x = 0; x < m_channel.Width; x++)
1479  for (y = 0; y < m_channel.Height; y++)
1480  m_channel[x, y] = m_baked[x, y];
1481 
1482  }
1483 
1484  private void InterfaceFlipTerrain(Object[] args)
1485  {
1486  String direction = (String)args[0];
1487 
1488  if (direction.ToLower().StartsWith("y"))
1489  {
1490  for (int x = 0; x < m_channel.Width; x++)
1491  {
1492  for (int y = 0; y < m_channel.Height / 2; y++)
1493  {
1494  double height = m_channel[x, y];
1495  double flippedHeight = m_channel[x, (int)m_channel.Height - 1 - y];
1496  m_channel[x, y] = flippedHeight;
1497  m_channel[x, (int)m_channel.Height - 1 - y] = height;
1498 
1499  }
1500  }
1501  }
1502  else if (direction.ToLower().StartsWith("x"))
1503  {
1504  for (int y = 0; y < m_channel.Height; y++)
1505  {
1506  for (int x = 0; x < m_channel.Width / 2; x++)
1507  {
1508  double height = m_channel[x, y];
1509  double flippedHeight = m_channel[(int)m_channel.Width - 1 - x, y];
1510  m_channel[x, y] = flippedHeight;
1511  m_channel[(int)m_channel.Width - 1 - x, y] = height;
1512 
1513  }
1514  }
1515  }
1516  else
1517  {
1518  MainConsole.Instance.OutputFormat("ERROR: Unrecognised direction {0} - need x or y", direction);
1519  }
1520  }
1521 
1522  private void InterfaceRescaleTerrain(Object[] args)
1523  {
1524  double desiredMin = (double)args[0];
1525  double desiredMax = (double)args[1];
1526 
1527  // determine desired scaling factor
1528  double desiredRange = desiredMax - desiredMin;
1529  //m_log.InfoFormat("Desired {0}, {1} = {2}", new Object[] { desiredMin, desiredMax, desiredRange });
1530 
1531  if (desiredRange == 0d)
1532  {
1533  // delta is zero so flatten at requested height
1534  InterfaceFillTerrain(new Object[] { args[1] });
1535  }
1536  else
1537  {
1538  //work out current heightmap range
1539  double currMin = double.MaxValue;
1540  double currMax = double.MinValue;
1541 
1542  int width = m_channel.Width;
1543  int height = m_channel.Height;
1544 
1545  for(int x = 0; x < width; x++)
1546  {
1547  for(int y = 0; y < height; y++)
1548  {
1549  double currHeight = m_channel[x, y];
1550  if (currHeight < currMin)
1551  {
1552  currMin = currHeight;
1553  }
1554  else if (currHeight > currMax)
1555  {
1556  currMax = currHeight;
1557  }
1558  }
1559  }
1560 
1561  double currRange = currMax - currMin;
1562  double scale = desiredRange / currRange;
1563 
1564  //m_log.InfoFormat("Current {0}, {1} = {2}", new Object[] { currMin, currMax, currRange });
1565  //m_log.InfoFormat("Scale = {0}", scale);
1566 
1567  // scale the heightmap accordingly
1568  for(int x = 0; x < width; x++)
1569  {
1570  for(int y = 0; y < height; y++)
1571  {
1572  double currHeight = m_channel[x, y] - currMin;
1573  m_channel[x, y] = desiredMin + (currHeight * scale);
1574  }
1575  }
1576 
1577  }
1578  }
1579 
1580  private void InterfaceElevateTerrain(Object[] args)
1581  {
1582  double val = (double)args[0];
1583 
1584  int x, y;
1585  for (x = 0; x < m_channel.Width; x++)
1586  for (y = 0; y < m_channel.Height; y++)
1587  m_channel[x, y] += val;
1588  }
1589 
1590  private void InterfaceMultiplyTerrain(Object[] args)
1591  {
1592  int x, y;
1593  double val = (double)args[0];
1594 
1595  for (x = 0; x < m_channel.Width; x++)
1596  for (y = 0; y < m_channel.Height; y++)
1597  m_channel[x, y] *= val;
1598  }
1599 
1600  private void InterfaceLowerTerrain(Object[] args)
1601  {
1602  int x, y;
1603  double val = (double)args[0];
1604 
1605  for (x = 0; x < m_channel.Width; x++)
1606  for (y = 0; y < m_channel.Height; y++)
1607  m_channel[x, y] -= val;
1608  }
1609 
1610  public void InterfaceFillTerrain(Object[] args)
1611  {
1612  int x, y;
1613  double val = (double)args[0];
1614 
1615  for (x = 0; x < m_channel.Width; x++)
1616  for (y = 0; y < m_channel.Height; y++)
1617  m_channel[x, y] = val;
1618  }
1619 
1620  private void InterfaceMinTerrain(Object[] args)
1621  {
1622  int x, y;
1623  double val = (double)args[0];
1624  for (x = 0; x < m_channel.Width; x++)
1625  {
1626  for(y = 0; y < m_channel.Height; y++)
1627  {
1628  m_channel[x, y] = Math.Max(val, m_channel[x, y]);
1629  }
1630  }
1631  }
1632 
1633  private void InterfaceMaxTerrain(Object[] args)
1634  {
1635  int x, y;
1636  double val = (double)args[0];
1637  for (x = 0; x < m_channel.Width; x++)
1638  {
1639  for(y = 0; y < m_channel.Height; y++)
1640  {
1641  m_channel[x, y] = Math.Min(val, m_channel[x, y]);
1642  }
1643  }
1644  }
1645 
1646  private void InterfaceShow(Object[] args)
1647  {
1648  Vector2 point;
1649 
1650  if (!ConsoleUtil.TryParseConsole2DVector((string)args[0], null, out point))
1651  {
1652  Console.WriteLine("ERROR: {0} is not a valid vector", args[0]);
1653  return;
1654  }
1655 
1656  double height = m_channel[(int)point.X, (int)point.Y];
1657 
1658  Console.WriteLine("Terrain height at {0} is {1}", point, height);
1659  }
1660 
1661  private void InterfaceShowDebugStats(Object[] args)
1662  {
1663  double max = Double.MinValue;
1664  double min = double.MaxValue;
1665  double sum = 0;
1666 
1667  int x;
1668  for(x = 0; x < m_channel.Width; x++)
1669  {
1670  int y;
1671  for(y = 0; y < m_channel.Height; y++)
1672  {
1673  sum += m_channel[x, y];
1674  if (max < m_channel[x, y])
1675  max = m_channel[x, y];
1676  if (min > m_channel[x, y])
1677  min = m_channel[x, y];
1678  }
1679  }
1680 
1681  double avg = sum / (m_channel.Height * m_channel.Width);
1682 
1683  MainConsole.Instance.OutputFormat("Channel {0}x{1}", m_channel.Width, m_channel.Height);
1684  MainConsole.Instance.OutputFormat("max/min/avg/sum: {0}/{1}/{2}/{3}", max, min, avg, sum);
1685  }
1686 
1687  private void InterfaceEnableExperimentalBrushes(Object[] args)
1688  {
1689  if ((bool)args[0])
1690  {
1691  m_painteffects[StandardTerrainEffects.Revert] = new WeatherSphere();
1692  m_painteffects[StandardTerrainEffects.Flatten] = new OlsenSphere();
1693  m_painteffects[StandardTerrainEffects.Smooth] = new ErodeSphere();
1694  }
1695  else
1696  {
1697  InstallDefaultEffects();
1698  }
1699  }
1700 
1701  private void InterfaceRunPluginEffect(Object[] args)
1702  {
1703  string firstArg = (string)args[0];
1704 
1705  if (firstArg == "list")
1706  {
1707  MainConsole.Instance.Output("List of loaded plugins");
1708  foreach(KeyValuePair<string, ITerrainEffect> kvp in m_plugineffects)
1709  {
1710  MainConsole.Instance.Output(kvp.Key);
1711  }
1712  return;
1713  }
1714 
1715  if (firstArg == "reload")
1716  {
1717  LoadPlugins();
1718  return;
1719  }
1720 
1721  if (m_plugineffects.ContainsKey(firstArg))
1722  {
1723  m_plugineffects[firstArg].RunEffect(m_channel);
1724  }
1725  else
1726  {
1727  MainConsole.Instance.Output("WARNING: No such plugin effect {0} loaded.", firstArg);
1728  }
1729  }
1730 
1731  private void InstallInterfaces()
1732  {
1733  Command loadFromFileCommand =
1734  new Command("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file.");
1735  loadFromFileCommand.AddArgument("filename",
1736  "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
1737  m_supportedFileExtensions, "String");
1738 
1739  Command saveToFileCommand =
1740  new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file.");
1741  saveToFileCommand.AddArgument("filename",
1742  "The destination filename for your heightmap, the file extension determines the format to save in. Supported extensions include: " +
1743  m_supportedFileExtensions, "String");
1744 
1745  Command loadFromTileCommand =
1746  new Command("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file.");
1747  loadFromTileCommand.AddArgument("filename",
1748  "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
1749  m_supportedFileExtensions, "String");
1750  loadFromTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
1751  loadFromTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
1752  loadFromTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
1753  "Integer");
1754  loadFromTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first section on the file",
1755  "Integer");
1756 
1757  Command saveToTileCommand =
1758  new Command("save-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceSaveTileFile, "Saves the current heightmap to the larger file.");
1759  saveToTileCommand.AddArgument("filename",
1760  "The file you wish to save to, the file extension determines the loader to be used. Supported extensions include: " +
1761  m_supportFileExtensionsForTileSave, "String");
1762  saveToTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
1763  saveToTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
1764  saveToTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
1765  "Integer");
1766  saveToTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first tile on the file\n"
1767  + "= Example =\n"
1768  + "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"
1769  + " # terrain save-tile ST06.png 2 3 9910 10234\n",
1770  "Integer");
1771 
1772  // Terrain adjustments
1773  Command fillRegionCommand =
1774  new Command("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value.");
1775  fillRegionCommand.AddArgument("value", "The numeric value of the height you wish to set your region to.",
1776  "Double");
1777 
1778  Command elevateCommand =
1779  new Command("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount.");
1780  elevateCommand.AddArgument("amount", "The amount of height to add to the terrain in meters.", "Double");
1781 
1782  Command lowerCommand =
1783  new Command("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount.");
1784  lowerCommand.AddArgument("amount", "The amount of height to remove from the terrain in meters.", "Double");
1785 
1786  Command multiplyCommand =
1787  new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified.");
1788  multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Double");
1789 
1790  Command bakeRegionCommand =
1791  new Command("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions baked map.");
1792  Command revertRegionCommand =
1793  new Command("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the baked map terrain into the regions heightmap.");
1794 
1795  Command flipCommand =
1796  new Command("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis");
1797  flipCommand.AddArgument("direction", "[x|y] the direction to flip the terrain in", "String");
1798 
1799  Command rescaleCommand =
1800  new Command("rescale", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRescaleTerrain, "Rescales the current terrain to fit between the given min and max heights");
1801  rescaleCommand.AddArgument("min", "min terrain height after rescaling", "Double");
1802  rescaleCommand.AddArgument("max", "max terrain height after rescaling", "Double");
1803 
1804  Command minCommand = new Command("min", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMinTerrain, "Sets the minimum terrain height to the specified value.");
1805  minCommand.AddArgument("min", "terrain height to use as minimum", "Double");
1806 
1807  Command maxCommand = new Command("max", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMaxTerrain, "Sets the maximum terrain height to the specified value.");
1808  maxCommand.AddArgument("min", "terrain height to use as maximum", "Double");
1809 
1810 
1811  // Debug
1812  Command showDebugStatsCommand =
1813  new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats,
1814  "Shows some information about the regions heightmap for debugging purposes.");
1815 
1816  Command showCommand =
1817  new Command("show", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceShow,
1818  "Shows terrain height at a given co-ordinate.");
1819  showCommand.AddArgument("point", "point in <x>,<y> format with no spaces (e.g. 45,45)", "String");
1820 
1821  Command experimentalBrushesCommand =
1822  new Command("newbrushes", CommandIntentions.COMMAND_HAZARDOUS, InterfaceEnableExperimentalBrushes,
1823  "Enables experimental brushes which replace the standard terrain brushes. WARNING: This is a debug setting and may be removed at any time.");
1824  experimentalBrushesCommand.AddArgument("Enabled?", "true / false - Enable new brushes", "Boolean");
1825 
1826  // Plugins
1827  Command pluginRunCommand =
1828  new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect");
1829  pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String");
1830 
1831  m_commander.RegisterCommand("load", loadFromFileCommand);
1832  m_commander.RegisterCommand("load-tile", loadFromTileCommand);
1833  m_commander.RegisterCommand("save", saveToFileCommand);
1834  m_commander.RegisterCommand("save-tile", saveToTileCommand);
1835  m_commander.RegisterCommand("fill", fillRegionCommand);
1836  m_commander.RegisterCommand("elevate", elevateCommand);
1837  m_commander.RegisterCommand("lower", lowerCommand);
1838  m_commander.RegisterCommand("multiply", multiplyCommand);
1839  m_commander.RegisterCommand("bake", bakeRegionCommand);
1840  m_commander.RegisterCommand("revert", revertRegionCommand);
1841  m_commander.RegisterCommand("newbrushes", experimentalBrushesCommand);
1842  m_commander.RegisterCommand("show", showCommand);
1843  m_commander.RegisterCommand("stats", showDebugStatsCommand);
1844  m_commander.RegisterCommand("effect", pluginRunCommand);
1845  m_commander.RegisterCommand("flip", flipCommand);
1846  m_commander.RegisterCommand("rescale", rescaleCommand);
1847  m_commander.RegisterCommand("min", minCommand);
1848  m_commander.RegisterCommand("max", maxCommand);
1849 
1850  // Add this to our scene so scripts can call these functions
1851  m_scene.RegisterModuleCommander(m_commander);
1852 
1853  // Add Modify command to Scene, since Command object requires fixed-length arglists
1854  m_scene.AddCommand("Terrain", this, "terrain modify",
1855  "terrain modify <operation> <value> [<area>] [<taper>]",
1856  "Modifies the terrain as instructed." +
1857  "\nEach operation can be limited to an area of effect:" +
1858  "\n * -ell=x,y,rx[,ry] constrains the operation to an ellipse centred at x,y" +
1859  "\n * -rec=x,y,dx[,dy] constrains the operation to a rectangle based at x,y" +
1860  "\nEach operation can have its effect tapered based on distance from centre:" +
1861  "\n * elliptical operations taper as cones" +
1862  "\n * rectangular operations taper as pyramids"
1863  ,
1864  ModifyCommand);
1865 
1866  }
1867 
1868  public void ModifyCommand(string module, string[] cmd)
1869  {
1870  string result;
1871  Scene scene = SceneManager.Instance.CurrentScene;
1872  if ((scene != null) && (scene != m_scene))
1873  {
1874  result = String.Empty;
1875  }
1876  else if (cmd.Length > 2)
1877  {
1878  string operationType = cmd[2];
1879 
1880 
1881  ITerrainModifier operation;
1882  if (!m_modifyOperations.TryGetValue(operationType, out operation))
1883  {
1884  result = String.Format("Terrain Modify \"{0}\" not found.", operationType);
1885  }
1886  else if ((cmd.Length > 3) && (cmd[3] == "usage"))
1887  {
1888  result = "Usage: " + operation.GetUsage();
1889  }
1890  else
1891  {
1892  result = operation.ModifyTerrain(m_channel, cmd);
1893  }
1894 
1895  if (result == String.Empty)
1896  {
1897  result = "Modified terrain";
1898  m_log.DebugFormat("Performed terrain operation {0}", operationType);
1899  }
1900  }
1901  else
1902  {
1903  result = "Usage: <operation-name> <arg1> <arg2>...";
1904  }
1905  if (result != String.Empty)
1906  {
1907  MainConsole.Instance.Output(result);
1908  }
1909  }
1910 
1911 #endregion
1912 
1913  }
1914 }
StandardTerrainEffects
A standard set of terrain brushes and effects recognised by viewers
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
void SaveToFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
Save a number of map tiles to a single big image file.
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
void LoadFromStream(string filename, Stream stream)
Load a terrain from a stream.
static bool TryParseConsole2DVector(string rawConsoleVector, Func< string, string > blankComponentFunc, out Vector2 vector)
Convert a vector input from the console to an OpenMetaverse.Vector2
Definition: ConsoleUtil.cs:321
void Initialise(IConfigSource config)
Creates and initialises a terrain module for a region
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
abstract bool IsTaintedAt(int xx, int yy)
void SaveToFile(string filename)
Saves the current heightmap to a specified file.
A single function call encapsulated in a class which enforces arguments when passing around as Object...
Definition: Command.cs:39
void LoadFromFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
Loads a tile from a larger terrain file and installs it into the region.
void LoadFromStream(string filename, Uri pathToTerrainHeightmap)
Loads a terrain file from the specified URI
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
void LoadFromStream(string filename, Vector3 displacement, float radianRotation, Vector2 rotationDisplacement, Stream stream)
Loads a terrain file from a stream and installs it in the scene.
void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId)
Modify Land
A class to enable modules to register console and script commands, which enforces typing and valid in...
Definition: Commander.cs:41
override Vector3 AbsolutePosition
Position of this avatar relative to the region the avatar is in
void PushTerrain(IClientAPI pClient)
When a client initially connects, all the terrain must be pushed to the viewer. This call causes all ...
void LoadFromStream(string filename, Vector3 displacement, float rotationDegrees, Vector2 boundingOrigin, Vector2 boundingSize, Stream stream)
void InstallPlugin(string pluginName, ITerrainEffect effect)
void UpdateBakedMap()
Saves the current state of the region into the baked map buffer.
void SaveToStream(string filename, Stream stream)
Saves the current heightmap to a specified stream.
void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY)
A new version of the old Channel class, simplified
void TaintTerrain()
Taint the terrain. This will lead to sending the terrain data to the clients again. Use this if you change terrain data outside of the terrain module (e.g. in osTerrainSetHeight)
void LoadFromFile(string filename)
Loads a terrain file from disk and installs it in the scene.