aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs')
-rw-r--r--OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs1001
1 files changed, 1001 insertions, 0 deletions
diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs
new file mode 100644
index 0000000..9de7338
--- /dev/null
+++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs
@@ -0,0 +1,1001 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Reflection;
32using OpenMetaverse;
33using log4net;
34using Nini.Config;
35using OpenSim.Framework;
36using OpenSim.Region.Framework.Interfaces;
37using OpenSim.Region.Framework.Scenes;
38using OpenSim.Region.CoreModules.Framework.InterfaceCommander;
39using OpenSim.Region.CoreModules.World.Terrain.FileLoaders;
40using OpenSim.Region.CoreModules.World.Terrain.FloodBrushes;
41using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes;
42
43namespace OpenSim.Region.CoreModules.World.Terrain
44{
45 public class TerrainModule : IRegionModule, ICommandableModule, ITerrainModule
46 {
47 #region StandardTerrainEffects enum
48
49 /// <summary>
50 /// A standard set of terrain brushes and effects recognised by viewers
51 /// </summary>
52 public enum StandardTerrainEffects : byte
53 {
54 Flatten = 0,
55 Raise = 1,
56 Lower = 2,
57 Smooth = 3,
58 Noise = 4,
59 Revert = 5,
60
61 // Extended brushes
62 Erode = 255,
63 Weather = 254,
64 Olsen = 253
65 }
66
67 #endregion
68
69 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
70
71 private readonly Commander m_commander = new Commander("terrain");
72
73 private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects =
74 new Dictionary<StandardTerrainEffects, ITerrainFloodEffect>();
75
76 private readonly Dictionary<string, ITerrainLoader> m_loaders = new Dictionary<string, ITerrainLoader>();
77
78 private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects =
79 new Dictionary<StandardTerrainEffects, ITerrainPaintableEffect>();
80
81 private ITerrainChannel m_channel;
82 private Dictionary<string, ITerrainEffect> m_plugineffects;
83 private ITerrainChannel m_revert;
84 private Scene m_scene;
85 private bool m_tainted;
86
87 #region ICommandableModule Members
88
89 public ICommander CommandInterface
90 {
91 get { return m_commander; }
92 }
93
94 #endregion
95
96 #region IRegionModule Members
97
98 /// <summary>
99 /// Creates and initialises a terrain module for a region
100 /// </summary>
101 /// <param name="scene">Region initialising</param>
102 /// <param name="config">Config for the region</param>
103 public void Initialise(Scene scene, IConfigSource config)
104 {
105 m_scene = scene;
106
107 // Install terrain module in the simulator
108 if (m_scene.Heightmap == null)
109 {
110 lock (m_scene)
111 {
112 m_channel = new TerrainChannel();
113 m_scene.Heightmap = m_channel;
114 m_revert = new TerrainChannel();
115 UpdateRevertMap();
116 }
117 }
118 else
119 {
120 m_channel = m_scene.Heightmap;
121 m_revert = new TerrainChannel();
122 UpdateRevertMap();
123 }
124
125 m_scene.RegisterModuleInterface<ITerrainModule>(this);
126 m_scene.EventManager.OnNewClient += EventManager_OnNewClient;
127 m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole;
128 m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick;
129 }
130
131 /// <summary>
132 /// Enables terrain module when called
133 /// </summary>
134 public void PostInitialise()
135 {
136 InstallDefaultEffects();
137 InstallInterfaces();
138 LoadPlugins();
139 }
140
141 public void Close()
142 {
143 }
144
145 public string Name
146 {
147 get { return "TerrainModule"; }
148 }
149
150 public bool IsSharedModule
151 {
152 get { return false; }
153 }
154
155 #endregion
156
157 #region ITerrainModule Members
158
159 /// <summary>
160 /// Loads a terrain file from disk and installs it in the scene.
161 /// </summary>
162 /// <param name="filename">Filename to terrain file. Type is determined by extension.</param>
163 public void LoadFromFile(string filename)
164 {
165 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
166 {
167 if (filename.EndsWith(loader.Key))
168 {
169 lock (m_scene)
170 {
171 try
172 {
173 ITerrainChannel channel = loader.Value.LoadFile(filename);
174 if (channel.Width != Constants.RegionSize || channel.Height != Constants.RegionSize)
175 {
176 // TerrainChannel expects a RegionSize x RegionSize map, currently
177 throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}",
178 Constants.RegionSize, Constants.RegionSize));
179 }
180 m_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height);
181 m_scene.Heightmap = channel;
182 m_channel = channel;
183 UpdateRevertMap();
184 }
185 catch (NotImplementedException)
186 {
187 m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
188 " parser does not support file loading. (May be save only)");
189 throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
190 }
191 catch (FileNotFoundException)
192 {
193 m_log.Error(
194 "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)");
195 throw new TerrainException(
196 String.Format("unable to load heightmap: file {0} not found (or permissions do not allow access", filename));
197 }
198 catch (ArgumentException e)
199 {
200 m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message);
201 throw new TerrainException(
202 String.Format("Unable to load heightmap: {0}", e.Message));
203 }
204 }
205 CheckForTerrainUpdates();
206 m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
207 return;
208 }
209 }
210
211 m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
212 throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
213 }
214
215 /// <summary>
216 /// Saves the current heightmap to a specified file.
217 /// </summary>
218 /// <param name="filename">The destination filename</param>
219 public void SaveToFile(string filename)
220 {
221 try
222 {
223 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
224 {
225 if (filename.EndsWith(loader.Key))
226 {
227 loader.Value.SaveFile(filename, m_channel);
228 return;
229 }
230 }
231 }
232 catch (NotImplementedException)
233 {
234 m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented.");
235 throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented"));
236 }
237 }
238
239 /// <summary>
240 /// Loads a terrain file from a stream and installs it in the scene.
241 /// </summary>
242 /// <param name="filename">Filename to terrain file. Type is determined by extension.</param>
243 /// <param name="stream"></param>
244 public void LoadFromStream(string filename, Stream stream)
245 {
246 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
247 {
248 if (@filename.EndsWith(loader.Key))
249 {
250 lock (m_scene)
251 {
252 try
253 {
254 ITerrainChannel channel = loader.Value.LoadStream(stream);
255 m_scene.Heightmap = channel;
256 m_channel = channel;
257 UpdateRevertMap();
258 }
259 catch (NotImplementedException)
260 {
261 m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
262 " parser does not support file loading. (May be save only)");
263 throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
264 }
265 }
266
267 CheckForTerrainUpdates();
268 m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
269 return;
270 }
271 }
272 m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
273 throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
274 }
275
276 /// <summary>
277 /// Modify Land
278 /// </summary>
279 /// <param name="pos">Land-position (X,Y,0)</param>
280 /// <param name="size">The size of the brush (0=small, 1=medium, 2=large)</param>
281 /// <param name="action">0=LAND_LEVEL, 1=LAND_RAISE, 2=LAND_LOWER, 3=LAND_SMOOTH, 4=LAND_NOISE, 5=LAND_REVERT</param>
282 /// <param name="agentId">UUID of script-owner</param>
283 public void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId)
284 {
285 client_OnModifyTerrain(user, (float)pos.Z, (float)0.25, size, action, pos.Y, pos.X, pos.Y, pos.X, agentId);
286 }
287
288 /// <summary>
289 /// Saves the current heightmap to a specified stream.
290 /// </summary>
291 /// <param name="filename">The destination filename. Used here only to identify the image type</param>
292 /// <param name="stream"></param>
293 public void SaveToStream(string filename, Stream stream)
294 {
295 try
296 {
297 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
298 {
299 if (filename.EndsWith(loader.Key))
300 {
301 loader.Value.SaveStream(stream, m_channel);
302 return;
303 }
304 }
305 }
306 catch (NotImplementedException)
307 {
308 m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented.");
309 throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented"));
310 }
311 }
312
313 #region Plugin Loading Methods
314
315 private void LoadPlugins()
316 {
317 m_plugineffects = new Dictionary<string, ITerrainEffect>();
318 // Load the files in the Terrain/ dir
319 string[] files = Directory.GetFiles("Terrain");
320 foreach (string file in files)
321 {
322 m_log.Info("Loading effects in " + file);
323 try
324 {
325 Assembly library = Assembly.LoadFrom(file);
326 foreach (Type pluginType in library.GetTypes())
327 {
328 try
329 {
330 if (pluginType.IsAbstract || pluginType.IsNotPublic)
331 continue;
332
333 string typeName = pluginType.Name;
334
335 if (pluginType.GetInterface("ITerrainEffect", false) != null)
336 {
337 ITerrainEffect terEffect = (ITerrainEffect) Activator.CreateInstance(library.GetType(pluginType.ToString()));
338
339 InstallPlugin(typeName, terEffect);
340 }
341 else if (pluginType.GetInterface("ITerrainLoader", false) != null)
342 {
343 ITerrainLoader terLoader = (ITerrainLoader) Activator.CreateInstance(library.GetType(pluginType.ToString()));
344 m_loaders[terLoader.FileExtension] = terLoader;
345 m_log.Info("L ... " + typeName);
346 }
347 }
348 catch (AmbiguousMatchException)
349 {
350 }
351 }
352 }
353 catch (BadImageFormatException)
354 {
355 }
356 }
357 }
358
359 public void InstallPlugin(string pluginName, ITerrainEffect effect)
360 {
361 lock (m_plugineffects)
362 {
363 if (!m_plugineffects.ContainsKey(pluginName))
364 {
365 m_plugineffects.Add(pluginName, effect);
366 m_log.Info("E ... " + pluginName);
367 }
368 else
369 {
370 m_plugineffects[pluginName] = effect;
371 m_log.Warn("E ... " + pluginName + " (Replaced)");
372 }
373 }
374 }
375
376 #endregion
377
378 #endregion
379
380 /// <summary>
381 /// Installs into terrain module the standard suite of brushes
382 /// </summary>
383 private void InstallDefaultEffects()
384 {
385 // Draggable Paint Brush Effects
386 m_painteffects[StandardTerrainEffects.Raise] = new RaiseSphere();
387 m_painteffects[StandardTerrainEffects.Lower] = new LowerSphere();
388 m_painteffects[StandardTerrainEffects.Smooth] = new SmoothSphere();
389 m_painteffects[StandardTerrainEffects.Noise] = new NoiseSphere();
390 m_painteffects[StandardTerrainEffects.Flatten] = new FlattenSphere();
391 m_painteffects[StandardTerrainEffects.Revert] = new RevertSphere(m_revert);
392 m_painteffects[StandardTerrainEffects.Erode] = new ErodeSphere();
393 m_painteffects[StandardTerrainEffects.Weather] = new WeatherSphere();
394 m_painteffects[StandardTerrainEffects.Olsen] = new OlsenSphere();
395
396 // Area of effect selection effects
397 m_floodeffects[StandardTerrainEffects.Raise] = new RaiseArea();
398 m_floodeffects[StandardTerrainEffects.Lower] = new LowerArea();
399 m_floodeffects[StandardTerrainEffects.Smooth] = new SmoothArea();
400 m_floodeffects[StandardTerrainEffects.Noise] = new NoiseArea();
401 m_floodeffects[StandardTerrainEffects.Flatten] = new FlattenArea();
402 m_floodeffects[StandardTerrainEffects.Revert] = new RevertArea(m_revert);
403
404 // Filesystem load/save loaders
405 m_loaders[".r32"] = new RAW32();
406 m_loaders[".f32"] = m_loaders[".r32"];
407 m_loaders[".ter"] = new Terragen();
408 m_loaders[".raw"] = new LLRAW();
409 m_loaders[".jpg"] = new JPEG();
410 m_loaders[".jpeg"] = m_loaders[".jpg"];
411 m_loaders[".bmp"] = new BMP();
412 m_loaders[".png"] = new PNG();
413 m_loaders[".gif"] = new GIF();
414 m_loaders[".tif"] = new TIFF();
415 m_loaders[".tiff"] = m_loaders[".tif"];
416 }
417
418 /// <summary>
419 /// Saves the current state of the region into the revert map buffer.
420 /// </summary>
421 public void UpdateRevertMap()
422 {
423 int x;
424 for (x = 0; x < m_channel.Width; x++)
425 {
426 int y;
427 for (y = 0; y < m_channel.Height; y++)
428 {
429 m_revert[x, y] = m_channel[x, y];
430 }
431 }
432 }
433
434 /// <summary>
435 /// Loads a tile from a larger terrain file and installs it into the region.
436 /// </summary>
437 /// <param name="filename">The terrain file to load</param>
438 /// <param name="fileWidth">The width of the file in units</param>
439 /// <param name="fileHeight">The height of the file in units</param>
440 /// <param name="fileStartX">Where to begin our slice</param>
441 /// <param name="fileStartY">Where to begin our slice</param>
442 public void LoadFromFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
443 {
444 int offsetX = (int) m_scene.RegionInfo.RegionLocX - fileStartX;
445 int offsetY = (int) m_scene.RegionInfo.RegionLocY - fileStartY;
446
447 if (offsetX >= 0 && offsetX < fileWidth && offsetY >= 0 && offsetY < fileHeight)
448 {
449 // this region is included in the tile request
450 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
451 {
452 if (filename.EndsWith(loader.Key))
453 {
454 lock (m_scene)
455 {
456 ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY,
457 fileWidth, fileHeight,
458 (int) Constants.RegionSize,
459 (int) Constants.RegionSize);
460 m_scene.Heightmap = channel;
461 m_channel = channel;
462 UpdateRevertMap();
463 }
464 return;
465 }
466 }
467 }
468 }
469
470 /// <summary>
471 /// Performs updates to the region periodically, synchronising physics and other heightmap aware sections
472 /// </summary>
473 private void EventManager_OnTerrainTick()
474 {
475 if (m_tainted)
476 {
477 m_tainted = false;
478 m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised());
479 m_scene.SaveTerrain();
480
481 // Clients who look at the map will never see changes after they looked at the map, so i've commented this out.
482 //m_scene.CreateTerrainTexture(true);
483 }
484 }
485
486 /// <summary>
487 /// Processes commandline input. Do not call directly.
488 /// </summary>
489 /// <param name="args">Commandline arguments</param>
490 private void EventManager_OnPluginConsole(string[] args)
491 {
492 if (args[0] == "terrain")
493 {
494 if (args.Length == 1)
495 {
496 m_commander.ProcessConsoleCommand("help", new string[0]);
497 return;
498 }
499
500 string[] tmpArgs = new string[args.Length - 2];
501 int i;
502 for (i = 2; i < args.Length; i++)
503 tmpArgs[i - 2] = args[i];
504
505 m_commander.ProcessConsoleCommand(args[1], tmpArgs);
506 }
507 }
508
509 /// <summary>
510 /// Installs terrain brush hook to IClientAPI
511 /// </summary>
512 /// <param name="client"></param>
513 private void EventManager_OnNewClient(IClientAPI client)
514 {
515 client.OnModifyTerrain += client_OnModifyTerrain;
516 client.OnBakeTerrain += client_OnBakeTerrain;
517 }
518
519 /// <summary>
520 /// Checks to see if the terrain has been modified since last check
521 /// but won't attempt to limit those changes to the limits specified in the estate settings
522 /// currently invoked by the command line operations in the region server only
523 /// </summary>
524 private void CheckForTerrainUpdates()
525 {
526 CheckForTerrainUpdates(false);
527 }
528
529 /// <summary>
530 /// Checks to see if the terrain has been modified since last check.
531 /// If it has been modified, every all the terrain patches are sent to the client.
532 /// If the call is asked to respect the estate settings for terrain_raise_limit and
533 /// terrain_lower_limit, it will clamp terrain updates between these values
534 /// currently invoked by client_OnModifyTerrain only and not the Commander interfaces
535 /// <param name="respectEstateSettings">should height map deltas be limited to the estate settings limits</param>
536 /// </summary>
537 private void CheckForTerrainUpdates(bool respectEstateSettings)
538 {
539 bool shouldTaint = false;
540 float[] serialised = m_channel.GetFloatsSerialised();
541 int x;
542 for (x = 0; x < m_channel.Width; x += Constants.TerrainPatchSize)
543 {
544 int y;
545 for (y = 0; y < m_channel.Height; y += Constants.TerrainPatchSize)
546 {
547 if (m_channel.Tainted(x, y))
548 {
549 // if we should respect the estate settings then
550 // fixup and height deltas that don't respect them
551 if (respectEstateSettings && LimitChannelChanges(x, y))
552 {
553 // this has been vetoed, so update
554 // what we are going to send to the client
555 serialised = m_channel.GetFloatsSerialised();
556 }
557
558 SendToClients(serialised, x, y);
559 shouldTaint = true;
560 }
561 }
562 }
563 if (shouldTaint)
564 {
565 m_tainted = true;
566 }
567 }
568
569 /// <summary>
570 /// Checks to see height deltas in the tainted terrain patch at xStart ,yStart
571 /// are all within the current estate limits
572 /// <returns>true if changes were limited, false otherwise</returns>
573 /// </summary>
574 private bool LimitChannelChanges(int xStart, int yStart)
575 {
576 bool changesLimited = false;
577 double minDelta = m_scene.RegionInfo.RegionSettings.TerrainLowerLimit;
578 double maxDelta = m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit;
579
580 // loop through the height map for this patch and compare it against
581 // the revert map
582 for (int x = xStart; x < xStart + Constants.TerrainPatchSize; x++)
583 {
584 for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++)
585 {
586
587 double requestedHeight = m_channel[x, y];
588 double bakedHeight = m_revert[x, y];
589 double requestedDelta = requestedHeight - bakedHeight;
590
591 if (requestedDelta > maxDelta)
592 {
593 m_channel[x, y] = bakedHeight + maxDelta;
594 changesLimited = true;
595 }
596 else if (requestedDelta < minDelta)
597 {
598 m_channel[x, y] = bakedHeight + minDelta; //as lower is a -ve delta
599 changesLimited = true;
600 }
601 }
602 }
603
604 return changesLimited;
605 }
606
607 /// <summary>
608 /// Sends a copy of the current terrain to the scenes clients
609 /// </summary>
610 /// <param name="serialised">A copy of the terrain as a 1D float array of size w*h</param>
611 /// <param name="x">The patch corner to send</param>
612 /// <param name="y">The patch corner to send</param>
613 private void SendToClients(float[] serialised, int x, int y)
614 {
615 m_scene.ForEachClient(
616 delegate(IClientAPI controller)
617 { controller.SendLayerData(
618 x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, serialised);
619 }
620 );
621 }
622
623 private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action,
624 float north, float west, float south, float east, UUID agentId)
625 {
626 bool god = m_scene.Permissions.IsGod(user);
627 bool allowed = false;
628 if (north == south && east == west)
629 {
630 if (m_painteffects.ContainsKey((StandardTerrainEffects) action))
631 {
632 bool[,] allowMask = new bool[m_channel.Width,m_channel.Height];
633 allowMask.Initialize();
634 int n = size + 1;
635 if (n > 2)
636 n = 4;
637
638 int zx = (int) (west + 0.5);
639 int zy = (int) (north + 0.5);
640
641 int dx;
642 for (dx=-n; dx<=n; dx++)
643 {
644 int dy;
645 for (dy=-n; dy<=n; dy++)
646 {
647 int x = zx + dx;
648 int y = zy + dy;
649 if (x>=0 && y>=0 && x<m_channel.Width && y<m_channel.Height)
650 {
651 if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0)))
652 {
653 allowMask[x, y] = true;
654 allowed = true;
655 }
656 }
657 }
658 }
659 if (allowed)
660 {
661 m_painteffects[(StandardTerrainEffects) action].PaintEffect(
662 m_channel, allowMask, west, south, height, size, seconds);
663
664 CheckForTerrainUpdates(!god); //revert changes outside estate limits
665 }
666 }
667 else
668 {
669 m_log.Debug("Unknown terrain brush type " + action);
670 }
671 }
672 else
673 {
674 if (m_floodeffects.ContainsKey((StandardTerrainEffects) action))
675 {
676 bool[,] fillArea = new bool[m_channel.Width,m_channel.Height];
677 fillArea.Initialize();
678
679 int x;
680 for (x = 0; x < m_channel.Width; x++)
681 {
682 int y;
683 for (y = 0; y < m_channel.Height; y++)
684 {
685 if (x < east && x > west)
686 {
687 if (y < north && y > south)
688 {
689 if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0)))
690 {
691 fillArea[x, y] = true;
692 allowed = true;
693 }
694 }
695 }
696 }
697 }
698
699 if (allowed)
700 {
701 m_floodeffects[(StandardTerrainEffects) action].FloodEffect(
702 m_channel, fillArea, size);
703
704 CheckForTerrainUpdates(!god); //revert changes outside estate limits
705 }
706 }
707 else
708 {
709 m_log.Debug("Unknown terrain flood type " + action);
710 }
711 }
712 }
713
714 private void client_OnBakeTerrain(IClientAPI remoteClient)
715 {
716 // Not a good permissions check (see client_OnModifyTerrain above), need to check the entire area.
717 // for now check a point in the centre of the region
718
719 if (m_scene.Permissions.CanIssueEstateCommand(remoteClient.AgentId, true))
720 {
721 InterfaceBakeTerrain(null); //bake terrain does not use the passed in parameter
722 }
723 }
724
725 #region Console Commands
726
727 private void InterfaceLoadFile(Object[] args)
728 {
729 LoadFromFile((string) args[0]);
730 CheckForTerrainUpdates();
731 }
732
733 private void InterfaceLoadTileFile(Object[] args)
734 {
735 LoadFromFile((string) args[0],
736 (int) args[1],
737 (int) args[2],
738 (int) args[3],
739 (int) args[4]);
740 CheckForTerrainUpdates();
741 }
742
743 private void InterfaceSaveFile(Object[] args)
744 {
745 SaveToFile((string) args[0]);
746 }
747
748 private void InterfaceBakeTerrain(Object[] args)
749 {
750 UpdateRevertMap();
751 }
752
753 private void InterfaceRevertTerrain(Object[] args)
754 {
755 int x, y;
756 for (x = 0; x < m_channel.Width; x++)
757 for (y = 0; y < m_channel.Height; y++)
758 m_channel[x, y] = m_revert[x, y];
759
760 CheckForTerrainUpdates();
761 }
762
763 private void InterfaceFlipTerrain(Object[] args)
764 {
765 String direction = (String)args[0];
766
767 if (direction.ToLower().StartsWith("y"))
768 {
769 for (int x = 0; x < Constants.RegionSize; x++)
770 {
771 for (int y = 0; y < Constants.RegionSize / 2; y++)
772 {
773 double height = m_channel[x, y];
774 double flippedHeight = m_channel[x, (int)Constants.RegionSize - 1 - y];
775 m_channel[x, y] = flippedHeight;
776 m_channel[x, (int)Constants.RegionSize - 1 - y] = height;
777
778 }
779 }
780 }
781 else if (direction.ToLower().StartsWith("x"))
782 {
783 for (int y = 0; y < Constants.RegionSize; y++)
784 {
785 for (int x = 0; x < Constants.RegionSize / 2; x++)
786 {
787 double height = m_channel[x, y];
788 double flippedHeight = m_channel[(int)Constants.RegionSize - 1 - x, y];
789 m_channel[x, y] = flippedHeight;
790 m_channel[(int)Constants.RegionSize - 1 - x, y] = height;
791
792 }
793 }
794 }
795 else
796 {
797 m_log.Error("Unrecognised direction - need x or y");
798 }
799
800
801 CheckForTerrainUpdates();
802 }
803
804 private void InterfaceElevateTerrain(Object[] args)
805 {
806 int x, y;
807 for (x = 0; x < m_channel.Width; x++)
808 for (y = 0; y < m_channel.Height; y++)
809 m_channel[x, y] += (double) args[0];
810 CheckForTerrainUpdates();
811 }
812
813 private void InterfaceMultiplyTerrain(Object[] args)
814 {
815 int x, y;
816 for (x = 0; x < m_channel.Width; x++)
817 for (y = 0; y < m_channel.Height; y++)
818 m_channel[x, y] *= (double) args[0];
819 CheckForTerrainUpdates();
820 }
821
822 private void InterfaceLowerTerrain(Object[] args)
823 {
824 int x, y;
825 for (x = 0; x < m_channel.Width; x++)
826 for (y = 0; y < m_channel.Height; y++)
827 m_channel[x, y] -= (double) args[0];
828 CheckForTerrainUpdates();
829 }
830
831 private void InterfaceFillTerrain(Object[] args)
832 {
833 int x, y;
834
835 for (x = 0; x < m_channel.Width; x++)
836 for (y = 0; y < m_channel.Height; y++)
837 m_channel[x, y] = (double) args[0];
838 CheckForTerrainUpdates();
839 }
840
841 private void InterfaceShowDebugStats(Object[] args)
842 {
843 double max = Double.MinValue;
844 double min = double.MaxValue;
845 double sum = 0;
846
847 int x;
848 for (x = 0; x < m_channel.Width; x++)
849 {
850 int y;
851 for (y = 0; y < m_channel.Height; y++)
852 {
853 sum += m_channel[x, y];
854 if (max < m_channel[x, y])
855 max = m_channel[x, y];
856 if (min > m_channel[x, y])
857 min = m_channel[x, y];
858 }
859 }
860
861 double avg = sum / (m_channel.Height * m_channel.Width);
862
863 m_log.Info("Channel " + m_channel.Width + "x" + m_channel.Height);
864 m_log.Info("max/min/avg/sum: " + max + "/" + min + "/" + avg + "/" + sum);
865 }
866
867 private void InterfaceEnableExperimentalBrushes(Object[] args)
868 {
869 if ((bool) args[0])
870 {
871 m_painteffects[StandardTerrainEffects.Revert] = new WeatherSphere();
872 m_painteffects[StandardTerrainEffects.Flatten] = new OlsenSphere();
873 m_painteffects[StandardTerrainEffects.Smooth] = new ErodeSphere();
874 }
875 else
876 {
877 InstallDefaultEffects();
878 }
879 }
880
881 private void InterfaceRunPluginEffect(Object[] args)
882 {
883 if ((string) args[0] == "list")
884 {
885 m_log.Info("List of loaded plugins");
886 foreach (KeyValuePair<string, ITerrainEffect> kvp in m_plugineffects)
887 {
888 m_log.Info(kvp.Key);
889 }
890 return;
891 }
892 if ((string) args[0] == "reload")
893 {
894 LoadPlugins();
895 return;
896 }
897 if (m_plugineffects.ContainsKey((string) args[0]))
898 {
899 m_plugineffects[(string) args[0]].RunEffect(m_channel);
900 CheckForTerrainUpdates();
901 }
902 else
903 {
904 m_log.Warn("No such plugin effect loaded.");
905 }
906 }
907
908 private void InstallInterfaces()
909 {
910 // Load / Save
911 string supportedFileExtensions = "";
912 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
913 supportedFileExtensions += " " + loader.Key + " (" + loader.Value + ")";
914
915 Command loadFromFileCommand =
916 new Command("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file.");
917 loadFromFileCommand.AddArgument("filename",
918 "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
919 supportedFileExtensions, "String");
920
921 Command saveToFileCommand =
922 new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file.");
923 saveToFileCommand.AddArgument("filename",
924 "The destination filename for your heightmap, the file extension determines the format to save in. Supported extensions include: " +
925 supportedFileExtensions, "String");
926
927 Command loadFromTileCommand =
928 new Command("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file.");
929 loadFromTileCommand.AddArgument("filename",
930 "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
931 supportedFileExtensions, "String");
932 loadFromTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
933 loadFromTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
934 loadFromTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
935 "Integer");
936 loadFromTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first section on the file",
937 "Integer");
938
939 // Terrain adjustments
940 Command fillRegionCommand =
941 new Command("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value.");
942 fillRegionCommand.AddArgument("value", "The numeric value of the height you wish to set your region to.",
943 "Double");
944
945 Command elevateCommand =
946 new Command("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount.");
947 elevateCommand.AddArgument("amount", "The amount of height to add to the terrain in meters.", "Double");
948
949 Command lowerCommand =
950 new Command("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount.");
951 lowerCommand.AddArgument("amount", "The amount of height to remove from the terrain in meters.", "Double");
952
953 Command multiplyCommand =
954 new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified.");
955 multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Double");
956
957 Command bakeRegionCommand =
958 new Command("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions revert map.");
959 Command revertRegionCommand =
960 new Command("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the revert map terrain into the regions heightmap.");
961
962 Command flipCommand =
963 new Command("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis");
964 flipCommand.AddArgument("direction", "[x|y] the direction to flip the terrain in", "String");
965
966 // Debug
967 Command showDebugStatsCommand =
968 new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats,
969 "Shows some information about the regions heightmap for debugging purposes.");
970
971 Command experimentalBrushesCommand =
972 new Command("newbrushes", CommandIntentions.COMMAND_HAZARDOUS, InterfaceEnableExperimentalBrushes,
973 "Enables experimental brushes which replace the standard terrain brushes. WARNING: This is a debug setting and may be removed at any time.");
974 experimentalBrushesCommand.AddArgument("Enabled?", "true / false - Enable new brushes", "Boolean");
975
976 //Plugins
977 Command pluginRunCommand =
978 new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect");
979 pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String");
980
981 m_commander.RegisterCommand("load", loadFromFileCommand);
982 m_commander.RegisterCommand("load-tile", loadFromTileCommand);
983 m_commander.RegisterCommand("save", saveToFileCommand);
984 m_commander.RegisterCommand("fill", fillRegionCommand);
985 m_commander.RegisterCommand("elevate", elevateCommand);
986 m_commander.RegisterCommand("lower", lowerCommand);
987 m_commander.RegisterCommand("multiply", multiplyCommand);
988 m_commander.RegisterCommand("bake", bakeRegionCommand);
989 m_commander.RegisterCommand("revert", revertRegionCommand);
990 m_commander.RegisterCommand("newbrushes", experimentalBrushesCommand);
991 m_commander.RegisterCommand("stats", showDebugStatsCommand);
992 m_commander.RegisterCommand("effect", pluginRunCommand);
993 m_commander.RegisterCommand("flip", flipCommand);
994
995 // Add this to our scene so scripts can call these functions
996 m_scene.RegisterModuleCommander(m_commander);
997 }
998
999 #endregion
1000 }
1001}