diff --git a/OpenSim/Data/MSSQL/MSSQLSimulationData.cs b/OpenSim/Data/MSSQL/MSSQLSimulationData.cs
index 0d09be6361..145b9c0aad 100644
--- a/OpenSim/Data/MSSQL/MSSQLSimulationData.cs
+++ b/OpenSim/Data/MSSQL/MSSQLSimulationData.cs
@@ -530,43 +530,52 @@ ELSE
///
public double[,] LoadTerrain(UUID regionID)
{
- double[,] terrain = new double[(int)Constants.RegionSize, (int)Constants.RegionSize];
- terrain.Initialize();
+ double[,] ret = null;
+ TerrainData terrData = LoadTerrain(regionID, (int)Constants.RegionSize, (int)Constants.RegionSize, (int)Constants.RegionHeight);
+ if (terrData != null)
+ ret = terrData.GetDoubles();
+ return ret;
+ }
+
+ // Returns 'null' if region not found
+ public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ)
+ {
+ TerrainData terrData = null;
string sql = "select top 1 RegionUUID, Revision, Heightfield from terrain where RegionUUID = @RegionUUID order by Revision desc";
using (SqlConnection conn = new SqlConnection(m_connectionString))
- using (SqlCommand cmd = new SqlCommand(sql, conn))
{
- // MySqlParameter param = new MySqlParameter();
- cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID));
- conn.Open();
- using (SqlDataReader reader = cmd.ExecuteReader())
+ using (SqlCommand cmd = new SqlCommand(sql, conn))
{
- int rev;
- if (reader.Read())
+ // MySqlParameter param = new MySqlParameter();
+ cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID));
+ conn.Open();
+ using (SqlDataReader reader = cmd.ExecuteReader())
{
- MemoryStream str = new MemoryStream((byte[])reader["Heightfield"]);
- BinaryReader br = new BinaryReader(str);
- for (int x = 0; x < (int)Constants.RegionSize; x++)
+ if (reader.Read())
{
- for (int y = 0; y < (int)Constants.RegionSize; y++)
- {
- terrain[x, y] = br.ReadDouble();
- }
+ int rev = (int)reader["Revision"];
+ byte[] blob = (byte[])reader["Heightfield"];
+ terrData = TerrainData.CreateFromDatabaseBlobFactory(pSizeX, pSizeY, pSizeZ, rev, blob);
}
- rev = (int)reader["Revision"];
+ else
+ {
+ _Log.Info("[REGION DB]: No terrain found for region");
+ return null;
+ }
+ _Log.Info("[REGION DB]: Loaded terrain");
}
- else
- {
- _Log.Info("[REGION DB]: No terrain found for region");
- return null;
- }
- _Log.Info("[REGION DB]: Loaded terrain revision r" + rev);
}
}
- return terrain;
+ return terrData;
+ }
+
+ // Legacy entry point for when terrain was always a 256x256 hieghtmap
+ public void StoreTerrain(double[,] ter, UUID regionID)
+ {
+ StoreTerrain(new HeightmapTerrainData(ter), regionID);
}
///
@@ -574,10 +583,8 @@ ELSE
///
/// terrain map data.
/// regionID.
- public void StoreTerrain(double[,] terrain, UUID regionID)
+ public void StoreTerrain(TerrainData terrData, UUID regionID)
{
- int revision = Util.UnixTimeSinceEpoch();
-
//Delete old terrain map
string sql = "delete from terrain where RegionUUID=@RegionUUID";
using (SqlConnection conn = new SqlConnection(m_connectionString))
@@ -590,17 +597,23 @@ ELSE
sql = "insert into terrain(RegionUUID, Revision, Heightfield) values(@RegionUUID, @Revision, @Heightfield)";
+ int terrainDBRevision;
+ Array terrainDBblob;
+ terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob);
+
using (SqlConnection conn = new SqlConnection(m_connectionString))
- using (SqlCommand cmd = new SqlCommand(sql, conn))
{
- cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID));
- cmd.Parameters.Add(_Database.CreateParameter("@Revision", revision));
- cmd.Parameters.Add(_Database.CreateParameter("@Heightfield", serializeTerrain(terrain)));
- conn.Open();
- cmd.ExecuteNonQuery();
+ using (SqlCommand cmd = new SqlCommand(sql, conn))
+ {
+ cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID));
+ cmd.Parameters.Add(_Database.CreateParameter("@Revision", terrainDBRevision));
+ cmd.Parameters.Add(_Database.CreateParameter("@Heightfield", terrainDBblob));
+ conn.Open();
+ cmd.ExecuteNonQuery();
+ }
}
- _Log.Info("[REGION DB]: Stored terrain revision r " + revision);
+ _Log.Info("[REGION DB]: Stored terrain");
}
///
@@ -1344,6 +1357,7 @@ VALUES
#region Private Methods
+ /*
///
/// Serializes the terrain data for storage in DB.
///
@@ -1367,6 +1381,7 @@ VALUES
return str.ToArray();
}
+ */
///
/// Stores new regionsettings.
diff --git a/OpenSim/Data/MySQL/MySQLSimulationData.cs b/OpenSim/Data/MySQL/MySQLSimulationData.cs
index cc844aef76..f910e443fc 100644
--- a/OpenSim/Data/MySQL/MySQLSimulationData.cs
+++ b/OpenSim/Data/MySQL/MySQLSimulationData.cs
@@ -48,8 +48,18 @@ namespace OpenSim.Data.MySQL
public class MySQLSimulationData : ISimulationDataStore
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+ private static string LogHeader = "[REGION DB MYSQL]";
private string m_connectionString;
+
+ ///
+ /// This lock was being used to serialize database operations when the connection was shared, but this has
+ /// been unnecessary for a long time after we switched to using MySQL's underlying connection pooling instead.
+ /// FIXME: However, the locks remain in many places since they are effectively providing a level of
+ /// transactionality. This should be replaced by more efficient database transactions which would not require
+ /// unrelated operations to block each other or unrelated operations on the same tables from blocking each
+ /// other.
+ ///
private object m_dbLock = new object();
protected virtual Assembly Assembly
@@ -91,7 +101,7 @@ namespace OpenSim.Data.MySQL
}
catch (Exception e)
{
- m_log.Error("[REGION DB]: MySQL error in ExecuteReader: " + e.Message);
+ m_log.ErrorFormat("{0} MySQL error in ExecuteReader: {1}", LogHeader, e);
throw;
}
@@ -574,12 +584,16 @@ namespace OpenSim.Data.MySQL
}
}
- public virtual void StoreTerrain(double[,] ter, UUID regionID)
+ // Legacy entry point for when terrain was always a 256x256 hieghtmap
+ public void StoreTerrain(double[,] ter, UUID regionID)
+ {
+ StoreTerrain(new HeightmapTerrainData(ter), regionID);
+ }
+
+ public void StoreTerrain(TerrainData terrData, UUID regionID)
{
Util.FireAndForget(delegate(object x)
{
- double[,] oldTerrain = LoadTerrain(regionID);
-
m_log.Info("[REGION DB]: Storing terrain");
lock (m_dbLock)
@@ -601,8 +615,12 @@ namespace OpenSim.Data.MySQL
"Revision, Heightfield) values (?RegionUUID, " +
"1, ?Heightfield)";
- cmd2.Parameters.AddWithValue("RegionUUID", regionID.ToString());
- cmd2.Parameters.AddWithValue("Heightfield", SerializeTerrain(ter, oldTerrain));
+ int terrainDBRevision;
+ Array terrainDBblob;
+ terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob);
+
+ cmd2.Parameters.AddWithValue("Revision", terrainDBRevision);
+ cmd2.Parameters.AddWithValue("Heightfield", terrainDBblob);
ExecuteNonQuery(cmd);
ExecuteNonQuery(cmd2);
@@ -618,9 +636,20 @@ namespace OpenSim.Data.MySQL
});
}
+ // Legacy region loading
public virtual double[,] LoadTerrain(UUID regionID)
{
- double[,] terrain = null;
+ double[,] ret = null;
+ TerrainData terrData = LoadTerrain(regionID, (int)Constants.RegionSize, (int)Constants.RegionSize, (int)Constants.RegionHeight);
+ if (terrData != null)
+ ret = terrData.GetDoubles();
+ return ret;
+ }
+
+ // Returns 'null' if region not found
+ public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ)
+ {
+ TerrainData terrData = null;
lock (m_dbLock)
{
@@ -640,32 +669,15 @@ namespace OpenSim.Data.MySQL
while (reader.Read())
{
int rev = Convert.ToInt32(reader["Revision"]);
-
- terrain = new double[(int)Constants.RegionSize, (int)Constants.RegionSize];
- terrain.Initialize();
-
- using (MemoryStream mstr = new MemoryStream((byte[])reader["Heightfield"]))
- {
- using (BinaryReader br = new BinaryReader(mstr))
- {
- for (int x = 0; x < (int)Constants.RegionSize; x++)
- {
- for (int y = 0; y < (int)Constants.RegionSize; y++)
- {
- terrain[x, y] = br.ReadDouble();
- }
- }
- }
-
- m_log.InfoFormat("[REGION DB]: Loaded terrain revision r{0}", rev);
- }
+ byte[] blob = (byte[])reader["Heightfield"];
+ terrData = TerrainData.CreateFromDatabaseBlobFactory(pSizeX, pSizeY, pSizeZ, rev, blob);
}
}
}
}
}
- return terrain;
+ return terrData;
}
public virtual void RemoveLandObject(UUID globalID)
diff --git a/OpenSim/Data/Null/NullSimulationData.cs b/OpenSim/Data/Null/NullSimulationData.cs
index 15824a9ebe..339e7f45b0 100644
--- a/OpenSim/Data/Null/NullSimulationData.cs
+++ b/OpenSim/Data/Null/NullSimulationData.cs
@@ -132,15 +132,33 @@ namespace OpenSim.Data.Null
return new List();
}
- Dictionary m_terrains = new Dictionary();
- public void StoreTerrain(double[,] ter, UUID regionID)
+ Dictionary m_terrains = new Dictionary();
+ public void StoreTerrain(TerrainData ter, UUID regionID)
{
if (m_terrains.ContainsKey(regionID))
m_terrains.Remove(regionID);
m_terrains.Add(regionID, ter);
}
+ // Legacy. Just don't do this.
+ public void StoreTerrain(double[,] ter, UUID regionID)
+ {
+ TerrainData terrData = new HeightmapTerrainData(ter);
+ StoreTerrain(terrData, regionID);
+ }
+
+ // Legacy. Just don't do this.
+ // Returns 'null' if region not found
public double[,] LoadTerrain(UUID regionID)
+ {
+ if (m_terrains.ContainsKey(regionID))
+ {
+ return m_terrains[regionID].GetDoubles();
+ }
+ return null;
+ }
+
+ public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ)
{
if (m_terrains.ContainsKey(regionID))
{
diff --git a/OpenSim/Data/PGSQL/PGSQLSimulationData.cs b/OpenSim/Data/PGSQL/PGSQLSimulationData.cs
index 3243635a3a..cd02e058bc 100644
--- a/OpenSim/Data/PGSQL/PGSQLSimulationData.cs
+++ b/OpenSim/Data/PGSQL/PGSQLSimulationData.cs
@@ -46,6 +46,7 @@ namespace OpenSim.Data.PGSQL
public class PGSQLSimulationData : ISimulationDataStore
{
private const string _migrationStore = "RegionStore";
+ private const string LogHeader = "[REGION DB PGSQL]";
// private static FileSystemDataStore Instance = new FileSystemDataStore();
private static readonly ILog _Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
@@ -523,8 +524,17 @@ namespace OpenSim.Data.PGSQL
///
public double[,] LoadTerrain(UUID regionID)
{
- double[,] terrain = new double[(int)Constants.RegionSize, (int)Constants.RegionSize];
- terrain.Initialize();
+ double[,] ret = null;
+ TerrainData terrData = LoadTerrain(regionID, (int)Constants.RegionSize, (int)Constants.RegionSize, (int)Constants.RegionHeight);
+ if (terrData != null)
+ ret = terrData.GetDoubles();
+ return ret;
+ }
+
+ // Returns 'null' if region not found
+ public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ)
+ {
+ TerrainData terrData = null;
string sql = @"select ""RegionUUID"", ""Revision"", ""Heightfield"" from terrain
where ""RegionUUID"" = :RegionUUID order by ""Revision"" desc limit 1; ";
@@ -540,16 +550,9 @@ namespace OpenSim.Data.PGSQL
int rev;
if (reader.Read())
{
- MemoryStream str = new MemoryStream((byte[])reader["Heightfield"]);
- BinaryReader br = new BinaryReader(str);
- for (int x = 0; x < (int)Constants.RegionSize; x++)
- {
- for (int y = 0; y < (int)Constants.RegionSize; y++)
- {
- terrain[x, y] = br.ReadDouble();
- }
- }
- rev = (int)reader["Revision"];
+ rev = Convert.ToInt32(reader["Revision"]);
+ byte[] blob = (byte[])reader["Heightfield"];
+ terrData = TerrainData.CreateFromDatabaseBlobFactory(pSizeX, pSizeY, pSizeZ, rev, blob);
}
else
{
@@ -560,7 +563,13 @@ namespace OpenSim.Data.PGSQL
}
}
- return terrain;
+ return terrData;
+ }
+
+ // Legacy entry point for when terrain was always a 256x256 heightmap
+ public void StoreTerrain(double[,] terrain, UUID regionID)
+ {
+ StoreTerrain(new HeightmapTerrainData(terrain), regionID);
}
///
@@ -568,35 +577,38 @@ namespace OpenSim.Data.PGSQL
///
/// terrain map data.
/// regionID.
- public void StoreTerrain(double[,] terrain, UUID regionID)
+ public void StoreTerrain(TerrainData terrData, UUID regionID)
{
- int revision = Util.UnixTimeSinceEpoch();
-
//Delete old terrain map
string sql = @"delete from terrain where ""RegionUUID""=:RegionUUID";
using (NpgsqlConnection conn = new NpgsqlConnection(m_connectionString))
- using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
{
- cmd.Parameters.Add(_Database.CreateParameter("RegionUUID", regionID));
- conn.Open();
- cmd.ExecuteNonQuery();
+ using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
+ {
+ cmd.Parameters.Add(_Database.CreateParameter("RegionUUID", regionID));
+ conn.Open();
+ cmd.ExecuteNonQuery();
+ }
}
-
- _Log.Info("[REGION DB]: Deleted terrain revision r " + revision);
+ int terrainDBRevision;
+ Array terrainDBblob;
+ terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob);
sql = @"insert into terrain(""RegionUUID"", ""Revision"", ""Heightfield"") values(:RegionUUID, :Revision, :Heightfield)";
using (NpgsqlConnection conn = new NpgsqlConnection(m_connectionString))
- using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
{
- cmd.Parameters.Add(_Database.CreateParameter("RegionUUID", regionID));
- cmd.Parameters.Add(_Database.CreateParameter("Revision", revision));
- cmd.Parameters.Add(_Database.CreateParameter("Heightfield", serializeTerrain(terrain)));
- conn.Open();
- cmd.ExecuteNonQuery();
+ using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
+ {
+ cmd.Parameters.Add(_Database.CreateParameter("RegionUUID", regionID));
+ cmd.Parameters.Add(_Database.CreateParameter("Revision", terrainDBRevision));
+ cmd.Parameters.Add(_Database.CreateParameter("Heightfield", terrainDBblob));
+ conn.Open();
+ cmd.ExecuteNonQuery();
+ }
}
- _Log.Info("[REGION DB]: Stored terrain revision r " + revision);
+ _Log.Info("[REGION DB]: Stored terrain revision r " + terrainDBRevision);
}
///
@@ -1349,6 +1361,7 @@ namespace OpenSim.Data.PGSQL
#region Private Methods
+ /*
///
/// Serializes the terrain data for storage in DB.
///
@@ -1372,6 +1385,7 @@ namespace OpenSim.Data.PGSQL
return str.ToArray();
}
+ */
///
/// Stores new regionsettings.
diff --git a/OpenSim/Data/SQLite/SQLiteSimulationData.cs b/OpenSim/Data/SQLite/SQLiteSimulationData.cs
index 4d6a80ad22..d28c2277e0 100644
--- a/OpenSim/Data/SQLite/SQLiteSimulationData.cs
+++ b/OpenSim/Data/SQLite/SQLiteSimulationData.cs
@@ -51,6 +51,7 @@ namespace OpenSim.Data.SQLite
public class SQLiteSimulationData : ISimulationDataStore
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+ private static readonly string LogHeader = "[REGION DB SQLLITE]";
private const string primSelect = "select * from prims";
private const string shapeSelect = "select * from primshapes";
@@ -819,12 +820,18 @@ namespace OpenSim.Data.SQLite
prim.Inventory.RestoreInventoryItems(inventory);
}
+ // Legacy entry point for when terrain was always a 256x256 hieghtmap
+ public void StoreTerrain(double[,] ter, UUID regionID)
+ {
+ StoreTerrain(new HeightmapTerrainData(ter), regionID);
+ }
+
///
/// Store a terrain revision in region storage
///
/// terrain heightfield
/// region UUID
- public void StoreTerrain(double[,] ter, UUID regionID)
+ public void StoreTerrain(TerrainData terrData, UUID regionID)
{
lock (ds)
{
@@ -853,11 +860,17 @@ namespace OpenSim.Data.SQLite
String sql = "insert into terrain(RegionUUID, Revision, Heightfield)" +
" values(:RegionUUID, :Revision, :Heightfield)";
+ int terrainDBRevision;
+ Array terrainDBblob;
+ terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob);
+
+ m_log.DebugFormat("{0} Storing terrain revision r {1}", LogHeader, terrainDBRevision);
+
using (SqliteCommand cmd = new SqliteCommand(sql, m_conn))
{
cmd.Parameters.Add(new SqliteParameter(":RegionUUID", regionID.ToString()));
- cmd.Parameters.Add(new SqliteParameter(":Revision", revision));
- cmd.Parameters.Add(new SqliteParameter(":Heightfield", serializeTerrain(ter)));
+ cmd.Parameters.Add(new SqliteParameter(":Revision", terrainDBRevision));
+ cmd.Parameters.Add(new SqliteParameter(":Heightfield", terrainDBblob));
cmd.ExecuteNonQuery();
}
}
@@ -870,11 +883,20 @@ namespace OpenSim.Data.SQLite
/// Heightfield data
public double[,] LoadTerrain(UUID regionID)
{
+ double[,] ret = null;
+ TerrainData terrData = LoadTerrain(regionID, (int)Constants.RegionSize, (int)Constants.RegionSize, (int)Constants.RegionHeight);
+ if (terrData != null)
+ ret = terrData.GetDoubles();
+ return ret;
+ }
+
+ // Returns 'null' if region not found
+ public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ)
+ {
+ TerrainData terrData = null;
+
lock (ds)
{
- double[,] terret = new double[(int)Constants.RegionSize, (int)Constants.RegionSize];
- terret.Initialize();
-
String sql = "select RegionUUID, Revision, Heightfield from terrain" +
" where RegionUUID=:RegionUUID order by Revision desc";
@@ -887,21 +909,9 @@ namespace OpenSim.Data.SQLite
int rev = 0;
if (row.Read())
{
- // TODO: put this into a function
- using (MemoryStream str = new MemoryStream((byte[])row["Heightfield"]))
- {
- using (BinaryReader br = new BinaryReader(str))
- {
- for (int x = 0; x < (int)Constants.RegionSize; x++)
- {
- for (int y = 0; y < (int)Constants.RegionSize; y++)
- {
- terret[x, y] = br.ReadDouble();
- }
- }
- }
- }
rev = Convert.ToInt32(row["Revision"]);
+ byte[] blob = (byte[])row["Heightfield"];
+ terrData = TerrainData.CreateFromDatabaseBlobFactory(pSizeX, pSizeY, pSizeZ, rev, blob);
}
else
{
@@ -912,8 +922,8 @@ namespace OpenSim.Data.SQLite
m_log.Debug("[SQLITE REGION DB]: Loaded terrain revision r" + rev.ToString());
}
}
- return terret;
}
+ return terrData;
}
public void RemoveLandObject(UUID globalID)
@@ -2016,6 +2026,7 @@ namespace OpenSim.Data.SQLite
return entry;
}
+ /*
///
///
///
@@ -2033,6 +2044,7 @@ namespace OpenSim.Data.SQLite
return str.ToArray();
}
+ */
// private void fillTerrainRow(DataRow row, UUID regionUUID, int rev, double[,] val)
// {
diff --git a/OpenSim/Framework/TerrainData.cs b/OpenSim/Framework/TerrainData.cs
new file mode 100644
index 0000000000..6b1be4e9fa
--- /dev/null
+++ b/OpenSim/Framework/TerrainData.cs
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) Contributors, http://opensimulator.org/
+ * See CONTRIBUTORS.TXT for a full list of copyright holders.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the OpenSimulator Project nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+
+using OpenMetaverse;
+
+using log4net;
+
+namespace OpenSim.Framework
+{
+ public abstract class TerrainData
+ {
+ // Terrain always is a square
+ public int SizeX { get; protected set; }
+ public int SizeY { get; protected set; }
+ public int SizeZ { get; protected set; }
+
+ // A height used when the user doesn't specify anything
+ public const float DefaultTerrainHeight = 21f;
+
+ public abstract float this[int x, int y] { get; set; }
+ // Someday terrain will have caves
+ public abstract float this[int x, int y, int z] { get; set; }
+
+ public abstract bool IsTaintedAt(int xx, int yy);
+ public abstract bool IsTaintedAt(int xx, int yy, bool clearOnTest);
+ public abstract void TaintAllTerrain();
+ public abstract void ClearTaint();
+
+ public abstract void ClearLand();
+ public abstract void ClearLand(float height);
+
+ // Return a representation of this terrain for storing as a blob in the database.
+ // Returns 'true' to say blob was stored in the 'out' locations.
+ public abstract bool GetDatabaseBlob(out int DBFormatRevisionCode, out Array blob);
+
+ // Given a revision code and a blob from the database, create and return the right type of TerrainData.
+ // The sizes passed are the expected size of the region. The database info will be used to
+ // initialize the heightmap of that sized region with as much data is in the blob.
+ // Return created TerrainData or 'null' if unsuccessful.
+ public static TerrainData CreateFromDatabaseBlobFactory(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob)
+ {
+ // For the moment, there is only one implementation class
+ return new HeightmapTerrainData(pSizeX, pSizeY, pSizeZ, pFormatCode, pBlob);
+ }
+
+ // return a special compressed representation of the heightmap in ints
+ public abstract int[] GetCompressedMap();
+ public abstract float CompressionFactor { get; }
+
+ public abstract float[] GetFloatsSerialized();
+ public abstract double[,] GetDoubles();
+ public abstract TerrainData Clone();
+ }
+
+ // The terrain is stored in the database as a blob with a 'revision' field.
+ // Some implementations of terrain storage would fill the revision field with
+ // the time the terrain was stored. When real revisions were added and this
+ // feature removed, that left some old entries with the time in the revision
+ // field.
+ // Thus, if revision is greater than 'RevisionHigh' then terrain db entry is
+ // left over and it is presumed to be 'Legacy256'.
+ // Numbers are arbitrary and are chosen to to reduce possible mis-interpretation.
+ // If a revision does not match any of these, it is assumed to be Legacy256.
+ public enum DBTerrainRevision
+ {
+ // Terrain is 'double[256,256]'
+ Legacy256 = 11,
+ // Terrain is 'int32, int32, float[,]' where the ints are X and Y dimensions
+ // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256.
+ Variable2D = 22,
+ // Terrain is 'int32, int32, int32, int16[]' where the ints are X and Y dimensions
+ // and third int is the 'compression factor'. The heights are compressed as
+ // "int compressedHeight = (int)(height * compressionFactor);"
+ // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256.
+ Compressed2D = 27,
+ // A revision that is not listed above or any revision greater than this value is 'Legacy256'.
+ RevisionHigh = 1234
+ }
+
+ // Version of terrain that is a heightmap.
+ // This should really be 'LLOptimizedHeightmapTerrainData' as it includes knowledge
+ // of 'patches' which are 16x16 terrain areas which can be sent separately to the viewer.
+ // The heighmap is kept as an array of integers. The integer values are converted to
+ // and from floats by TerrainCompressionFactor.
+ public class HeightmapTerrainData : TerrainData
+ {
+ private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+ private static string LogHeader = "[HEIGHTMAP TERRAIN DATA]";
+
+ // TerrainData.this[x, y]
+ public override float this[int x, int y]
+ {
+ get { return FromCompressedHeight(m_heightmap[x, y]); }
+ set {
+ int newVal = ToCompressedHeight(value);
+ if (m_heightmap[x, y] != newVal)
+ {
+ m_heightmap[x, y] = newVal;
+ m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize] = true;
+ }
+ }
+ }
+
+ // TerrainData.this[x, y, z]
+ public override float this[int x, int y, int z]
+ {
+ get { return this[x, y]; }
+ set { this[x, y] = value; }
+ }
+
+ // TerrainData.ClearTaint
+ public override void ClearTaint()
+ {
+ SetAllTaint(false);
+ }
+
+ // TerrainData.TaintAllTerrain
+ public override void TaintAllTerrain()
+ {
+ SetAllTaint(true);
+ }
+
+ private void SetAllTaint(bool setting)
+ {
+ for (int ii = 0; ii < m_taint.GetLength(0); ii++)
+ for (int jj = 0; jj < m_taint.GetLength(1); jj++)
+ m_taint[ii, jj] = setting;
+ }
+
+ // TerrainData.ClearLand
+ public override void ClearLand()
+ {
+ ClearLand(DefaultTerrainHeight);
+ }
+ // TerrainData.ClearLand(float)
+ public override void ClearLand(float pHeight)
+ {
+ int flatHeight = ToCompressedHeight(pHeight);
+ for (int xx = 0; xx < SizeX; xx++)
+ for (int yy = 0; yy < SizeY; yy++)
+ m_heightmap[xx, yy] = flatHeight;
+ }
+
+ // Return 'true' of the patch that contains these region coordinates has been modified.
+ // Note that checking the taint clears it.
+ // There is existing code that relies on this feature.
+ public override bool IsTaintedAt(int xx, int yy, bool clearOnTest)
+ {
+ int tx = xx / Constants.TerrainPatchSize;
+ int ty = yy / Constants.TerrainPatchSize;
+ bool ret = m_taint[tx, ty];
+ if (ret && clearOnTest)
+ m_taint[tx, ty] = false;
+ return ret;
+ }
+
+ // Old form that clears the taint flag when we check it.
+ public override bool IsTaintedAt(int xx, int yy)
+ {
+ return IsTaintedAt(xx, yy, true /* clearOnTest */);
+ }
+
+ // TerrainData.GetDatabaseBlob
+ // The user wants something to store in the database.
+ public override bool GetDatabaseBlob(out int DBRevisionCode, out Array blob)
+ {
+ bool ret = false;
+ if (SizeX == Constants.RegionSize && SizeY == Constants.RegionSize)
+ {
+ DBRevisionCode = (int)DBTerrainRevision.Legacy256;
+ blob = ToLegacyTerrainSerialization();
+ ret = true;
+ }
+ else
+ {
+ DBRevisionCode = (int)DBTerrainRevision.Compressed2D;
+ blob = ToCompressedTerrainSerialization();
+ ret = true;
+ }
+ return ret;
+ }
+
+ // TerrainData.CompressionFactor
+ private float m_compressionFactor = 100.0f;
+ public override float CompressionFactor { get { return m_compressionFactor; } }
+
+ // TerrainData.GetCompressedMap
+ public override int[] GetCompressedMap()
+ {
+ int[] newMap = new int[SizeX * SizeY];
+
+ int ind = 0;
+ for (int xx = 0; xx < SizeX; xx++)
+ for (int yy = 0; yy < SizeY; yy++)
+ newMap[ind++] = m_heightmap[xx, yy];
+
+ return newMap;
+
+ }
+ // TerrainData.Clone
+ public override TerrainData Clone()
+ {
+ HeightmapTerrainData ret = new HeightmapTerrainData(SizeX, SizeY, SizeZ);
+ ret.m_heightmap = (int[,])this.m_heightmap.Clone();
+ return ret;
+ }
+
+ // TerrainData.GetFloatsSerialized
+ // This one dimensional version is ordered so height = map[y*sizeX+x];
+ // DEPRECATED: don't use this function as it does not retain the dimensions of the terrain
+ // and the caller will probably do the wrong thing if the terrain is not the legacy 256x256.
+ public override float[] GetFloatsSerialized()
+ {
+ int points = SizeX * SizeY;
+ float[] heights = new float[points];
+
+ int idx = 0;
+ for (int jj = 0; jj < SizeY; jj++)
+ for (int ii = 0; ii < SizeX; ii++)
+ {
+ heights[idx++] = FromCompressedHeight(m_heightmap[ii, jj]);
+ }
+
+ return heights;
+ }
+
+ // TerrainData.GetDoubles
+ public override double[,] GetDoubles()
+ {
+ double[,] ret = new double[SizeX, SizeY];
+ for (int xx = 0; xx < SizeX; xx++)
+ for (int yy = 0; yy < SizeY; yy++)
+ ret[xx, yy] = FromCompressedHeight(m_heightmap[xx, yy]);
+
+ return ret;
+ }
+
+
+ // =============================================================
+
+ private int[,] m_heightmap;
+ // Remember subregions of the heightmap that has changed.
+ private bool[,] m_taint;
+
+ // To save space (especially for large regions), keep the height as a short integer
+ // that is coded as the float height times the compression factor (usually '100'
+ // to make for two decimal points).
+ public int ToCompressedHeight(double pHeight)
+ {
+ return (int)(pHeight * CompressionFactor);
+ }
+
+ public float FromCompressedHeight(int pHeight)
+ {
+ return ((float)pHeight) / CompressionFactor;
+ }
+
+ // To keep with the legacy theme, create an instance of this class based on the
+ // way terrain used to be passed around.
+ public HeightmapTerrainData(double[,] pTerrain)
+ {
+ SizeX = pTerrain.GetLength(0);
+ SizeY = pTerrain.GetLength(1);
+ SizeZ = (int)Constants.RegionHeight;
+ m_compressionFactor = 100.0f;
+
+ m_heightmap = new int[SizeX, SizeY];
+ for (int ii = 0; ii < SizeX; ii++)
+ {
+ for (int jj = 0; jj < SizeY; jj++)
+ {
+ m_heightmap[ii, jj] = ToCompressedHeight(pTerrain[ii, jj]);
+
+ }
+ }
+ // m_log.DebugFormat("{0} new by doubles. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
+
+ m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
+ ClearTaint();
+ }
+
+ // Create underlying structures but don't initialize the heightmap assuming the caller will immediately do that
+ public HeightmapTerrainData(int pX, int pY, int pZ)
+ {
+ SizeX = pX;
+ SizeY = pY;
+ SizeZ = pZ;
+ m_compressionFactor = 100.0f;
+ m_heightmap = new int[SizeX, SizeY];
+ m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize];
+ // m_log.DebugFormat("{0} new by dimensions. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
+ ClearTaint();
+ ClearLand(0f);
+ }
+
+ public HeightmapTerrainData(int[] cmap, float pCompressionFactor, int pX, int pY, int pZ) : this(pX, pY, pZ)
+ {
+ m_compressionFactor = pCompressionFactor;
+ int ind = 0;
+ for (int xx = 0; xx < SizeX; xx++)
+ for (int yy = 0; yy < SizeY; yy++)
+ m_heightmap[xx, yy] = cmap[ind++];
+ // m_log.DebugFormat("{0} new by compressed map. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ);
+ }
+
+ // Create a heighmap from a database blob
+ public HeightmapTerrainData(int pSizeX, int pSizeY, int pSizeZ, int pFormatCode, byte[] pBlob) : this(pSizeX, pSizeY, pSizeZ)
+ {
+ switch ((DBTerrainRevision)pFormatCode)
+ {
+ case DBTerrainRevision.Compressed2D:
+ FromCompressedTerrainSerialization(pBlob);
+ m_log.DebugFormat("{0} HeightmapTerrainData create from Compressed2D serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
+ break;
+ default:
+ FromLegacyTerrainSerialization(pBlob);
+ m_log.DebugFormat("{0} HeightmapTerrainData create from legacy serialization. Size=<{1},{2}>", LogHeader, SizeX, SizeY);
+ break;
+ }
+ }
+
+ // Just create an array of doubles. Presumes the caller implicitly knows the size.
+ public Array ToLegacyTerrainSerialization()
+ {
+ Array ret = null;
+
+ using (MemoryStream str = new MemoryStream((int)Constants.RegionSize * (int)Constants.RegionSize * sizeof(double)))
+ {
+ using (BinaryWriter bw = new BinaryWriter(str))
+ {
+ for (int xx = 0; xx < Constants.RegionSize; xx++)
+ {
+ for (int yy = 0; yy < Constants.RegionSize; yy++)
+ {
+ double height = this[xx, yy];
+ if (height == 0.0)
+ height = double.Epsilon;
+ bw.Write(height);
+ }
+ }
+ }
+ ret = str.ToArray();
+ }
+ return ret;
+ }
+
+ // Just create an array of doubles. Presumes the caller implicitly knows the size.
+ public void FromLegacyTerrainSerialization(byte[] pBlob)
+ {
+ // In case database info doesn't match real terrain size, initialize the whole terrain.
+ ClearLand();
+
+ using (MemoryStream mstr = new MemoryStream(pBlob))
+ {
+ using (BinaryReader br = new BinaryReader(mstr))
+ {
+ for (int xx = 0; xx < (int)Constants.RegionSize; xx++)
+ {
+ for (int yy = 0; yy < (int)Constants.RegionSize; yy++)
+ {
+ float val = (float)br.ReadDouble();
+ if (xx < SizeX && yy < SizeY)
+ m_heightmap[xx, yy] = ToCompressedHeight(val);
+ }
+ }
+ }
+ ClearTaint();
+ }
+ }
+
+ // See the reader below.
+ public Array ToCompressedTerrainSerialization()
+ {
+ Array ret = null;
+ using (MemoryStream str = new MemoryStream((3 * sizeof(Int32)) + (SizeX * SizeY * sizeof(Int16))))
+ {
+ using (BinaryWriter bw = new BinaryWriter(str))
+ {
+ bw.Write((Int32)DBTerrainRevision.Compressed2D);
+ bw.Write((Int32)SizeX);
+ bw.Write((Int32)SizeY);
+ bw.Write((Int32)CompressionFactor);
+ for (int yy = 0; yy < SizeY; yy++)
+ for (int xx = 0; xx < SizeX; xx++)
+ {
+ bw.Write((Int16)m_heightmap[xx, yy]);
+ }
+ }
+ ret = str.ToArray();
+ }
+ return ret;
+ }
+
+ // Initialize heightmap from blob consisting of:
+ // int32, int32, int32, int32, int16[]
+ // where the first int32 is format code, next two int32s are the X and y of heightmap data and
+ // the forth int is the compression factor for the following int16s
+ // This is just sets heightmap info. The actual size of the region was set on this instance's
+ // creation and any heights not initialized by theis blob are set to the default height.
+ public void FromCompressedTerrainSerialization(byte[] pBlob)
+ {
+ Int32 hmFormatCode, hmSizeX, hmSizeY, hmCompressionFactor;
+
+ using (MemoryStream mstr = new MemoryStream(pBlob))
+ {
+ using (BinaryReader br = new BinaryReader(mstr))
+ {
+ hmFormatCode = br.ReadInt32();
+ hmSizeX = br.ReadInt32();
+ hmSizeY = br.ReadInt32();
+ hmCompressionFactor = br.ReadInt32();
+
+ m_compressionFactor = hmCompressionFactor;
+
+ // In case database info doesn't match real terrain size, initialize the whole terrain.
+ ClearLand();
+
+ for (int yy = 0; yy < hmSizeY; yy++)
+ {
+ for (int xx = 0; xx < hmSizeX; xx++)
+ {
+ Int16 val = br.ReadInt16();
+ if (xx < SizeX && yy < SizeY)
+ m_heightmap[xx, yy] = val;
+ }
+ }
+ }
+ ClearTaint();
+
+ m_log.InfoFormat("{0} Read compressed 2d heightmap. Heightmap size=<{1},{2}>. Region size=<{3},{4}>. CompFact={5}",
+ LogHeader, hmSizeX, hmSizeY, SizeX, SizeY, hmCompressionFactor);
+ }
+ }
+ }
+}
diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs
index d78ade5fa7..d5c77ec3e1 100644
--- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs
+++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs
@@ -67,7 +67,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders
{
using (Bitmap bitmap = new Bitmap(filename))
{
- ITerrainChannel retval = new TerrainChannel(true);
+ ITerrainChannel retval = new TerrainChannel(w, h);
for (int x = 0; x < retval.Width; x++)
{
diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs
index 4d738a58ec..9a88804a72 100644
--- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs
+++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs
@@ -71,6 +71,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+#pragma warning disable 414
+ private static readonly string LogHeader = "[TERRAIN MODULE]";
+#pragma warning restore 414
+
private readonly Commander m_commander = new Commander("terrain");
private readonly Dictionary m_floodeffects =
@@ -81,8 +85,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private readonly Dictionary m_painteffects =
new Dictionary();
- private ITerrainChannel m_channel;
private Dictionary m_plugineffects;
+ private ITerrainChannel m_channel;
private ITerrainChannel m_revert;
private Scene m_scene;
private volatile bool m_tainted;
@@ -90,6 +94,85 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private String m_InitialTerrain = "pinhead-island";
+ // If true, send terrain patch updates to clients based on their view distance
+ private bool m_sendTerrainUpdatesByViewDistance = true;
+
+ // Class to keep the per client collection of terrain patches that must be sent.
+ // A patch is set to 'true' meaning it should be sent to the client. Once the
+ // patch packet is queued to the client, the bit for that patch is set to 'false'.
+ private class PatchUpdates
+ {
+ private bool[,] updated; // for each patch, whether it needs to be sent to this client
+ private int updateCount; // number of patches that need to be sent
+ public ScenePresence Presence; // a reference to the client to send to
+ public PatchUpdates(TerrainData terrData, ScenePresence pPresence)
+ {
+ updated = new bool[terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize];
+ updateCount = 0;
+ Presence = pPresence;
+ // Initially, send all patches to the client
+ SetAll(true);
+ }
+ // Returns 'true' if there are any patches marked for sending
+ public bool HasUpdates()
+ {
+ return (updateCount > 0);
+ }
+ public void SetByXY(int x, int y, bool state)
+ {
+ this.SetByPatch(x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, state);
+ }
+ public bool GetByPatch(int patchX, int patchY)
+ {
+ return updated[patchX, patchY];
+ }
+ public void SetByPatch(int patchX, int patchY, bool state)
+ {
+ bool prevState = updated[patchX, patchY];
+ if (!prevState && state)
+ updateCount++;
+ if (prevState && !state)
+ updateCount--;
+ updated[patchX, patchY] = state;
+ }
+ public void SetAll(bool state)
+ {
+ updateCount = 0;
+ for (int xx = 0; xx < updated.GetLength(0); xx++)
+ for (int yy = 0; yy < updated.GetLength(1); yy++)
+ updated[xx, yy] = state;
+ if (state)
+ updateCount = updated.GetLength(0) * updated.GetLength(1);
+ }
+ // Logically OR's the terrain data's patch taint map into this client's update map.
+ public void SetAll(TerrainData terrData)
+ {
+ if (updated.GetLength(0) != (terrData.SizeX / Constants.TerrainPatchSize)
+ || updated.GetLength(1) != (terrData.SizeY / Constants.TerrainPatchSize))
+ {
+ throw new Exception(
+ String.Format("{0} PatchUpdates.SetAll: patch array not same size as terrain. arr=<{1},{2}>, terr=<{3},{4}>",
+ LogHeader, updated.GetLength(0), updated.GetLength(1),
+ terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize)
+ );
+ }
+ for (int xx = 0; xx < terrData.SizeX; xx += Constants.TerrainPatchSize)
+ {
+ for (int yy = 0; yy < terrData.SizeY; yy += Constants.TerrainPatchSize)
+ {
+ // Only set tainted. The patch bit may be set if the patch was to be sent later.
+ if (terrData.IsTaintedAt(xx, yy, false))
+ {
+ this.SetByXY(xx, yy, true);
+ }
+ }
+ }
+ }
+ }
+
+ // The flags of which terrain patches to send for each of the ScenePresence's
+ private Dictionary m_perClientPatchUpdates = new Dictionary();
+
///
/// Human readable list of terrain file extensions that are supported.
///
@@ -118,7 +201,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
IConfig terrainConfig = config.Configs["Terrain"];
if (terrainConfig != null)
+ {
m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain);
+ m_sendTerrainUpdatesByViewDistance = terrainConfig.GetBoolean("SendTerrainUpdatesByViewDistance", m_sendTerrainUpdatesByViewDistance);
+ }
}
public void AddRegion(Scene scene)
@@ -130,22 +216,24 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
if (m_scene.Heightmap == null)
{
- m_channel = new TerrainChannel(m_InitialTerrain);
+ m_channel = new TerrainChannel(m_InitialTerrain, (int)m_scene.RegionInfo.RegionSizeX,
+ (int)m_scene.RegionInfo.RegionSizeY,
+ (int)m_scene.RegionInfo.RegionSizeZ);
m_scene.Heightmap = m_channel;
- m_revert = new TerrainChannel();
UpdateRevertMap();
}
else
{
m_channel = m_scene.Heightmap;
- m_revert = new TerrainChannel();
UpdateRevertMap();
}
m_scene.RegisterModuleInterface(this);
m_scene.EventManager.OnNewClient += EventManager_OnNewClient;
+ m_scene.EventManager.OnClientClosed += EventManager_OnClientClosed;
m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole;
m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick;
+ m_scene.EventManager.OnFrame += EventManager_OnFrame;
}
InstallDefaultEffects();
@@ -184,8 +272,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain
// remove the commands
m_scene.UnregisterModuleCommander(m_commander.Name);
// remove the event-handlers
+ m_scene.EventManager.OnFrame -= EventManager_OnFrame;
m_scene.EventManager.OnTerrainTick -= EventManager_OnTerrainTick;
m_scene.EventManager.OnPluginConsole -= EventManager_OnPluginConsole;
+ m_scene.EventManager.OnClientClosed -= EventManager_OnClientClosed;
m_scene.EventManager.OnNewClient -= EventManager_OnNewClient;
// remove the interface
m_scene.UnregisterModuleInterface(this);
@@ -230,11 +320,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain
try
{
ITerrainChannel channel = loader.Value.LoadFile(filename);
- if (channel.Width != Constants.RegionSize || channel.Height != Constants.RegionSize)
+ if (channel.Width != m_scene.RegionInfo.RegionSizeX || channel.Height != m_scene.RegionInfo.RegionSizeY)
{
// TerrainChannel expects a RegionSize x RegionSize map, currently
throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}",
- Constants.RegionSize, Constants.RegionSize));
+ m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY));
}
m_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height);
m_scene.Heightmap = channel;
@@ -261,7 +351,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
String.Format("Unable to load heightmap: {0}", e.Message));
}
}
- CheckForTerrainUpdates();
m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
return;
}
@@ -309,12 +398,18 @@ namespace OpenSim.Region.CoreModules.World.Terrain
LoadFromStream(filename, URIFetch(pathToTerrainHeightmap));
}
+ public void LoadFromStream(string filename, Stream stream)
+ {
+ LoadFromStream(filename, Vector3.Zero, 0f, Vector2.Zero, stream);
+ }
+
///
/// Loads a terrain file from a stream and installs it in the scene.
///
/// Filename to terrain file. Type is determined by extension.
///
- public void LoadFromStream(string filename, Stream stream)
+ public void LoadFromStream(string filename, Vector3 displacement,
+ float radianRotation, Vector2 rotationDisplacement, Stream stream)
{
foreach (KeyValuePair loader in m_loaders)
{
@@ -325,8 +420,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
try
{
ITerrainChannel channel = loader.Value.LoadStream(stream);
- m_scene.Heightmap = channel;
- m_channel = channel;
+ m_channel.Merge(channel, displacement, radianRotation, rotationDisplacement);
UpdateRevertMap();
}
catch (NotImplementedException)
@@ -337,7 +431,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
}
}
- CheckForTerrainUpdates();
m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
return;
}
@@ -406,9 +499,46 @@ namespace OpenSim.Region.CoreModules.World.Terrain
}
}
+ // Someone diddled terrain outside the normal code paths. Set the taintedness for all clients.
+ // ITerrainModule.TaintTerrain()
public void TaintTerrain ()
{
- CheckForTerrainUpdates();
+ lock (m_perClientPatchUpdates)
+ {
+ // Set the flags for all clients so the tainted patches will be sent out
+ foreach (PatchUpdates pups in m_perClientPatchUpdates.Values)
+ {
+ pups.SetAll(m_scene.Heightmap.GetTerrainData());
+ }
+ }
+ }
+
+ // ITerrainModule.PushTerrain()
+ public void PushTerrain(IClientAPI pClient)
+ {
+ if (m_sendTerrainUpdatesByViewDistance)
+ {
+ ScenePresence presence = m_scene.GetScenePresence(pClient.AgentId);
+ if (presence != null)
+ {
+ lock (m_perClientPatchUpdates)
+ {
+ PatchUpdates pups;
+ if (!m_perClientPatchUpdates.TryGetValue(pClient.AgentId, out pups))
+ {
+ // There is a ScenePresence without a send patch map. Create one.
+ pups = new PatchUpdates(m_scene.Heightmap.GetTerrainData(), presence);
+ m_perClientPatchUpdates.Add(presence.UUID, pups);
+ }
+ pups.SetAll(true);
+ }
+ }
+ }
+ else
+ {
+ // The traditional way is to call into the protocol stack to send them all.
+ pClient.SendLayerData(new float[10]);
+ }
}
#region Plugin Loading Methods
@@ -532,6 +662,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
///
public void UpdateRevertMap()
{
+ /*
int x;
for (x = 0; x < m_channel.Width; x++)
{
@@ -541,6 +672,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain
m_revert[x, y] = m_channel[x, y];
}
}
+ */
+ m_revert = m_channel.MakeCopy();
}
///
@@ -567,8 +700,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY,
fileWidth, fileHeight,
- (int) Constants.RegionSize,
- (int) Constants.RegionSize);
+ (int) m_scene.RegionInfo.RegionSizeX,
+ (int) m_scene.RegionInfo.RegionSizeY);
m_scene.Heightmap = channel;
m_channel = channel;
UpdateRevertMap();
@@ -615,8 +748,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
loader.Value.SaveFile(m_channel, filename, offsetX, offsetY,
fileWidth, fileHeight,
- (int)Constants.RegionSize,
- (int)Constants.RegionSize);
+ (int)m_scene.RegionInfo.RegionSizeX,
+ (int)m_scene.RegionInfo.RegionSizeY);
MainConsole.Instance.OutputFormat(
"Saved terrain from ({0},{1}) to ({2},{3}) from {4} to {5}",
@@ -633,8 +766,45 @@ namespace OpenSim.Region.CoreModules.World.Terrain
m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave);
}
+ ///
+ /// Called before processing of every simulation frame.
+ /// This is used to check to see of any of the terrain is tainted and, if so, schedule
+ /// updates for all the presences.
+ /// This also checks to see if there are updates that need to be sent for each presence.
+ /// This is where the logic is to send terrain updates to clients.
+ ///
+ private void EventManager_OnFrame()
+ {
+ TerrainData terrData = m_channel.GetTerrainData();
+
+ bool shouldTaint = false;
+ for (int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize)
+ {
+ for (int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize)
+ {
+ if (terrData.IsTaintedAt(x, y))
+ {
+ // Found a patch that was modified. Push this flag into the clients.
+ SendToClients(terrData, x, y);
+ shouldTaint = true;
+ }
+ }
+ }
+
+ // This event also causes changes to be sent to the clients
+ CheckSendingPatchesToClients();
+
+ // If things changes, generate some events
+ if (shouldTaint)
+ {
+ m_scene.EventManager.TriggerTerrainTainted();
+ m_tainted = true;
+ }
+ }
+
///
/// Performs updates to the region periodically, synchronising physics and other heightmap aware sections
+ /// Called infrequently (like every 5 seconds or so). Best used for storing terrain.
///
private void EventManager_OnTerrainTick()
{
@@ -644,8 +814,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised());
m_scene.SaveTerrain();
- m_scene.EventManager.TriggerTerrainUpdate();
-
// Clients who look at the map will never see changes after they looked at the map, so i've commented this out.
//m_scene.CreateTerrainTexture(true);
}
@@ -687,54 +855,48 @@ namespace OpenSim.Region.CoreModules.World.Terrain
}
///
- /// Checks to see if the terrain has been modified since last check
- /// but won't attempt to limit those changes to the limits specified in the estate settings
- /// currently invoked by the command line operations in the region server only
+ /// Installs terrain brush hook to IClientAPI
///
- private void CheckForTerrainUpdates()
+ ///
+ private void EventManager_OnClientClosed(UUID client, Scene scene)
{
- CheckForTerrainUpdates(false);
- }
-
- ///
- /// Checks to see if the terrain has been modified since last check.
- /// If it has been modified, every all the terrain patches are sent to the client.
- /// If the call is asked to respect the estate settings for terrain_raise_limit and
- /// terrain_lower_limit, it will clamp terrain updates between these values
- /// currently invoked by client_OnModifyTerrain only and not the Commander interfaces
- /// should height map deltas be limited to the estate settings limits
- ///
- private void CheckForTerrainUpdates(bool respectEstateSettings)
- {
- bool shouldTaint = false;
- float[] serialised = m_channel.GetFloatsSerialised();
- int x;
- for (x = 0; x < m_channel.Width; x += Constants.TerrainPatchSize)
+ ScenePresence presence = scene.GetScenePresence(client);
+ if (presence != null)
{
- int y;
- for (y = 0; y < m_channel.Height; y += Constants.TerrainPatchSize)
- {
- if (m_channel.Tainted(x, y))
- {
- // if we should respect the estate settings then
- // fixup and height deltas that don't respect them
- if (respectEstateSettings && LimitChannelChanges(x, y))
- {
- // this has been vetoed, so update
- // what we are going to send to the client
- serialised = m_channel.GetFloatsSerialised();
- }
+ presence.ControllingClient.OnModifyTerrain -= client_OnModifyTerrain;
+ presence.ControllingClient.OnBakeTerrain -= client_OnBakeTerrain;
+ presence.ControllingClient.OnLandUndo -= client_OnLandUndo;
+ presence.ControllingClient.OnUnackedTerrain -= client_OnUnackedTerrain;
+ }
- SendToClients(serialised, x, y);
- shouldTaint = true;
+ lock (m_perClientPatchUpdates)
+ m_perClientPatchUpdates.Remove(client);
+ }
+
+ ///
+ /// Scan over changes in the terrain and limit height changes. This enforces the
+ /// non-estate owner limits on rate of terrain editting.
+ /// Returns 'true' if any heights were limited.
+ ///
+ private bool EnforceEstateLimits()
+ {
+ TerrainData terrData = m_channel.GetTerrainData();
+
+ bool wasLimited = false;
+ for (int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize)
+ {
+ for (int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize)
+ {
+ if (terrData.IsTaintedAt(x, y, false /* clearOnTest */))
+ {
+ // If we should respect the estate settings then
+ // fixup and height deltas that don't respect them.
+ // Note that LimitChannelChanges() modifies the TerrainChannel with the limited height values.
+ wasLimited |= LimitChannelChanges(terrData, x, y);
}
}
}
- if (shouldTaint)
- {
- m_scene.EventManager.TriggerTerrainTainted();
- m_tainted = true;
- }
+ return wasLimited;
}
///
@@ -742,11 +904,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain
/// are all within the current estate limits
/// true if changes were limited, false otherwise
///
- private bool LimitChannelChanges(int xStart, int yStart)
+ private bool LimitChannelChanges(TerrainData terrData, int xStart, int yStart)
{
bool changesLimited = false;
- double minDelta = m_scene.RegionInfo.RegionSettings.TerrainLowerLimit;
- double maxDelta = m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit;
+ float minDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit;
+ float maxDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit;
// loop through the height map for this patch and compare it against
// the revert map
@@ -754,19 +916,18 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++)
{
-
- double requestedHeight = m_channel[x, y];
- double bakedHeight = m_revert[x, y];
- double requestedDelta = requestedHeight - bakedHeight;
+ float requestedHeight = terrData[x, y];
+ float bakedHeight = (float)m_revert[x, y];
+ float requestedDelta = requestedHeight - bakedHeight;
if (requestedDelta > maxDelta)
{
- m_channel[x, y] = bakedHeight + maxDelta;
+ terrData[x, y] = bakedHeight + maxDelta;
changesLimited = true;
}
else if (requestedDelta < minDelta)
{
- m_channel[x, y] = bakedHeight + minDelta; //as lower is a -ve delta
+ terrData[x, y] = bakedHeight + minDelta; //as lower is a -ve delta
changesLimited = true;
}
}
@@ -794,14 +955,154 @@ namespace OpenSim.Region.CoreModules.World.Terrain
/// A copy of the terrain as a 1D float array of size w*h
/// The patch corner to send
/// The patch corner to send
- private void SendToClients(float[] serialised, int x, int y)
+ private void SendToClients(TerrainData terrData, int x, int y)
{
- m_scene.ForEachClient(
- delegate(IClientAPI controller)
- { controller.SendLayerData(
- x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, serialised);
+ if (m_sendTerrainUpdatesByViewDistance)
+ {
+ // Add that this patch needs to be sent to the accounting for each client.
+ lock (m_perClientPatchUpdates)
+ {
+ m_scene.ForEachScenePresence(presence =>
+ {
+ PatchUpdates thisClientUpdates;
+ if (!m_perClientPatchUpdates.TryGetValue(presence.UUID, out thisClientUpdates))
+ {
+ // There is a ScenePresence without a send patch map. Create one.
+ thisClientUpdates = new PatchUpdates(terrData, presence);
+ m_perClientPatchUpdates.Add(presence.UUID, thisClientUpdates);
+ }
+ thisClientUpdates.SetByXY(x, y, true);
+ }
+ );
+ }
+ }
+ else
+ {
+ // Legacy update sending where the update is sent out as soon as noticed
+ // We know the actual terrain data passed is ignored. This kludge saves changing IClientAPI.
+ //float[] heightMap = terrData.GetFloatsSerialized();
+ float[] heightMap = new float[10];
+ m_scene.ForEachClient(
+ delegate(IClientAPI controller)
+ {
+ controller.SendLayerData(x / Constants.TerrainPatchSize,
+ y / Constants.TerrainPatchSize,
+ heightMap);
}
- );
+ );
+ }
+ }
+
+ private class PatchesToSend : IComparable
+ {
+ public int PatchX;
+ public int PatchY;
+ public float Dist;
+ public PatchesToSend(int pX, int pY, float pDist)
+ {
+ PatchX = pX;
+ PatchY = pY;
+ Dist = pDist;
+ }
+ public int CompareTo(PatchesToSend other)
+ {
+ return Dist.CompareTo(other.Dist);
+ }
+ }
+
+ // Called each frame time to see if there are any patches to send to any of the
+ // ScenePresences.
+ // Loop through all the per-client info and send any patches necessary.
+ private void CheckSendingPatchesToClients()
+ {
+ lock (m_perClientPatchUpdates)
+ {
+ foreach (PatchUpdates pups in m_perClientPatchUpdates.Values)
+ {
+ if (pups.HasUpdates())
+ {
+ // There is something that could be sent to this client.
+ List toSend = GetModifiedPatchesInViewDistance(pups);
+ if (toSend.Count > 0)
+ {
+ // m_log.DebugFormat("{0} CheckSendingPatchesToClient: sending {1} patches to {2} in region {3}",
+ // LogHeader, toSend.Count, pups.Presence.Name, m_scene.RegionInfo.RegionName);
+ // Sort the patches to send by the distance from the presence
+ toSend.Sort();
+ /*
+ foreach (PatchesToSend pts in toSend)
+ {
+ pups.Presence.ControllingClient.SendLayerData(pts.PatchX, pts.PatchY, null);
+ // presence.ControllingClient.SendLayerData(xs.ToArray(), ys.ToArray(), null, TerrainPatch.LayerType.Land);
+ }
+ */
+
+ int[] xPieces = new int[toSend.Count];
+ int[] yPieces = new int[toSend.Count];
+ float[] patchPieces = new float[toSend.Count * 2];
+ int pieceIndex = 0;
+ foreach (PatchesToSend pts in toSend)
+ {
+ patchPieces[pieceIndex++] = pts.PatchX;
+ patchPieces[pieceIndex++] = pts.PatchY;
+ }
+ pups.Presence.ControllingClient.SendLayerData(-toSend.Count, 0, patchPieces);
+ }
+ }
+ }
+ }
+ }
+
+ private List GetModifiedPatchesInViewDistance(PatchUpdates pups)
+ {
+ List ret = new List();
+
+ ScenePresence presence = pups.Presence;
+ if (presence == null)
+ return ret;
+
+ // Compute the area of patches within our draw distance
+ int startX = (((int) (presence.AbsolutePosition.X - presence.DrawDistance))/Constants.TerrainPatchSize) - 2;
+ startX = Math.Max(startX, 0);
+ startX = Math.Min(startX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize);
+ int startY = (((int) (presence.AbsolutePosition.Y - presence.DrawDistance))/Constants.TerrainPatchSize) - 2;
+ startY = Math.Max(startY, 0);
+ startY = Math.Min(startY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize);
+ int endX = (((int) (presence.AbsolutePosition.X + presence.DrawDistance))/Constants.TerrainPatchSize) + 2;
+ endX = Math.Max(endX, 0);
+ endX = Math.Min(endX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize);
+ int endY = (((int) (presence.AbsolutePosition.Y + presence.DrawDistance))/Constants.TerrainPatchSize) + 2;
+ endY = Math.Max(endY, 0);
+ endY = Math.Min(endY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize);
+ // m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. rName={1}, ddist={2}, apos={3}, start=<{4},{5}>, end=<{6},{7}>",
+ // LogHeader, m_scene.RegionInfo.RegionName,
+ // presence.DrawDistance, presence.AbsolutePosition,
+ // startX, startY, endX, endY);
+ for (int x = startX; x < endX; x++)
+ {
+ for (int y = startY; y < endY; y++)
+ {
+ //Need to make sure we don't send the same ones over and over
+ Vector3 presencePos = presence.AbsolutePosition;
+ Vector3 patchPos = new Vector3(x * Constants.TerrainPatchSize, y * Constants.TerrainPatchSize, presencePos.Z);
+ if (pups.GetByPatch(x, y))
+ {
+ //Check which has less distance, camera or avatar position, both have to be done.
+ //Its not a radius, its a diameter and we add 50 so that it doesn't look like it cuts off
+ if (Util.DistanceLessThan(presencePos, patchPos, presence.DrawDistance + 50)
+ || Util.DistanceLessThan(presence.CameraPosition, patchPos, presence.DrawDistance + 50))
+ {
+ //They can see it, send it to them
+ pups.SetByPatch(x, y, false);
+ float dist = Vector3.DistanceSquared(presencePos, patchPos);
+ ret.Add(new PatchesToSend(x, y, dist));
+ //Wait and send them all at once
+ // pups.client.SendLayerData(x, y, null);
+ }
+ }
+ }
+ }
+ return ret;
}
private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action,
@@ -846,7 +1147,9 @@ namespace OpenSim.Region.CoreModules.World.Terrain
m_painteffects[(StandardTerrainEffects) action].PaintEffect(
m_channel, allowMask, west, south, height, size, seconds);
- CheckForTerrainUpdates(!god); //revert changes outside estate limits
+ //revert changes outside estate limits
+ if (!god)
+ EnforceEstateLimits();
}
}
else
@@ -884,10 +1187,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain
if (allowed)
{
StoreUndoState();
- m_floodeffects[(StandardTerrainEffects) action].FloodEffect(
- m_channel, fillArea, size);
+ m_floodeffects[(StandardTerrainEffects) action].FloodEffect(m_channel, fillArea, size);
- CheckForTerrainUpdates(!god); //revert changes outside estate limits
+ //revert changes outside estate limits
+ if (!god)
+ EnforceEstateLimits();
}
}
else
@@ -911,7 +1215,9 @@ namespace OpenSim.Region.CoreModules.World.Terrain
protected void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY)
{
//m_log.Debug("Terrain packet unacked, resending patch: " + patchX + " , " + patchY);
- client.SendLayerData(patchX, patchY, m_scene.Heightmap.GetFloatsSerialised());
+ // SendLayerData does not use the heightmap parameter. This kludge is so as to not change IClientAPI.
+ float[] heightMap = new float[10];
+ client.SendLayerData(patchX, patchY, heightMap);
}
private void StoreUndoState()
@@ -938,7 +1244,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private void InterfaceLoadFile(Object[] args)
{
LoadFromFile((string) args[0]);
- CheckForTerrainUpdates();
}
private void InterfaceLoadTileFile(Object[] args)
@@ -948,7 +1253,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
(int) args[2],
(int) args[3],
(int) args[4]);
- CheckForTerrainUpdates();
}
private void InterfaceSaveFile(Object[] args)
@@ -977,7 +1281,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] = m_revert[x, y];
- CheckForTerrainUpdates();
}
private void InterfaceFlipTerrain(Object[] args)
@@ -986,28 +1289,28 @@ namespace OpenSim.Region.CoreModules.World.Terrain
if (direction.ToLower().StartsWith("y"))
{
- for (int x = 0; x < Constants.RegionSize; x++)
+ for (int x = 0; x < m_channel.Width; x++)
{
- for (int y = 0; y < Constants.RegionSize / 2; y++)
+ for (int y = 0; y < m_channel.Height / 2; y++)
{
double height = m_channel[x, y];
- double flippedHeight = m_channel[x, (int)Constants.RegionSize - 1 - y];
+ double flippedHeight = m_channel[x, (int)m_channel.Height - 1 - y];
m_channel[x, y] = flippedHeight;
- m_channel[x, (int)Constants.RegionSize - 1 - y] = height;
+ m_channel[x, (int)m_channel.Height - 1 - y] = height;
}
}
}
else if (direction.ToLower().StartsWith("x"))
{
- for (int y = 0; y < Constants.RegionSize; y++)
+ for (int y = 0; y < m_channel.Height; y++)
{
- for (int x = 0; x < Constants.RegionSize / 2; x++)
+ for (int x = 0; x < m_channel.Width / 2; x++)
{
double height = m_channel[x, y];
- double flippedHeight = m_channel[(int)Constants.RegionSize - 1 - x, y];
+ double flippedHeight = m_channel[(int)m_channel.Width - 1 - x, y];
m_channel[x, y] = flippedHeight;
- m_channel[(int)Constants.RegionSize - 1 - x, y] = height;
+ m_channel[(int)m_channel.Width - 1 - x, y] = height;
}
}
@@ -1016,9 +1319,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
m_log.Error("Unrecognised direction - need x or y");
}
-
-
- CheckForTerrainUpdates();
}
private void InterfaceRescaleTerrain(Object[] args)
@@ -1076,7 +1376,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
}
}
- CheckForTerrainUpdates();
}
}
@@ -1087,7 +1386,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
for (x = 0; x < m_channel.Width; x++)
for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] += (double) args[0];
- CheckForTerrainUpdates();
}
private void InterfaceMultiplyTerrain(Object[] args)
@@ -1096,7 +1394,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
for (x = 0; x < m_channel.Width; x++)
for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] *= (double) args[0];
- CheckForTerrainUpdates();
}
private void InterfaceLowerTerrain(Object[] args)
@@ -1105,17 +1402,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain
for (x = 0; x < m_channel.Width; x++)
for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] -= (double) args[0];
- CheckForTerrainUpdates();
}
- private void InterfaceFillTerrain(Object[] args)
+ public void InterfaceFillTerrain(Object[] args)
{
int x, y;
for (x = 0; x < m_channel.Width; x++)
for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] = (double) args[0];
- CheckForTerrainUpdates();
}
private void InterfaceMinTerrain(Object[] args)
@@ -1128,7 +1423,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
m_channel[x, y] = Math.Max((double)args[0], m_channel[x, y]);
}
}
- CheckForTerrainUpdates();
}
private void InterfaceMaxTerrain(Object[] args)
@@ -1141,7 +1435,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
m_channel[x, y] = Math.Min((double)args[0], m_channel[x, y]);
}
}
- CheckForTerrainUpdates();
}
private void InterfaceShowDebugStats(Object[] args)
@@ -1204,7 +1497,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
if (m_plugineffects.ContainsKey(firstArg))
{
m_plugineffects[firstArg].RunEffect(m_channel);
- CheckForTerrainUpdates();
}
else
{
diff --git a/OpenSim/Region/Framework/Interfaces/ISimulationDataService.cs b/OpenSim/Region/Framework/Interfaces/ISimulationDataService.cs
index 3e97a7ae3d..13358cb360 100644
--- a/OpenSim/Region/Framework/Interfaces/ISimulationDataService.cs
+++ b/OpenSim/Region/Framework/Interfaces/ISimulationDataService.cs
@@ -68,13 +68,22 @@ namespace OpenSim.Region.Framework.Interfaces
///
/// HeightField data
/// region UUID
+ void StoreTerrain(TerrainData terrain, UUID regionID);
+
+ // Legacy version kept for downward compabibility
void StoreTerrain(double[,] terrain, UUID regionID);
///
/// Load the latest terrain revision from region storage
///
/// the region UUID
+ /// the X dimension of the region being filled
+ /// the Y dimension of the region being filled
+ /// the Z dimension of the region being filled
/// Heightfield data
+ TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ);
+
+ // Legacy version kept for downward compabibility
double[,] LoadTerrain(UUID regionID);
void StoreLandObject(ILandObject Parcel);
diff --git a/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs b/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs
index 17bd48be67..e09f775642 100644
--- a/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs
+++ b/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs
@@ -79,13 +79,22 @@ namespace OpenSim.Region.Framework.Interfaces
///
/// HeightField data
/// region UUID
+ void StoreTerrain(TerrainData terrain, UUID regionID);
+
+ // Legacy version kept for downward compabibility
void StoreTerrain(double[,] terrain, UUID regionID);
///
/// Load the latest terrain revision from region storage
///
/// the region UUID
+ /// the X dimension of the terrain being filled
+ /// the Y dimension of the terrain being filled
+ /// the Z dimension of the terrain being filled
/// Heightfield data
+ TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ);
+
+ // Legacy version kept for downward compabibility
double[,] LoadTerrain(UUID regionID);
void StoreLandObject(ILandObject Parcel);
@@ -136,4 +145,5 @@ namespace OpenSim.Region.Framework.Interfaces
void Shutdown();
}
+
}
diff --git a/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs b/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs
index e467701351..f660b8dbe0 100644
--- a/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs
+++ b/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs
@@ -25,13 +25,23 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+using OpenSim.Framework;
+using OpenMetaverse;
+
namespace OpenSim.Region.Framework.Interfaces
{
public interface ITerrainChannel
{
- int Height { get; }
+ int Width { get;} // X dimension
+ int Height { get;} // Y dimension
+ int Altitude { get;} // Z dimension
+
double this[int x, int y] { get; set; }
- int Width { get; }
+
+ float GetHeightAtXYZ(float x, float y, float z);
+
+ // Return the packaged terrain data for passing into lower levels of communication
+ TerrainData GetTerrainData();
///
/// Squash the entire heightmap into a single dimensioned array
@@ -40,9 +50,14 @@ namespace OpenSim.Region.Framework.Interfaces
float[] GetFloatsSerialised();
double[,] GetDoubles();
+
+ // Check if a location has been updated. Clears the taint flag as a side effect.
bool Tainted(int x, int y);
+
ITerrainChannel MakeCopy();
string SaveToXmlString();
void LoadFromXmlString(string data);
+ // Merge some terrain into this channel
+ void Merge(ITerrainChannel newTerrain, Vector3 displacement, float radianRotation, Vector2 rotationDisplacement);
}
}
diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs
index 03270d7db0..f5458c1292 100644
--- a/OpenSim/Region/Framework/Scenes/Scene.cs
+++ b/OpenSim/Region/Framework/Scenes/Scene.cs
@@ -1935,7 +1935,7 @@ namespace OpenSim.Region.Framework.Scenes
{
try
{
- double[,] map = SimulationDataService.LoadTerrain(RegionInfo.RegionID);
+ TerrainData map = SimulationDataService.LoadTerrain(RegionInfo.RegionID, (int)RegionInfo.RegionSizeX, (int)RegionInfo.RegionSizeY, (int)RegionInfo.RegionSizeZ);
if (map == null)
{
// This should be in the Terrain module, but it isn't because
@@ -1946,7 +1946,7 @@ namespace OpenSim.Region.Framework.Scenes
m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain);
m_log.InfoFormat("[TERRAIN]: No default terrain. Generating a new terrain {0}.", m_InitialTerrain);
- Heightmap = new TerrainChannel(m_InitialTerrain);
+ Heightmap = new TerrainChannel(m_InitialTerrain, (int)RegionInfo.RegionSizeX, (int)RegionInfo.RegionSizeY, (int)RegionInfo.RegionSizeZ);
SimulationDataService.StoreTerrain(Heightmap.GetDoubles(), RegionInfo.RegionID);
}
diff --git a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs
index b6e0a97fce..3d563a6523 100644
--- a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs
+++ b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs
@@ -25,13 +25,20 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+using System;
+using System.IO;
+using System.Text;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Serialization;
+
+using OpenSim.Data;
using OpenSim.Framework;
using OpenSim.Region.Framework.Interfaces;
-using System;
-using System.Text;
-using System.Xml;
-using System.IO;
-using System.Xml.Serialization;
+
+using OpenMetaverse;
+
+using log4net;
namespace OpenSim.Region.Framework.Scenes
{
@@ -40,140 +47,136 @@ namespace OpenSim.Region.Framework.Scenes
///
public class TerrainChannel : ITerrainChannel
{
- private readonly bool[,] taint;
- private double[,] map;
+ private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+ private static string LogHeader = "[TERRAIN CHANNEL]";
+ protected TerrainData m_terrainData;
+
+ public int Width { get { return m_terrainData.SizeX; } } // X dimension
+ // Unfortunately, for historical reasons, in this module 'Width' is X and 'Height' is Y
+ public int Height { get { return m_terrainData.SizeY; } } // Y dimension
+ public int Altitude { get { return m_terrainData.SizeZ; } } // Y dimension
+
+ // Default, not-often-used builder
public TerrainChannel()
{
- map = new double[Constants.RegionSize, Constants.RegionSize];
- taint = new bool[Constants.RegionSize / 16, Constants.RegionSize / 16];
-
- PinHeadIsland();
+ m_terrainData = new HeightmapTerrainData((int)Constants.RegionSize, (int)Constants.RegionSize, (int)Constants.RegionHeight);
+ FlatLand();
+ // PinHeadIsland();
}
- public TerrainChannel(String type)
+ // Create terrain of given size
+ public TerrainChannel(int pX, int pY)
{
- map = new double[Constants.RegionSize, Constants.RegionSize];
- taint = new bool[Constants.RegionSize / 16, Constants.RegionSize / 16];
+ m_terrainData = new HeightmapTerrainData(pX, pY, (int)Constants.RegionHeight);
+ }
+ // Create terrain of specified size and initialize with specified terrain.
+ // TODO: join this with the terrain initializers.
+ public TerrainChannel(String type, int pX, int pY, int pZ)
+ {
+ m_terrainData = new HeightmapTerrainData(pX, pY, pZ);
if (type.Equals("flat"))
FlatLand();
else
PinHeadIsland();
}
- public TerrainChannel(double[,] import)
+ // Create channel passed a heightmap and expected dimensions of the region.
+ // The heightmap might not fit the passed size so accomodations must be made.
+ public TerrainChannel(double[,] pM, int pSizeX, int pSizeY, int pAltitude)
{
- map = import;
- taint = new bool[import.GetLength(0),import.GetLength(1)];
+ int hmSizeX = pM.GetLength(0);
+ int hmSizeY = pM.GetLength(1);
+
+ m_terrainData = new HeightmapTerrainData(pSizeX, pSizeY, pAltitude);
+
+ for (int xx = 0; xx < pSizeX; xx++)
+ for (int yy = 0; yy < pSizeY; yy++)
+ if (xx > hmSizeX || yy > hmSizeY)
+ m_terrainData[xx, yy] = TerrainData.DefaultTerrainHeight;
+ else
+ m_terrainData[xx, yy] = (float)pM[xx, yy];
}
- public TerrainChannel(bool createMap)
+ public TerrainChannel(TerrainData pTerrData)
{
- if (createMap)
- {
- map = new double[Constants.RegionSize,Constants.RegionSize];
- taint = new bool[Constants.RegionSize / 16,Constants.RegionSize / 16];
- }
- }
-
- public TerrainChannel(int w, int h)
- {
- map = new double[w,h];
- taint = new bool[w / 16,h / 16];
+ m_terrainData = pTerrData;
}
#region ITerrainChannel Members
- public int Width
- {
- get { return map.GetLength(0); }
- }
-
- public int Height
- {
- get { return map.GetLength(1); }
- }
-
+ // ITerrainChannel.MakeCopy()
public ITerrainChannel MakeCopy()
{
- TerrainChannel copy = new TerrainChannel(false);
- copy.map = (double[,]) map.Clone();
-
- return copy;
+ return this.Copy();
}
+ // ITerrainChannel.GetTerrainData()
+ public TerrainData GetTerrainData()
+ {
+ return m_terrainData;
+ }
+
+ // ITerrainChannel.GetFloatsSerialized()
+ // This one dimensional version is ordered so height = map[y*sizeX+x];
+ // DEPRECATED: don't use this function as it does not retain the dimensions of the terrain
+ // and the caller will probably do the wrong thing if the terrain is not the legacy 256x256.
public float[] GetFloatsSerialised()
{
- // Move the member variables into local variables, calling
- // member variables 256*256 times gets expensive
- int w = Width;
- int h = Height;
- float[] heights = new float[w * h];
+ return m_terrainData.GetFloatsSerialized();
+ }
+
+ // ITerrainChannel.GetDoubles()
+ public double[,] GetDoubles()
+ {
+ double[,] heights = new double[Width, Height];
- int i, j; // map coordinates
int idx = 0; // index into serialized array
- for (i = 0; i < h; i++)
+ for (int ii = 0; ii < Width; ii++)
{
- for (j = 0; j < w; j++)
+ for (int jj = 0; jj < Height; jj++)
{
- heights[idx++] = (float)map[j, i];
+ heights[ii, jj] = (double)m_terrainData[ii, jj];
+ idx++;
}
}
return heights;
}
- public double[,] GetDoubles()
- {
- return map;
- }
-
+ // ITerrainChannel.this[x,y]
public double this[int x, int y]
{
- get
- {
- if (x < 0) x = 0;
- if (y < 0) y = 0;
- if (x >= (int)Constants.RegionSize) x = (int)Constants.RegionSize - 1;
- if (y >= (int)Constants.RegionSize) y = (int)Constants.RegionSize - 1;
-
- return map[x, y];
+ get {
+ if (x < 0 || x >= Width || y < 0 || y >= Height)
+ return 0;
+ return (double)m_terrainData[x, y];
}
set
{
- // Will "fix" terrain hole problems. Although not fantastically.
if (Double.IsNaN(value) || Double.IsInfinity(value))
return;
- if (map[x, y] != value)
- {
- taint[x / 16, y / 16] = true;
- map[x, y] = value;
- }
+ m_terrainData[x, y] = (float)value;
}
}
+ // ITerrainChannel.GetHieghtAtXYZ(x, y, z)
+ public float GetHeightAtXYZ(float x, float y, float z)
+ {
+ if (x < 0 || x >= Width || y < 0 || y >= Height)
+ return 0;
+ return m_terrainData[(int)x, (int)y];
+ }
+
+ // ITerrainChannel.Tainted()
public bool Tainted(int x, int y)
{
- if (taint[x / 16, y / 16])
- {
- taint[x / 16, y / 16] = false;
- return true;
- }
- return false;
- }
-
- #endregion
-
- public TerrainChannel Copy()
- {
- TerrainChannel copy = new TerrainChannel(false);
- copy.map = (double[,]) map.Clone();
-
- return copy;
+ return m_terrainData.IsTaintedAt(x, y);
}
+ // ITerrainChannel.SaveToXmlString()
public string SaveToXmlString()
{
XmlWriterSettings settings = new XmlWriterSettings();
@@ -189,13 +192,7 @@ namespace OpenSim.Region.Framework.Scenes
}
}
- private void WriteXml(XmlWriter writer)
- {
- writer.WriteStartElement(String.Empty, "TerrainMap", String.Empty);
- ToXml(writer);
- writer.WriteEndElement();
- }
-
+ // ITerrainChannel.LoadFromXmlString()
public void LoadFromXmlString(string data)
{
StringReader sr = new StringReader(data);
@@ -207,12 +204,124 @@ namespace OpenSim.Region.Framework.Scenes
sr.Close();
}
- private void ReadXml(XmlReader reader)
+ // ITerrainChannel.Merge
+ public void Merge(ITerrainChannel newTerrain, Vector3 displacement, float radianRotation, Vector2 rotationDisplacement)
{
- reader.ReadStartElement("TerrainMap");
- FromXml(reader);
+ m_log.DebugFormat("{0} Merge. inSize=<{1},{2}>, disp={3}, rot={4}, rotDisp={5}, outSize=<{6},{7}>", LogHeader,
+ newTerrain.Width, newTerrain.Height,
+ displacement, radianRotation, rotationDisplacement,
+ m_terrainData.SizeX, m_terrainData.SizeY);
+ for (int xx = 0; xx < newTerrain.Width; xx++)
+ {
+ for (int yy = 0; yy < newTerrain.Height; yy++)
+ {
+ int dispX = (int)displacement.X;
+ int dispY = (int)displacement.Y;
+ float newHeight = (float)newTerrain[xx, yy] + displacement.Z;
+ if (radianRotation == 0)
+ {
+ // If no rotation, place the new height in the specified location
+ dispX += xx;
+ dispY += yy;
+ if (dispX >= 0 && dispX < m_terrainData.SizeX && dispY >= 0 && dispY < m_terrainData.SizeY)
+ {
+ m_terrainData[dispX, dispY] = newHeight;
+ }
+ }
+ else
+ {
+ // If rotating, we have to smooth the result because the conversion
+ // to ints will mean heightmap entries will not get changed
+ // First compute the rotation location for the new height.
+ dispX += (int)(rotationDisplacement.X
+ + ((float)xx - rotationDisplacement.X) * Math.Cos(radianRotation)
+ - ((float)yy - rotationDisplacement.Y) * Math.Sin(radianRotation) );
+
+ dispY += (int)(rotationDisplacement.Y
+ + ((float)xx - rotationDisplacement.X) * Math.Sin(radianRotation)
+ + ((float)yy - rotationDisplacement.Y) * Math.Cos(radianRotation) );
+
+ if (dispX >= 0 && dispX < m_terrainData.SizeX && dispY >= 0 && dispY < m_terrainData.SizeY)
+ {
+ float oldHeight = m_terrainData[dispX, dispY];
+ // Smooth the heights around this location if the old height is far from this one
+ for (int sxx = dispX - 2; sxx < dispX + 2; sxx++)
+ {
+ for (int syy = dispY - 2; syy < dispY + 2; syy++)
+ {
+ if (sxx >= 0 && sxx < m_terrainData.SizeX && syy >= 0 && syy < m_terrainData.SizeY)
+ {
+ if (sxx == dispX && syy == dispY)
+ {
+ // Set height for the exact rotated point
+ m_terrainData[dispX, dispY] = newHeight;
+ }
+ else
+ {
+ if (Math.Abs(m_terrainData[sxx, syy] - newHeight) > 1f)
+ {
+ // If the adjacent height is far off, force it to this height
+ m_terrainData[sxx, syy] = newHeight;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (dispX >= 0 && dispX < m_terrainData.SizeX && dispY >= 0 && dispY < m_terrainData.SizeY)
+ {
+ m_terrainData[dispX, dispY] = (float)newTerrain[xx, yy];
+ }
+ }
+ }
+ }
}
+ #endregion
+
+ public TerrainChannel Copy()
+ {
+ TerrainChannel copy = new TerrainChannel();
+ copy.m_terrainData = m_terrainData.Clone();
+ return copy;
+ }
+
+ private void WriteXml(XmlWriter writer)
+ {
+ if (Width == Constants.RegionSize && Height == Constants.RegionSize)
+ {
+ // Downward compatibility for legacy region terrain maps.
+ // If region is exactly legacy size, return the old format XML.
+ writer.WriteStartElement(String.Empty, "TerrainMap", String.Empty);
+ ToXml(writer);
+ writer.WriteEndElement();
+ }
+ else
+ {
+ // New format XML that includes width and length.
+ writer.WriteStartElement(String.Empty, "TerrainMap2", String.Empty);
+ ToXml2(writer);
+ writer.WriteEndElement();
+ }
+ }
+
+ private void ReadXml(XmlReader reader)
+ {
+ // Check the first element. If legacy element, use the legacy reader.
+ if (reader.IsStartElement("TerrainMap"))
+ {
+ reader.ReadStartElement("TerrainMap");
+ FromXml(reader);
+ }
+ else
+ {
+ reader.ReadStartElement("TerrainMap2");
+ FromXml2(reader);
+ }
+ }
+
+ // Write legacy terrain map. Presumed to be 256x256 of data encoded as floats in a byte array.
private void ToXml(XmlWriter xmlWriter)
{
float[] mapData = GetFloatsSerialised();
@@ -226,12 +335,15 @@ namespace OpenSim.Region.Framework.Scenes
serializer.Serialize(xmlWriter, buffer);
}
+ // Read legacy terrain map. Presumed to be 256x256 of data encoded as floats in a byte array.
private void FromXml(XmlReader xmlReader)
{
XmlSerializer serializer = new XmlSerializer(typeof(byte[]));
byte[] dataArray = (byte[])serializer.Deserialize(xmlReader);
int index = 0;
+ m_terrainData = new HeightmapTerrainData(Height, Width, (int)Constants.RegionHeight);
+
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
@@ -244,35 +356,63 @@ namespace OpenSim.Region.Framework.Scenes
}
}
+ private class TerrainChannelXMLPackage
+ {
+ public int Version;
+ public int SizeX;
+ public int SizeY;
+ public int SizeZ;
+ public float CompressionFactor;
+ public int[] Map;
+ public TerrainChannelXMLPackage(int pX, int pY, int pZ, float pCompressionFactor, int[] pMap)
+ {
+ Version = 1;
+ SizeX = pX;
+ SizeY = pY;
+ SizeZ = pZ;
+ CompressionFactor = pCompressionFactor;
+ Map = pMap;
+ }
+ }
+
+ // New terrain serialization format that includes the width and length.
+ private void ToXml2(XmlWriter xmlWriter)
+ {
+ TerrainChannelXMLPackage package = new TerrainChannelXMLPackage(Width, Height, Altitude, m_terrainData.CompressionFactor,
+ m_terrainData.GetCompressedMap());
+ XmlSerializer serializer = new XmlSerializer(typeof(TerrainChannelXMLPackage));
+ serializer.Serialize(xmlWriter, package);
+ }
+
+ // New terrain serialization format that includes the width and length.
+ private void FromXml2(XmlReader xmlReader)
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(TerrainChannelXMLPackage));
+ TerrainChannelXMLPackage package = (TerrainChannelXMLPackage)serializer.Deserialize(xmlReader);
+ m_terrainData = new HeightmapTerrainData(package.Map, package.CompressionFactor, package.SizeX, package.SizeY, package.SizeZ);
+ }
+
+ // Fill the heightmap with the center bump terrain
private void PinHeadIsland()
{
- int x;
- for (x = 0; x < Constants.RegionSize; x++)
+ for (int x = 0; x < Width; x++)
{
- int y;
- for (y = 0; y < Constants.RegionSize; y++)
+ for (int y = 0; y < Height; y++)
{
- map[x, y] = TerrainUtil.PerlinNoise2D(x, y, 2, 0.125) * 10;
- double spherFacA = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 50) * 0.01;
- double spherFacB = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 100) * 0.001;
- if (map[x, y] < spherFacA)
- map[x, y] = spherFacA;
- if (map[x, y] < spherFacB)
- map[x, y] = spherFacB;
+ m_terrainData[x, y] = (float)TerrainUtil.PerlinNoise2D(x, y, 2, 0.125) * 10;
+ float spherFacA = (float)(TerrainUtil.SphericalFactor(x, y, m_terrainData.SizeX / 2.0, m_terrainData.SizeY / 2.0, 50) * 0.01d);
+ float spherFacB = (float)(TerrainUtil.SphericalFactor(x, y, m_terrainData.SizeX / 2.0, m_terrainData.SizeY / 2.0, 100) * 0.001d);
+ if (m_terrainData[x, y]< spherFacA)
+ m_terrainData[x, y]= spherFacA;
+ if (m_terrainData[x, y]< spherFacB)
+ m_terrainData[x, y] = spherFacB;
}
}
}
private void FlatLand()
{
- int x;
- for (x = 0; x < Constants.RegionSize; x++)
- {
- int y;
- for (y = 0; y < Constants.RegionSize; y++)
- map[x, y] = 21;
- }
+ m_terrainData.ClearLand();
}
-
}
}
diff --git a/OpenSim/Services/Connectors/Simulation/SimulationDataService.cs b/OpenSim/Services/Connectors/Simulation/SimulationDataService.cs
index 96c02d90fc..475983816c 100644
--- a/OpenSim/Services/Connectors/Simulation/SimulationDataService.cs
+++ b/OpenSim/Services/Connectors/Simulation/SimulationDataService.cs
@@ -100,6 +100,11 @@ namespace OpenSim.Services.Connectors
return m_database.LoadObjects(regionUUID);
}
+ public void StoreTerrain(TerrainData terrain, UUID regionID)
+ {
+ m_database.StoreTerrain(terrain, regionID);
+ }
+
public void StoreTerrain(double[,] terrain, UUID regionID)
{
m_database.StoreTerrain(terrain, regionID);
@@ -110,6 +115,11 @@ namespace OpenSim.Services.Connectors
return m_database.LoadTerrain(regionID);
}
+ public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ)
+ {
+ return m_database.LoadTerrain(regionID, pSizeX, pSizeY, pSizeZ);
+ }
+
public void StoreLandObject(ILandObject Parcel)
{
m_database.StoreLandObject(Parcel);
diff --git a/OpenSim/Tests/Common/Mock/MockRegionDataPlugin.cs b/OpenSim/Tests/Common/Mock/MockRegionDataPlugin.cs
index 5c1ec0bebd..3ab90207a5 100644
--- a/OpenSim/Tests/Common/Mock/MockRegionDataPlugin.cs
+++ b/OpenSim/Tests/Common/Mock/MockRegionDataPlugin.cs
@@ -69,11 +69,21 @@ namespace OpenSim.Data.Null
m_store.StoreTerrain(terrain, regionID);
}
+ public void StoreTerrain(TerrainData terrain, UUID regionID)
+ {
+ m_store.StoreTerrain(terrain, regionID);
+ }
+
public double[,] LoadTerrain(UUID regionID)
{
return m_store.LoadTerrain(regionID);
}
+ public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ)
+ {
+ return m_store.LoadTerrain(regionID, pSizeX, pSizeY, pSizeZ);
+ }
+
public void StoreLandObject(ILandObject Parcel)
{
m_store.StoreLandObject(Parcel);
@@ -159,7 +169,7 @@ namespace OpenSim.Data.Null
protected Dictionary m_sceneObjectParts = new Dictionary();
protected Dictionary> m_primItems
= new Dictionary>();
- protected Dictionary m_terrains = new Dictionary();
+ protected Dictionary m_terrains = new Dictionary();
protected Dictionary m_landData = new Dictionary();
public void Initialise(string dbfile)
@@ -304,15 +314,28 @@ namespace OpenSim.Data.Null
return new List(objects.Values);
}
- public void StoreTerrain(double[,] ter, UUID regionID)
+ public void StoreTerrain(TerrainData ter, UUID regionID)
{
m_terrains[regionID] = ter;
}
+ public void StoreTerrain(double[,] ter, UUID regionID)
+ {
+ m_terrains[regionID] = new HeightmapTerrainData(ter);
+ }
+
+ public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ)
+ {
+ if (m_terrains.ContainsKey(regionID))
+ return m_terrains[regionID];
+ else
+ return null;
+ }
+
public double[,] LoadTerrain(UUID regionID)
{
if (m_terrains.ContainsKey(regionID))
- return m_terrains[regionID];
+ return m_terrains[regionID].GetDoubles();
else
return null;
}