Merge branch 'master' into lickx

This commit is contained in:
lickx
2025-11-27 05:52:32 +01:00
15 changed files with 1258 additions and 134 deletions

View File

@@ -40,6 +40,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders
/// </summary>
internal class BMP : GenericSystemDrawing
{
public override int SupportedHeight
{
get { return 256; }
}
/// <summary>
/// Exports a file to a image on the disk using a System.Drawing exporter.
/// </summary>
@@ -76,5 +81,10 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders
{
return false;
}
public override bool SupportsExtendedTileSave()
{
return false;
}
}
}

View File

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

View File

@@ -49,6 +49,11 @@ namespace OpenSim.Region.CoreModules.World.Terrain.FileLoaders
get { return ".gsd"; }
}
public virtual int SupportedHeight
{
get { return 256; }
}
/// <summary>
/// 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;
}
/// <summary>
/// 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();
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,32 +25,22 @@
* 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 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; }
}
/// <summary>
/// Exports a stream using a System.Drawing exporter.
/// </summary>
/// <param name="stream">The target stream</param>
/// <param name="map">The terrain channel being saved</param>
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 +49,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;
}
}
/// <summary>
/// Exports a stream to 32 bit tiff
/// </summary>
/// <param name="stream">The target stream</param>
/// <param name="map">The terrain channel being saved</param>
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<ushort, (ushort Type, uint Count, uint ValueOffset)> 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<int> bits = new List<int>();
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
}
}

View File

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

View File

@@ -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
/// <param name="regionSizeX">The width of a map tile.</param>
/// <param name="regionSizeY">The height of a map tile.</param>
void SaveFile(ITerrainChannel map, string filename, int offsetX, int offsetY, int fileWidth, int fileHeight, int regionSizeX, int regionSizeY);
/// <summary>
/// Save all or part of region to larger or smaller file
/// </summary>
/// <remarks>
/// If the image file already exists then the tiles saved will replace those already in the file - other tiles
/// will be untouched.
/// </remarks>
/// <param name="filename">The terrain file to save</param>
/// <param name="fileWidth">Width of the file to save to, overwritten by existing file</param>
/// <param name="fileHeight">Height of the file to save to, overwritten by existing file</param>
/// <param name="startX">Start point in meters on x axis on existing terrain map</param>
/// <param name="startY">Start point in meters on y axis on existing terrain map</param>
/// <param name="stopX">End point in meters on x axis on existing terrain map</param>
/// <param name="stopY">End point in meters on y axis on existing terrain map</param>
/// <param name="offsetX">Offset start point in meters on x axis to write to file</param>
/// <param name="offsetY">Offset start point in meters on y axis to write to file</param>
void SaveFile(ITerrainChannel map, string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY);
}
}

View File

@@ -25,7 +25,6 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
@@ -74,17 +73,13 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private object _modifyLock = new();
#pragma warning disable 414
private static readonly string LogHeader = "[TERRAIN MODULE]";
#pragma warning restore 414
private const string LogHeader = "[TERRAIN]: ";
private readonly Commander m_commander = new Commander("terrain");
private readonly Dictionary<string, ITerrainLoader> m_loaders = new Dictionary<string, ITerrainLoader>();
private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects =
new Dictionary<StandardTerrainEffects, ITerrainFloodEffect>();
private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects =
new Dictionary<StandardTerrainEffects, ITerrainPaintableEffect>();
private Dictionary<string, ITerrainModifier> m_modifyOperations = new Dictionary<string, ITerrainModifier>();
private readonly Commander m_commander = new("terrain");
private readonly Dictionary<string, ITerrainLoader> m_loaders = [];
private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects = [];
private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects = [];
private Dictionary<string, ITerrainModifier> m_modifyOperations = [];
private Dictionary<string, ITerrainEffect> m_plugineffects;
private ITerrainChannel m_channel;
private ITerrainChannel m_baked;
@@ -208,15 +203,15 @@ namespace OpenSim.Region.CoreModules.World.Terrain
}
// The flags of which terrain patches to send for each of the ScenePresence's
private Dictionary<UUID, PatchUpdates> m_perClientPatchUpdates = new Dictionary<UUID, PatchUpdates>();
private Dictionary<UUID, PatchUpdates> m_perClientPatchUpdates = [];
/// <summary>
/// Human readable list of terrain file extensions that are supported.
/// </summary>
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 +279,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<string, ITerrainLoader> loader in m_loaders)
foreach (KeyValuePair<string, ITerrainLoader> 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 +363,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
if (filename.EndsWith(loader.Key))
{
lock(m_scene)
lock (m_scene)
{
try
{
@@ -369,38 +374,34 @@ 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.Debug($"{LogHeader}Loaded terrain, wd/ht: {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)");
throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
m_log.ErrorFormat($"{LogHeader}Unable to load heightmap, the {loader.Value} parser does not support file loading. (May be save only)");
throw new TerrainException($"unable to load heightmap: parser {loader.Value} does not support loading");
}
catch(FileNotFoundException)
catch (FileNotFoundException)
{
m_log.Error(
"[TERRAIN]: 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));
m_log.ErrorFormat($"{LogHeader}Unable to load heightmap, file not found. (A directory permissions error may also cause this)");
throw new TerrainException($"unable to load heightmap: file {filename} not found (or permissions do not allow access");
}
catch(ArgumentException e)
catch (ArgumentException e)
{
m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message);
throw new TerrainException(
String.Format("Unable to load heightmap: {0}", e.Message));
m_log.Error($"{LogHeader}Unable to load heightmap: {e.Message}");
throw new TerrainException($"Unable to load heightmap: {e.Message}");
}
}
m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
m_log.Info($"{LogHeader}File ({filename}) loaded successfully");
return;
}
}
m_log.Error("[TERRAIN]: 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));
m_log.Error($"{LogHeader}Unable to load heightmap, no file loader available for that format.");
throw new TerrainException($"unable to load heightmap from file {filename}: no loader available for that format");
}
/// <summary>
@@ -413,22 +414,21 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
foreach(KeyValuePair<string, ITerrainLoader> 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.Info($"{LogHeader}Saved terrain from {m_scene.RegionInfo.RegionName} to {filename}");
return;
}
}
}
catch(IOException ioe)
{
m_log.Error(String.Format("[TERRAIN]: Unable to save to {0}, {1}", filename, ioe.Message));
m_log.Error($"{LogHeader}Unable to save to {filename}, {ioe.Message}");
}
m_log.ErrorFormat(
"[TERRAIN]: Could not save terrain from {0} to {1}. Valid file extensions are {2}",
m_scene.RegionInfo.RegionName, filename, m_supportedFileExtensions);
m_log.Error(
$"{LogHeader}Could not save terrain from {m_scene.RegionInfo.RegionName} to {filename}. Valid file extensions are {m_supportedFileExtensions}");
}
/// <summary>
@@ -468,18 +468,17 @@ 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)");
throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
m_log.Error($"{LogHeader}Unable to load heightmap, the {loader.Value} parser does not support file loading. (May be save only)");
throw new TerrainException($"unable to load heightmap: parser {loader.Value} does not support loading");
}
}
m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
m_log.Info($"{LogHeader}File ({filename}) loaded successfully");
return;
}
}
m_log.Error("[TERRAIN]: 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));
m_log.Error($"{LogHeader}Unable to load heightmap, no file loader available for that format.");
throw new TerrainException($"unable to load heightmap from file {filename}: no loader available for that format");
}
public void LoadFromStream(string filename, Vector3 displacement,
@@ -499,17 +498,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)");
throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
m_log.ErrorFormat($"{LogHeader}Unable to load heightmap, the { loader.Value} parser does not support file loading. (May be save only)");
throw new TerrainException("unable to load heightmap: parser {loader.Value} does not support loading");
}
}
m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
m_log.Info($"{LogHeader}File ({filename}) loaded successfully");
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));
}
@@ -526,7 +524,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
Stream file = response.GetResponseStream();
if (response.ContentLength == 0)
throw new Exception(String.Format("{0} returned an empty file", uri.ToString()));
throw new Exception($"{uri} returned an empty file");
// return new BufferedStream(file, (int) response.ContentLength);
return new BufferedStream(file, 1000000);
@@ -585,7 +583,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
{
foreach(KeyValuePair<string, ITerrainLoader> 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,8 +592,8 @@ 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.");
throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented"));
m_log.Error($"{LogHeader}Unable to save to {filename}, saving of this file format has not been implemented.");
throw new TerrainException("Unable to save heightmap: saving of this file format not implemented");
}
}
@@ -627,7 +625,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 +644,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private void LoadPlugins()
{
m_plugineffects = new Dictionary<string, ITerrainEffect>();
m_plugineffects = [];
LoadPlugins(Assembly.GetCallingAssembly());
string plugineffectsPath = "Terrain";
@@ -657,7 +655,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.Info($"{LogHeader}Loading effects in {file}");
try
{
Assembly library = Assembly.LoadFrom(file);
@@ -844,7 +842,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
// this region is included in the tile request
foreach(KeyValuePair<string, ITerrainLoader> 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 +866,43 @@ namespace OpenSim.Region.CoreModules.World.Terrain
m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave);
}
/// <summary>
/// Save all or part of region to larger or smaller file
/// </summary>
/// <remarks>
/// If the image file already exists then the tiles saved will replace those already in the file - other tiles
/// will be untouched.
/// </remarks>
/// <param name="filename">The terrain file to save</param>
/// <param name="fileWidth">Width of the file to save to, overwritten by existing file</param>
/// <param name="fileHeight">Height of the file to save to, overwritten by existing file</param>
/// <param name="startX">Start point in meters on x axis on existing terrain map</param>
/// <param name="startY">Start point in meters on y axis on existing terrain map</param>
/// <param name="stopX">End point in meters on x axis on existing terrain map</param>
/// <param name="stopY">End point in meters on y axis on existing terrain map</param>
/// <param name="offsetX">Offset start point in meters on x axis to write to file</param>
/// <param name="offsetY">Offset start point in meters on y axis to write to file</param>
public void SaveToFile(string filename, int fileWidth, int fileHeight, int startX, int startY, int stopX, int stopY, int offsetX, int offsetY)
{
foreach (KeyValuePair<string, ITerrainLoader> 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);
}
/// <summary>
/// This is used to check to see of any of the terrain is tainted and, if so, schedule
@@ -885,7 +920,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
}
}
object TerrainCheckUpdatesLock = new object();
object TerrainCheckUpdatesLock = new();
private void EventManager_TerrainCheckUpdatesAsync(object o)
{
@@ -952,7 +987,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 +1160,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 +1236,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
next = 0;
int npatchs = 0;
List<int> patchs = new List<int>(128);
List<int> patchs = new(128);
while ((next = pups.taints.GetAndClearNextTrue(next)) >= 0)
{
patchs.Add(next);
@@ -1237,7 +1272,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain
private List<PatchesToSend> GetModifiedPatchesInViewDistance(PatchUpdates pups)
{
List<PatchesToSend> ret = new List<PatchesToSend>();
List<PatchesToSend> ret = [];
if (!pups.HasUpdates())
return ret;
@@ -1518,7 +1553,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 +1590,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 +1621,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 +1667,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 +1781,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 +1838,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 +1851,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 +1875,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 +1889,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 <x>,<y> 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 +2007,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);
}

View File

@@ -439,7 +439,13 @@ namespace OpenSim.Region.CoreModules.World.Warp3DMap
delegate (SceneObjectGroup group)
{
foreach (SceneObjectPart child in group.Parts)
CreatePrim(renderer, child);
{
try { CreatePrim(renderer, child); }
catch (Exception e)
{
m_log.Warn($"[Warp3D] failed to render prim {child.Name} at {child.GetWorldPosition()}: {e.Message}");
}
}
}
);
}

View File

@@ -68,5 +68,6 @@ namespace OpenSim.Region.Framework.Interfaces
/// <param name="boundingOrigin">&lt;x, y&gt;</param>
/// <param name="boundingSize">&lt;x, y&gt;</param>
void MergeWithBounding(ITerrainChannel newTerrain, Vector3 displacement, float rotationDegrees, Vector2 boundingOrigin, Vector2 boundingSize);
int MaxHeight();
}
}

View File

@@ -2290,14 +2290,17 @@ namespace OpenSim.Region.Framework.Scenes
public void SetText(string text, Vector3 color, double alpha)
{
Color = Color.FromArgb(0xff - (int) (alpha * 0xff),
Color newcolor = Color.FromArgb(0xff - (int) (alpha * 0xff),
(int) (color.X * 0xff),
(int) (color.Y * 0xff),
(int) (color.Z * 0xff));
Text = text;
HasGroupChanged = true;
m_rootPart.ScheduleFullUpdate();
if(Text != text || newcolor!= Color)
{
Text = text;
Color = newcolor;
HasGroupChanged = true;
m_rootPart.ScheduleFullUpdate();
}
}
/// <summary>

View File

@@ -47,8 +47,9 @@ namespace OpenSim.Region.Framework.Scenes
/// </summary>
public class TerrainChannel : ITerrainChannel
{
const string LogHeader = "[TERRAIN CHANNEL]: ";
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static string LogHeader = "[TERRAIN CHANNEL]";
protected TerrainData m_terrainData;
@@ -175,12 +176,29 @@ namespace OpenSim.Region.Framework.Scenes
return m_terrainData.IsTaintedAt(x, y);
}
public int MaxHeight()
{
float max = float.MinValue;
for (int ii = 0; ii < Width; ii++)
{
for (int jj = 0; jj < Height; jj++)
{
float cur = m_terrainData[ii, jj];
if(cur > max)
max = cur;
}
}
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 +212,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 +225,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 +313,7 @@ namespace OpenSim.Region.Framework.Scenes
/// <param name="boundingSize">&lt;x, y&gt;</param>
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 +328,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 +382,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 +431,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 +442,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 +493,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 +540,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);
}