diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/BMP.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/BMP.cs index ec2d085878..ba80091842 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/BMP.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/BMP.cs @@ -40,6 +40,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders /// internal class BMP : GenericSystemDrawing { + public override int SupportedHeight + { + get { return 256; } + } + /// /// Exports a file to a image on the disk using a System.Drawing exporter. /// @@ -76,5 +81,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { return false; } + + public override bool SupportsExtendedTileSave() + { + return false; + } } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GIF.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GIF.cs index 384370890d..0956534eaa 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GIF.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GIF.cs @@ -34,6 +34,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { internal class GIF : GenericSystemDrawing { + public override int SupportedHeight + { + get { return 256; } + } public override void SaveFile(string filename, ITerrainChannel map) { using(Bitmap colours = CreateGrayscaleBitmapFromMap(map)) @@ -61,5 +65,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { return false; } + + public override bool SupportsExtendedTileSave() + { + return false; + } } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs index bcd9dcd21c..251d8a6bad 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/GenericSystemDrawing.cs @@ -49,6 +49,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders get { return ".gsd"; } } + public virtual int SupportedHeight + { + get { return 256; } + } + /// /// Loads a file from a specified filename on the disk, /// parses the image using the System.Drawing parsers @@ -203,6 +208,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders return false; } + public virtual bool SupportsExtendedTileSave() + { + return false; + } + /// /// Protected method, generates a grayscale bitmap /// image from a specified terrain channel. @@ -244,6 +254,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders } return bmp; } + + public virtual void SaveFile(ITerrainChannel map, string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY) + { + throw new NotImplementedException(); + } } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/JPEG.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/JPEG.cs index d604dc72f3..0abb4693d2 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/JPEG.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/JPEG.cs @@ -42,6 +42,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders get { return ".jpg"; } } + public int SupportedHeight + { + get { return 256; } + } + public ITerrainChannel LoadFile(string filename) { throw new NotImplementedException(); @@ -94,6 +99,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { return false; } + public bool SupportsExtendedTileSave() + { + return false; + } + private static Bitmap CreateBitmapFromMap(ITerrainChannel map) { @@ -125,5 +135,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders } return bmp; } + + public void SaveFile(ITerrainChannel map, string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY) + { + throw new NotImplementedException(); + } } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs index d8f72d942d..3ec50cbe68 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/LLRAW.cs @@ -255,6 +255,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { get { return ".raw"; } } + public int SupportedHeight + { + get { return 4096; } + } + public virtual void SaveFile(ITerrainChannel m_channel, string filename, int offsetX, int offsetY, @@ -276,5 +281,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { return false; } + + public bool SupportsExtendedTileSave() + { + return false; + } + + public void SaveFile(ITerrainChannel map, string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/PNG.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/PNG.cs index 8ea8e9d61d..f135d6b3d4 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/PNG.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/PNG.cs @@ -34,6 +34,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { internal class PNG : GenericSystemDrawing { + public override int SupportedHeight + { + get { return 256; } + } + public override void SaveFile(string filename, ITerrainChannel map) { using(Bitmap colours = CreateGrayscaleBitmapFromMap(map)) @@ -61,5 +66,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { return true; } + + public override bool SupportsExtendedTileSave() + { + return false; + } } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs index d467abb80a..1f99f13721 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/RAW32.cs @@ -43,6 +43,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders get { return ".r32"; } } + public int SupportedHeight + { + get { return 4096; } + } + public ITerrainChannel LoadFile(string filename) { FileInfo file = new FileInfo(filename); @@ -190,5 +195,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { return false; } + + public bool SupportsExtendedTileSave() + { + return false; + } + + public void SaveFile(ITerrainChannel map, string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY) + { + throw new NotImplementedException(); + } } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/TIFF.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/TIFF.cs index d103a6fd1e..1327ef5d98 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/TIFF.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/TIFF.cs @@ -25,32 +25,23 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +using System; +using System.Collections.Generic; using System.Drawing; -using System.Drawing.Imaging; using System.IO; +using System.Linq; +using OpenMetaverse.ImportExport.Collada14; using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { internal class TIFF : GenericSystemDrawing { - public override void SaveFile(string filename, ITerrainChannel map) + public override int SupportedHeight { - using(Bitmap colours = CreateGrayscaleBitmapFromMap(map)) - colours.Save(filename,ImageFormat.Tiff); + get { return 4096; } } - - /// - /// Exports a stream using a System.Drawing exporter. - /// - /// The target stream - /// The terrain channel being saved - public override void SaveStream(Stream stream, ITerrainChannel map) - { - using(Bitmap colours = CreateGrayscaleBitmapFromMap(map)) - colours.Save(stream,ImageFormat.Tiff); - } - public override string ToString() { return "TIFF"; @@ -59,7 +50,926 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders //Returns true if this extension is supported for terrain save-tile public override bool SupportsTileSave() { - return false; + return true; } + + public override bool SupportsExtendedTileSave() + { + return true; + } + + public override ITerrainChannel LoadFile(string filename) + { + return LoadTIFF(filename, out int _, null, null); + } + + public override ITerrainChannel LoadFile(string filename, int x, int y, int fileWidth, int fileHeight, int w, int h) + { + return LoadTIFF(filename, out int _, null, [x, y, fileWidth, fileHeight, w, h]); + } + + public override ITerrainChannel LoadStream(Stream stream) + { + return LoadTIFF(String.Empty, out int _, stream, null); + } + + public override void SaveFile(string filename, ITerrainChannel map) + { + // From now saving in 32 bit format only + FileStream fs = new(filename, FileMode.Create); + CreateFile32Bit(fs, map); + } + + // Saves existing terrain to larger or smaller file with meter based coordinates and offsets + public override void SaveFile(ITerrainChannel map, string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY) + { + if (map is null) + { + Console.WriteLine("Existing terrain not found"); + return; + } + + if (startX >= map.Width || stopX >= map.Width || startY >= map.Height || stopY >= map.Height) + { + Console.WriteLine("Map coordinates outside of region"); + return; + } + + if (offsetX >= fileWidth || offsetY >= fileHeight || startX + offsetX >= fileWidth || stopX + offsetX >= fileWidth || startY + offsetY >= fileHeight || stopY + offsetY >= fileHeight) + { + Console.WriteLine("Save target coordinates outside of file"); + return; + } + + string tempName = null; + int bitsPerPixel = 16; + ITerrainChannel existingBitmap = null; + + if (File.Exists(filename)) + { + try + { + tempName = Path.GetTempFileName(); + File.Copy(filename, tempName, true); + Console.WriteLine("Using existing file " + tempName); + ITerrainChannel loaded = LoadTIFF(tempName, out bitsPerPixel, null, null); + + if (loaded != null) + { + existingBitmap = loaded; + } + } + catch (Exception e) + { + Console.WriteLine("Unable to copy temp file " + e.ToString()); + return; + } + } + + if (existingBitmap is null || !File.Exists(filename)) + { + Console.WriteLine("Generating new image"); + existingBitmap = new TerrainChannel(fileWidth, fileHeight); + } + + if ((bitsPerPixel.Equals(8) || bitsPerPixel.Equals(16)) && existingBitmap is not null) + { + ITerrainChannel cutmap = new TerrainChannel(stopX - startX, stopY - startY); + // First cut map to size + for (int x = 0; x < cutmap.Width; x++) + { + for (int y = 0; y < cutmap.Height; y++) + { + cutmap[x, y] = map[startX + x, startY + y]; + } + } + + // Now insert it at the requested spot + for (int x = 0; x < existingBitmap.Width; x++) + { + for (int y = 0; y < existingBitmap.Height; y++) + { + if (x >= offsetX || y >= offsetY) + { + existingBitmap[x, y] = cutmap[(x - offsetX), (y - offsetY)]; + } + } + } + + Console.WriteLine(existingBitmap.Width + " " + existingBitmap.Height); + + if (bitsPerPixel.Equals(32)) + { + FileStream fs = new(filename, FileMode.Create); + CreateFile32Bit(fs, existingBitmap); + } + else if (bitsPerPixel.Equals(16)) + { + FileStream fs = new(filename, FileMode.Create); + CreateFile16Bit(fs, existingBitmap); + } + else if (bitsPerPixel.Equals(8)) + { + CreateFile8Bit(filename, existingBitmap); + } + + if (tempName is not null) + { + if (File.Exists(tempName)) + { + try + { + Console.WriteLine("Removing " + tempName); + File.Delete(tempName); + } + catch (Exception e) + { + Console.WriteLine("Could not remove temp file " + e.ToString()); + return; + } + } + } + } + else + { + Console.WriteLine("Unrecognized color bit depth!"); + return; + } + + } + + public override void SaveFile(ITerrainChannel map, string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int regionSizeX, int regionSizeY) + { + // We need to do this because: + // "Saving the image to the same file it was constructed from is not allowed and throws an exception." + string tempName = null; + + ITerrainChannel existingBitmap = null; + + // Assume 16 bit from now on + int bitsPerPixel = 16; + + // Load existing file or create a new heightmap + if (File.Exists(filename)) + { + try + { + tempName = Path.GetTempFileName(); + File.Copy(filename, tempName, true); + Console.WriteLine("Using existing file " + tempName); + ITerrainChannel loaded = LoadTIFF(tempName, out bitsPerPixel, null, null); + + if (loaded != null) + { + if (loaded.Width.Equals(fileWidth * regionSizeX) && loaded.Height.Equals(fileHeight * regionSizeY)) + { + existingBitmap = loaded; + } + } + } + catch (Exception e) + { + Console.WriteLine("Unable to copy temp file " + e.ToString()); + return; + } + } + + if (existingBitmap is null || !File.Exists(filename)) + { + Console.WriteLine("Generating new image"); + existingBitmap = new TerrainChannel(fileWidth * regionSizeX, fileHeight * regionSizeY); + } + + if ((bitsPerPixel.Equals(8) || bitsPerPixel.Equals(16)) && existingBitmap is not null) + { + // Go over the whole thing and if the selected offsets match place the data there + for (int x = 0; x < existingBitmap.Width; x++) + { + for (int y = 0; y < existingBitmap.Height; y++) + { + if (x >= (offsetX * regionSizeX) && x <= ((offsetX * regionSizeX) + map.Width) + && y >= (offsetY * regionSizeY) && y <= ((offsetY * regionSizeY) + map.Height)) + { + existingBitmap[x, y] = map[x - (offsetX * regionSizeX), y - (offsetY * regionSizeY)]; + } + } + } + + Console.WriteLine(existingBitmap.Width + " " + existingBitmap.Height); + + if (bitsPerPixel.Equals(32)) + { + FileStream fs = new(filename, FileMode.Create); + CreateFile32Bit(fs, existingBitmap); + } + else if (bitsPerPixel.Equals(16)) + { + FileStream fs = new(filename, FileMode.Create); + CreateFile16Bit(fs, existingBitmap); + } + else if (bitsPerPixel.Equals(8)) + { + CreateFile8Bit(filename, existingBitmap); + } + + if (tempName is not null) + { + if (File.Exists(tempName)) + { + try + { + Console.WriteLine("Removing " + tempName); + File.Delete(tempName); + } + catch (Exception e) + { + Console.WriteLine("Could not remove temp file " + e.ToString()); + return; + } + } + } + } + else + { + Console.WriteLine("Unrecognized color bit depth!"); + return; + } + } + + + /// + /// Exports a stream to 32 bit tiff + /// + /// The target stream + /// The terrain channel being saved + public override void SaveStream(Stream stream, ITerrainChannel map) + { + // Switched to 32 bit from now on + CreateFile32Bit(stream, map); + } + + protected static ITerrainChannel LoadTIFF(string filename, out int bitsPerPixel, Stream stream = null, int[] options = null) + { + bitsPerPixel = 0; + if (stream == null) + { + FileStream fs = new(filename, FileMode.Open, FileAccess.Read); + bitsPerPixel = GetColorDepth(fs); + //int bitsPerPixel = Image.GetPixelFormatSize(file.PixelFormat); + if (!bitsPerPixel.Equals(0)) + { + if (bitsPerPixel.Equals(8)) + { + if (options is not null) + return Load8BitBitmapTile(filename, options[0], options[1], options[2], options[3], options[4], options[5]); + return LoadFrom8BitStream(fs); + } + else if (bitsPerPixel.Equals(16)) + { + if (options is not null) + return Load16BitBitmapTile(filename, options[0], options[1], options[2], options[3], options[4], options[5]); + return LoadFrom16BitStream(fs); + } + else if (bitsPerPixel.Equals(32)) + { + if (options is not null) + return Load32BitBitmapTile(filename, options[0], options[1], options[2], options[3], options[4], options[5]); + return LoadFrom32BitStream(fs); + } + } + } + else + { + bitsPerPixel = GetColorDepth(stream); + //int bitsPerPixel = Image.GetPixelFormatSize(file.PixelFormat); + if (!bitsPerPixel.Equals(0)) + { + if (bitsPerPixel.Equals(8)) + return LoadFrom8BitStream(stream); + else if (bitsPerPixel.Equals(16)) + return LoadFrom16BitStream(stream); + else if (bitsPerPixel.Equals(32)) + return LoadFrom32BitStream(stream); + } + } + Console.WriteLine("Unrecognized color bit depth!"); + return null; + } + + protected static void CreateFile8Bit(string filename, ITerrainChannel map) + { + Bitmap newbitmap = CreateGrayscaleBitmapFromMap(map); + newbitmap.Save(filename); + } + + protected static void CreateFile16Bit(Stream fs, ITerrainChannel map) + { + int width = map.Width; + int height = map.Height; + + ushort bitsPerSample = 16; + + // TIFF Header + fs.Write(BitConverter.GetBytes((ushort)0x4949), 0, 2); // Byte order: Little endian (II) + fs.Write(BitConverter.GetBytes((ushort)42), 0, 2); // TIFF magic number + fs.Write(BitConverter.GetBytes((uint)8), 0, 4); // Offset to first IFD (starts right after header) + + // IFD (Image File Directory) + fs.Write(BitConverter.GetBytes((ushort)8), 0, 2); // Number of directory entries + + long entriesStartOffset = fs.Position; + fs.Seek(8 * 12 + 4, SeekOrigin.Current); // Skip space for entries and next IFD offset + + // Image Width tag + fs.Seek(entriesStartOffset, SeekOrigin.Begin); + WriteIfdEntry(fs, 256, 4, 1, (uint)width); + + // Image Length tag + fs.Seek(entriesStartOffset + 12, SeekOrigin.Begin); + WriteIfdEntry(fs, 257, 4, 1, (uint)height); + + // Bits per Sample tag + fs.Seek(entriesStartOffset + 24, SeekOrigin.Begin); + WriteIfdEntry(fs, 258, 3, 1, bitsPerSample); + + // Compression tag + fs.Seek(entriesStartOffset + 36, SeekOrigin.Begin); + WriteIfdEntry(fs, 259, 3, 1, 1); // No compression (1) + + // Photometric Interpretation tag + fs.Seek(entriesStartOffset + 48, SeekOrigin.Begin); + WriteIfdEntry(fs, 262, 3, 1, 1); // Black is zero + + // Strip Offsets tag + fs.Seek(entriesStartOffset + 60, SeekOrigin.Begin); + long stripOffsetsPos = fs.Position; + WriteIfdEntry(fs, 273, 4, 1, 0); // Placeholder for strip offsets + + // Rows per Strip tag + fs.Seek(entriesStartOffset + 72, SeekOrigin.Begin); + WriteIfdEntry(fs, 278, 4, 1, (uint)height); + + // Strip Byte Counts tag + fs.Seek(entriesStartOffset + 84, SeekOrigin.Begin); + long stripByteCountsPos = fs.Position; + WriteIfdEntry(fs, 279, 4, 1, 0); // Placeholder for strip byte counts + + // Next IFD offset + fs.Seek(entriesStartOffset + 96, SeekOrigin.Begin); + fs.Write(BitConverter.GetBytes((uint)0), 0, 4); // No next IFD + + // Write Image Data + long imageDataOffset = fs.Position; + fs.Seek(stripOffsetsPos, SeekOrigin.Begin); + fs.Write(BitConverter.GetBytes((uint)imageDataOffset), 0, 4); + fs.Seek(stripByteCountsPos, SeekOrigin.Begin); + fs.Write(BitConverter.GetBytes((uint)(width * height * (bitsPerSample / 8))), 0, 4); + + fs.Seek(imageDataOffset, SeekOrigin.Begin); + + // Write grayscale image data (16-bit per pixel) + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float terrainheight = map[x, y] + 256; + ushort pixelValue = (ushort)((int)Normalize(terrainheight)); + fs.Write(BitConverter.GetBytes(pixelValue), 0, 2); + } + } + fs.Dispose(); + } + + /*protected static void CreateFile16Bit(Stream fs, ITerrainChannel map) + { + int width = map.Width; + int height = map.Height; + + ushort bitsPerSample = 16; + + BinaryWriter writer = new(fs); + + // TIFF Header + writer.Write((ushort)0x4949); // Byte order: Little endian (II) + writer.Write((ushort)42); // TIFF magic number + writer.Write((uint)8); // Offset to first IFD (starts right after header) + + // IFD (Image File Directory) + writer.Write((ushort)8); // Number of directory entries + + long entriesStartOffset = fs.Position; + fs.Seek(8 * 12 + 4, SeekOrigin.Current); // Skip space for entries and next IFD offset + + // Image Width tag + writer.Seek((int)entriesStartOffset, SeekOrigin.Begin); + writer.Write((ushort)256); // Tag ID + writer.Write((ushort)4); // Type: LONG + writer.Write((uint)1); // Count + writer.Write((uint)width); // Value + + // Image Length tag + writer.Seek((int)entriesStartOffset + 12, SeekOrigin.Begin); + writer.Write((ushort)257); // Tag ID + writer.Write((ushort)4); // Type: LONG + writer.Write((uint)1); // Count + writer.Write((uint)height); // Value + + // Bits per Sample tag + writer.Seek((int)entriesStartOffset + 24, SeekOrigin.Begin); + writer.Write((ushort)258); // Tag ID + writer.Write((ushort)3); // Type: SHORT + writer.Write((uint)1); // Count + writer.Write((uint)bitsPerSample); // Value + + // Compression tag + writer.Seek((int)entriesStartOffset + 36, SeekOrigin.Begin); + writer.Write((ushort)259); // Tag ID + writer.Write((ushort)3); // Type: SHORT + writer.Write((uint)1); // Count + writer.Write((ushort)1); // No compression (1) + + // Photometric Interpretation tag + writer.Seek((int)entriesStartOffset + 48, SeekOrigin.Begin); + writer.Write((ushort)262); // Tag ID + writer.Write((ushort)3); // Type: SHORT + writer.Write((uint)1); // Count + writer.Write((ushort)1); // Black is zero + + // Strip Offsets tag + writer.Seek((int)entriesStartOffset + 60, SeekOrigin.Begin); + writer.Write((ushort)273); // Tag ID + writer.Write((ushort)4); // Type: LONG + writer.Write((uint)1); // Count + long stripOffsetsPos = fs.Position; + writer.Write((uint)0); // Placeholder for strip offsets + + // Rows per Strip tag + writer.Seek((int)entriesStartOffset + 72, SeekOrigin.Begin); + writer.Write((ushort)278); // Tag ID + writer.Write((ushort)4); // Type: LONG + writer.Write((uint)1); // Count + writer.Write((uint)height); // Value + + // Strip Byte Counts tag + writer.Seek((int)entriesStartOffset + 84, SeekOrigin.Begin); + writer.Write((ushort)279); // Tag ID + writer.Write((ushort)4); // Type: LONG + writer.Write((uint)1); // Count + long stripByteCountsPos = fs.Position; + writer.Write((uint)0); // Placeholder for strip byte counts + + // Next IFD offset + writer.Seek((int)entriesStartOffset + 96, SeekOrigin.Begin); + writer.Write((uint)0); // No next IFD + + // Write Image Data + long imageDataOffset = fs.Position; + writer.Seek((int)stripOffsetsPos, SeekOrigin.Begin); + writer.Write((uint)imageDataOffset); + writer.Seek((int)stripByteCountsPos, SeekOrigin.Begin); + writer.Write((uint)(width * height * (bitsPerSample / 8))); + + writer.Seek((int)imageDataOffset, SeekOrigin.Begin); + + // Write grayscale image data (16-bit per pixel) + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + float terrainheight = map[x, y] + 256; + ushort pixelValue = (ushort)((int)Normalize(terrainheight)); + writer.Write(pixelValue); + } + } + fs.Dispose(); + }*/ + + protected static void CreateFile32Bit(Stream fs, ITerrainChannel map) + { + Console.WriteLine("Saving 32 bit..."); + + // Prepare TIFF header + byte[] tiffHeader = new byte[8] { 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00 }; + fs.Write(tiffHeader, 0, 8); + + // Number of IFD entries + ushort numEntries = 13; + fs.Write(BitConverter.GetBytes(numEntries), 0, 2); + + // Calculate offsets + uint resolutionOffset = (uint)(8 + 2 + numEntries * 12 + 4); + uint stripOffset = resolutionOffset + 16; // 16 bytes for XResolution and YResolution + uint stripByteCount = (uint)(map.Width * map.Height * 4); + + // Write IFD entries + WriteIfdEntry(fs, 0x0100, 4, 1, (uint)map.Width); // ImageWidth + WriteIfdEntry(fs, 0x0101, 4, 1, (uint)map.Height); // ImageLength + WriteIfdEntry(fs, 0x0102, 3, 1, 32); // BitsPerSample (32 bits per sample) + WriteIfdEntry(fs, 0x0103, 3, 1, 1); // Compression (1 = No compression) + WriteIfdEntry(fs, 0x0106, 3, 1, 1); // Photometric Interpretation (1 = BlackIsZero) + WriteIfdEntry(fs, 0x0111, 4, 1, stripOffset); // StripOffsets + WriteIfdEntry(fs, 0x0115, 3, 1, 1); // SamplesPerPixel (1 = grayscale) + WriteIfdEntry(fs, 0x0116, 4, 1, (uint)map.Height); // RowsPerStrip (same as image height) + WriteIfdEntry(fs, 0x0117, 4, 1, stripByteCount); // StripByteCounts + WriteIfdEntry(fs, 0x011A, 5, 1, resolutionOffset); // XResolution (offset to resolution data) + WriteIfdEntry(fs, 0x011B, 5, 1, resolutionOffset + 8); // YResolution (offset to resolution data) + WriteIfdEntry(fs, 0x0128, 3, 1, 2); // ResolutionUnit (2 = inch) + WriteIfdEntry(fs, 0x0153, 3, 1, 3); // SampleFormat (3 = Floating Point) + + // Offset to next IFD (0 = no next IFD) + fs.Write(BitConverter.GetBytes(0u), 0, 4); + + // Write resolution data (XResolution and YResolution) + WriteRational(fs, (uint)map.Width, 1); + WriteRational(fs, (uint)map.Height, 1); + + // Write pixel data + for (int y = 0; y < map.Height; y++) + { + for (int x = 0; x < map.Width; x++) + { + float terrainheight = map[x, y] + 256.0f; + float pixelValue = Normalize(terrainheight); + //Console.Write(pixelValue.ToString() + " "); + byte[] pixelBytes = BitConverter.GetBytes(pixelValue); + fs.Write(pixelBytes, 0, pixelBytes.Length); + } + } + fs.Dispose(); + } + + protected static ITerrainChannel LoadFrom8BitStream(Stream stream) + { + Bitmap bitmap = new(stream); + ITerrainChannel retval = new TerrainChannel(bitmap.Width, bitmap.Height); + + for (int x = 0; x < bitmap.Width; x++) + { + for (int y = 0; y < bitmap.Height; y++) + { + retval[x, y] = bitmap.GetPixel(x, bitmap.Height - y - 1).GetBrightness() * 128; + } + } + return retval; + } + + protected static ITerrainChannel LoadFrom16BitStream(Stream stream) + { + BinaryReader reader = new(stream); + + // TIFF Header + ushort byteOrder = reader.ReadUInt16(); + if (byteOrder != 0x4949) // Only handle little-endian (II) + throw new Exception("Unsupported byte order"); + + ushort magicNumber = reader.ReadUInt16(); + if (magicNumber != 42) + throw new Exception("Invalid TIFF file"); + + uint ifdOffset = reader.ReadUInt32(); + stream.Seek(ifdOffset, SeekOrigin.Begin); + + // IFD (Image File Directory) + ushort numberOfEntries = reader.ReadUInt16(); + + Dictionary ifdEntries = []; + + for (int i = 0; i < numberOfEntries; i++) + { + ushort tagID = reader.ReadUInt16(); + ushort type = reader.ReadUInt16(); + uint count = reader.ReadUInt32(); + uint valueOffset = reader.ReadUInt32(); + ifdEntries[tagID] = (type, count, valueOffset); + } + + uint width = (ifdEntries.TryGetValue(256, out (ushort Type, uint Count, uint ValueOffset) out1)) ? out1.ValueOffset : 0; + uint height = (ifdEntries.TryGetValue(257, out (ushort Type, uint Count, uint ValueOffset) out2)) ? out2.ValueOffset : 0; + uint bitsPerSample = (ifdEntries.TryGetValue(258, out (ushort Type, uint Count, uint ValueOffset) out3)) ? out3.ValueOffset : 0; + uint stripOffsets = (ifdEntries.TryGetValue(273, out (ushort Type, uint Count, uint ValueOffset) out4)) ? out4.ValueOffset : 0; + uint stripByteCounts = (ifdEntries.TryGetValue(279, out (ushort Type, uint Count, uint ValueOffset) out5)) ? out5.ValueOffset : 0; + + if (width == 0 || height == 0 || bitsPerSample != 16 || stripOffsets == 0 || stripByteCounts == 0) + throw new Exception("Invalid TIFF metadata"); + + // Calculate the size of the image data + long imageDataSize = width * height * (bitsPerSample / 8); + + // Seek from the end of the file to the start of the image data + stream.Seek(-imageDataSize, SeekOrigin.End); + + Console.WriteLine("Image " + width.ToString() + " " + height.ToString()); + + ITerrainChannel map = new TerrainChannel((int)width, (int)height); + + // Convert heightmap values back to the original range + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + float heightvalue = InverseNormalize(reader.ReadUInt16()) - 255; + //Console.Write(heightvalue.ToString() + " "); + map[x, y] = heightvalue; + } + } + return map; + } + + public static ITerrainChannel LoadFrom32BitStream(Stream fs) + { + ITerrainChannel map = null; + int width = 0; + int height = 0; + + // Read TIFF header + byte[] tiffHeader = new byte[8]; + fs.Read(tiffHeader, 0, 8); + + if (tiffHeader[0] != 0x49 || tiffHeader[1] != 0x49 || tiffHeader[2] != 0x2A || tiffHeader[3] != 0x00) + throw new Exception("Not a valid little-endian TIFF file"); + + // Read the IFD (Image File Directory) offset + uint ifdOffset = BitConverter.ToUInt32(tiffHeader, 4); + fs.Seek(ifdOffset, SeekOrigin.Begin); + + // Read the number of IFD entries + byte[] numEntriesBytes = new byte[2]; + fs.Read(numEntriesBytes, 0, 2); + ushort numEntries = BitConverter.ToUInt16(numEntriesBytes, 0); + + uint imageWidth = 0, imageHeight = 0, stripOffset = 0; + ushort bitsPerSample = 0, sampleFormat = 0; + + // Read each IFD entry + for (int i = 0; i < numEntries; i++) + { + byte[] entry = new byte[12]; + fs.Read(entry, 0, 12); + + ushort tag = BitConverter.ToUInt16(entry, 0); + ushort type = BitConverter.ToUInt16(entry, 2); + uint count = BitConverter.ToUInt32(entry, 4); + uint valueOffset = BitConverter.ToUInt32(entry, 8); + + switch (tag) + { + case 0x0100: // ImageWidth + imageWidth = valueOffset; + break; + case 0x0101: // ImageLength + imageHeight = valueOffset; + break; + case 0x0102: // BitsPerSample + bitsPerSample = (ushort)valueOffset; + break; + case 0x0111: // StripOffsets + stripOffset = valueOffset; + break; + case 0x0153: // SampleFormat + sampleFormat = (ushort)valueOffset; + break; + } + } + + if (!imageWidth.Equals(0) && !imageHeight.Equals(0)) + { + width = (int)imageWidth; + height = (int)imageHeight; + + if (bitsPerSample != 32 || sampleFormat != 3) + throw new Exception("TIFF file is not a 32-bit floating-point grayscale image"); + + // Seek to the strip offset to read pixel data + fs.Seek(stripOffset, SeekOrigin.Begin); + + map = new TerrainChannel(width, height); + + // Read pixel data + for (int y = 0; y < imageHeight; y++) + { + for (int x = 0; x < imageWidth; x++) + { + byte[] pixelBytes = new byte[4]; + fs.Read(pixelBytes, 0, 4); + float pixelValue = BitConverter.ToSingle(pixelBytes, 0); + map[x, y] = InverseNormalize(pixelValue) - 256; + } + } + } + fs.Dispose(); + return map; + } + + protected static ITerrainChannel Load8BitBitmapTile(string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int w, int h) + { + ITerrainChannel retval = new TerrainChannel(w, h); + + string filePath = filename; + + FileStream fs = new(filePath, FileMode.Open, FileAccess.Read); + ITerrainChannel map = LoadFrom8BitStream(fs); + + for (int x = offsetX; x < map.Width && x < fileWidth; x++) + { + for (int y = offsetY; y < map.Height && y < fileHeight; y++) + { + retval[x, y] = map[x, y]; + } + } + + return retval; + } + + protected static ITerrainChannel Load16BitBitmapTile(string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int w, int h) + { + ITerrainChannel retval = new TerrainChannel(w, h); + + string filePath = filename; + + FileStream fs = new(filePath, FileMode.Open, FileAccess.Read); + ITerrainChannel map = LoadFrom16BitStream(fs); + + for (int x = offsetX; x < map.Width && x < fileWidth; x++) + { + for (int y = offsetY; y < map.Height && y < fileHeight; y++) + { + retval[x, y] = map[x, y]; + } + } + + return retval; + } + + protected static ITerrainChannel Load32BitBitmapTile(string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int w, int h) + { + ITerrainChannel retval = new TerrainChannel(w, h); + + string filePath = filename; + + FileStream fs = new(filePath, FileMode.Open, FileAccess.Read); + ITerrainChannel map = LoadFrom32BitStream(fs); + + for (int x = offsetX; x < map.Width && x < fileWidth; x++) + { + for (int y = offsetY; y < map.Height && y < fileHeight; y++) + { + retval[x, y] = map[x, y]; + } + } + + return retval; + } + + #region Helpers + + private static void WriteRational(Stream fs, uint numerator, uint denominator) + { + fs.Write(BitConverter.GetBytes(numerator), 0, 4); + fs.Write(BitConverter.GetBytes(denominator), 0, 4); + } + + private static void WriteIfdEntry(Stream fs, ushort tag, ushort type, uint count, uint value) + { + fs.Write(BitConverter.GetBytes(tag), 0, 2); + fs.Write(BitConverter.GetBytes(type), 0, 2); + fs.Write(BitConverter.GetBytes(count), 0, 4); + fs.Write(BitConverter.GetBytes(value), 0, 4); + } + + protected static float Normalize(float value) + { + float minHeight = 0; + float maxHeight = 4096; + return (value - minHeight) / (maxHeight - minHeight); + } + + protected static float InverseNormalize(float value) + { + float minHeight = 0; + float maxHeight = 4096; + return minHeight + value * (maxHeight - minHeight); + } + + protected static int GetColorDepth(Stream stream) + { + byte[] buffer = new byte[8]; + + // Read byte order mark (first 2 bytes) + stream.Read(buffer, 0, 2); + bool isLittleEndian = buffer[0] == 'I' && buffer[1] == 'I'; + + // Read TIFF magic number (next 2 bytes) + stream.Read(buffer, 0, 2); + ushort magicNumber = BitConverter.ToUInt16(isLittleEndian ? buffer : buffer.Reverse().ToArray(), 0); + if (magicNumber != 42) + { + Console.WriteLine("Not a valid TIFF file."); + stream.Position = 0; + return 0; + } + + // Read offset to first IFD (next 4 bytes) + stream.Read(buffer, 0, 4); + uint ifdOffset = BitConverter.ToUInt32(isLittleEndian ? buffer : buffer.Reverse().ToArray(), 0); + + // Move to the first IFD + stream.Seek(ifdOffset, SeekOrigin.Begin); + + // Read number of directory entries (next 2 bytes) + stream.Read(buffer, 0, 2); + ushort numEntries = BitConverter.ToUInt16(isLittleEndian ? buffer : buffer.Reverse().ToArray(), 0); + + buffer = new byte[12]; // Allocate buffer for IFD entries + + for (int i = 0; i < numEntries; i++) + { + // Read each IFD entry (12 bytes each) + stream.Read(buffer, 0, 12); + + // Extract the tag ID + ushort tagId = BitConverter.ToUInt16(isLittleEndian ? buffer : buffer.Take(2).Reverse().ToArray(), 0); + + // Check for BitsPerSample tag (ID 258) + if (tagId == 258) + { + ushort type = BitConverter.ToUInt16(isLittleEndian ? buffer.Skip(2).Take(2).ToArray() : buffer.Skip(2).Take(2).Reverse().ToArray(), 0); + uint count = BitConverter.ToUInt32(isLittleEndian ? buffer.Skip(4).Take(4).ToArray() : buffer.Skip(4).Take(4).Reverse().ToArray(), 0); + + // Read the value/offset + uint valueOffset = BitConverter.ToUInt32(isLittleEndian ? buffer.Skip(8).Take(4).ToArray() : buffer.Skip(8).Take(4).Reverse().ToArray(), 0); + + if (count == 1) + { + ushort bitsPerSample = (ushort)valueOffset; + Console.WriteLine($"Color depth: {bitsPerSample} bits per sample"); + if (bitsPerSample == 8) + { + Console.WriteLine("The image is 8-bit."); + stream.Position = 0; + return 8; + } + else if (bitsPerSample == 16) + { + Console.WriteLine("The image is 16-bit."); + stream.Position = 0; + return 16; + } + else if (bitsPerSample == 32) + { + Console.WriteLine("The image is 32-bit."); + stream.Position = 0; + return 32; + } + else + { + Console.WriteLine("The image is neither 8-bit nor 16-bit."); + stream.Position = 0; + return 0; + } + } + else + { + // Handle the case where BitsPerSample is an offset to an array + long currentPos = stream.Position; + stream.Seek(valueOffset, SeekOrigin.Begin); + List bits = new List(); + for (int j = 0; j < count; j++) + { + stream.Read(buffer, 0, 2); + ushort bitsPerSample = BitConverter.ToUInt16(isLittleEndian ? buffer : buffer.Take(2).Reverse().ToArray(), 0); + Console.WriteLine($"Color depth: {bitsPerSample} bits per sample (channel {j + 1})"); + bits.Add(bitsPerSample); + } + stream.Seek(currentPos, SeekOrigin.Begin); + if (bits.Any(b => b == 32)) + { + stream.Position = 0; + return 32; + } + else if (bits.Any(b => b == 16)) + { + stream.Position = 0; + return 16; + } + else if (bits.Any(b => b == 8)) + { + stream.Position = 0; + return 8; + } + else + { + stream.Position = 0; + return 0; + } + } + } + } + + Console.WriteLine("BitsPerSample tag not found."); + stream.Position = 0; + return 0; + } + + #endregion } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs index d61a09aaa6..01ba700c3b 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/FileLoaders/Terragen.cs @@ -43,6 +43,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders { #region ITerrainLoader Members + public int SupportedHeight + { + get { return 4096; } + } + public ITerrainChannel LoadFile(string filename) { FileInfo file = new FileInfo(filename); @@ -326,6 +331,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders return false; } + public bool SupportsExtendedTileSave() + { + return false; + } + /// /// terragen SCAL floats need to be written intel ordered regardless of /// big or little endian system @@ -348,5 +358,9 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders return retVal ; } + public void SaveFile(ITerrainChannel map, string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY) + { + throw new NotImplementedException(); + } } } diff --git a/OpenSim/Region/CoreModules/World/Terrain/ITerrainLoader.cs b/OpenSim/Region/CoreModules/World/Terrain/ITerrainLoader.cs index 855d1314d5..825b5a5ca1 100644 --- a/OpenSim/Region/CoreModules/World/Terrain/ITerrainLoader.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/ITerrainLoader.cs @@ -35,8 +35,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain // Returns true if that extension can be used for terrain save-tile // (Look into each file in Region.CoreModules.World.Terrain.FileLoaders) bool SupportsTileSave(); + bool SupportsExtendedTileSave(); string FileExtension { get; } + int SupportedHeight { get; } ITerrainChannel LoadFile(string filename); ITerrainChannel LoadFile(string filename, int fileStartX, int fileStartY, int fileWidth, int fileHeight, int sectionWidth, int sectionHeight); ITerrainChannel LoadStream(Stream stream); @@ -58,5 +60,25 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// The width of a map tile. /// The height of a map tile. void SaveFile(ITerrainChannel map, string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int regionSizeX, int regionSizeY); + + /// + /// Save all or part of region to larger or smaller file + /// + /// + /// If the image file already exists then the tiles saved will replace those already in the file - other tiles + /// will be untouched. + /// + /// The terrain file to save + /// Width of the file to save to, overwritten by existing file + /// Height of the file to save to, overwritten by existing file + /// Start point in meters on x axis on existing terrain map + /// Start point in meters on y axis on existing terrain map + /// End point in meters on x axis on existing terrain map + /// End point in meters on y axis on existing terrain map + /// Offset start point in meters on x axis to write to file + /// Offset start point in meters on y axis to write to file + void SaveFile(ITerrainChannel map, string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY); + + } } \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index 6f32bc501d..eb3afc1a94 100755 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -75,16 +75,14 @@ namespace OpenSim.Region.CoreModules.World.Terrain private object _modifyLock = new(); #pragma warning disable 414 - private static readonly string LogHeader = "[TERRAIN MODULE]"; + private static readonly string LogHeader = "[TERRAIN]: "; #pragma warning restore 414 - private readonly Commander m_commander = new Commander("terrain"); - private readonly Dictionary m_loaders = new Dictionary(); - private readonly Dictionary m_floodeffects = - new Dictionary(); - private readonly Dictionary m_painteffects = - new Dictionary(); - private Dictionary m_modifyOperations = new Dictionary(); + private readonly Commander m_commander = new("terrain"); + private readonly Dictionary m_loaders = []; + private readonly Dictionary m_floodeffects = []; + private readonly Dictionary m_painteffects = []; + private Dictionary m_modifyOperations = []; private Dictionary m_plugineffects; private ITerrainChannel m_channel; private ITerrainChannel m_baked; @@ -208,15 +206,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain } // The flags of which terrain patches to send for each of the ScenePresence's - private Dictionary m_perClientPatchUpdates = new Dictionary(); + private Dictionary m_perClientPatchUpdates = []; /// /// Human readable list of terrain file extensions that are supported. /// - private string m_supportedFileExtensions = ""; + private string m_supportedFileExtensions = String.Empty; //For terrain save-tile file extensions - private string m_supportFileExtensionsForTileSave = ""; + private string m_supportFileExtensionsForTileSave = String.Empty; #region ICommandableModule Members @@ -284,13 +282,23 @@ namespace OpenSim.Region.CoreModules.World.Terrain LoadPlugins(); // Generate user-readable extensions list - string supportedFilesSeparator = ""; - string supportedFilesSeparatorForTileSave = ""; + string supportedFilesSeparator = String.Empty; + string supportedFilesSeparatorForTileSave = String.Empty; m_supportFileExtensionsForTileSave = ""; - foreach(KeyValuePair loader in m_loaders) + foreach (KeyValuePair loader in m_loaders) { m_supportedFileExtensions += supportedFilesSeparator + loader.Key + " (" + loader.Value + ")"; + + if (loader.Value.SupportedHeight < 257) + { + m_supportedFileExtensions += " Terrain max altitude: 256"; + } + else if (loader.Value.SupportedHeight > 256) + { + m_supportedFileExtensions += " Terrain max altitude: 4096"; + } + supportedFilesSeparator = ", "; //For terrain save-tile file extensions @@ -358,7 +366,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { if (filename.EndsWith(loader.Key)) { - lock(m_scene) + lock (m_scene) { try { @@ -369,37 +377,35 @@ namespace OpenSim.Region.CoreModules.World.Terrain throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}", 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(LogHeader + "Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height); m_scene.Heightmap = channel; m_channel = channel; UpdateBakedMap(); } - catch(NotImplementedException) + catch (NotImplementedException) { - m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + - " parser does not support file loading. (May be save only)"); + m_log.ErrorFormat(LogHeader + "Unable to load heightmap, the {0} parser does not support file loading. (May be save only)", loader.Value); throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value)); } - catch(FileNotFoundException) + catch (FileNotFoundException) { - m_log.Error( - "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)"); + m_log.ErrorFormat(LogHeader + "Unable to load heightmap, file not found. (A directory permissions error may also cause this)"); throw new TerrainException( String.Format("unable to load heightmap: file {0} not found (or permissions do not allow access", filename)); } - catch(ArgumentException e) + catch (ArgumentException e) { - m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message); + m_log.ErrorFormat(LogHeader + "Unable to load heightmap: {0}", e.Message); throw new TerrainException( String.Format("Unable to load heightmap: {0}", e.Message)); } } - m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); + m_log.InfoFormat(LogHeader + "File ({0}) loaded successfully", filename); return; } } - m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format."); + m_log.Error(LogHeader + "Unable to load heightmap, no file loader available for that format."); throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename)); } @@ -413,21 +419,20 @@ namespace OpenSim.Region.CoreModules.World.Terrain { foreach(KeyValuePair loader in m_loaders) { - if (filename.EndsWith(loader.Key)) + if (filename.EndsWith(loader.Key) && loader.Value.SupportedHeight > m_channel.MaxHeight()) { loader.Value.SaveFile(filename, m_channel); - m_log.InfoFormat("[TERRAIN]: Saved terrain from {0} to {1}", m_scene.RegionInfo.RegionName, filename); + m_log.InfoFormat(LogHeader + "Saved terrain from {0} to {1}", m_scene.RegionInfo.RegionName, filename); return; } } } catch(IOException ioe) { - m_log.Error(String.Format("[TERRAIN]: Unable to save to {0}, {1}", filename, ioe.Message)); + m_log.ErrorFormat(LogHeader + "Unable to save to {0}, {1}", filename, ioe.Message); } - m_log.ErrorFormat( - "[TERRAIN]: Could not save terrain from {0} to {1}. Valid file extensions are {2}", + m_log.ErrorFormat(LogHeader + "Could not save terrain from {0} to {1}. Valid file extensions are {2}", m_scene.RegionInfo.RegionName, filename, m_supportedFileExtensions); } @@ -468,13 +473,12 @@ namespace OpenSim.Region.CoreModules.World.Terrain } catch(NotImplementedException) { - m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + - " parser does not support file loading. (May be save only)"); + m_log.ErrorFormat(LogHeader + "Unable to load heightmap, the {0} parser does not support file loading. (May be save only)", loader.Value); throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value)); } } - m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); + m_log.InfoFormat(LogHeader + "File ({0}) loaded successfully", filename); return; } } @@ -499,17 +503,16 @@ namespace OpenSim.Region.CoreModules.World.Terrain } catch (NotImplementedException) { - m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + - " parser does not support file loading. (May be save only)"); + m_log.ErrorFormat(LogHeader + "Unable to load heightmap, the {0} parser does not support file loading. (May be save only)", loader.Value); throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value)); } } - m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); + m_log.InfoFormat(LogHeader + "File ({0}) loaded successfully", filename); return; } } - m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format."); + m_log.Error(LogHeader + "Unable to load heightmap, no file loader available for that format."); throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename)); } @@ -585,7 +588,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { foreach(KeyValuePair loader in m_loaders) { - if (filename.EndsWith(loader.Key)) + if (filename.EndsWith(loader.Key) && loader.Value.SupportedHeight > m_channel.MaxHeight()) { loader.Value.SaveStream(stream, m_channel); return; @@ -594,7 +597,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain } catch(NotImplementedException) { - m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented."); + m_log.ErrorFormat(LogHeader + "Unable to save to {0}, saving of this file format has not been implemented.", filename); throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented")); } } @@ -627,7 +630,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain 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); + pups = new(m_scene.Heightmap.GetTerrainData(), presence); m_perClientPatchUpdates.Add(presence.UUID, pups); } else @@ -646,7 +649,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void LoadPlugins() { - m_plugineffects = new Dictionary(); + m_plugineffects = []; LoadPlugins(Assembly.GetCallingAssembly()); string plugineffectsPath = "Terrain"; @@ -657,7 +660,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain string[] files = Directory.GetFiles(plugineffectsPath); foreach(string file in files) { - m_log.Info("Loading effects in " + file); + m_log.InfoFormat(LogHeader + "Loading effects in {0}", file); try { Assembly library = Assembly.LoadFrom(file); @@ -844,7 +847,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain // this region is included in the tile request foreach(KeyValuePair loader in m_loaders) { - if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave()) + if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave() && loader.Value.SupportedHeight > m_channel.MaxHeight()) { lock(m_scene) { @@ -868,6 +871,43 @@ namespace OpenSim.Region.CoreModules.World.Terrain m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave); } + /// + /// Save all or part of region to larger or smaller file + /// + /// + /// If the image file already exists then the tiles saved will replace those already in the file - other tiles + /// will be untouched. + /// + /// The terrain file to save + /// Width of the file to save to, overwritten by existing file + /// Height of the file to save to, overwritten by existing file + /// Start point in meters on x axis on existing terrain map + /// Start point in meters on y axis on existing terrain map + /// End point in meters on x axis on existing terrain map + /// End point in meters on y axis on existing terrain map + /// Offset start point in meters on x axis to write to file + /// Offset start point in meters on y axis to write to file + public void SaveToFile(string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY) + { + foreach (KeyValuePair loader in m_loaders) + { + if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave() && loader.Value.SupportsExtendedTileSave() && loader.Value.SupportedHeight > m_channel.MaxHeight()) + { + lock (m_scene) + { + loader.Value.SaveFile(m_channel, filename, fileWidth, fileHeight, startX, startY, stopX, stopY, offsetX, offsetY); + + MainConsole.Instance.Output( + "Saved terrain from ({0},{1}) to ({2},{3}) to {4} starting from ({5},{6})", + startX, startY, stopX, stopY, filename, offsetX, offsetY); + } + } + } + + MainConsole.Instance.Output( + "ERROR: Could not save terrain from {0} to {1}. Valid file extensions are {2}", + m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave); + } /// /// This is used to check to see of any of the terrain is tainted and, if so, schedule @@ -885,7 +925,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain } } - object TerrainCheckUpdatesLock = new object(); + object TerrainCheckUpdatesLock = new(); private void EventManager_TerrainCheckUpdatesAsync(object o) { @@ -952,7 +992,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { if (args.Length == 1) { - m_commander.ProcessConsoleCommand("help", new string[0]); + m_commander.ProcessConsoleCommand("help", []); return; } @@ -1125,7 +1165,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain int sx = terrData.SizeX / Constants.TerrainPatchSize; int py = patchIndex / sx; int px = patchIndex - py * sx; - int[] map = new int[]{px, py}; + int[] map = [px, py]; m_scene.ForEachClient( delegate (IClientAPI controller) { @@ -1201,7 +1241,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain next = 0; int npatchs = 0; - List patchs = new List(128); + List patchs = new(128); while ((next = pups.taints.GetAndClearNextTrue(next)) >= 0) { patchs.Add(next); @@ -1237,7 +1277,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain private List GetModifiedPatchesInViewDistance(PatchUpdates pups) { - List ret = new List(); + List ret = []; if (!pups.HasUpdates()) return ret; @@ -1518,7 +1558,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { //m_log.Debug("Terrain packet unacked, resending patch: " + patchX + " , " + patchY); // SendLayerData does not use the heightmap parameter. This kludge is so as to not change IClientAPI. - client.SendLayerData(new int[]{patchX, patchY}); + client.SendLayerData([patchX, patchY]); } private void StoreUndoState() @@ -1555,6 +1595,19 @@ namespace OpenSim.Region.CoreModules.World.Terrain (int)args[4]); } + private void InterfaceSaveTileFileExtended(Object[] args) + { + SaveToFile((string)args[0], + (int)args[1], + (int)args[2], + (int)args[3], + (int)args[4], + (int)args[5], + (int)args[6], + (int)args[7], + (int)args[8]); + } + private void InterfaceBakeTerrain(Object[] args) { UpdateBakedMap(); @@ -1573,7 +1626,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { String direction = (String)args[0]; - if (direction.ToLower().StartsWith("y")) + if (direction.StartsWith("y", StringComparison.CurrentCultureIgnoreCase)) { for (int x = 0; x < m_channel.Width; x++) { @@ -1619,7 +1672,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain if (desiredRange == 0d) { // delta is zero so flatten at requested height - InterfaceFillTerrain(new Object[] { args[1] }); + InterfaceFillTerrain([args[1]]); } else { @@ -1733,9 +1786,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void InterfaceShow(Object[] args) { - Vector2 point; - - if (!ConsoleUtil.TryParseConsole2DVector((string)args[0], null, out point)) + if (!ConsoleUtil.TryParseConsole2DVector((string)args[0], null, out Vector2 point)) { Console.WriteLine("ERROR: {0} is not a valid vector", args[0]); return; @@ -1792,9 +1843,9 @@ namespace OpenSim.Region.CoreModules.World.Terrain return; } - if (m_plugineffects.ContainsKey(firstArg)) + if (m_plugineffects.TryGetValue(firstArg, out ITerrainEffect value)) { - m_plugineffects[firstArg].RunEffect(m_channel); + value.RunEffect(m_channel); } else { @@ -1805,19 +1856,19 @@ namespace OpenSim.Region.CoreModules.World.Terrain private void InstallInterfaces() { Command loadFromFileCommand = - new Command("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file."); + new("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file."); loadFromFileCommand.AddArgument("filename", "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " + m_supportedFileExtensions, "String"); Command saveToFileCommand = - new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file."); + new("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file."); saveToFileCommand.AddArgument("filename", "The destination filename for your heightmap, the file extension determines the format to save in. Supported extensions include: " + m_supportedFileExtensions, "String"); Command loadFromTileCommand = - new Command("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file."); + new("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file."); loadFromTileCommand.AddArgument("filename", "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " + m_supportedFileExtensions, "String"); @@ -1829,7 +1880,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain "Integer"); Command saveToTileCommand = - new Command("save-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceSaveTileFile, "Saves the current heightmap to the larger file."); + new("save-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceSaveTileFile, "Saves the current heightmap to the larger file."); saveToTileCommand.AddArgument("filename", "The file you wish to save to, the file extension determines the loader to be used. Supported extensions include: " + m_supportFileExtensionsForTileSave, "String"); @@ -1843,64 +1894,80 @@ namespace OpenSim.Region.CoreModules.World.Terrain + " # terrain save-tile ST06.png 2 3 9910 10234\n", "Integer"); + Command saveToTileExtendedCommand = + new("save-tile-ext", CommandIntentions.COMMAND_HAZARDOUS, InterfaceSaveTileFileExtended, "Saves the current heightmap to a larger or smaller file."); + saveToTileExtendedCommand.AddArgument("filename", + "The file you wish to save to, the file extension determines the loader to be used. Supported extensions include: " + + m_supportFileExtensionsForTileSave, "String"); + saveToTileExtendedCommand.AddArgument("file width", "The width of the file in tiles", "Integer"); + saveToTileExtendedCommand.AddArgument("file height", "The height of the file in tiles", "Integer"); + saveToTileExtendedCommand.AddArgument("start X", "Start point in meters on x axis on existing terrain map", "Integer"); + saveToTileExtendedCommand.AddArgument("start Y", "Start point in meters on y axis on existing terrain map", "Integer"); + saveToTileExtendedCommand.AddArgument("stop X", "End point in meters on x axis on existing terrain map", "Integer"); + saveToTileExtendedCommand.AddArgument("stop Y", "End point in meters on y axis on existing terrain map", "Integer"); + saveToTileExtendedCommand.AddArgument("offset X", "Offset start point in meters on x axis to write to file", "Integer"); + saveToTileExtendedCommand.AddArgument("offset Y", "Offset start point in meters on y axis to write to file", "Integer"); + + // Terrain adjustments Command fillRegionCommand = - new Command("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value."); + new("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value."); fillRegionCommand.AddArgument("value", "The numeric value of the height you wish to set your region to.", "Float"); Command elevateCommand = - new Command("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount."); + new("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount."); elevateCommand.AddArgument("amount", "The amount of height to add to the terrain in meters.", "Float"); Command lowerCommand = - new Command("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount."); + new("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount."); lowerCommand.AddArgument("amount", "The amount of height to remove from the terrain in meters.", "Float"); Command multiplyCommand = - new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified."); + new("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified."); multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Float"); Command bakeRegionCommand = - new Command("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions baked map."); + new("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions baked map."); Command revertRegionCommand = - new Command("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the baked map terrain into the regions heightmap."); + new("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the baked map terrain into the regions heightmap."); Command flipCommand = - new Command("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis"); + new("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis"); flipCommand.AddArgument("direction", "[x|y] the direction to flip the terrain in", "String"); Command rescaleCommand = - new Command("rescale", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRescaleTerrain, "Rescales the current terrain to fit between the given min and max heights"); + new("rescale", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRescaleTerrain, "Rescales the current terrain to fit between the given min and max heights"); rescaleCommand.AddArgument("min", "min terrain height after rescaling", "Float"); rescaleCommand.AddArgument("max", "max terrain height after rescaling", "Float"); - Command minCommand = new Command("min", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMinTerrain, "Sets the minimum terrain height to the specified value."); + Command minCommand = new("min", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMinTerrain, "Sets the minimum terrain height to the specified value."); minCommand.AddArgument("min", "terrain height to use as minimum", "Float"); - Command maxCommand = new Command("max", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMaxTerrain, "Sets the maximum terrain height to the specified value."); + Command maxCommand = new("max", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMaxTerrain, "Sets the maximum terrain height to the specified value."); maxCommand.AddArgument("min", "terrain height to use as maximum", "Float"); // Debug Command showDebugStatsCommand = - new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats, + new("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats, "Shows some information about the regions heightmap for debugging purposes."); Command showCommand = - new Command("show", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceShow, + new("show", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceShow, "Shows terrain height at a given co-ordinate."); showCommand.AddArgument("point", "point in , format with no spaces (e.g. 45,45)", "String"); // Plugins Command pluginRunCommand = - new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect"); + new("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect"); pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String"); m_commander.RegisterCommand("load", loadFromFileCommand); m_commander.RegisterCommand("load-tile", loadFromTileCommand); m_commander.RegisterCommand("save", saveToFileCommand); m_commander.RegisterCommand("save-tile", saveToTileCommand); + m_commander.RegisterCommand("save-file-ext", saveToTileExtendedCommand); m_commander.RegisterCommand("fill", fillRegionCommand); m_commander.RegisterCommand("elevate", elevateCommand); m_commander.RegisterCommand("lower", lowerCommand); @@ -1945,9 +2012,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { string operationType = cmd[2]; - - ITerrainModifier operation; - if (!m_modifyOperations.TryGetValue(operationType, out operation)) + if (!m_modifyOperations.TryGetValue(operationType, out ITerrainModifier operation)) { result = String.Format("Terrain Modify \"{0}\" not found.", operationType); } diff --git a/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs b/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs index d5073589f2..3232b619e9 100644 --- a/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs +++ b/OpenSim/Region/Framework/Interfaces/ITerrainChannel.cs @@ -68,5 +68,6 @@ namespace OpenSim.Region.Framework.Interfaces /// <x, y> /// <x, y> void MergeWithBounding(ITerrainChannel newTerrain, Vector3 displacement, float rotationDegrees, Vector2 boundingOrigin, Vector2 boundingSize); + int MaxHeight(); } } diff --git a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs index fc404c2d9c..4ac97d9c8a 100644 --- a/OpenSim/Region/Framework/Scenes/TerrainChannel.cs +++ b/OpenSim/Region/Framework/Scenes/TerrainChannel.cs @@ -48,7 +48,7 @@ namespace OpenSim.Region.Framework.Scenes public class TerrainChannel : ITerrainChannel { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private static string LogHeader = "[TERRAIN CHANNEL]"; + private static string LogHeader = "[TERRAIN CHANNEL]: "; protected TerrainData m_terrainData; @@ -175,12 +175,27 @@ namespace OpenSim.Region.Framework.Scenes return m_terrainData.IsTaintedAt(x, y); } + public int MaxHeight() + { + float max = 0; + for (int ii = 0; ii < Width; ii++) + { + for (int jj = 0; jj < Height; jj++) + { + max = (m_terrainData[ii, jj] > max) ? m_terrainData[ii, jj] : max; + } + } + return (int)max + 1; + } + // ITerrainChannel.SaveToXmlString() public string SaveToXmlString() { - XmlWriterSettings settings = new XmlWriterSettings(); - settings.Encoding = Util.UTF8; - using (StringWriter sw = new StringWriter()) + XmlWriterSettings settings = new() + { + Encoding = Util.UTF8 + }; + using (StringWriter sw = new()) { using (XmlWriter writer = XmlWriter.Create(sw, settings)) { @@ -194,9 +209,9 @@ namespace OpenSim.Region.Framework.Scenes // ITerrainChannel.LoadFromXmlString() public void LoadFromXmlString(string data) { - using(StringReader sr = new StringReader(data)) + using(StringReader sr = new(data)) { - using(XmlTextReader reader = new XmlTextReader(sr)) + using(XmlTextReader reader = new(sr)) { reader.DtdProcessing = DtdProcessing.Ignore; ReadXml(reader); @@ -207,7 +222,7 @@ namespace OpenSim.Region.Framework.Scenes // ITerrainChannel.Merge public void Merge(ITerrainChannel newTerrain, Vector3 displacement, float radianRotation, Vector2 rotationDisplacement) { - m_log.DebugFormat("{0} Merge. inSize=<{1},{2}>, disp={3}, rot={4}, rotDisp={5}, outSize=<{6},{7}>", LogHeader, + m_log.DebugFormat(LogHeader + "{0} Merge. inSize=<{1},{2}>, disp={3}, rot={4}, rotDisp={5}, outSize=<{6},{7}>", LogHeader, newTerrain.Width, newTerrain.Height, displacement, radianRotation, rotationDisplacement, m_terrainData.SizeX, m_terrainData.SizeY); @@ -295,7 +310,7 @@ namespace OpenSim.Region.Framework.Scenes /// <x, y> public void MergeWithBounding(ITerrainChannel newTerrain, Vector3 displacement, float rotationDegrees, Vector2 boundingOrigin, Vector2 boundingSize) { - m_log.DebugFormat("{0} MergeWithBounding: inSize=<{1},{2}>, rot={3}, boundingOrigin={4}, boundingSize={5}, disp={6}, outSize=<{7},{8}>", + m_log.DebugFormat(LogHeader + "{0} MergeWithBounding: inSize=<{1},{2}>, rot={3}, boundingOrigin={4}, boundingSize={5}, disp={6}, outSize=<{7},{8}>", LogHeader, newTerrain.Width, newTerrain.Height, rotationDegrees, boundingOrigin.ToString(), boundingSize.ToString(), displacement, m_terrainData.SizeX, m_terrainData.SizeY); @@ -310,7 +325,7 @@ namespace OpenSim.Region.Framework.Scenes int tmpY = baseY + baseY / 2; int centreX = tmpX / 2; int centreY = tmpY / 2; - TerrainData terrain_tmp = new TerrainData(tmpX, tmpY, (int)Constants.RegionHeight); + TerrainData terrain_tmp = new(tmpX, tmpY, (int)Constants.RegionHeight); for (int xx = 0; xx < tmpX; xx++) for (int yy = 0; yy < tmpY; yy++) terrain_tmp[xx, yy] = -65535f; //use this height like an 'alpha' mask channel @@ -364,7 +379,7 @@ namespace OpenSim.Region.Framework.Scenes } catch (Exception) //just in case we've still not taken care of every way the arrays might go out of bounds! ;) { - m_log.DebugFormat("{0} MergeWithBounding - Rotate: Out of Bounds sx={1} sy={2} dx={3} dy={4}", sx, sy, x, y); + m_log.DebugFormat(LogHeader + "{0} MergeWithBounding - Rotate: Out of Bounds sx={1} sy={2} dx={3} dy={4}", sx, sy, x, y); } } } @@ -413,7 +428,7 @@ namespace OpenSim.Region.Framework.Scenes } catch (Exception) //just in case we've still not taken care of every way the arrays might go out of bounds! ;) { - m_log.DebugFormat("{0} MergeWithBounding - Bound & Displace: Out of Bounds sx={1} sy={2} dx={3} dy={4}", x, y, dx, dy); + m_log.DebugFormat(LogHeader + "{0} MergeWithBounding - Bound & Displace: Out of Bounds sx={1} sy={2} dx={3} dy={4}", x, y, dx, dy); } } } @@ -424,8 +439,10 @@ namespace OpenSim.Region.Framework.Scenes public TerrainChannel Copy() { - TerrainChannel copy = new TerrainChannel(); - copy.m_terrainData = m_terrainData.Clone(); + TerrainChannel copy = new() + { + m_terrainData = m_terrainData.Clone() + }; return copy; } @@ -473,14 +490,14 @@ namespace OpenSim.Region.Framework.Scenes byte[] value = BitConverter.GetBytes(mapData[i]); Array.Copy(value, 0, buffer, (i * 4), 4); } - XmlSerializer serializer = new XmlSerializer(typeof(byte[])); + XmlSerializer serializer = new(typeof(byte[])); 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[])); + XmlSerializer serializer = new(typeof(byte[])); byte[] dataArray = (byte[])serializer.Deserialize(xmlReader); int index = 0; @@ -520,16 +537,16 @@ 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_terrainData.CompressionFactor, + TerrainChannelXMLPackage package = new(Width, Height, Altitude, m_terrainData.CompressionFactor, m_terrainData.GetCompressedMap()); - XmlSerializer serializer = new XmlSerializer(typeof(TerrainChannelXMLPackage)); + XmlSerializer serializer = new(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)); + XmlSerializer serializer = new(typeof(TerrainChannelXMLPackage)); TerrainChannelXMLPackage package = (TerrainChannelXMLPackage)serializer.Deserialize(xmlReader); m_terrainData = new TerrainData(package.Map, package.CompressionFactor, package.SizeX, package.SizeY, package.SizeZ); }