diff --git a/OpenSim/Data/MSSQL/MSSQLSimulationData.cs b/OpenSim/Data/MSSQL/MSSQLSimulationData.cs index 8adddc9c65..dbfd16c4a5 100644 --- a/OpenSim/Data/MSSQL/MSSQLSimulationData.cs +++ b/OpenSim/Data/MSSQL/MSSQLSimulationData.cs @@ -49,6 +49,7 @@ namespace OpenSim.Data.MSSQL // private static FileSystemDataStore Instance = new FileSystemDataStore(); private static readonly ILog _Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[REGION DB MSSQL]"; /// /// The database manager @@ -569,15 +570,19 @@ ELSE return terrain; } + // Legacy entry point for when terrain was always a 256x256 hieghtmap + public void StoreTerrain(double[,] ter, UUID regionID) + { + StoreTerrain(new HeightmapTerrainData(ter), regionID); + } + /// /// Stores the terrain map to DB. /// /// terrain map data. /// regionID. - public void StoreTerrain(double[,] terrain, UUID regionID) + public void StoreTerrain(TerrainData terrData, UUID regionID) { - int revision = (int)DBTerrainRevision.Legacy256; - //Delete old terrain map string sql = "delete from terrain where RegionUUID=@RegionUUID"; using (SqlConnection conn = new SqlConnection(m_connectionString)) @@ -590,17 +595,21 @@ ELSE sql = "insert into terrain(RegionUUID, Revision, Heightfield) values(@RegionUUID, @Revision, @Heightfield)"; + int terrainDBRevision; + Array terrainDBblob; + terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob); + using (SqlConnection conn = new SqlConnection(m_connectionString)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.Add(_Database.CreateParameter("@RegionUUID", regionID)); - cmd.Parameters.Add(_Database.CreateParameter("@Revision", revision)); - cmd.Parameters.Add(_Database.CreateParameter("@Heightfield", serializeTerrain(terrain))); + cmd.Parameters.Add(_Database.CreateParameter("@Revision", terrainDBRevision)); + cmd.Parameters.Add(_Database.CreateParameter("@Heightfield", terrainDBblob)); conn.Open(); cmd.ExecuteNonQuery(); } - _Log.Info("[REGION DB]: Stored terrain revision r " + revision); + _Log.InfoFormat("{0} Stored terrain revision r={1}", LogHeader, terrainDBRevision); } /// @@ -1344,30 +1353,6 @@ VALUES #region Private Methods - /// - /// Serializes the terrain data for storage in DB. - /// - /// terrain data - /// - private static Array serializeTerrain(double[,] val) - { - MemoryStream str = new MemoryStream(((int)Constants.RegionSize * (int)Constants.RegionSize) * sizeof(double)); - BinaryWriter bw = new BinaryWriter(str); - - // TODO: COMPATIBILITY - Add byte-order conversions - for (int x = 0; x < (int)Constants.RegionSize; x++) - for (int y = 0; y < (int)Constants.RegionSize; y++) - { - double height = val[x, y]; - if (height == 0.0) - height = double.Epsilon; - - bw.Write(height); - } - - return str.ToArray(); - } - /// /// Stores new regionsettings. /// diff --git a/OpenSim/Data/MySQL/MySQLSimulationData.cs b/OpenSim/Data/MySQL/MySQLSimulationData.cs index 5751dc8a1c..4bd861762e 100644 --- a/OpenSim/Data/MySQL/MySQLSimulationData.cs +++ b/OpenSim/Data/MySQL/MySQLSimulationData.cs @@ -48,6 +48,7 @@ namespace OpenSim.Data.MySQL public class MySQLSimulationData : ISimulationDataStore { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[REGION DB MYSQL]"; private string m_connectionString; private object m_dbLock = new object(); @@ -91,7 +92,7 @@ namespace OpenSim.Data.MySQL } catch (Exception e) { - m_log.Error("[REGION DB]: MySQL error in ExecuteReader: " + e.Message); + m_log.ErrorFormat("{0} MySQL error in ExecuteReader: {1}", LogHeader, e); throw; } @@ -572,11 +573,14 @@ namespace OpenSim.Data.MySQL } } + // Legacy entry point for when terrain was always a 256x256 hieghtmap public void StoreTerrain(double[,] ter, UUID regionID) { - m_log.Info("[REGION DB]: Storing terrain"); - int revision = (int)DBTerrainRevision.Legacy256; + StoreTerrain(new HeightmapTerrainData(ter), regionID); + } + public void StoreTerrain(TerrainData terrData, UUID regionID) + { lock (m_dbLock) { using (MySqlConnection dbcon = new MySqlConnection(m_connectionString)) @@ -590,11 +594,18 @@ namespace OpenSim.Data.MySQL ExecuteNonQuery(cmd); + int terrainDBRevision; + Array terrainDBblob; + terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob); + + m_log.InfoFormat("{0} Storing terrain. X={1}, Y={2}, rev={3}", + LogHeader, terrData.SizeX, terrData.SizeY, terrainDBRevision); + cmd.CommandText = "insert into terrain (RegionUUID, Revision, Heightfield)" + "values (?RegionUUID, ?Revision, ?Heightfield)"; - - cmd.Parameters.AddWithValue("Revision", revision); - cmd.Parameters.AddWithValue("Heightfield", SerializeTerrain(ter)); + + cmd.Parameters.AddWithValue("Revision", terrainDBRevision); + cmd.Parameters.AddWithValue("Heightfield", terrainDBblob); ExecuteNonQuery(cmd); } @@ -1525,30 +1536,6 @@ namespace OpenSim.Data.MySQL return entry; } - /// - /// - /// - /// - /// - private static Array SerializeTerrain(double[,] val) - { - MemoryStream str = new MemoryStream(((int)Constants.RegionSize * (int)Constants.RegionSize) *sizeof (double)); - BinaryWriter bw = new BinaryWriter(str); - - // TODO: COMPATIBILITY - Add byte-order conversions - for (int x = 0; x < (int)Constants.RegionSize; x++) - for (int y = 0; y < (int)Constants.RegionSize; y++) - { - double height = val[x, y]; - if (height == 0.0) - height = double.Epsilon; - - bw.Write(height); - } - - return str.ToArray(); - } - /// /// Fill the prim command with prim values /// diff --git a/OpenSim/Data/SQLite/SQLiteSimulationData.cs b/OpenSim/Data/SQLite/SQLiteSimulationData.cs index b70af6b3e7..cce59c1e83 100644 --- a/OpenSim/Data/SQLite/SQLiteSimulationData.cs +++ b/OpenSim/Data/SQLite/SQLiteSimulationData.cs @@ -51,6 +51,7 @@ namespace OpenSim.Data.SQLite public class SQLiteSimulationData : ISimulationDataStore { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[REGION DB SQLLITE]"; private const string primSelect = "select * from prims"; private const string shapeSelect = "select * from primshapes"; @@ -819,17 +820,21 @@ namespace OpenSim.Data.SQLite prim.Inventory.RestoreInventoryItems(inventory); } + // Legacy entry point for when terrain was always a 256x256 hieghtmap + public void StoreTerrain(double[,] ter, UUID regionID) + { + StoreTerrain(new HeightmapTerrainData(ter), regionID); + } + /// /// Store a terrain revision in region storage /// /// terrain heightfield /// region UUID - public void StoreTerrain(double[,] ter, UUID regionID) + public void StoreTerrain(TerrainData terrData, UUID regionID) { lock (ds) { - int revision = (int)DBTerrainRevision.Legacy256; - using ( SqliteCommand cmd = new SqliteCommand("delete from terrain where RegionUUID=:RegionUUID", m_conn)) { @@ -839,15 +844,20 @@ namespace OpenSim.Data.SQLite // the following is an work around for .NET. The perf // issues associated with it aren't as bad as you think. - m_log.Debug("[SQLITE REGION DB]: Storing terrain revision r" + revision.ToString()); String sql = "insert into terrain(RegionUUID, Revision, Heightfield)" + " values(:RegionUUID, :Revision, :Heightfield)"; + int terrainDBRevision; + Array terrainDBblob; + terrData.GetDatabaseBlob(out terrainDBRevision, out terrainDBblob); + + m_log.DebugFormat("{0} Storing terrain revision r {1}", LogHeader, terrainDBRevision); + using (SqliteCommand cmd = new SqliteCommand(sql, m_conn)) { cmd.Parameters.Add(new SqliteParameter(":RegionUUID", regionID.ToString())); - cmd.Parameters.Add(new SqliteParameter(":Revision", revision)); - cmd.Parameters.Add(new SqliteParameter(":Heightfield", serializeTerrain(ter))); + cmd.Parameters.Add(new SqliteParameter(":Revision", terrainDBRevision)); + cmd.Parameters.Add(new SqliteParameter(":Heightfield", terrainDBblob)); cmd.ExecuteNonQuery(); } } @@ -2006,24 +2016,6 @@ namespace OpenSim.Data.SQLite return entry; } - /// - /// - /// - /// - /// - private static Array serializeTerrain(double[,] val) - { - MemoryStream str = new MemoryStream(((int)Constants.RegionSize * (int)Constants.RegionSize) * sizeof(double)); - BinaryWriter bw = new BinaryWriter(str); - - // TODO: COMPATIBILITY - Add byte-order conversions - for (int x = 0; x < (int)Constants.RegionSize; x++) - for (int y = 0; y < (int)Constants.RegionSize; y++) - bw.Write(val[x, y]); - - return str.ToArray(); - } - // private void fillTerrainRow(DataRow row, UUID regionUUID, int rev, double[,] val) // { // row["RegionUUID"] = regionUUID; diff --git a/OpenSim/Framework/Constants.cs b/OpenSim/Framework/Constants.cs index 79791327db..9ddb34be5c 100644 --- a/OpenSim/Framework/Constants.cs +++ b/OpenSim/Framework/Constants.cs @@ -40,7 +40,7 @@ namespace OpenSim.Framework public const float TerrainCompression = 100.0f; // Since terrain is stored in 16x16 heights, regions must be a multiple of this number and that is the minimum public const int MinRegionSize = 16; - public const byte TerrainPatchSize = 16; + public const int TerrainPatchSize = 16; public const string DefaultTexture = "89556747-24cb-43ed-920b-47caed15465f"; diff --git a/OpenSim/Framework/TerrainData.cs b/OpenSim/Framework/TerrainData.cs index 8cb1aefd7a..bee6814c67 100644 --- a/OpenSim/Framework/TerrainData.cs +++ b/OpenSim/Framework/TerrainData.cs @@ -44,9 +44,19 @@ namespace OpenSim.Framework // Someday terrain will have caves public abstract float this[int x, int y, int z] { get; set; } + public bool IsTainted { get; protected set; } + public abstract bool IsTaintedAt(int xx, int yy); + public abstract void ClearTaint(); + // 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); + + // return a special compressed representation of the heightmap in shorts + public abstract short[] GetCompressedMap(); + public abstract void SetCompressedMap(short[] cmap); + + public abstract TerrainData Clone(); } // The terrain is stored as a blob in the database with a 'revision' field. @@ -72,13 +82,23 @@ namespace OpenSim.Framework // 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 short integers. The integer values are converted to + // and from floats by TerrainCompressionFactor. public class HeightmapTerrainData : TerrainData { // TerrainData.this[x, y] public override float this[int x, int y] { - get { return m_heightmap[x * SizeX + y]; } - set { m_heightmap[x * SizeX + y] = value; } + get { return FromCompressedHeight(m_heightmap[x, y]); } + set { + short 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] @@ -88,6 +108,20 @@ namespace OpenSim.Framework set { this[x, y] = value; } } + // TerrainData.ClearTaint + public override void ClearTaint() + { + IsTainted = false; + for (int ii = 0; ii < m_taint.GetLength(0); ii++) + for (int jj = 0; jj < m_taint.GetLength(1); jj++) + m_taint[ii, jj] = false; + } + + public override bool IsTaintedAt(int xx, int yy) + { + return m_taint[xx / Constants.TerrainPatchSize, yy / Constants.TerrainPatchSize]; + } + // TerrainData.GetDatabaseBlob // The user wants something to store in the database. public override bool GetDatabaseBlob(out int DBRevisionCode, out Array blob) @@ -96,7 +130,53 @@ namespace OpenSim.Framework blob = LegacyTerrainSerialization(); return false; } - private float[] m_heightmap; + + public override short[] GetCompressedMap() + { + short[] newMap = new short[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; + + } + public override void SetCompressedMap(short[] cmap) + { + int ind = 0; + for (int xx = 0; xx < SizeX; xx++) + for (int yy = 0; yy < SizeY; yy++) + m_heightmap[xx, yy] = cmap[ind++]; + } + + // TerrainData.Clone + public override TerrainData Clone() + { + HeightmapTerrainData ret = new HeightmapTerrainData(SizeX, SizeY, SizeZ); + ret.m_heightmap = (short[,])this.m_heightmap.Clone(); + return ret; + } + + // ============================================================= + + private short[,] 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 static short ToCompressedHeight(double pHeight) + { + return (short)(pHeight * Constants.TerrainCompression); + } + + public static float FromCompressedHeight(short pHeight) + { + return ((float)pHeight) / Constants.TerrainCompression; + } // To keep with the legacy theme, this can be created with the way terrain // used to passed around as. @@ -106,26 +186,37 @@ namespace OpenSim.Framework SizeY = pTerrain.GetLength(1); SizeZ = (int)Constants.RegionHeight; - int idx = 0; - m_heightmap = new float[SizeX * SizeY]; + m_heightmap = new short[SizeX, SizeY]; for (int ii = 0; ii < SizeX; ii++) { for (int jj = 0; jj < SizeY; jj++) { - m_heightmap[idx++] = (float)pTerrain[ii, jj]; + m_heightmap[ii, jj] = ToCompressedHeight(pTerrain[ii, jj]); } } + + m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize]; + ClearTaint(); } - public HeightmapTerrainData(float[] pHeightmap, int pX, int pY, int pZ) + // Create underlying structures but don't initialize the heightmap assuming the caller will immediately do that + public HeightmapTerrainData(int pX, int pY, int pZ) { - m_heightmap = pHeightmap; SizeX = pX; SizeY = pY; SizeZ = pZ; + m_heightmap = new short[SizeX, SizeY]; + m_taint = new bool[SizeX / Constants.TerrainPatchSize, SizeY / Constants.TerrainPatchSize]; + ClearTaint(); } + public HeightmapTerrainData(short[] cmap, int pX, int pY, int pZ) : this(pX, pY, pZ) + { + SetCompressedMap(cmap); + } + + // Just create an array of doubles. Presumes the caller implicitly knows the size. public Array LegacyTerrainSerialization() { @@ -135,12 +226,12 @@ namespace OpenSim.Framework using (BinaryWriter bw = new BinaryWriter(str)) { // TODO: COMPATIBILITY - Add byte-order conversions - for (int ii = 0; ii < m_heightmap.Length; ii++) + for (int ii = 0; ii < SizeX; ii++) + for (int jj = 0; jj < SizeY; jj++) { - double height = (double)m_heightmap[ii]; + double height = this[ii, jj]; if (height == 0.0) height = double.Epsilon; - bw.Write(height); } } diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index fea9ddf25f..7984accb61 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -34,11 +34,13 @@ using System.Text; using System.Threading; using System.Timers; using System.Xml; + using log4net; using OpenMetaverse; using OpenMetaverse.Packets; using OpenMetaverse.Messages.Linden; using OpenMetaverse.StructuredData; + using OpenSim.Framework; using OpenSim.Framework.Client; using OpenSim.Framework.Monitoring; @@ -48,7 +50,6 @@ using OpenSim.Services.Interfaces; using Timer = System.Timers.Timer; using AssetLandmark = OpenSim.Framework.AssetLandmark; using RegionFlags = OpenMetaverse.RegionFlags; -using Nini.Config; using System.IO; using PermissionMask = OpenSim.Framework.PermissionMask; @@ -307,6 +308,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP private const float m_sunPainDaHalfOrbitalCutoff = 4.712388980384689858f; private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[LLCLIENTVIEW]"; protected static Dictionary PacketHandlers = new Dictionary(); //Global/static handlers for all clients /// @@ -447,7 +449,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP // ~LLClientView() // { -// m_log.DebugFormat("[LLCLIENTVIEW]: Destructor called for {0}, circuit code {1}", Name, CircuitCode); +// m_log.DebugFormat("{0} Destructor called for {1}, circuit code {2}", LogHeader, Name, CircuitCode); // } /// @@ -513,9 +515,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // there is some unidentified connection problem, not where we have issues due to deadlock if (!IsActive && !force) { - m_log.DebugFormat( - "[CLIENT]: Not attempting to close inactive client {0} in {1} since force flag is not set", - Name, m_scene.Name); + m_log.DebugFormat( "{0} Not attempting to close inactive client {1} in {2} since force flag is not set", + LogHeader, Name, m_scene.Name); return; } @@ -1162,10 +1163,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// private void DoSendLayerData(object o) { - float[] map = LLHeightFieldMoronize((float[])o); + float[] map = (float[])o; try { + // Send LayerData in typerwriter pattern //for (int y = 0; y < 16; y++) //{ // for (int x = 0; x < 16; x++) @@ -1230,7 +1232,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP // } /// - /// Sends a specified patch to a client + /// Sends a terrain packet for the point specified. + /// This is a legacy call that has refarbed the terrain into a flat map of floats. + /// We just use the terrain from the region we know about. /// /// Patch coordinate (x) 0..15 /// Patch coordinate (y) 0..15 @@ -1239,10 +1243,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP { try { - int[] patches = new int[] { py * 16 + px }; - float[] heightmap = (map.Length == 65536) ? map : LLHeightFieldMoronize(map); - - LayerDataPacket layerpack = TerrainCompressor.CreateLandPacket(heightmap, patches); + // For unknown reasons, after this point, patch numbers are swapped X for y. + // That means, that for - /// Munges heightfield into the LLUDP backed in restricted heightfield. - /// - /// float array in the base; Constants.RegionSize - /// float array in the base 256 - internal float[] LLHeightFieldMoronize(float[] map) - { - if (map.Length == 65536) - return map; - else - { - float[] returnmap = new float[65536]; - - if (map.Length < 65535) - { - // rebase the vector stride to 256 - for (int i = 0; i < Constants.RegionSize; i++) - Array.Copy(map, i * (int)Constants.RegionSize, returnmap, i * 256, (int)Constants.RegionSize); - } - else - { - for (int i = 0; i < 256; i++) - Array.Copy(map, i * (int)Constants.RegionSize, returnmap, i * 256, 256); - } - - //Array.Copy(map,0,returnmap,0,(map.Length < 65536)? map.Length : 65536); - - return returnmap; - } - - } - /// /// Send the wind matrix to the client /// @@ -2780,8 +2751,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP { if (req.AssetInf.Data == null) { - m_log.ErrorFormat("Cannot send asset {0} ({1}), asset data is null", - req.AssetInf.ID, req.AssetInf.Metadata.ContentType); + m_log.ErrorFormat("{0} Cannot send asset {1} ({2}), asset data is null", + LogHeader, req.AssetInf.ID, req.AssetInf.Metadata.ContentType); return; } diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index 2fff4c1ac0..eb6187b319 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -30,10 +30,14 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Net; + using log4net; using Nini.Config; + using OpenMetaverse; using Mono.Addins; + +using OpenSim.Data; using OpenSim.Framework; using OpenSim.Region.CoreModules.Framework.InterfaceCommander; using OpenSim.Region.CoreModules.World.Terrain.FileLoaders; @@ -130,8 +134,9 @@ namespace OpenSim.Region.CoreModules.World.Terrain { if (m_scene.Heightmap == null) { - m_channel = new TerrainChannel(m_InitialTerrain, - m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY, m_scene.RegionInfo.RegionSizeZ); + 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; UpdateRevertMap(); } @@ -707,7 +712,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void CheckForTerrainUpdates(bool respectEstateSettings) { bool shouldTaint = false; - float[] serialised = m_channel.GetFloatsSerialised(); + float[] terrData = m_channel.GetFloatsSerialised(); int x; for (x = 0; x < m_channel.Width; x += Constants.TerrainPatchSize) { @@ -716,16 +721,16 @@ namespace OpenSim.Region.CoreModules.World.Terrain { if (m_channel.Tainted(x, y)) { - // if we should respect the estate settings then - // fixup and height deltas that don't respect them + // 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. if (respectEstateSettings && LimitChannelChanges(x, y)) { - // this has been vetoed, so update - // what we are going to send to the client - serialised = m_channel.GetFloatsSerialised(); + // Terrain heights were modified. Refetch the terrain info. + terrData = m_channel.GetFloatsSerialised(); } - SendToClients(serialised, x, y); + SendToClients(terrData, x, y); shouldTaint = true; } } @@ -794,13 +799,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// A copy of the terrain as a 1D float array of size w*h /// The patch corner to send /// The patch corner to send - private void SendToClients(float[] serialised, int x, int y) + private void SendToClients(float[] heightMap, int x, int y) { m_scene.ForEachClient( delegate(IClientAPI controller) - { controller.SendLayerData( - x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, serialised); - } + { controller.SendLayerData( x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, heightMap); } ); } diff --git a/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs b/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs index 847d245366..5ba5b31793 100644 --- a/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs +++ b/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs @@ -136,24 +136,4 @@ namespace OpenSim.Region.Framework.Interfaces void Shutdown(); } - // The terrain is stored as a blob in the database 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 shorts are X and Y dimensions - // The dimensions are presumed to be multiples of 16 and, more likely, multiples of 256. - Variable2D = 22, - // A revision that is not listed above or any revision greater than this value is 'Legacy256'. - RevisionHigh = 1234 - } - } diff --git a/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs b/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs index 3c060a47e2..cc8a23661f 100644 --- a/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs +++ b/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs @@ -25,6 +25,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using OpenSim.Framework; + namespace OpenSim.Region.Framework.Interfaces { public interface ITerrainChannel @@ -35,18 +37,20 @@ namespace OpenSim.Region.Framework.Interfaces double this[int x, int y] { get; set; } + // Return the packaged terrain data for passing into lower levels of communication + TerrainData GetTerrainData(); + /// /// Squash the entire heightmap into a single dimensioned array /// /// float[] GetFloatsSerialised(); - // Get version of map as a single dimensioned array and each value compressed - // into an int (compressedHeight = (int)(floatHeight * Constants.TerrainCompression);) - // This is done to make the map smaller as it can get pretty larger for variable sized regions. - short[] GetCompressedMap(); double[,] GetDoubles(); + + // Check if a location has been updated. Clears the taint flag as a side effect. bool Tainted(int x, int y); + ITerrainChannel MakeCopy(); string SaveToXmlString(); void LoadFromXmlString(string data); diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 49e32c6f45..e2880e389f 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -1905,7 +1905,7 @@ namespace OpenSim.Region.Framework.Scenes m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain); m_log.InfoFormat("[TERRAIN]: No default terrain. Generating a new terrain {0}.", m_InitialTerrain); - Heightmap = new TerrainChannel(m_InitialTerrain, RegionInfo.RegionSizeX, RegionInfo.RegionSizeY, RegionInfo.RegionSizeZ); + Heightmap = new TerrainChannel(m_InitialTerrain, (int)RegionInfo.RegionSizeX, (int)RegionInfo.RegionSizeY, (int)RegionInfo.RegionSizeZ); SimulationDataService.StoreTerrain(Heightmap.GetDoubles(), RegionInfo.RegionID); } diff --git a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs index fef93bf7c3..65e890fe6a 100644 --- a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs +++ b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs @@ -25,13 +25,18 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using System; +using System.IO; +using System.Text; +using System.Reflection; +using System.Xml; +using System.Xml.Serialization; + +using OpenSim.Data; using OpenSim.Framework; using OpenSim.Region.Framework.Interfaces; -using System; -using System.Text; -using System.Xml; -using System.IO; -using System.Xml.Serialization; + +using log4net; namespace OpenSim.Region.Framework.Scenes { @@ -40,18 +45,20 @@ namespace OpenSim.Region.Framework.Scenes /// public class TerrainChannel : ITerrainChannel { - protected bool[,] m_taint; - protected short[] m_map; + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[TERRAIN CHANNEL]"; - public int Width { get; private set; } // X dimension + 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; private set; } // Y dimension - public int Altitude { get; private set; } // Y dimension + 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() { - InitializeStructures(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight, false); + m_terrainData = new HeightmapTerrainData((int)Constants.RegionSize, (int)Constants.RegionSize, (int)Constants.RegionHeight); FlatLand(); // PinHeadIsland(); } @@ -59,27 +66,23 @@ namespace OpenSim.Region.Framework.Scenes // Create terrain of given size public TerrainChannel(int pX, int pY) { - InitializeStructures((uint)pX, (uint)pY, Constants.RegionHeight, true); + m_terrainData = new HeightmapTerrainData(pX, pY, (int)Constants.RegionHeight); } // Create terrain of specified size and initialize with specified terrain. // TODO: join this with the terrain initializers. - public TerrainChannel(String type, uint pX, uint pY, uint pZ) + public TerrainChannel(String type, int pX, int pY, int pZ) { - InitializeStructures(pX, pY, pZ, false); + m_terrainData = new HeightmapTerrainData(pX, pY, pZ); if (type.Equals("flat")) FlatLand(); else PinHeadIsland(); } - public TerrainChannel(double[,] pM, uint pH) + public TerrainChannel(double[,] pM, uint pAltitude) { - InitializeStructures((uint)pM.GetLength(0), (uint)pM.GetLength(1), pH, false); - int idx = 0; - for (int ii = 0; ii < Height; ii++) - for (int jj = 0; jj < Width; jj++) - m_map[idx++] = ToCompressedHeight(pM[ii, jj]); + m_terrainData = new HeightmapTerrainData(pM); } #region ITerrainChannel Members @@ -90,20 +93,23 @@ namespace OpenSim.Region.Framework.Scenes return this.Copy(); } - // ITerrainChannel.GetCompressedMap() - public short[] GetCompressedMap() + // ITerrainChannel.GetTerrainData() + public TerrainData GetTerrainData() { - return m_map; + return m_terrainData; } // ITerrainChannel.GetFloatsSerialized() + // NOTICE that the one dimensional form is ordered by Y!! public float[] GetFloatsSerialised() { int points = Width * Height; float[] heights = new float[points]; - for (int ii = 0; ii < points; ii++) - heights[ii] = FromCompressedHeight(m_map[ii]); + int idx = 0; + for (int ii = 0; ii < Height; ii++) + for (int jj = 0; jj < Width; jj++) + heights[idx++] = m_terrainData[jj, ii]; return heights; } @@ -116,11 +122,11 @@ namespace OpenSim.Region.Framework.Scenes double[,] heights = new double[w, l]; int idx = 0; // index into serialized array - for (int ii = 0; ii < l; ii++) + for (int ii = 0; ii < w; ii++) { - for (int jj = 0; jj < w; jj++) + for (int jj = 0; jj < l; jj++) { - heights[ii, jj] = (double)FromCompressedHeight(m_map[idx]); + heights[ii, jj] = (double)m_terrainData[ii, jj]; idx++; } } @@ -131,31 +137,20 @@ namespace OpenSim.Region.Framework.Scenes // ITerrainChannel.this[x,y] public double this[int x, int y] { - get { return m_map[x * Width + y]; } + get { return (double)m_terrainData[x, y]; } set { - // Will "fix" terrain hole problems. Although not fantastically. if (Double.IsNaN(value) || Double.IsInfinity(value)) return; - int idx = x * Width + y; - if (m_map[idx] != value) - { - m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize] = true; - m_map[idx] = ToCompressedHeight(value); - } + m_terrainData[x, y] = (float)value; } } // ITerrainChannel.Tainted() public bool Tainted(int x, int y) { - if (m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize]) - { - m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize] = false; - return true; - } - return false; + return m_terrainData.IsTaintedAt(x, y); } // ITerrainChannel.SaveToXmlString() @@ -188,49 +183,25 @@ namespace OpenSim.Region.Framework.Scenes #endregion - private void InitializeStructures(uint pX, uint pY, uint pZ, bool shouldInitializeHeightmap) - { - Width = (int)pX; - Height = (int)pY; - Altitude = (int)pZ; - m_map = new short[Width * Height]; - m_taint = new bool[Width / Constants.TerrainPatchSize, Height / Constants.TerrainPatchSize]; - ClearTaint(); - if (shouldInitializeHeightmap) - { - FlatLand(); - } - } - - public void ClearTaint() - { - for (int ii = 0; ii < Width / Constants.TerrainPatchSize; ii++) - for (int jj = 0; jj < Height / Constants.TerrainPatchSize; jj++) - m_taint[ii, jj] = false; - } - + /* // 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 short ToCompressedHeight(double pHeight) + public static short ToCompressedHeight(double pHeight) { return (short)(pHeight * Constants.TerrainCompression); } - public float FromCompressedHeight(short pHeight) + public static float FromCompressedHeight(short pHeight) { return ((float)pHeight) / Constants.TerrainCompression; } + */ public TerrainChannel Copy() { TerrainChannel copy = new TerrainChannel(); - copy.m_map = (short[])m_map.Clone(); - copy.m_taint = (bool[,])m_taint.Clone(); - copy.Width = Width; - copy.Height = Height; - copy.Altitude = Altitude; - + copy.m_terrainData = m_terrainData.Clone(); return copy; } @@ -289,6 +260,8 @@ namespace OpenSim.Region.Framework.Scenes byte[] dataArray = (byte[])serializer.Deserialize(xmlReader); int index = 0; + m_terrainData = new HeightmapTerrainData(Width, Height, Altitude); + for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) @@ -321,7 +294,7 @@ namespace OpenSim.Region.Framework.Scenes // New terrain serialization format that includes the width and length. private void ToXml2(XmlWriter xmlWriter) { - TerrainChannelXMLPackage package = new TerrainChannelXMLPackage(Width, Height, Altitude, m_map); + TerrainChannelXMLPackage package = new TerrainChannelXMLPackage(Width, Height, Altitude, m_terrainData.GetCompressedMap()); XmlSerializer serializer = new XmlSerializer(typeof(TerrainChannelXMLPackage)); serializer.Serialize(xmlWriter, package); } @@ -331,38 +304,32 @@ namespace OpenSim.Region.Framework.Scenes { XmlSerializer serializer = new XmlSerializer(typeof(TerrainChannelXMLPackage)); TerrainChannelXMLPackage package = (TerrainChannelXMLPackage)serializer.Deserialize(xmlReader); - Width = package.SizeX; - Height = package.SizeY; - Altitude = package.SizeZ; - m_map = package.Map; + m_terrainData = new HeightmapTerrainData(package.Map, package.SizeX, package.SizeY, package.SizeZ); } // Fill the heightmap with the center bump terrain private void PinHeadIsland() { - int x; - for (x = 0; x < Width; x++) + for (int x = 0; x < Width; x++) { - int y; - for (y = 0; y < Height; y++) + for (int y = 0; y < Height; y++) { - int idx = x * (int)Width + y; - m_map[idx] = ToCompressedHeight(TerrainUtil.PerlinNoise2D(x, y, 2, 0.125) * 10); - short spherFacA = ToCompressedHeight(TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 50) * 0.01); - short spherFacB = ToCompressedHeight(TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 100) * 0.001); - if (m_map[idx] < spherFacA) - m_map[idx] = spherFacA; - if (m_map[idx] < spherFacB) - m_map[idx] = spherFacB; + m_terrainData[x, y] = (float)TerrainUtil.PerlinNoise2D(x, y, 2, 0.125) * 10; + float spherFacA = (float)(TerrainUtil.SphericalFactor(x, y, m_terrainData.SizeX / 2.0, m_terrainData.SizeY / 2.0, 50) * 0.01d); + float spherFacB = (float)(TerrainUtil.SphericalFactor(x, y, m_terrainData.SizeX / 2.0, m_terrainData.SizeY / 2.0, 100) * 0.001d); + if (m_terrainData[x, y]< spherFacA) + m_terrainData[x, y]= spherFacA; + if (m_terrainData[x, y]< spherFacB) + m_terrainData[x, y] = spherFacB; } } } private void FlatLand() { - short flatHeight = ToCompressedHeight(21); - for (int ii = 0; ii < m_map.Length; ii++) - m_map[ii] = flatHeight; + for (int xx = 0; xx < Width; xx++) + for (int yy = 0; yy < Height; yy++) + m_terrainData[xx, yy] = 21; } } } diff --git a/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs b/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs index 2e856bc844..511745df14 100644 --- a/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs +++ b/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs @@ -113,22 +113,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP // routines (like IClientAPI) only pass the float array of heights around. This entry // converts that legacy representation into the more compact represenation used in // TerrainChannel. Someday fix the plumbing between here and the scene. - public static LayerDataPacket CreateLandPacket(float[] heightmap, int patchX, int patchY, uint sizeX, uint sizeY) + public static LayerDataPacket CreateLandPacket(TerrainData terrData, int patchX, int patchY) { int[] xPieces = new int[1]; int[] yPieces = new int[1]; - - short[] newmap = new short[heightmap.Length]; - for (int ii = 0; ii < heightmap.Length; ii++) - newmap[ii] = TerrainChannel.ToCompressedHeight(heightmap[ii]); - xPieces[0] = patchX; // patch X dimension yPieces[0] = patchY; m_log.DebugFormat("{0} CreateLandPacket. patchX={1}, patchY={2}, sizeX={3}, sizeY={4}", - LogHeader, patchX, patchY, sizeX, sizeY); + LogHeader, patchX, patchY, terrData.SizeX, terrData.SizeY); - return CreateLandPacket(newmap, xPieces, yPieces, (int)TerrainPatch.LayerType.Land, sizeX, sizeY); + return CreateLandPacket(terrData, xPieces, yPieces, (int)TerrainPatch.LayerType.Land); } /// @@ -153,8 +148,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// /// - public static LayerDataPacket CreateLandPacket(short[] heightmap, int[] x, int[] y, byte type, - uint pRegionSizeX, uint pRegionSizeY) + public static LayerDataPacket CreateLandPacket(TerrainData terrData, int[] x, int[] y, byte type) { LayerDataPacket layer = new LayerDataPacket {LayerID = {Type = type}}; @@ -168,7 +162,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP bitpack.PackBits(type, 8); for (int i = 0; i < x.Length; i++) - CreatePatchFromHeightmap(bitpack, heightmap, x[i], y[i], pRegionSizeX, pRegionSizeY); + CreatePatchFromHeightmap(bitpack, terrData, x[i], y[i]); bitpack.PackBits(END_OF_PATCHES, 8); @@ -217,14 +211,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// /// - public static void CreatePatchFromHeightmap(BitPack output, short[] heightmap, int patchX, int patchY, - uint pRegionSizeX, uint pRegionSizeY) + public static void CreatePatchFromHeightmap(BitPack output, TerrainData terrData, int patchX, int patchY) { - TerrainPatch.Header header = PrescanPatch(heightmap, patchX, patchY, pRegionSizeX, pRegionSizeY); + TerrainPatch.Header header = PrescanPatch(terrData, patchX, patchY); header.QuantWBits = 136; // If larger than legacy region size, pack patch X and Y info differently. - if (pRegionSizeX > Constants.RegionSize || pRegionSizeY > Constants.RegionSize) + if (terrData.SizeX > Constants.RegionSize || terrData.SizeY > Constants.RegionSize) { header.PatchIDs = (patchY & 0xFFFF); header.PatchIDs += (patchX << 16); @@ -237,8 +230,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // NOTE: No idea what prequant and postquant should be or what they do int wbits; - int[] patch = CompressPatch(heightmap, patchX, patchY, header, 10, pRegionSizeX, pRegionSizeY, out wbits); - wbits = EncodePatchHeader(output, header, patch, pRegionSizeX, pRegionSizeY, wbits); + int[] patch = CompressPatch(terrData, patchX, patchY, header, 10, out wbits); + wbits = EncodePatchHeader(output, header, patch, (uint)terrData.SizeX, (uint)terrData.SizeY, wbits); EncodePatch(output, patch, 0, wbits); } @@ -262,19 +255,18 @@ namespace OpenSim.Region.ClientStack.LindenUDP } // Scan the height info we're returning and return a patch packet header for this patch. - // TODO. Why are patches ordered Y,X rather than X,Y? - private static TerrainPatch.Header PrescanPatch(short[] heightmap, int patchX, int patchY, - uint pRegionSizeX, uint pRegionSizeY) + private static TerrainPatch.Header PrescanPatch(TerrainData terrData, int patchX, int patchY) { TerrainPatch.Header header = new TerrainPatch.Header(); - short zmax = -32767; - short zmin = 32767; + float zmax = -99999999.0f; + float zmin = 99999999.0f; for (int j = patchY*16; j < (patchY + 1)*16; j++) { for (int i = patchX*16; i < (patchX + 1)*16; i++) { - short val = heightmap[j*pRegionSizeX + i]; + // short val = heightmap[j*pRegionSizeX + i]; + float val = terrData[j, i]; if (val > zmax) zmax = val; if (val < zmin) zmin = val; } @@ -282,8 +274,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP // Since the the min and max values are the shorts, rescale to be real values. // TODO: all this logic should go into the class wrapping the short values. - header.DCOffset = TerrainChannel.FromCompressedHeight(zmin); - header.Range = (int)(TerrainChannel.FromCompressedHeight(zmax) - TerrainChannel.FromCompressedHeight(zmin) + 1.0f); + header.DCOffset = zmin; + header.Range = (int)(zmax - zmin + 1.0f); return header; } @@ -812,8 +804,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP return itemp; } - private static int[] CompressPatch(short[] heightmap, int patchX, int patchY, TerrainPatch.Header header, - int prequant, uint pRegionSizeX, uint pRegionSizeY, out int wbits) + private static int[] CompressPatch(TerrainData terrData, int patchX, int patchY, TerrainPatch.Header header, + int prequant, out int wbits) { float[] block = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize]; int wordsize = prequant; @@ -827,19 +819,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP int k = 0; - premult /= Constants.TerrainCompression; // put here short to float factor - int jPatchLimit = patchY; - if (patchY >= (pRegionSizeY / Constants.TerrainPatchSize)) + if (patchY >= (terrData.SizeY / Constants.TerrainPatchSize)) { - jPatchLimit = (int)(pRegionSizeY - Constants.TerrainPatchSize) / Constants.TerrainPatchSize; + jPatchLimit = (int)(terrData.SizeY - Constants.TerrainPatchSize) / Constants.TerrainPatchSize; } jPatchLimit = (jPatchLimit + 1) * Constants.TerrainPatchSize; int iPatchLimit = patchX; - if (patchX >= (pRegionSizeX / Constants.TerrainPatchSize)) + if (patchX >= (terrData.SizeX / Constants.TerrainPatchSize)) { - iPatchLimit = (int)(pRegionSizeX - Constants.TerrainPatchSize) / Constants.TerrainPatchSize; + iPatchLimit = (int)(terrData.SizeX - Constants.TerrainPatchSize) / Constants.TerrainPatchSize; } iPatchLimit = (iPatchLimit + 1) * Constants.TerrainPatchSize; @@ -847,7 +837,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP { for (int i = patchX * Constants.TerrainPatchSize; i < iPatchLimit; i++) { - block[k++] = (heightmap[j*pRegionSizeX + i])*premult - sub; + // block[k++] = (heightmap[j*pRegionSizeX + i])*premult - sub; + block[k++] = terrData[j, i] - sub; } } diff --git a/OpenSim/Services/Interfaces/IGridService.cs b/OpenSim/Services/Interfaces/IGridService.cs index 14b6d1a4ad..56171b1bea 100644 --- a/OpenSim/Services/Interfaces/IGridService.cs +++ b/OpenSim/Services/Interfaces/IGridService.cs @@ -246,8 +246,8 @@ namespace OpenSim.Services.Interfaces public GridRegion(RegionInfo ConvertFrom) { m_regionName = ConvertFrom.RegionName; - m_regionLocX = (int)(ConvertFrom.LegacyRegionLocX * Constants.RegionSize); - m_regionLocY = (int)(ConvertFrom.LegacyRegionLocY * Constants.RegionSize); + m_regionLocX = (int)(ConvertFrom.RegionWorldLocX); + m_regionLocY = (int)(ConvertFrom.RegionWorldLocY); m_internalEndPoint = ConvertFrom.InternalEndPoint; m_externalHostName = ConvertFrom.ExternalHostName; m_httpPort = ConvertFrom.HttpPort;