varregion: refactor use of 'double heightmap[,]' into references to new class TerrainData

and push the implementation from Scene into the database readers and writers.
avinationmerge
Robert Adams 2015-03-27 19:32:50 -07:00
parent d74d74c910
commit bedafb8fae
15 changed files with 1373 additions and 339 deletions

View File

@ -530,43 +530,52 @@ ELSE
/// <returns></returns> /// <returns></returns>
public double[,] LoadTerrain(UUID regionID) public double[,] LoadTerrain(UUID regionID)
{ {
double[,] terrain = new double[(int)Constants.RegionSize, (int)Constants.RegionSize]; double[,] ret = null;
terrain.Initialize(); 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"; 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 (SqlConnection conn = new SqlConnection(m_connectionString))
using (SqlCommand cmd = new SqlCommand(sql, conn))
{ {
// MySqlParameter param = new MySqlParameter(); using (SqlCommand cmd = new SqlCommand(sql, conn))
cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID));
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{ {
int rev; // MySqlParameter param = new MySqlParameter();
if (reader.Read()) cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID));
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{ {
MemoryStream str = new MemoryStream((byte[])reader["Heightfield"]); if (reader.Read())
BinaryReader br = new BinaryReader(str);
for (int x = 0; x < (int)Constants.RegionSize; x++)
{ {
for (int y = 0; y < (int)Constants.RegionSize; y++) int rev = (int)reader["Revision"];
{ byte[] blob = (byte[])reader["Heightfield"];
terrain[x, y] = br.ReadDouble(); 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);
} }
/// <summary> /// <summary>
@ -574,10 +583,8 @@ ELSE
/// </summary> /// </summary>
/// <param name="terrain">terrain map data.</param> /// <param name="terrain">terrain map data.</param>
/// <param name="regionID">regionID.</param> /// <param name="regionID">regionID.</param>
public void StoreTerrain(double[,] terrain, UUID regionID) public void StoreTerrain(TerrainData terrData, UUID regionID)
{ {
int revision = Util.UnixTimeSinceEpoch();
//Delete old terrain map //Delete old terrain map
string sql = "delete from terrain where RegionUUID=@RegionUUID"; string sql = "delete from terrain where RegionUUID=@RegionUUID";
using (SqlConnection conn = new SqlConnection(m_connectionString)) using (SqlConnection conn = new SqlConnection(m_connectionString))
@ -590,17 +597,23 @@ ELSE
sql = "insert into terrain(RegionUUID, Revision, Heightfield) values(@RegionUUID, @Revision, @Heightfield)"; 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 (SqlConnection conn = new SqlConnection(m_connectionString))
using (SqlCommand cmd = new SqlCommand(sql, conn))
{ {
cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID)); using (SqlCommand cmd = new SqlCommand(sql, conn))
cmd.Parameters.Add(_Database.CreateParameter("@Revision", revision)); {
cmd.Parameters.Add(_Database.CreateParameter("@Heightfield", serializeTerrain(terrain))); cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID));
conn.Open(); cmd.Parameters.Add(_Database.CreateParameter("@Revision", terrainDBRevision));
cmd.ExecuteNonQuery(); 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");
} }
/// <summary> /// <summary>
@ -1344,6 +1357,7 @@ VALUES
#region Private Methods #region Private Methods
/*
/// <summary> /// <summary>
/// Serializes the terrain data for storage in DB. /// Serializes the terrain data for storage in DB.
/// </summary> /// </summary>
@ -1367,6 +1381,7 @@ VALUES
return str.ToArray(); return str.ToArray();
} }
*/
/// <summary> /// <summary>
/// Stores new regionsettings. /// Stores new regionsettings.

View File

@ -48,8 +48,18 @@ namespace OpenSim.Data.MySQL
public class MySQLSimulationData : ISimulationDataStore public class MySQLSimulationData : ISimulationDataStore
{ {
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static string LogHeader = "[REGION DB MYSQL]";
private string m_connectionString; private string m_connectionString;
/// <summary>
/// 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.
/// </summary>
private object m_dbLock = new object(); private object m_dbLock = new object();
protected virtual Assembly Assembly protected virtual Assembly Assembly
@ -91,7 +101,7 @@ namespace OpenSim.Data.MySQL
} }
catch (Exception e) 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; 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) Util.FireAndForget(delegate(object x)
{ {
double[,] oldTerrain = LoadTerrain(regionID);
m_log.Info("[REGION DB]: Storing terrain"); m_log.Info("[REGION DB]: Storing terrain");
lock (m_dbLock) lock (m_dbLock)
@ -601,8 +615,12 @@ namespace OpenSim.Data.MySQL
"Revision, Heightfield) values (?RegionUUID, " + "Revision, Heightfield) values (?RegionUUID, " +
"1, ?Heightfield)"; "1, ?Heightfield)";
cmd2.Parameters.AddWithValue("RegionUUID", regionID.ToString()); int terrainDBRevision;
cmd2.Parameters.AddWithValue("Heightfield", SerializeTerrain(ter, oldTerrain)); Array terrainDBblob;
terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob);
cmd2.Parameters.AddWithValue("Revision", terrainDBRevision);
cmd2.Parameters.AddWithValue("Heightfield", terrainDBblob);
ExecuteNonQuery(cmd); ExecuteNonQuery(cmd);
ExecuteNonQuery(cmd2); ExecuteNonQuery(cmd2);
@ -618,9 +636,20 @@ namespace OpenSim.Data.MySQL
}); });
} }
// Legacy region loading
public virtual double[,] LoadTerrain(UUID regionID) 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) lock (m_dbLock)
{ {
@ -640,32 +669,15 @@ namespace OpenSim.Data.MySQL
while (reader.Read()) while (reader.Read())
{ {
int rev = Convert.ToInt32(reader["Revision"]); int rev = Convert.ToInt32(reader["Revision"]);
byte[] blob = (byte[])reader["Heightfield"];
terrain = new double[(int)Constants.RegionSize, (int)Constants.RegionSize]; terrData = TerrainData.CreateFromDatabaseBlobFactory(pSizeX, pSizeY, pSizeZ, rev, blob);
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);
}
} }
} }
} }
} }
} }
return terrain; return terrData;
} }
public virtual void RemoveLandObject(UUID globalID) public virtual void RemoveLandObject(UUID globalID)

View File

@ -132,15 +132,33 @@ namespace OpenSim.Data.Null
return new List<SceneObjectGroup>(); return new List<SceneObjectGroup>();
} }
Dictionary<UUID, double[,]> m_terrains = new Dictionary<UUID, double[,]>(); Dictionary<UUID, TerrainData> m_terrains = new Dictionary<UUID, TerrainData>();
public void StoreTerrain(double[,] ter, UUID regionID) public void StoreTerrain(TerrainData ter, UUID regionID)
{ {
if (m_terrains.ContainsKey(regionID)) if (m_terrains.ContainsKey(regionID))
m_terrains.Remove(regionID); m_terrains.Remove(regionID);
m_terrains.Add(regionID, ter); 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) 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)) if (m_terrains.ContainsKey(regionID))
{ {

View File

@ -46,6 +46,7 @@ namespace OpenSim.Data.PGSQL
public class PGSQLSimulationData : ISimulationDataStore public class PGSQLSimulationData : ISimulationDataStore
{ {
private const string _migrationStore = "RegionStore"; private const string _migrationStore = "RegionStore";
private const string LogHeader = "[REGION DB PGSQL]";
// private static FileSystemDataStore Instance = new FileSystemDataStore(); // private static FileSystemDataStore Instance = new FileSystemDataStore();
private static readonly ILog _Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static readonly ILog _Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
@ -523,8 +524,17 @@ namespace OpenSim.Data.PGSQL
/// <returns></returns> /// <returns></returns>
public double[,] LoadTerrain(UUID regionID) public double[,] LoadTerrain(UUID regionID)
{ {
double[,] terrain = new double[(int)Constants.RegionSize, (int)Constants.RegionSize]; double[,] ret = null;
terrain.Initialize(); 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 string sql = @"select ""RegionUUID"", ""Revision"", ""Heightfield"" from terrain
where ""RegionUUID"" = :RegionUUID order by ""Revision"" desc limit 1; "; where ""RegionUUID"" = :RegionUUID order by ""Revision"" desc limit 1; ";
@ -540,16 +550,9 @@ namespace OpenSim.Data.PGSQL
int rev; int rev;
if (reader.Read()) if (reader.Read())
{ {
MemoryStream str = new MemoryStream((byte[])reader["Heightfield"]); rev = Convert.ToInt32(reader["Revision"]);
BinaryReader br = new BinaryReader(str); byte[] blob = (byte[])reader["Heightfield"];
for (int x = 0; x < (int)Constants.RegionSize; x++) terrData = TerrainData.CreateFromDatabaseBlobFactory(pSizeX, pSizeY, pSizeZ, rev, blob);
{
for (int y = 0; y < (int)Constants.RegionSize; y++)
{
terrain[x, y] = br.ReadDouble();
}
}
rev = (int)reader["Revision"];
} }
else 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);
} }
/// <summary> /// <summary>
@ -568,35 +577,38 @@ namespace OpenSim.Data.PGSQL
/// </summary> /// </summary>
/// <param name="terrain">terrain map data.</param> /// <param name="terrain">terrain map data.</param>
/// <param name="regionID">regionID.</param> /// <param name="regionID">regionID.</param>
public void StoreTerrain(double[,] terrain, UUID regionID) public void StoreTerrain(TerrainData terrData, UUID regionID)
{ {
int revision = Util.UnixTimeSinceEpoch();
//Delete old terrain map //Delete old terrain map
string sql = @"delete from terrain where ""RegionUUID""=:RegionUUID"; string sql = @"delete from terrain where ""RegionUUID""=:RegionUUID";
using (NpgsqlConnection conn = new NpgsqlConnection(m_connectionString)) using (NpgsqlConnection conn = new NpgsqlConnection(m_connectionString))
using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
{ {
cmd.Parameters.Add(_Database.CreateParameter("RegionUUID", regionID)); using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
conn.Open(); {
cmd.ExecuteNonQuery(); cmd.Parameters.Add(_Database.CreateParameter("RegionUUID", regionID));
conn.Open();
cmd.ExecuteNonQuery();
}
} }
int terrainDBRevision;
_Log.Info("[REGION DB]: Deleted terrain revision r " + revision); Array terrainDBblob;
terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob);
sql = @"insert into terrain(""RegionUUID"", ""Revision"", ""Heightfield"") values(:RegionUUID, :Revision, :Heightfield)"; sql = @"insert into terrain(""RegionUUID"", ""Revision"", ""Heightfield"") values(:RegionUUID, :Revision, :Heightfield)";
using (NpgsqlConnection conn = new NpgsqlConnection(m_connectionString)) using (NpgsqlConnection conn = new NpgsqlConnection(m_connectionString))
using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
{ {
cmd.Parameters.Add(_Database.CreateParameter("RegionUUID", regionID)); using (NpgsqlCommand cmd = new NpgsqlCommand(sql, conn))
cmd.Parameters.Add(_Database.CreateParameter("Revision", revision)); {
cmd.Parameters.Add(_Database.CreateParameter("Heightfield", serializeTerrain(terrain))); cmd.Parameters.Add(_Database.CreateParameter("RegionUUID", regionID));
conn.Open(); cmd.Parameters.Add(_Database.CreateParameter("Revision", terrainDBRevision));
cmd.ExecuteNonQuery(); 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);
} }
/// <summary> /// <summary>
@ -1349,6 +1361,7 @@ namespace OpenSim.Data.PGSQL
#region Private Methods #region Private Methods
/*
/// <summary> /// <summary>
/// Serializes the terrain data for storage in DB. /// Serializes the terrain data for storage in DB.
/// </summary> /// </summary>
@ -1372,6 +1385,7 @@ namespace OpenSim.Data.PGSQL
return str.ToArray(); return str.ToArray();
} }
*/
/// <summary> /// <summary>
/// Stores new regionsettings. /// Stores new regionsettings.

View File

@ -51,6 +51,7 @@ namespace OpenSim.Data.SQLite
public class SQLiteSimulationData : ISimulationDataStore public class SQLiteSimulationData : ISimulationDataStore
{ {
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 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 primSelect = "select * from prims";
private const string shapeSelect = "select * from primshapes"; private const string shapeSelect = "select * from primshapes";
@ -819,12 +820,18 @@ namespace OpenSim.Data.SQLite
prim.Inventory.RestoreInventoryItems(inventory); 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);
}
/// <summary> /// <summary>
/// Store a terrain revision in region storage /// Store a terrain revision in region storage
/// </summary> /// </summary>
/// <param name="ter">terrain heightfield</param> /// <param name="ter">terrain heightfield</param>
/// <param name="regionID">region UUID</param> /// <param name="regionID">region UUID</param>
public void StoreTerrain(double[,] ter, UUID regionID) public void StoreTerrain(TerrainData terrData, UUID regionID)
{ {
lock (ds) lock (ds)
{ {
@ -853,11 +860,17 @@ namespace OpenSim.Data.SQLite
String sql = "insert into terrain(RegionUUID, Revision, Heightfield)" + String sql = "insert into terrain(RegionUUID, Revision, Heightfield)" +
" values(: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)) using (SqliteCommand cmd = new SqliteCommand(sql, m_conn))
{ {
cmd.Parameters.Add(new SqliteParameter(":RegionUUID", regionID.ToString())); cmd.Parameters.Add(new SqliteParameter(":RegionUUID", regionID.ToString()));
cmd.Parameters.Add(new SqliteParameter(":Revision", revision)); cmd.Parameters.Add(new SqliteParameter(":Revision", terrainDBRevision));
cmd.Parameters.Add(new SqliteParameter(":Heightfield", serializeTerrain(ter))); cmd.Parameters.Add(new SqliteParameter(":Heightfield", terrainDBblob));
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }
} }
@ -870,11 +883,20 @@ namespace OpenSim.Data.SQLite
/// <returns>Heightfield data</returns> /// <returns>Heightfield data</returns>
public double[,] LoadTerrain(UUID regionID) 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) lock (ds)
{ {
double[,] terret = new double[(int)Constants.RegionSize, (int)Constants.RegionSize];
terret.Initialize();
String sql = "select RegionUUID, Revision, Heightfield from terrain" + String sql = "select RegionUUID, Revision, Heightfield from terrain" +
" where RegionUUID=:RegionUUID order by Revision desc"; " where RegionUUID=:RegionUUID order by Revision desc";
@ -887,21 +909,9 @@ namespace OpenSim.Data.SQLite
int rev = 0; int rev = 0;
if (row.Read()) 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"]); rev = Convert.ToInt32(row["Revision"]);
byte[] blob = (byte[])row["Heightfield"];
terrData = TerrainData.CreateFromDatabaseBlobFactory(pSizeX, pSizeY, pSizeZ, rev, blob);
} }
else else
{ {
@ -912,8 +922,8 @@ namespace OpenSim.Data.SQLite
m_log.Debug("[SQLITE REGION DB]: Loaded terrain revision r" + rev.ToString()); m_log.Debug("[SQLITE REGION DB]: Loaded terrain revision r" + rev.ToString());
} }
} }
return terret;
} }
return terrData;
} }
public void RemoveLandObject(UUID globalID) public void RemoveLandObject(UUID globalID)
@ -2016,6 +2026,7 @@ namespace OpenSim.Data.SQLite
return entry; return entry;
} }
/*
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
@ -2033,6 +2044,7 @@ namespace OpenSim.Data.SQLite
return str.ToArray(); return str.ToArray();
} }
*/
// private void fillTerrainRow(DataRow row, UUID regionUUID, int rev, double[,] val) // private void fillTerrainRow(DataRow row, UUID regionUUID, int rev, double[,] val)
// { // {

View File

@ -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);
}
}
}
}

View File

@ -67,7 +67,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders
{ {
using (Bitmap bitmap = new Bitmap(filename)) 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++) for (int x = 0; x < retval.Width; x++)
{ {

View File

@ -71,6 +71,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 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 Commander m_commander = new Commander("terrain");
private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects = private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects =
@ -81,8 +85,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects = private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects =
new Dictionary<StandardTerrainEffects, ITerrainPaintableEffect>(); new Dictionary<StandardTerrainEffects, ITerrainPaintableEffect>();
private ITerrainChannel m_channel;
private Dictionary<string, ITerrainEffect> m_plugineffects; private Dictionary<string, ITerrainEffect> m_plugineffects;
private ITerrainChannel m_channel;
private ITerrainChannel m_revert; private ITerrainChannel m_revert;
private Scene m_scene; private Scene m_scene;
private volatile bool m_tainted; private volatile bool m_tainted;
@ -90,6 +94,85 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private String m_InitialTerrain = "pinhead-island"; 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<UUID, PatchUpdates> m_perClientPatchUpdates = new Dictionary<UUID, PatchUpdates>();
/// <summary> /// <summary>
/// Human readable list of terrain file extensions that are supported. /// Human readable list of terrain file extensions that are supported.
/// </summary> /// </summary>
@ -118,7 +201,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{ {
IConfig terrainConfig = config.Configs["Terrain"]; IConfig terrainConfig = config.Configs["Terrain"];
if (terrainConfig != null) if (terrainConfig != null)
{
m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain); m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain);
m_sendTerrainUpdatesByViewDistance = terrainConfig.GetBoolean("SendTerrainUpdatesByViewDistance", m_sendTerrainUpdatesByViewDistance);
}
} }
public void AddRegion(Scene scene) public void AddRegion(Scene scene)
@ -130,22 +216,24 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{ {
if (m_scene.Heightmap == null) 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_scene.Heightmap = m_channel;
m_revert = new TerrainChannel();
UpdateRevertMap(); UpdateRevertMap();
} }
else else
{ {
m_channel = m_scene.Heightmap; m_channel = m_scene.Heightmap;
m_revert = new TerrainChannel();
UpdateRevertMap(); UpdateRevertMap();
} }
m_scene.RegisterModuleInterface<ITerrainModule>(this); m_scene.RegisterModuleInterface<ITerrainModule>(this);
m_scene.EventManager.OnNewClient += EventManager_OnNewClient; m_scene.EventManager.OnNewClient += EventManager_OnNewClient;
m_scene.EventManager.OnClientClosed += EventManager_OnClientClosed;
m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole;
m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick; m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick;
m_scene.EventManager.OnFrame += EventManager_OnFrame;
} }
InstallDefaultEffects(); InstallDefaultEffects();
@ -184,8 +272,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain
// remove the commands // remove the commands
m_scene.UnregisterModuleCommander(m_commander.Name); m_scene.UnregisterModuleCommander(m_commander.Name);
// remove the event-handlers // remove the event-handlers
m_scene.EventManager.OnFrame -= EventManager_OnFrame;
m_scene.EventManager.OnTerrainTick -= EventManager_OnTerrainTick; m_scene.EventManager.OnTerrainTick -= EventManager_OnTerrainTick;
m_scene.EventManager.OnPluginConsole -= EventManager_OnPluginConsole; m_scene.EventManager.OnPluginConsole -= EventManager_OnPluginConsole;
m_scene.EventManager.OnClientClosed -= EventManager_OnClientClosed;
m_scene.EventManager.OnNewClient -= EventManager_OnNewClient; m_scene.EventManager.OnNewClient -= EventManager_OnNewClient;
// remove the interface // remove the interface
m_scene.UnregisterModuleInterface<ITerrainModule>(this); m_scene.UnregisterModuleInterface<ITerrainModule>(this);
@ -230,11 +320,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain
try try
{ {
ITerrainChannel channel = loader.Value.LoadFile(filename); 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 // TerrainChannel expects a RegionSize x RegionSize map, currently
throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}", 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_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height);
m_scene.Heightmap = channel; m_scene.Heightmap = channel;
@ -261,7 +351,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
String.Format("Unable to load heightmap: {0}", e.Message)); String.Format("Unable to load heightmap: {0}", e.Message));
} }
} }
CheckForTerrainUpdates();
m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
return; return;
} }
@ -309,12 +398,18 @@ namespace OpenSim.Region.CoreModules.World.Terrain
LoadFromStream(filename, URIFetch(pathToTerrainHeightmap)); LoadFromStream(filename, URIFetch(pathToTerrainHeightmap));
} }
public void LoadFromStream(string filename, Stream stream)
{
LoadFromStream(filename, Vector3.Zero, 0f, Vector2.Zero, stream);
}
/// <summary> /// <summary>
/// Loads a terrain file from a stream and installs it in the scene. /// Loads a terrain file from a stream and installs it in the scene.
/// </summary> /// </summary>
/// <param name="filename">Filename to terrain file. Type is determined by extension.</param> /// <param name="filename">Filename to terrain file. Type is determined by extension.</param>
/// <param name="stream"></param> /// <param name="stream"></param>
public void LoadFromStream(string filename, Stream stream) public void LoadFromStream(string filename, Vector3 displacement,
float radianRotation, Vector2 rotationDisplacement, Stream stream)
{ {
foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders) foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
{ {
@ -325,8 +420,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
try try
{ {
ITerrainChannel channel = loader.Value.LoadStream(stream); ITerrainChannel channel = loader.Value.LoadStream(stream);
m_scene.Heightmap = channel; m_channel.Merge(channel, displacement, radianRotation, rotationDisplacement);
m_channel = channel;
UpdateRevertMap(); UpdateRevertMap();
} }
catch (NotImplementedException) catch (NotImplementedException)
@ -337,7 +431,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
} }
} }
CheckForTerrainUpdates();
m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
return; 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 () 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 #region Plugin Loading Methods
@ -532,6 +662,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
/// </summary> /// </summary>
public void UpdateRevertMap() public void UpdateRevertMap()
{ {
/*
int x; int x;
for (x = 0; x < m_channel.Width; 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[x, y] = m_channel[x, y];
} }
} }
*/
m_revert = m_channel.MakeCopy();
} }
/// <summary> /// <summary>
@ -567,8 +700,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{ {
ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY, ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY,
fileWidth, fileHeight, fileWidth, fileHeight,
(int) Constants.RegionSize, (int) m_scene.RegionInfo.RegionSizeX,
(int) Constants.RegionSize); (int) m_scene.RegionInfo.RegionSizeY);
m_scene.Heightmap = channel; m_scene.Heightmap = channel;
m_channel = channel; m_channel = channel;
UpdateRevertMap(); UpdateRevertMap();
@ -615,8 +748,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{ {
loader.Value.SaveFile(m_channel, filename, offsetX, offsetY, loader.Value.SaveFile(m_channel, filename, offsetX, offsetY,
fileWidth, fileHeight, fileWidth, fileHeight,
(int)Constants.RegionSize, (int)m_scene.RegionInfo.RegionSizeX,
(int)Constants.RegionSize); (int)m_scene.RegionInfo.RegionSizeY);
MainConsole.Instance.OutputFormat( MainConsole.Instance.OutputFormat(
"Saved terrain from ({0},{1}) to ({2},{3}) from {4} to {5}", "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); m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave);
} }
/// <summary>
/// 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.
/// </summary>
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;
}
}
/// <summary> /// <summary>
/// Performs updates to the region periodically, synchronising physics and other heightmap aware sections /// 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.
/// </summary> /// </summary>
private void EventManager_OnTerrainTick() private void EventManager_OnTerrainTick()
{ {
@ -644,8 +814,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised()); m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised());
m_scene.SaveTerrain(); 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. // 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); //m_scene.CreateTerrainTexture(true);
} }
@ -687,54 +855,48 @@ namespace OpenSim.Region.CoreModules.World.Terrain
} }
/// <summary> /// <summary>
/// Checks to see if the terrain has been modified since last check /// Installs terrain brush hook to IClientAPI
/// 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
/// </summary> /// </summary>
private void CheckForTerrainUpdates() /// <param name="client"></param>
private void EventManager_OnClientClosed(UUID client, Scene scene)
{ {
CheckForTerrainUpdates(false); ScenePresence presence = scene.GetScenePresence(client);
if (presence != null)
{
presence.ControllingClient.OnModifyTerrain -= client_OnModifyTerrain;
presence.ControllingClient.OnBakeTerrain -= client_OnBakeTerrain;
presence.ControllingClient.OnLandUndo -= client_OnLandUndo;
presence.ControllingClient.OnUnackedTerrain -= client_OnUnackedTerrain;
}
lock (m_perClientPatchUpdates)
m_perClientPatchUpdates.Remove(client);
} }
/// <summary> /// <summary>
/// Checks to see if the terrain has been modified since last check. /// Scan over changes in the terrain and limit height changes. This enforces the
/// If it has been modified, every all the terrain patches are sent to the client. /// non-estate owner limits on rate of terrain editting.
/// If the call is asked to respect the estate settings for terrain_raise_limit and /// Returns 'true' if any heights were limited.
/// terrain_lower_limit, it will clamp terrain updates between these values
/// currently invoked by client_OnModifyTerrain only and not the Commander interfaces
/// <param name="respectEstateSettings">should height map deltas be limited to the estate settings limits</param>
/// </summary> /// </summary>
private void CheckForTerrainUpdates(bool respectEstateSettings) private bool EnforceEstateLimits()
{ {
bool shouldTaint = false; TerrainData terrData = m_channel.GetTerrainData();
float[] serialised = m_channel.GetFloatsSerialised();
int x;
for (x = 0; x < m_channel.Width; x += Constants.TerrainPatchSize)
{
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();
}
SendToClients(serialised, x, y); bool wasLimited = false;
shouldTaint = true; 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) return wasLimited;
{
m_scene.EventManager.TriggerTerrainTainted();
m_tainted = true;
}
} }
/// <summary> /// <summary>
@ -742,11 +904,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain
/// are all within the current estate limits /// are all within the current estate limits
/// <returns>true if changes were limited, false otherwise</returns> /// <returns>true if changes were limited, false otherwise</returns>
/// </summary> /// </summary>
private bool LimitChannelChanges(int xStart, int yStart) private bool LimitChannelChanges(TerrainData terrData, int xStart, int yStart)
{ {
bool changesLimited = false; bool changesLimited = false;
double minDelta = m_scene.RegionInfo.RegionSettings.TerrainLowerLimit; float minDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit;
double maxDelta = m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit; float maxDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit;
// loop through the height map for this patch and compare it against // loop through the height map for this patch and compare it against
// the revert map // the revert map
@ -754,19 +916,18 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{ {
for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++) for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++)
{ {
float requestedHeight = terrData[x, y];
double requestedHeight = m_channel[x, y]; float bakedHeight = (float)m_revert[x, y];
double bakedHeight = m_revert[x, y]; float requestedDelta = requestedHeight - bakedHeight;
double requestedDelta = requestedHeight - bakedHeight;
if (requestedDelta > maxDelta) if (requestedDelta > maxDelta)
{ {
m_channel[x, y] = bakedHeight + maxDelta; terrData[x, y] = bakedHeight + maxDelta;
changesLimited = true; changesLimited = true;
} }
else if (requestedDelta < minDelta) 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; changesLimited = true;
} }
} }
@ -794,14 +955,154 @@ namespace OpenSim.Region.CoreModules.World.Terrain
/// <param name="serialised">A copy of the terrain as a 1D float array of size w*h</param> /// <param name="serialised">A copy of the terrain as a 1D float array of size w*h</param>
/// <param name="x">The patch corner to send</param> /// <param name="x">The patch corner to send</param>
/// <param name="y">The patch corner to send</param> /// <param name="y">The patch corner to send</param>
private void SendToClients(float[] serialised, int x, int y) private void SendToClients(TerrainData terrData, int x, int y)
{ {
m_scene.ForEachClient( if (m_sendTerrainUpdatesByViewDistance)
delegate(IClientAPI controller) {
{ controller.SendLayerData( // Add that this patch needs to be sent to the accounting for each client.
x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, serialised); 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<PatchesToSend>
{
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<PatchesToSend> 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<PatchesToSend> GetModifiedPatchesInViewDistance(PatchUpdates pups)
{
List<PatchesToSend> ret = new List<PatchesToSend>();
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, 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_painteffects[(StandardTerrainEffects) action].PaintEffect(
m_channel, allowMask, west, south, height, size, seconds); m_channel, allowMask, west, south, height, size, seconds);
CheckForTerrainUpdates(!god); //revert changes outside estate limits //revert changes outside estate limits
if (!god)
EnforceEstateLimits();
} }
} }
else else
@ -884,10 +1187,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain
if (allowed) if (allowed)
{ {
StoreUndoState(); StoreUndoState();
m_floodeffects[(StandardTerrainEffects) action].FloodEffect( m_floodeffects[(StandardTerrainEffects) action].FloodEffect(m_channel, fillArea, size);
m_channel, fillArea, size);
CheckForTerrainUpdates(!god); //revert changes outside estate limits //revert changes outside estate limits
if (!god)
EnforceEstateLimits();
} }
} }
else else
@ -911,7 +1215,9 @@ namespace OpenSim.Region.CoreModules.World.Terrain
protected void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY) protected void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY)
{ {
//m_log.Debug("Terrain packet unacked, resending patch: " + patchX + " , " + 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() private void StoreUndoState()
@ -938,7 +1244,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private void InterfaceLoadFile(Object[] args) private void InterfaceLoadFile(Object[] args)
{ {
LoadFromFile((string) args[0]); LoadFromFile((string) args[0]);
CheckForTerrainUpdates();
} }
private void InterfaceLoadTileFile(Object[] args) private void InterfaceLoadTileFile(Object[] args)
@ -948,7 +1253,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
(int) args[2], (int) args[2],
(int) args[3], (int) args[3],
(int) args[4]); (int) args[4]);
CheckForTerrainUpdates();
} }
private void InterfaceSaveFile(Object[] args) private void InterfaceSaveFile(Object[] args)
@ -977,7 +1281,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
for (y = 0; y < m_channel.Height; y++) for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] = m_revert[x, y]; m_channel[x, y] = m_revert[x, y];
CheckForTerrainUpdates();
} }
private void InterfaceFlipTerrain(Object[] args) private void InterfaceFlipTerrain(Object[] args)
@ -986,28 +1289,28 @@ namespace OpenSim.Region.CoreModules.World.Terrain
if (direction.ToLower().StartsWith("y")) 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 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, 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")) 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 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[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"); m_log.Error("Unrecognised direction - need x or y");
} }
CheckForTerrainUpdates();
} }
private void InterfaceRescaleTerrain(Object[] args) 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 (x = 0; x < m_channel.Width; x++)
for (y = 0; y < m_channel.Height; y++) for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] += (double) args[0]; m_channel[x, y] += (double) args[0];
CheckForTerrainUpdates();
} }
private void InterfaceMultiplyTerrain(Object[] args) private void InterfaceMultiplyTerrain(Object[] args)
@ -1096,7 +1394,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
for (x = 0; x < m_channel.Width; x++) for (x = 0; x < m_channel.Width; x++)
for (y = 0; y < m_channel.Height; y++) for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] *= (double) args[0]; m_channel[x, y] *= (double) args[0];
CheckForTerrainUpdates();
} }
private void InterfaceLowerTerrain(Object[] args) private void InterfaceLowerTerrain(Object[] args)
@ -1105,17 +1402,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain
for (x = 0; x < m_channel.Width; x++) for (x = 0; x < m_channel.Width; x++)
for (y = 0; y < m_channel.Height; y++) for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] -= (double) args[0]; m_channel[x, y] -= (double) args[0];
CheckForTerrainUpdates();
} }
private void InterfaceFillTerrain(Object[] args) public void InterfaceFillTerrain(Object[] args)
{ {
int x, y; int x, y;
for (x = 0; x < m_channel.Width; x++) for (x = 0; x < m_channel.Width; x++)
for (y = 0; y < m_channel.Height; y++) for (y = 0; y < m_channel.Height; y++)
m_channel[x, y] = (double) args[0]; m_channel[x, y] = (double) args[0];
CheckForTerrainUpdates();
} }
private void InterfaceMinTerrain(Object[] args) 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]); m_channel[x, y] = Math.Max((double)args[0], m_channel[x, y]);
} }
} }
CheckForTerrainUpdates();
} }
private void InterfaceMaxTerrain(Object[] args) 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]); m_channel[x, y] = Math.Min((double)args[0], m_channel[x, y]);
} }
} }
CheckForTerrainUpdates();
} }
private void InterfaceShowDebugStats(Object[] args) private void InterfaceShowDebugStats(Object[] args)
@ -1204,7 +1497,6 @@ namespace OpenSim.Region.CoreModules.World.Terrain
if (m_plugineffects.ContainsKey(firstArg)) if (m_plugineffects.ContainsKey(firstArg))
{ {
m_plugineffects[firstArg].RunEffect(m_channel); m_plugineffects[firstArg].RunEffect(m_channel);
CheckForTerrainUpdates();
} }
else else
{ {

View File

@ -68,13 +68,22 @@ namespace OpenSim.Region.Framework.Interfaces
/// </summary> /// </summary>
/// <param name="ter">HeightField data</param> /// <param name="ter">HeightField data</param>
/// <param name="regionID">region UUID</param> /// <param name="regionID">region UUID</param>
void StoreTerrain(TerrainData terrain, UUID regionID);
// Legacy version kept for downward compabibility
void StoreTerrain(double[,] terrain, UUID regionID); void StoreTerrain(double[,] terrain, UUID regionID);
/// <summary> /// <summary>
/// Load the latest terrain revision from region storage /// Load the latest terrain revision from region storage
/// </summary> /// </summary>
/// <param name="regionID">the region UUID</param> /// <param name="regionID">the region UUID</param>
/// <param name="sizeX">the X dimension of the region being filled</param>
/// <param name="sizeY">the Y dimension of the region being filled</param>
/// <param name="sizeZ">the Z dimension of the region being filled</param>
/// <returns>Heightfield data</returns> /// <returns>Heightfield data</returns>
TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ);
// Legacy version kept for downward compabibility
double[,] LoadTerrain(UUID regionID); double[,] LoadTerrain(UUID regionID);
void StoreLandObject(ILandObject Parcel); void StoreLandObject(ILandObject Parcel);

View File

@ -79,13 +79,22 @@ namespace OpenSim.Region.Framework.Interfaces
/// </summary> /// </summary>
/// <param name="ter">HeightField data</param> /// <param name="ter">HeightField data</param>
/// <param name="regionID">region UUID</param> /// <param name="regionID">region UUID</param>
void StoreTerrain(TerrainData terrain, UUID regionID);
// Legacy version kept for downward compabibility
void StoreTerrain(double[,] terrain, UUID regionID); void StoreTerrain(double[,] terrain, UUID regionID);
/// <summary> /// <summary>
/// Load the latest terrain revision from region storage /// Load the latest terrain revision from region storage
/// </summary> /// </summary>
/// <param name="regionID">the region UUID</param> /// <param name="regionID">the region UUID</param>
/// <param name="pSizeX">the X dimension of the terrain being filled</param>
/// <param name="pSizeY">the Y dimension of the terrain being filled</param>
/// <param name="pSizeZ">the Z dimension of the terrain being filled</param>
/// <returns>Heightfield data</returns> /// <returns>Heightfield data</returns>
TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ);
// Legacy version kept for downward compabibility
double[,] LoadTerrain(UUID regionID); double[,] LoadTerrain(UUID regionID);
void StoreLandObject(ILandObject Parcel); void StoreLandObject(ILandObject Parcel);
@ -136,4 +145,5 @@ namespace OpenSim.Region.Framework.Interfaces
void Shutdown(); void Shutdown();
} }
} }

View File

@ -25,13 +25,23 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
using OpenSim.Framework;
using OpenMetaverse;
namespace OpenSim.Region.Framework.Interfaces namespace OpenSim.Region.Framework.Interfaces
{ {
public interface ITerrainChannel 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; } 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();
/// <summary> /// <summary>
/// Squash the entire heightmap into a single dimensioned array /// Squash the entire heightmap into a single dimensioned array
@ -40,9 +50,14 @@ namespace OpenSim.Region.Framework.Interfaces
float[] GetFloatsSerialised(); float[] GetFloatsSerialised();
double[,] GetDoubles(); double[,] GetDoubles();
// Check if a location has been updated. Clears the taint flag as a side effect.
bool Tainted(int x, int y); bool Tainted(int x, int y);
ITerrainChannel MakeCopy(); ITerrainChannel MakeCopy();
string SaveToXmlString(); string SaveToXmlString();
void LoadFromXmlString(string data); void LoadFromXmlString(string data);
// Merge some terrain into this channel
void Merge(ITerrainChannel newTerrain, Vector3 displacement, float radianRotation, Vector2 rotationDisplacement);
} }
} }

View File

@ -1935,7 +1935,7 @@ namespace OpenSim.Region.Framework.Scenes
{ {
try 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) if (map == null)
{ {
// This should be in the Terrain module, but it isn't because // 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_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain);
m_log.InfoFormat("[TERRAIN]: No default terrain. Generating a new terrain {0}.", 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); SimulationDataService.StoreTerrain(Heightmap.GetDoubles(), RegionInfo.RegionID);
} }

View File

@ -25,13 +25,20 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * 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.Framework;
using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Interfaces;
using System;
using System.Text; using OpenMetaverse;
using System.Xml;
using System.IO; using log4net;
using System.Xml.Serialization;
namespace OpenSim.Region.Framework.Scenes namespace OpenSim.Region.Framework.Scenes
{ {
@ -40,140 +47,136 @@ namespace OpenSim.Region.Framework.Scenes
/// </summary> /// </summary>
public class TerrainChannel : ITerrainChannel public class TerrainChannel : ITerrainChannel
{ {
private readonly bool[,] taint; private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private double[,] map; 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() public TerrainChannel()
{ {
map = new double[Constants.RegionSize, Constants.RegionSize]; m_terrainData = new HeightmapTerrainData((int)Constants.RegionSize, (int)Constants.RegionSize, (int)Constants.RegionHeight);
taint = new bool[Constants.RegionSize / 16, Constants.RegionSize / 16]; FlatLand();
// PinHeadIsland();
PinHeadIsland();
} }
public TerrainChannel(String type) // Create terrain of given size
public TerrainChannel(int pX, int pY)
{ {
map = new double[Constants.RegionSize, Constants.RegionSize]; m_terrainData = new HeightmapTerrainData(pX, pY, (int)Constants.RegionHeight);
taint = new bool[Constants.RegionSize / 16, Constants.RegionSize / 16]; }
// 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")) if (type.Equals("flat"))
FlatLand(); FlatLand();
else else
PinHeadIsland(); 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; int hmSizeX = pM.GetLength(0);
taint = new bool[import.GetLength(0),import.GetLength(1)]; 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) m_terrainData = pTerrData;
{
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];
} }
#region ITerrainChannel Members #region ITerrainChannel Members
public int Width // ITerrainChannel.MakeCopy()
{
get { return map.GetLength(0); }
}
public int Height
{
get { return map.GetLength(1); }
}
public ITerrainChannel MakeCopy() public ITerrainChannel MakeCopy()
{ {
TerrainChannel copy = new TerrainChannel(false); return this.Copy();
copy.map = (double[,]) map.Clone();
return 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() public float[] GetFloatsSerialised()
{ {
// Move the member variables into local variables, calling return m_terrainData.GetFloatsSerialized();
// member variables 256*256 times gets expensive }
int w = Width;
int h = Height; // ITerrainChannel.GetDoubles()
float[] heights = new float[w * h]; public double[,] GetDoubles()
{
double[,] heights = new double[Width, Height];
int i, j; // map coordinates
int idx = 0; // index into serialized array 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; return heights;
} }
public double[,] GetDoubles() // ITerrainChannel.this[x,y]
{
return map;
}
public double this[int x, int y] public double this[int x, int y]
{ {
get get {
{ if (x < 0 || x >= Width || y < 0 || y >= Height)
if (x < 0) x = 0; return 0;
if (y < 0) y = 0; return (double)m_terrainData[x, y];
if (x >= (int)Constants.RegionSize) x = (int)Constants.RegionSize - 1;
if (y >= (int)Constants.RegionSize) y = (int)Constants.RegionSize - 1;
return map[x, y];
} }
set set
{ {
// Will "fix" terrain hole problems. Although not fantastically.
if (Double.IsNaN(value) || Double.IsInfinity(value)) if (Double.IsNaN(value) || Double.IsInfinity(value))
return; return;
if (map[x, y] != value) m_terrainData[x, y] = (float)value;
{
taint[x / 16, y / 16] = true;
map[x, y] = 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) public bool Tainted(int x, int y)
{ {
if (taint[x / 16, y / 16]) return m_terrainData.IsTaintedAt(x, y);
{
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;
} }
// ITerrainChannel.SaveToXmlString()
public string SaveToXmlString() public string SaveToXmlString()
{ {
XmlWriterSettings settings = new XmlWriterSettings(); XmlWriterSettings settings = new XmlWriterSettings();
@ -189,13 +192,7 @@ namespace OpenSim.Region.Framework.Scenes
} }
} }
private void WriteXml(XmlWriter writer) // ITerrainChannel.LoadFromXmlString()
{
writer.WriteStartElement(String.Empty, "TerrainMap", String.Empty);
ToXml(writer);
writer.WriteEndElement();
}
public void LoadFromXmlString(string data) public void LoadFromXmlString(string data)
{ {
StringReader sr = new StringReader(data); StringReader sr = new StringReader(data);
@ -207,12 +204,124 @@ namespace OpenSim.Region.Framework.Scenes
sr.Close(); sr.Close();
} }
private void ReadXml(XmlReader reader) // ITerrainChannel.Merge
public void Merge(ITerrainChannel newTerrain, Vector3 displacement, float radianRotation, Vector2 rotationDisplacement)
{ {
reader.ReadStartElement("TerrainMap"); m_log.DebugFormat("{0} Merge. inSize=<{1},{2}>, disp={3}, rot={4}, rotDisp={5}, outSize=<{6},{7}>", LogHeader,
FromXml(reader); 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) private void ToXml(XmlWriter xmlWriter)
{ {
float[] mapData = GetFloatsSerialised(); float[] mapData = GetFloatsSerialised();
@ -226,12 +335,15 @@ namespace OpenSim.Region.Framework.Scenes
serializer.Serialize(xmlWriter, buffer); 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) private void FromXml(XmlReader xmlReader)
{ {
XmlSerializer serializer = new XmlSerializer(typeof(byte[])); XmlSerializer serializer = new XmlSerializer(typeof(byte[]));
byte[] dataArray = (byte[])serializer.Deserialize(xmlReader); byte[] dataArray = (byte[])serializer.Deserialize(xmlReader);
int index = 0; int index = 0;
m_terrainData = new HeightmapTerrainData(Height, Width, (int)Constants.RegionHeight);
for (int y = 0; y < Height; y++) for (int y = 0; y < Height; y++)
{ {
for (int x = 0; x < Width; x++) 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() private void PinHeadIsland()
{ {
int x; for (int x = 0; x < Width; x++)
for (x = 0; x < Constants.RegionSize; x++)
{ {
int y; for (int y = 0; y < Height; y++)
for (y = 0; y < Constants.RegionSize; y++)
{ {
map[x, y] = TerrainUtil.PerlinNoise2D(x, y, 2, 0.125) * 10; m_terrainData[x, y] = (float)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; float spherFacA = (float)(TerrainUtil.SphericalFactor(x, y, m_terrainData.SizeX / 2.0, m_terrainData.SizeY / 2.0, 50) * 0.01d);
double spherFacB = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 100) * 0.001; float spherFacB = (float)(TerrainUtil.SphericalFactor(x, y, m_terrainData.SizeX / 2.0, m_terrainData.SizeY / 2.0, 100) * 0.001d);
if (map[x, y] < spherFacA) if (m_terrainData[x, y]< spherFacA)
map[x, y] = spherFacA; m_terrainData[x, y]= spherFacA;
if (map[x, y] < spherFacB) if (m_terrainData[x, y]< spherFacB)
map[x, y] = spherFacB; m_terrainData[x, y] = spherFacB;
} }
} }
} }
private void FlatLand() private void FlatLand()
{ {
int x; m_terrainData.ClearLand();
for (x = 0; x < Constants.RegionSize; x++)
{
int y;
for (y = 0; y < Constants.RegionSize; y++)
map[x, y] = 21;
}
} }
} }
} }

View File

@ -100,6 +100,11 @@ namespace OpenSim.Services.Connectors
return m_database.LoadObjects(regionUUID); return m_database.LoadObjects(regionUUID);
} }
public void StoreTerrain(TerrainData terrain, UUID regionID)
{
m_database.StoreTerrain(terrain, regionID);
}
public void StoreTerrain(double[,] terrain, UUID regionID) public void StoreTerrain(double[,] terrain, UUID regionID)
{ {
m_database.StoreTerrain(terrain, regionID); m_database.StoreTerrain(terrain, regionID);
@ -110,6 +115,11 @@ namespace OpenSim.Services.Connectors
return m_database.LoadTerrain(regionID); 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) public void StoreLandObject(ILandObject Parcel)
{ {
m_database.StoreLandObject(Parcel); m_database.StoreLandObject(Parcel);

View File

@ -69,11 +69,21 @@ namespace OpenSim.Data.Null
m_store.StoreTerrain(terrain, regionID); m_store.StoreTerrain(terrain, regionID);
} }
public void StoreTerrain(TerrainData terrain, UUID regionID)
{
m_store.StoreTerrain(terrain, regionID);
}
public double[,] LoadTerrain(UUID regionID) public double[,] LoadTerrain(UUID regionID)
{ {
return m_store.LoadTerrain(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) public void StoreLandObject(ILandObject Parcel)
{ {
m_store.StoreLandObject(Parcel); m_store.StoreLandObject(Parcel);
@ -159,7 +169,7 @@ namespace OpenSim.Data.Null
protected Dictionary<UUID, SceneObjectPart> m_sceneObjectParts = new Dictionary<UUID, SceneObjectPart>(); protected Dictionary<UUID, SceneObjectPart> m_sceneObjectParts = new Dictionary<UUID, SceneObjectPart>();
protected Dictionary<UUID, ICollection<TaskInventoryItem>> m_primItems protected Dictionary<UUID, ICollection<TaskInventoryItem>> m_primItems
= new Dictionary<UUID, ICollection<TaskInventoryItem>>(); = new Dictionary<UUID, ICollection<TaskInventoryItem>>();
protected Dictionary<UUID, double[,]> m_terrains = new Dictionary<UUID, double[,]>(); protected Dictionary<UUID, TerrainData> m_terrains = new Dictionary<UUID, TerrainData>();
protected Dictionary<UUID, LandData> m_landData = new Dictionary<UUID, LandData>(); protected Dictionary<UUID, LandData> m_landData = new Dictionary<UUID, LandData>();
public void Initialise(string dbfile) public void Initialise(string dbfile)
@ -304,15 +314,28 @@ namespace OpenSim.Data.Null
return new List<SceneObjectGroup>(objects.Values); return new List<SceneObjectGroup>(objects.Values);
} }
public void StoreTerrain(double[,] ter, UUID regionID) public void StoreTerrain(TerrainData ter, UUID regionID)
{ {
m_terrains[regionID] = ter; 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) public double[,] LoadTerrain(UUID regionID)
{ {
if (m_terrains.ContainsKey(regionID)) if (m_terrains.ContainsKey(regionID))
return m_terrains[regionID]; return m_terrains[regionID].GetDoubles();
else else
return null; return null;
} }