28 using System.Collections.Generic;
30 using System.Reflection;
32 using System.Threading;
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;
52 [Extension(Path =
"/OpenSim/RegionModules", NodeName =
"RegionModule", Id =
"TerrainModule")]
55 #region StandardTerrainEffects enum
77 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
79 #pragma warning disable 414
80 private static readonly
string LogHeader =
"[TERRAIN MODULE]";
81 #pragma warning restore 414
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;
94 private Scene m_scene;
95 private volatile bool m_tainted;
97 private String m_InitialTerrain =
"pinhead-island";
100 private bool m_sendTerrainUpdatesByViewDistance =
true;
105 private class PatchUpdates
107 private bool[,] updated;
108 private int updateCount;
111 public int sendAllcurrentX;
112 public int sendAllcurrentY;
117 updated =
new bool[terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize];
119 Presence = pPresence;
124 public bool HasUpdates()
126 return (updateCount > 0);
129 public void SetByXY(
int x,
int y,
bool state)
131 this.SetByPatch(x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, state);
134 public bool GetByPatch(
int patchX,
int patchY)
136 return updated[patchX, patchY];
139 public void SetByPatch(
int patchX,
int patchY,
bool state)
141 bool prevState = updated[patchX, patchY];
142 if (!prevState && state)
144 if (prevState && !state)
146 updated[patchX, patchY] = state;
149 public void SetAll(
bool state)
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;
156 updateCount = updated.GetLength(0) * updated.GetLength(1);
165 if (updated.GetLength(0) != (terrData.SizeX / Constants.TerrainPatchSize)
166 || updated.GetLength(1) != (terrData.SizeY / Constants.TerrainPatchSize))
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),
175 for (
int xx = 0; xx < terrData.SizeX; xx += Constants.TerrainPatchSize)
177 for (
int yy = 0; yy < terrData.SizeY; yy += Constants.TerrainPatchSize)
182 this.SetByXY(xx, yy,
true);
190 private Dictionary<UUID, PatchUpdates> m_perClientPatchUpdates =
new Dictionary<UUID, PatchUpdates>();
195 private string m_supportedFileExtensions =
"";
198 private string m_supportFileExtensionsForTileSave =
"";
200 #region ICommandableModule Members
203 get {
return m_commander; }
208 #region INonSharedRegionModule Members
217 IConfig terrainConfig = config.Configs[
"Terrain"];
218 if (terrainConfig != null)
220 m_InitialTerrain = terrainConfig.GetString(
"InitialTerrain", m_InitialTerrain);
221 m_sendTerrainUpdatesByViewDistance =
222 terrainConfig.GetBoolean(
223 "SendTerrainUpdatesByViewDistance",m_sendTerrainUpdatesByViewDistance);
234 if (m_scene.Heightmap == null)
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;
245 m_channel = m_scene.Heightmap;
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;
257 InstallDefaultEffects();
261 string supportedFilesSeparator =
"";
262 string supportedFilesSeparatorForTileSave =
"";
264 m_supportFileExtensionsForTileSave =
"";
265 foreach(KeyValuePair<string, ITerrainLoader> loader
in m_loaders)
267 m_supportedFileExtensions += supportedFilesSeparator + loader.Key +
" (" + loader.Value +
")";
268 supportedFilesSeparator =
", ";
271 if (loader.Value.SupportsTileSave() ==
true)
273 m_supportFileExtensionsForTileSave += supportedFilesSeparatorForTileSave + loader.Key +
" (" + loader.Value +
")";
274 supportedFilesSeparatorForTileSave =
", ";
291 m_scene.UnregisterModuleCommander(m_commander.Name);
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;
308 public Type ReplaceableInterface {
313 get {
return "TerrainModule"; }
318 #region ITerrainModule Members
331 foreach(KeyValuePair<string, ITerrainLoader> loader
in m_loaders)
333 if (filename.EndsWith(loader.Key))
340 if (channel.
Width != m_scene.RegionInfo.RegionSizeX || channel.
Height != m_scene.RegionInfo.RegionSizeY)
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));
346 m_log.DebugFormat(
"[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height);
347 m_scene.Heightmap = channel;
351 catch(NotImplementedException)
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));
357 catch(FileNotFoundException)
360 "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)");
362 String.Format(
"unable to load heightmap: file {0} not found (or permissions do not allow access", filename));
364 catch(ArgumentException e)
366 m_log.ErrorFormat(
"[TERRAIN]: Unable to load heightmap: {0}", e.Message);
368 String.Format(
"Unable to load heightmap: {0}", e.Message));
371 m_log.Info(
"[TERRAIN]: File (" + filename +
") loaded successfully");
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));
388 foreach(KeyValuePair<string, ITerrainLoader> loader
in m_loaders)
390 if (filename.EndsWith(loader.Key))
392 loader.Value.SaveFile(filename, m_channel);
393 m_log.InfoFormat(
"[TERRAIN]: Saved terrain from {0} to {1}", m_scene.RegionInfo.RegionName, filename);
398 catch(IOException ioe)
400 m_log.Error(String.Format(
"[TERRAIN]: Unable to save to {0}, {1}", filename, ioe.Message));
404 "[TERRAIN]: Could not save terrain from {0} to {1}. Valid file extensions are {2}",
405 m_scene.RegionInfo.RegionName, filename, m_supportedFileExtensions);
415 LoadFromStream(filename, URIFetch(pathToTerrainHeightmap));
420 LoadFromStream(filename, Vector3.Zero, 0f, Vector2.Zero, stream);
429 float radianRotation, Vector2 rotationDisplacement, Stream stream)
431 foreach(KeyValuePair<string, ITerrainLoader> loader
in m_loaders)
433 if (filename.EndsWith(loader.Key))
440 m_channel.Merge(channel, displacement, radianRotation, rotationDisplacement);
443 catch(NotImplementedException)
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));
451 m_log.Info(
"[TERRAIN]: File (" + filename +
") loaded successfully");
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));
460 float rotationDegrees, Vector2 boundingOrigin, Vector2 boundingSize, Stream stream)
462 foreach (KeyValuePair<string, ITerrainLoader> loader
in m_loaders)
464 if (filename.EndsWith(loader.Key))
471 m_channel.MergeWithBounding(channel, displacement, rotationDegrees, boundingOrigin, boundingSize);
474 catch (NotImplementedException)
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));
482 m_log.Info(
"[TERRAIN]: File (" + filename +
") loaded successfully");
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));
490 private static Stream URIFetch(Uri uri)
492 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
496 request.ContentLength = 0;
497 request.KeepAlive =
false;
499 WebResponse response = request.GetResponse();
500 Stream file = response.GetResponseStream();
502 if (response.ContentLength == 0)
503 throw new Exception(
String.Format(
"{0} returned an empty file", uri.ToString()));
506 return new BufferedStream(file, 1000000);
516 public void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId)
518 float duration = 0.25f;
522 client_OnModifyTerrain(user, (
float)pos.Z, duration, size, action, pos.Y, pos.X, pos.Y, pos.X, agentId);
534 foreach(KeyValuePair<string, ITerrainLoader> loader
in m_loaders)
536 if (filename.EndsWith(loader.Key))
538 loader.Value.SaveStream(stream, m_channel);
543 catch(NotImplementedException)
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"));
554 lock (m_perClientPatchUpdates)
557 foreach (PatchUpdates pups
in m_perClientPatchUpdates.Values)
559 pups.SetAll(m_scene.Heightmap.GetTerrainData());
567 if (m_sendTerrainUpdatesByViewDistance)
569 ScenePresence presence = m_scene.GetScenePresence(pClient.AgentId);
570 if (presence != null)
572 lock (m_perClientPatchUpdates)
575 if (!m_perClientPatchUpdates.TryGetValue(pClient.
AgentId, out pups))
578 pups =
new PatchUpdates(m_scene.Heightmap.GetTerrainData(), presence);
579 m_perClientPatchUpdates.Add(presence.UUID, pups);
588 pClient.SendLayerData(
new float[10]);
592 #region Plugin Loading Methods
594 private void LoadPlugins()
596 m_plugineffects =
new Dictionary<string, ITerrainEffect>();
597 LoadPlugins(Assembly.GetCallingAssembly());
598 string plugineffectsPath =
"Terrain";
601 if (!Directory.Exists(plugineffectsPath))
604 string[] files = Directory.GetFiles(plugineffectsPath);
605 foreach(
string file
in files)
607 m_log.Info(
"Loading effects in " + file);
610 Assembly library = Assembly.LoadFrom(file);
611 LoadPlugins(library);
613 catch(BadImageFormatException)
619 private void LoadPlugins(Assembly library)
621 foreach(Type pluginType
in library.GetTypes())
625 if (pluginType.IsAbstract || pluginType.IsNotPublic)
628 string typeName = pluginType.Name;
630 if (pluginType.GetInterface(
"ITerrainEffect",
false) != null)
634 InstallPlugin(typeName, terEffect);
636 else if (pluginType.GetInterface(
"ITerrainLoader",
false) != null)
638 ITerrainLoader terLoader = (ITerrainLoader)Activator.CreateInstance(library.GetType(pluginType.ToString()));
639 m_loaders[terLoader.FileExtension] = terLoader;
640 m_log.Info(
"L ... " + typeName);
643 catch(AmbiguousMatchException)
651 lock(m_plugineffects)
653 if (!m_plugineffects.ContainsKey(pluginName))
655 m_plugineffects.Add(pluginName, effect);
656 m_log.Info(
"E ... " + pluginName);
660 m_plugineffects[pluginName] = effect;
661 m_log.Info(
"E ... " + pluginName +
" (Replaced)");
673 private void InstallDefaultEffects()
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();
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);
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"];
724 m_baked = m_channel.MakeCopy();
725 m_painteffects[StandardTerrainEffects.Revert] =
new RevertSphere(m_baked);
726 m_floodeffects[StandardTerrainEffects.Revert] =
new RevertArea(m_baked);
737 public void LoadFromFile(
string filename,
int fileWidth,
int fileHeight,
int fileStartX,
int fileStartY)
739 int offsetX = (int)m_scene.RegionInfo.RegionLocX - fileStartX;
740 int offsetY = (
int)m_scene.RegionInfo.RegionLocY - fileStartY;
742 if (offsetX >= 0 && offsetX < fileWidth && offsetY >= 0 && offsetY < fileHeight)
745 foreach(KeyValuePair<string, ITerrainLoader> loader
in m_loaders)
747 if (filename.EndsWith(loader.Key))
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;
778 public void SaveToFile(
string filename,
int fileWidth,
int fileHeight,
int fileStartX,
int fileStartY)
780 int offsetX = (int)m_scene.RegionInfo.RegionLocX - fileStartX;
781 int offsetY = (
int)m_scene.RegionInfo.RegionLocY - fileStartY;
783 if (offsetX < 0 || offsetX >= fileWidth || offsetY < 0 || offsetY >= fileHeight)
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);
793 foreach(KeyValuePair<string, ITerrainLoader> loader
in m_loaders)
795 if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave())
799 loader.Value.SaveFile(m_channel, filename, offsetX, offsetY,
800 fileWidth, fileHeight,
801 (int)m_scene.RegionInfo.RegionSizeX,
802 (
int)m_scene.RegionInfo.RegionSizeY);
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);
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);
827 private void EventManager_TerrainCheckUpdates()
830 EventManager_TerrainCheckUpdatesAsync);
833 object TerrainCheckUpdatesLock =
new object();
835 private void EventManager_TerrainCheckUpdatesAsync(
object o)
838 if(Monitor.TryEnter(TerrainCheckUpdatesLock))
843 bool shouldTaint =
false;
844 for (
int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize)
846 for (
int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize)
851 SendToClients(terrData, x, y);
858 CheckSendingPatchesToClients();
863 m_scene.EventManager.TriggerTerrainTainted();
866 Monitor.Exit(TerrainCheckUpdatesLock);
874 private void EventManager_OnTerrainTick()
879 m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised());
880 m_scene.SaveTerrain();
891 private void EventManager_OnPluginConsole(
string[] args)
893 if (args[0] ==
"terrain")
895 if (args.Length == 1)
897 m_commander.ProcessConsoleCommand(
"help",
new string[0]);
901 string[] tmpArgs =
new string[args.Length - 2];
903 for(i = 2; i < args.Length; i++)
904 tmpArgs[i - 2] = args[i];
906 m_commander.ProcessConsoleCommand(args[1], tmpArgs);
914 private void EventManager_OnNewClient(
IClientAPI client)
916 client.OnModifyTerrain += client_OnModifyTerrain;
917 client.OnBakeTerrain += client_OnBakeTerrain;
918 client.OnLandUndo += client_OnLandUndo;
919 client.OnUnackedTerrain += client_OnUnackedTerrain;
926 private void EventManager_OnClientClosed(UUID client,
Scene scene)
929 if (presence != null)
931 presence.ControllingClient.OnModifyTerrain -= client_OnModifyTerrain;
932 presence.ControllingClient.OnBakeTerrain -= client_OnBakeTerrain;
933 presence.ControllingClient.OnLandUndo -= client_OnLandUndo;
934 presence.ControllingClient.OnUnackedTerrain -= client_OnUnackedTerrain;
936 lock (m_perClientPatchUpdates)
937 m_perClientPatchUpdates.Remove(client);
945 private
bool EnforceEstateLimits()
949 bool wasLimited =
false;
950 for (
int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize)
952 for (
int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize)
959 wasLimited |= LimitChannelChanges(terrData, x, y);
971 private bool LimitChannelChanges(
TerrainData terrData,
int xStart,
int yStart)
973 bool changesLimited =
false;
974 float minDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit;
975 float maxDelta = (
float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit;
979 for (
int x = xStart; x < xStart + Constants.TerrainPatchSize; x++)
981 for(
int y = yStart; y < yStart + Constants.TerrainPatchSize; y++)
983 float requestedHeight = terrData[x, y];
984 float bakedHeight = (float)m_baked[x, y];
985 float requestedDelta = requestedHeight - bakedHeight;
987 if (requestedDelta > maxDelta)
989 terrData[x, y] = bakedHeight + maxDelta;
990 changesLimited =
true;
992 else if (requestedDelta < minDelta)
994 terrData[x, y] = bakedHeight + minDelta;
995 changesLimited =
true;
1000 return changesLimited;
1003 private void client_OnLandUndo(
IClientAPI client)
1013 private void SendToClients(
TerrainData terrData,
int x,
int y)
1015 if (m_sendTerrainUpdatesByViewDistance)
1018 lock (m_perClientPatchUpdates)
1020 m_scene.ForEachScenePresence(presence =>
1022 PatchUpdates thisClientUpdates;
1023 if (!m_perClientPatchUpdates.TryGetValue(presence.UUID, out thisClientUpdates))
1026 thisClientUpdates =
new PatchUpdates(terrData, presence);
1027 m_perClientPatchUpdates.Add(presence.UUID, thisClientUpdates);
1029 thisClientUpdates.SetByXY(x, y,
true);
1039 float[] heightMap =
new float[10];
1040 m_scene.ForEachClient(
1043 controller.SendLayerData(x / Constants.TerrainPatchSize,
1044 y / Constants.TerrainPatchSize,
1051 private class PatchesToSend : IComparable<PatchesToSend>
1056 public PatchesToSend(
int pX,
int pY,
float pDist)
1062 public int CompareTo(PatchesToSend other)
1064 return Dist.CompareTo(other.Dist);
1071 private void CheckSendingPatchesToClients()
1073 lock (m_perClientPatchUpdates)
1075 foreach (PatchUpdates pups
in m_perClientPatchUpdates.Values)
1077 if(pups.Presence.IsDeleted)
1081 if (!pups.Presence.ControllingClient.CanSendLayerData())
1084 if (pups.HasUpdates())
1086 if (m_sendTerrainUpdatesByViewDistance)
1089 List<PatchesToSend> toSend = GetModifiedPatchesInViewDistance(pups);
1090 if (toSend.Count > 0)
1104 int[] xPieces =
new int[toSend.Count];
1105 int[] yPieces =
new int[toSend.Count];
1106 float[] patchPieces =
new float[toSend.Count * 2];
1108 foreach (PatchesToSend pts
in toSend)
1110 patchPieces[pieceIndex++] = pts.PatchX;
1111 patchPieces[pieceIndex++] = pts.PatchY;
1113 pups.Presence.ControllingClient.SendLayerData(-toSend.Count, 0, patchPieces);
1115 if (pups.sendAll && toSend.Count < 1024)
1116 SendAllModifiedPatchs(pups);
1119 SendAllModifiedPatchs(pups);
1124 private void SendAllModifiedPatchs(PatchUpdates pups)
1130 int limitY = (
int)m_scene.RegionInfo.RegionSizeY / Constants.TerrainPatchSize;
1132 if (pups.sendAllcurrentX >= limitX && pups.sendAllcurrentY >= limitY)
1134 pups.sendAll =
false;
1135 pups.sendAllcurrentX = 0;
1136 pups.sendAllcurrentY = 0;
1141 List<PatchesToSend> patchs =
new List<PatchesToSend>();
1142 int x = pups.sendAllcurrentX;
1143 int y = pups.sendAllcurrentY;
1146 for (; y < limitY; y++)
1148 for (; x < limitX; x++)
1150 if (pups.GetByPatch(x, y))
1152 pups.SetByPatch(x, y,
false);
1153 patchs.Add(
new PatchesToSend(x, y, 0));
1154 if (++npatchs >= 128)
1166 if (x >= limitX && y >= limitY)
1168 pups.sendAll =
false;
1169 pups.sendAllcurrentX = 0;
1170 pups.sendAllcurrentY = 0;
1174 pups.sendAllcurrentX = x;
1175 pups.sendAllcurrentY = y;
1178 npatchs = patchs.Count;
1181 int[] xPieces =
new int[npatchs];
1182 int[] yPieces =
new int[npatchs];
1183 float[] patchPieces =
new float[npatchs * 2];
1185 foreach (PatchesToSend pts
in patchs)
1187 patchPieces[pieceIndex++] = pts.PatchX;
1188 patchPieces[pieceIndex++] = pts.PatchY;
1190 pups.Presence.ControllingClient.SendLayerData(-npatchs, 0, patchPieces);
1194 private List<PatchesToSend> GetModifiedPatchesInViewDistance(PatchUpdates pups)
1196 List<PatchesToSend> ret =
new List<PatchesToSend>();
1201 if (presence == null)
1204 float minz = presence.AbsolutePosition.Z;
1206 minz = presence.CameraPosition.Z;
1220 || Math.Abs(presence.AbsolutePosition.Y - presence.CameraPosition.Y) > 30)
1223 testposY = (
int)presence.CameraPosition.Y / Constants.TerrainPatchSize;
1228 testposY = (
int)presence.AbsolutePosition.Y / Constants.TerrainPatchSize;
1231 int limitY = (
int)m_scene.RegionInfo.RegionSizeY / Constants.TerrainPatchSize;
1234 int startX = testposX - DrawDistance;
1237 else if (startX >= limitX)
1238 startX = limitX - 1;
1240 int startY = testposY - DrawDistance;
1243 else if (startY >= limitY)
1244 startY = limitY - 1;
1246 int endX = testposX + DrawDistance;
1249 else if (endX > limitX)
1252 int endY = testposY + DrawDistance;
1255 else if (endY > limitY)
1262 DrawDistance *= DrawDistance;
1264 for (
int x = startX; x < endX; x++)
1266 for (
int y = startY; y < endY; y++)
1268 if (pups.GetByPatch(x, y))
1270 distx = x - testposX;
1271 disty = y - testposY;
1272 distsq = distx * distx + disty * disty;
1273 if (distsq < DrawDistance)
1275 pups.SetByPatch(x, y,
false);
1276 ret.Add(
new PatchesToSend(x, y, (
float)distsq));
1277 if (npatchs++ > 1024)
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)
1292 bool god = m_scene.Permissions.IsGod(user);
1293 bool allowed =
false;
1294 if (north == south && east == west)
1296 if (m_painteffects.ContainsKey((StandardTerrainEffects)action))
1298 bool[,] allowMask =
new bool[m_channel.Width, m_channel.Height];
1299 allowMask.Initialize();
1304 int zx = (int)(west + 0.5);
1305 int zy = (int)(north + 0.5);
1307 int startX = zx - n;
1311 int startY = zy - n;
1316 if (endX >= m_channel.Width)
1317 endX = m_channel.Width - 1;
1319 if (endY >= m_channel.Height)
1320 endY = m_channel.Height - 1;
1324 for (x = startX; x <= endX; x++)
1326 for (y = startY; y <= endY; y++)
1328 if (m_scene.Permissions.CanTerraformLand(agentId,
new Vector3(x, y, 0)))
1330 allowMask[x, y] =
true;
1338 m_painteffects[(StandardTerrainEffects) action].PaintEffect(
1339 m_channel, allowMask, west, south, height, size, seconds,
1340 startX, endX, startY, endY);
1344 EnforceEstateLimits();
1349 m_log.Debug(
"Unknown terrain brush type " + action);
1354 if (m_floodeffects.ContainsKey((StandardTerrainEffects)action))
1356 bool[,] fillArea =
new bool[m_channel.Width, m_channel.Height];
1357 fillArea.Initialize();
1359 int startX = (int)west;
1360 int startY = (int)south;
1361 int endX = (int)east;
1362 int endY = (int)north;
1366 else if (startX >= m_channel.Width)
1367 startX = m_channel.Width - 1;
1371 else if (endX >= m_channel.Width)
1372 endX = m_channel.Width - 1;
1376 else if (startY >= m_channel.Height)
1377 startY = m_channel.Height - 1;
1381 else if (endY >= m_channel.Height)
1382 endY = m_channel.Height - 1;
1387 for (x = startX; x <= endX; x++)
1389 for (y = startY; y <= endY; y++)
1391 if (m_scene.Permissions.CanTerraformLand(agentId,
new Vector3(x, y, 0)))
1393 fillArea[x, y] =
true;
1402 m_floodeffects[(StandardTerrainEffects)action].FloodEffect(m_channel, fillArea, size,
1403 startX, endX, startY, endY);
1407 EnforceEstateLimits();
1412 m_log.Debug(
"Unknown terrain flood type " + action);
1417 private void client_OnBakeTerrain(
IClientAPI remoteClient)
1422 if (m_scene.Permissions.CanIssueEstateCommand(remoteClient.
AgentId,
true))
1424 InterfaceBakeTerrain(null);
1432 float[] heightMap =
new float[10];
1433 client.SendLayerData(patchX, patchY, heightMap);
1436 private void StoreUndoState()
1440 #region Console Commands
1442 private void InterfaceLoadFile(Object[] args)
1444 LoadFromFile((
string) args[0]);
1447 private void InterfaceLoadTileFile(Object[] args)
1449 LoadFromFile((
string) args[0],
1456 private void InterfaceSaveFile(Object[] args)
1458 SaveToFile((
string)args[0]);
1461 private void InterfaceSaveTileFile(Object[] args)
1463 SaveToFile((
string)args[0],
1470 private void InterfaceBakeTerrain(Object[] args)
1475 private void InterfaceRevertTerrain(Object[] args)
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];
1484 private void InterfaceFlipTerrain(Object[] args)
1488 if (direction.ToLower().StartsWith(
"y"))
1490 for (
int x = 0; x < m_channel.Width; x++)
1492 for (
int y = 0; y < m_channel.Height / 2; y++)
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;
1502 else if (direction.ToLower().StartsWith("x"))
1504 for (
int y = 0; y < m_channel.Height; y++)
1506 for (
int x = 0; x < m_channel.Width / 2; x++)
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;
1518 MainConsole.Instance.OutputFormat(
"ERROR: Unrecognised direction {0} - need x or y", direction);
1522 private void InterfaceRescaleTerrain(Object[] args)
1524 double desiredMin = (double)args[0];
1525 double desiredMax = (double)args[1];
1528 double desiredRange = desiredMax - desiredMin;
1531 if (desiredRange == 0d)
1534 InterfaceFillTerrain(
new Object[] { args[1] });
1539 double currMin = double.MaxValue;
1540 double currMax = double.MinValue;
1542 int width = m_channel.Width;
1543 int height = m_channel.Height;
1545 for(
int x = 0; x < width; x++)
1547 for(
int y = 0; y < height; y++)
1549 double currHeight = m_channel[x, y];
1550 if (currHeight < currMin)
1552 currMin = currHeight;
1554 else if (currHeight > currMax)
1556 currMax = currHeight;
1561 double currRange = currMax - currMin;
1562 double scale = desiredRange / currRange;
1568 for(
int x = 0; x < width; x++)
1570 for(
int y = 0; y < height; y++)
1572 double currHeight = m_channel[x, y] - currMin;
1573 m_channel[x, y] = desiredMin + (currHeight * scale);
1580 private void InterfaceElevateTerrain(Object[] args)
1582 double val = (double)args[0];
1585 for (x = 0; x < m_channel.Width; x++)
1586 for (y = 0; y < m_channel.Height; y++)
1587 m_channel[x, y] += val;
1590 private void InterfaceMultiplyTerrain(Object[] args)
1593 double val = (double)args[0];
1595 for (x = 0; x < m_channel.Width; x++)
1596 for (y = 0; y < m_channel.Height; y++)
1597 m_channel[x, y] *= val;
1600 private void InterfaceLowerTerrain(Object[] args)
1603 double val = (double)args[0];
1605 for (x = 0; x < m_channel.Width; x++)
1606 for (y = 0; y < m_channel.Height; y++)
1607 m_channel[x, y] -= val;
1613 double val = (double)args[0];
1615 for (x = 0; x < m_channel.Width; x++)
1616 for (y = 0; y < m_channel.Height; y++)
1617 m_channel[x, y] = val;
1620 private void InterfaceMinTerrain(Object[] args)
1623 double val = (double)args[0];
1624 for (x = 0; x < m_channel.Width; x++)
1626 for(y = 0; y < m_channel.Height; y++)
1628 m_channel[x, y] = Math.Max(val, m_channel[x, y]);
1633 private void InterfaceMaxTerrain(Object[] args)
1636 double val = (double)args[0];
1637 for (x = 0; x < m_channel.Width; x++)
1639 for(y = 0; y < m_channel.Height; y++)
1641 m_channel[x, y] = Math.Min(val, m_channel[x, y]);
1646 private void InterfaceShow(Object[] args)
1652 Console.WriteLine(
"ERROR: {0} is not a valid vector", args[0]);
1656 double height = m_channel[(int)point.X, (
int)point.Y];
1658 Console.WriteLine(
"Terrain height at {0} is {1}", point, height);
1661 private void InterfaceShowDebugStats(Object[] args)
1663 double max = Double.MinValue;
1664 double min = double.MaxValue;
1668 for(x = 0; x < m_channel.Width; x++)
1671 for(y = 0; y < m_channel.Height; y++)
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];
1681 double avg = sum / (m_channel.Height * m_channel.Width);
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);
1687 private void InterfaceEnableExperimentalBrushes(Object[] args)
1691 m_painteffects[StandardTerrainEffects.Revert] =
new WeatherSphere();
1692 m_painteffects[StandardTerrainEffects.Flatten] =
new OlsenSphere();
1693 m_painteffects[StandardTerrainEffects.Smooth] =
new ErodeSphere();
1697 InstallDefaultEffects();
1701 private void InterfaceRunPluginEffect(Object[] args)
1703 string firstArg = (string)args[0];
1705 if (firstArg ==
"list")
1707 MainConsole.Instance.Output(
"List of loaded plugins");
1708 foreach(KeyValuePair<string, ITerrainEffect> kvp
in m_plugineffects)
1710 MainConsole.Instance.Output(kvp.Key);
1715 if (firstArg ==
"reload")
1721 if (m_plugineffects.ContainsKey(firstArg))
1723 m_plugineffects[firstArg].RunEffect(m_channel);
1727 MainConsole.Instance.Output(
"WARNING: No such plugin effect {0} loaded.", firstArg);
1731 private void InstallInterfaces()
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");
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");
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",
1754 loadFromTileCommand.AddArgument(
"minimum Y tile",
"The Y region coordinate of the first section on the file",
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",
1766 saveToTileCommand.AddArgument(
"minimum Y tile",
"The Y region coordinate of the first tile on the file\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",
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.",
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");
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");
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");
1791 new Command(
"bake",
CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain,
"Saves the current terrain into the regions baked map.");
1793 new Command(
"revert",
CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain,
"Loads the baked map terrain into the regions heightmap.");
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");
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");
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");
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");
1812 Command showDebugStatsCommand =
1814 "Shows some information about the regions heightmap for debugging purposes.");
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");
1821 Command experimentalBrushesCommand =
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");
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");
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);
1851 m_scene.RegisterModuleCommander(m_commander);
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"
1871 Scene scene = SceneManager.Instance.CurrentScene;
1872 if ((scene != null) && (scene != m_scene))
1874 result = String.Empty;
1876 else if (cmd.Length > 2)
1878 string operationType = cmd[2];
1882 if (!m_modifyOperations.TryGetValue(operationType, out operation))
1884 result = String.Format(
"Terrain Modify \"{0}\" not found.", operationType);
1886 else if ((cmd.Length > 3) && (cmd[3] ==
"usage"))
1888 result =
"Usage: " + operation.GetUsage();
1892 result = operation.ModifyTerrain(m_channel, cmd);
1895 if (result ==
String.Empty)
1897 result =
"Modified terrain";
1898 m_log.DebugFormat(
"Performed terrain operation {0}", operationType);
1903 result =
"Usage: <operation-name> <arg1> <arg2>...";
1905 if (result !=
String.Empty)
1907 MainConsole.Instance.Output(result);
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
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...
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 UndoTerrain(ITerrainChannel channel)
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...
void ModifyCommand(string module, string[] cmd)
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 ...
const int TerrainPatchSize
void LoadFromStream(string filename, Vector3 displacement, float rotationDegrees, Vector2 boundingOrigin, Vector2 boundingSize, Stream stream)
void InterfaceFillTerrain(Object[] args)
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)
Thermal Weathering Paint Brush
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)
Speed-Optimised Hybrid Erosion Brush
void LoadFromFile(string filename)
Loads a terrain file from disk and installs it in the scene.