diff --git a/OpenSim/Framework/Constants.cs b/OpenSim/Framework/Constants.cs index a2eb5ee18e..79791327db 100644 --- a/OpenSim/Framework/Constants.cs +++ b/OpenSim/Framework/Constants.cs @@ -30,9 +30,18 @@ namespace OpenSim.Framework { public class Constants { + // 'RegionSize' captures the legacy region size. + // DO NOT USE THIS FOR ANY NEW CODE. Use Scene.RegionSize[XYZ] as a region might not + // be the legacy region size. public const uint RegionSize = 256; public const uint RegionHeight = 4096; + + // Terrain heightmap is kept as shorts that are the float value times this compression factor + 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 string DefaultTexture = "89556747-24cb-43ed-920b-47caed15465f"; public enum EstateAccessCodex : uint diff --git a/OpenSim/Framework/RegionInfo.cs b/OpenSim/Framework/RegionInfo.cs index 2d3c9ea9d6..882fe332bd 100644 --- a/OpenSim/Framework/RegionInfo.cs +++ b/OpenSim/Framework/RegionInfo.cs @@ -472,7 +472,7 @@ namespace OpenSim.Framework /// The x co-ordinate of this region in map tiles (e.g. 1000). /// Coordinate is scaled as world coordinates divided by the legacy region size /// and is thus is the number of legacy regions. - /// This entrypoint exists for downward compatability for external modules. + /// DO NOT USE FOR NEW CODE! This entrypoint exists for downward compatability with external modules. /// public uint RegionLocX { @@ -480,6 +480,18 @@ namespace OpenSim.Framework set { LegacyRegionLocX = value; } } + /// + /// The y co-ordinate of this region in map tiles (e.g. 1000). + /// Coordinate is scaled as world coordinates divided by the legacy region size + /// and is thus is the number of legacy regions. + /// DO NOT USE FOR NEW CODE! This entrypoint exists for downward compatability with external modules. + /// + public uint RegionLocY + { + get { return LegacyRegionLocY; } + set { LegacyRegionLocY = value; } + } + public void SetDefaultRegionSize() { RegionWorldLocX = 0; @@ -490,19 +502,6 @@ namespace OpenSim.Framework RegionSizeZ = Constants.RegionHeight; } - - /// - /// The y co-ordinate of this region in map tiles (e.g. 1000). - /// Coordinate is scaled as world coordinates divided by the legacy region size - /// and is thus is the number of legacy regions. - /// This entrypoint exists for downward compatability for external modules. - /// - public uint RegionLocY - { - get { return LegacyRegionLocY; } - set { LegacyRegionLocY = value; } - } - // A unique region handle is created from the region's world coordinates. // This cannot be changed because some code expects to receive the region handle and then // compute the region coordinates from it. diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index 7b7daedf30..3396c322db 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -1240,9 +1240,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP try { int[] patches = new int[] { py * 16 + px }; - float[] heightmap = (map.Length == 65536) ? - map : - LLHeightFieldMoronize(map); + float[] heightmap = (map.Length == 65536) ? map : LLHeightFieldMoronize(map); LayerDataPacket layerpack = TerrainCompressor.CreateLandPacket(heightmap, patches); diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs index d78ade5fa7..d5c77ec3e1 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs @@ -67,7 +67,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { using (Bitmap bitmap = new Bitmap(filename)) { - ITerrainChannel retval = new TerrainChannel(true); + ITerrainChannel retval = new TerrainChannel(w, h); for (int x = 0; x < retval.Width; x++) { diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index c1ffd229db..2fff4c1ac0 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -130,15 +130,14 @@ namespace OpenSim.Region.CoreModules.World.Terrain { if (m_scene.Heightmap == null) { - m_channel = new TerrainChannel(m_InitialTerrain); + m_channel = new TerrainChannel(m_InitialTerrain, + m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY, m_scene.RegionInfo.RegionSizeZ); m_scene.Heightmap = m_channel; - m_revert = new TerrainChannel(); UpdateRevertMap(); } else { m_channel = m_scene.Heightmap; - m_revert = new TerrainChannel(); UpdateRevertMap(); } @@ -532,6 +531,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// public void UpdateRevertMap() { + /* int x; for (x = 0; x < m_channel.Width; x++) { @@ -541,6 +541,8 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_revert[x, y] = m_channel[x, y]; } } + */ + m_revert = m_channel.MakeCopy(); } /// diff --git a/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs b/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs index c936a8478f..847d245366 100644 --- a/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs +++ b/OpenSim/Region/Framework/Interfaces/ISimulationDataStore.cs @@ -134,26 +134,26 @@ namespace OpenSim.Region.Framework.Interfaces Dictionary GetExtra(UUID regionID); 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 + } + + // 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 e467701351..3c060a47e2 100644 --- a/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs +++ b/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs @@ -29,15 +29,21 @@ namespace OpenSim.Region.Framework.Interfaces { public interface ITerrainChannel { - int Height { get; } + int Width { get;} // X dimension + int Height { get;} // Y dimension + int Altitude { get;} // Z dimension + double this[int x, int y] { get; set; } - int Width { get; } /// /// 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(); bool Tainted(int x, int y); diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index e63963a69a..b714c7fa0e 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -1905,13 +1905,13 @@ namespace OpenSim.Region.Framework.Scenes m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain); m_log.InfoFormat("[TERRAIN]: No default terrain. Generating a new terrain {0}.", m_InitialTerrain); - Heightmap = new TerrainChannel(m_InitialTerrain); + Heightmap = new TerrainChannel(m_InitialTerrain, RegionInfo.RegionSizeX, RegionInfo.RegionSizeY, RegionInfo.RegionSizeZ); SimulationDataService.StoreTerrain(Heightmap.GetDoubles(), RegionInfo.RegionID); } else { - Heightmap = new TerrainChannel(map); + Heightmap = new TerrainChannel(map, RegionInfo.RegionSizeZ); } } catch (IOException e) diff --git a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs index c0ca48ef39..fef93bf7c3 100644 --- a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs +++ b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs @@ -40,132 +40,125 @@ namespace OpenSim.Region.Framework.Scenes /// public class TerrainChannel : ITerrainChannel { - private readonly bool[,] taint; - private double[,] map; + protected bool[,] m_taint; + protected short[] m_map; + public int Width { get; private set; } // 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 + + // Default, not-often-used builder public TerrainChannel() { - map = new double[Constants.RegionSize, Constants.RegionSize]; - taint = new bool[Constants.RegionSize / 16, Constants.RegionSize / 16]; - - PinHeadIsland(); + InitializeStructures(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight, false); + FlatLand(); + // PinHeadIsland(); } - public TerrainChannel(String type) + // Create terrain of given size + public TerrainChannel(int pX, int pY) { - map = new double[Constants.RegionSize, Constants.RegionSize]; - taint = new bool[Constants.RegionSize / 16, Constants.RegionSize / 16]; + InitializeStructures((uint)pX, (uint)pY, Constants.RegionHeight, true); + } + // 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) + { + InitializeStructures(pX, pY, pZ, false); if (type.Equals("flat")) FlatLand(); else PinHeadIsland(); } - public TerrainChannel(double[,] import) + public TerrainChannel(double[,] pM, uint pH) { - map = import; - taint = new bool[import.GetLength(0),import.GetLength(1)]; - } - - public TerrainChannel(bool createMap) - { - if (createMap) - { - map = new double[Constants.RegionSize,Constants.RegionSize]; - taint = new bool[Constants.RegionSize / 16,Constants.RegionSize / 16]; - } - } - - public TerrainChannel(int w, int h) - { - map = new double[w,h]; - taint = new bool[w / 16,h / 16]; + 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]); } #region ITerrainChannel Members - public int Width - { - get { return map.GetLength(0); } - } - - public int Height - { - get { return map.GetLength(1); } - } - + // ITerrainChannel.MakeCopy() public ITerrainChannel MakeCopy() { - TerrainChannel copy = new TerrainChannel(false); - copy.map = (double[,]) map.Clone(); - - return copy; + return this.Copy(); } + // ITerrainChannel.GetCompressedMap() + public short[] GetCompressedMap() + { + return m_map; + } + + // ITerrainChannel.GetFloatsSerialized() public float[] GetFloatsSerialised() { - // Move the member variables into local variables, calling - // member variables 256*256 times gets expensive - int w = Width; - int h = Height; - float[] heights = new float[w * h]; + int points = Width * Height; + float[] heights = new float[points]; + + for (int ii = 0; ii < points; ii++) + heights[ii] = FromCompressedHeight(m_map[ii]); + + return heights; + } + + // ITerrainChannel.GetDoubles() + public double[,] GetDoubles() + { + int w = Width; + int l = Height; + double[,] heights = new double[w, l]; - int i, j; // map coordinates int idx = 0; // index into serialized array - for (i = 0; i < h; i++) + for (int ii = 0; ii < l; ii++) { - for (j = 0; j < w; j++) + for (int jj = 0; jj < w; jj++) { - heights[idx++] = (float)map[j, i]; + heights[ii, jj] = (double)FromCompressedHeight(m_map[idx]); + idx++; } } return heights; } - public double[,] GetDoubles() - { - return map; - } - + // ITerrainChannel.this[x,y] public double this[int x, int y] { - get { return map[x, y]; } + get { return m_map[x * Width + y]; } set { // Will "fix" terrain hole problems. Although not fantastically. if (Double.IsNaN(value) || Double.IsInfinity(value)) return; - if (map[x, y] != value) + int idx = x * Width + y; + if (m_map[idx] != value) { - taint[x / 16, y / 16] = true; - map[x, y] = value; + m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize] = true; + m_map[idx] = ToCompressedHeight(value); } } } + // ITerrainChannel.Tainted() public bool Tainted(int x, int y) { - if (taint[x / 16, y / 16]) + if (m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize]) { - taint[x / 16, y / 16] = false; + m_taint[x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize] = 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() { XmlWriterSettings settings = new XmlWriterSettings(); @@ -181,13 +174,7 @@ namespace OpenSim.Region.Framework.Scenes } } - private void WriteXml(XmlWriter writer) - { - writer.WriteStartElement(String.Empty, "TerrainMap", String.Empty); - ToXml(writer); - writer.WriteEndElement(); - } - + // ITerrainChannel.LoadFromXmlString() public void LoadFromXmlString(string data) { StringReader sr = new StringReader(data); @@ -199,12 +186,89 @@ namespace OpenSim.Region.Framework.Scenes sr.Close(); } - private void ReadXml(XmlReader reader) + #endregion + + private void InitializeStructures(uint pX, uint pY, uint pZ, bool shouldInitializeHeightmap) { - reader.ReadStartElement("TerrainMap"); - FromXml(reader); + 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) + { + return (short)(pHeight * Constants.TerrainCompression); + } + + public 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; + + return copy; + } + + private void WriteXml(XmlWriter writer) + { + if (Width == Constants.RegionSize && Height == Constants.RegionSize) + { + // Downward compatibility for legacy region terrain maps. + // If region is exactly legacy size, return the old format XML. + writer.WriteStartElement(String.Empty, "TerrainMap", String.Empty); + ToXml(writer); + writer.WriteEndElement(); + } + else + { + // New format XML that includes width and length. + writer.WriteStartElement(String.Empty, "TerrainMap2", String.Empty); + ToXml2(writer); + writer.WriteEndElement(); + } + } + + private void ReadXml(XmlReader reader) + { + // Check the first element. If legacy element, use the legacy reader. + if (reader.IsStartElement("TerrainMap")) + { + reader.ReadStartElement("TerrainMap"); + FromXml(reader); + } + else + { + reader.ReadStartElement("TerrainMap2"); + FromXml2(reader); + } + } + + // Write legacy terrain map. Presumed to be 256x256 of data encoded as floats in a byte array. private void ToXml(XmlWriter xmlWriter) { float[] mapData = GetFloatsSerialised(); @@ -218,6 +282,7 @@ namespace OpenSim.Region.Framework.Scenes serializer.Serialize(xmlWriter, buffer); } + // Read legacy terrain map. Presumed to be 256x256 of data encoded as floats in a byte array. private void FromXml(XmlReader xmlReader) { XmlSerializer serializer = new XmlSerializer(typeof(byte[])); @@ -236,35 +301,68 @@ namespace OpenSim.Region.Framework.Scenes } } + private class TerrainChannelXMLPackage + { + public int Version; + public int SizeX; + public int SizeY; + public int SizeZ; + public short[] Map; + public TerrainChannelXMLPackage(int pX, int pY, int pZ, short[] pMap) + { + Version = 1; + SizeX = pX; + SizeY = pY; + SizeZ = pZ; + 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_map); + 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); + Width = package.SizeX; + Height = package.SizeY; + Altitude = package.SizeZ; + m_map = package.Map; + } + + // Fill the heightmap with the center bump terrain private void PinHeadIsland() { int x; - for (x = 0; x < Constants.RegionSize; x++) + for (x = 0; x < Width; x++) { int y; - for (y = 0; y < Constants.RegionSize; y++) + for (y = 0; y < Height; y++) { - map[x, y] = TerrainUtil.PerlinNoise2D(x, y, 2, 0.125) * 10; - double spherFacA = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 50) * 0.01; - double spherFacB = TerrainUtil.SphericalFactor(x, y, Constants.RegionSize / 2.0, Constants.RegionSize / 2.0, 100) * 0.001; - if (map[x, y] < spherFacA) - map[x, y] = spherFacA; - if (map[x, y] < spherFacB) - map[x, y] = spherFacB; + 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; } } } private void FlatLand() { - int x; - for (x = 0; x < Constants.RegionSize; x++) - { - int y; - for (y = 0; y < Constants.RegionSize; y++) - map[x, y] = 21; - } + short flatHeight = ToCompressedHeight(21); + for (int ii = 0; ii < m_map.Length; ii++) + m_map[ii] = flatHeight; } - } }