/*
* Copyright (c) 2006-2014, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using OpenMetaverse.Packets;
namespace OpenMetaverse
{
[Serializable]
public class TerrainPatch
{
#region Enums and Structs
public enum LayerType : byte
{
Land = 0x4C, // 'L'
LandExtended = 0x4D, // 'M'
Water = 0x57, // 'W'
WaterExtended = 0x57, // 'X'
Wind = 0x37, // '7'
WindExtended = 0x39, // '9'
Cloud = 0x38, // '8'
CloudExtended = 0x3A // ':'
}
public struct GroupHeader
{
public int Stride;
public int PatchSize;
public LayerType Type;
}
public struct Header
{
public float DCOffset;
public int Range;
public int QuantWBits;
public int PatchIDs;
public uint WordBits;
public int X
{
get { return PatchIDs >> 5; }
set { PatchIDs += (value << 5); }
}
public int Y
{
get { return PatchIDs & 0x1F; }
set { PatchIDs |= value & 0x1F; }
}
}
#endregion Enums and Structs
/// X position of this patch
public int X;
/// Y position of this patch
public int Y;
/// A 16x16 array of floats holding decompressed layer data
public float[] Data;
}
public static class TerrainCompressor
{
public const int PATCHES_PER_EDGE = 16;
public const int END_OF_PATCHES = 97;
private const float OO_SQRT2 = 0.7071067811865475244008443621049f;
private const int STRIDE = 264;
private const int ZERO_CODE = 0x0;
private const int ZERO_EOB = 0x2;
private const int POSITIVE_VALUE = 0x6;
private const int NEGATIVE_VALUE = 0x7;
private static readonly float[] DequantizeTable16 = new float[16 * 16];
private static readonly float[] DequantizeTable32 = new float[16 * 16];
private static readonly float[] CosineTable16 = new float[16 * 16];
//private static readonly float[] CosineTable32 = new float[16 * 16];
private static readonly int[] CopyMatrix16 = new int[16 * 16];
private static readonly int[] CopyMatrix32 = new int[16 * 16];
private static readonly float[] QuantizeTable16 = new float[16 * 16];
static TerrainCompressor()
{
// Initialize the decompression tables
BuildDequantizeTable16();
SetupCosines16();
BuildCopyMatrix16();
BuildQuantizeTable16();
}
public static LayerDataPacket CreateLayerDataPacket(TerrainPatch[] patches, TerrainPatch.LayerType type)
{
LayerDataPacket layer = new LayerDataPacket();
layer.LayerID.Type = (byte)type;
TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader();
header.Stride = STRIDE;
header.PatchSize = 16;
header.Type = type;
// Should be enough to fit even the most poorly packed data
byte[] data = new byte[patches.Length * 16 * 16 * 2];
BitPack bitpack = new BitPack(data, 0);
bitpack.PackBits(header.Stride, 16);
bitpack.PackBits(header.PatchSize, 8);
bitpack.PackBits((int)header.Type, 8);
for (int i = 0; i < patches.Length; i++)
CreatePatch(bitpack, patches[i].Data, patches[i].X, patches[i].Y);
bitpack.PackBits(END_OF_PATCHES, 8);
layer.LayerData.Data = new byte[bitpack.BytePos + 1];
Buffer.BlockCopy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1);
return layer;
}
///
/// Creates a LayerData packet for compressed land data given a full
/// simulator heightmap and an array of indices of patches to compress
///
/// A 256 * 256 array of floating point values
/// specifying the height at each meter in the simulator
/// Array of indexes in the 16x16 grid of patches
/// for this simulator. For example if 1 and 17 are specified, patches
/// x=1,y=0 and x=1,y=1 are sent
///
public static LayerDataPacket CreateLandPacket(float[] heightmap, int[] patches)
{
LayerDataPacket layer = new LayerDataPacket();
layer.LayerID.Type = (byte)TerrainPatch.LayerType.Land;
TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader();
header.Stride = STRIDE;
header.PatchSize = 16;
header.Type = TerrainPatch.LayerType.Land;
byte[] data = new byte[1536];
BitPack bitpack = new BitPack(data, 0);
bitpack.PackBits(header.Stride, 16);
bitpack.PackBits(header.PatchSize, 8);
bitpack.PackBits((int)header.Type, 8);
for (int i = 0; i < patches.Length; i++)
CreatePatchFromHeightmap(bitpack, heightmap, patches[i] % 16, (patches[i] - (patches[i] % 16)) / 16);
bitpack.PackBits(END_OF_PATCHES, 8);
layer.LayerData.Data = new byte[bitpack.BytePos + 1];
Buffer.BlockCopy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1);
return layer;
}
public static LayerDataPacket CreateLandPacket(float[] patchData, int x, int y)
{
LayerDataPacket layer = new LayerDataPacket();
layer.LayerID.Type = (byte)TerrainPatch.LayerType.Land;
TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader();
header.Stride = STRIDE;
header.PatchSize = 16;
header.Type = TerrainPatch.LayerType.Land;
byte[] data = new byte[1536];
BitPack bitpack = new BitPack(data, 0);
bitpack.PackBits(header.Stride, 16);
bitpack.PackBits(header.PatchSize, 8);
bitpack.PackBits((int)header.Type, 8);
CreatePatch(bitpack, patchData, x, y);
bitpack.PackBits(END_OF_PATCHES, 8);
layer.LayerData.Data = new byte[bitpack.BytePos + 1];
Buffer.BlockCopy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1);
return layer;
}
public static LayerDataPacket CreateLandPacket(float[,] patchData, int x, int y)
{
LayerDataPacket layer = new LayerDataPacket();
layer.LayerID.Type = (byte)TerrainPatch.LayerType.Land;
TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader();
header.Stride = STRIDE;
header.PatchSize = 16;
header.Type = TerrainPatch.LayerType.Land;
byte[] data = new byte[1536];
BitPack bitpack = new BitPack(data, 0);
bitpack.PackBits(header.Stride, 16);
bitpack.PackBits(header.PatchSize, 8);
bitpack.PackBits((int)header.Type, 8);
CreatePatch(bitpack, patchData, x, y);
bitpack.PackBits(END_OF_PATCHES, 8);
layer.LayerData.Data = new byte[bitpack.BytePos + 1];
Buffer.BlockCopy(bitpack.Data, 0, layer.LayerData.Data, 0, bitpack.BytePos + 1);
return layer;
}
public static void CreatePatch(BitPack output, float[] patchData, int x, int y)
{
if (patchData.Length != 16 * 16)
throw new ArgumentException("Patch data must be a 16x16 array");
TerrainPatch.Header header = PrescanPatch(patchData);
header.QuantWBits = 136;
header.PatchIDs = (y & 0x1F);
header.PatchIDs += (x << 5);
// NOTE: No idea what prequant and postquant should be or what they do
int[] patch = CompressPatch(patchData, header, 10);
int wbits = EncodePatchHeader(output, header, patch);
EncodePatch(output, patch, 0, wbits);
}
public static void CreatePatch(BitPack output, float[,] patchData, int x, int y)
{
if (patchData.Length != 16 * 16)
throw new ArgumentException("Patch data must be a 16x16 array");
TerrainPatch.Header header = PrescanPatch(patchData);
header.QuantWBits = 136;
header.PatchIDs = (y & 0x1F);
header.PatchIDs += (x << 5);
// NOTE: No idea what prequant and postquant should be or what they do
int[] patch = CompressPatch(patchData, header, 10);
int wbits = EncodePatchHeader(output, header, patch);
EncodePatch(output, patch, 0, wbits);
}
///
/// Add a patch of terrain to a BitPacker
///
/// BitPacker to write the patch to
/// Heightmap of the simulator, must be a 256 *
/// 256 float array
/// X offset of the patch to create, valid values are
/// from 0 to 15
/// Y offset of the patch to create, valid values are
/// from 0 to 15
public static void CreatePatchFromHeightmap(BitPack output, float[] heightmap, int x, int y)
{
if (heightmap.Length != 256 * 256)
throw new ArgumentException("Heightmap data must be 256x256");
if (x < 0 || x > 15 || y < 0 || y > 15)
throw new ArgumentException("X and Y patch offsets must be from 0 to 15");
TerrainPatch.Header header = PrescanPatch(heightmap, x, y);
header.QuantWBits = 136;
header.PatchIDs = (y & 0x1F);
header.PatchIDs += (x << 5);
// NOTE: No idea what prequant and postquant should be or what they do
int[] patch = CompressPatch(heightmap, x, y, header, 10);
int wbits = EncodePatchHeader(output, header, patch);
EncodePatch(output, patch, 0, wbits);
}
private static TerrainPatch.Header PrescanPatch(float[] patch)
{
TerrainPatch.Header header = new TerrainPatch.Header();
float zmax = -99999999.0f;
float zmin = 99999999.0f;
for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 16; i++)
{
float val = patch[j * 16 + i];
if (val > zmax) zmax = val;
if (val < zmin) zmin = val;
}
}
header.DCOffset = zmin;
header.Range = (int)((zmax - zmin) + 1.0f);
return header;
}
private static TerrainPatch.Header PrescanPatch(float[,] patch)
{
TerrainPatch.Header header = new TerrainPatch.Header();
float zmax = -99999999.0f;
float zmin = 99999999.0f;
for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 16; i++)
{
float val = patch[j, i];
if (val > zmax) zmax = val;
if (val < zmin) zmin = val;
}
}
header.DCOffset = zmin;
header.Range = (int)((zmax - zmin) + 1.0f);
return header;
}
private static TerrainPatch.Header PrescanPatch(float[] heightmap, int patchX, int patchY)
{
TerrainPatch.Header header = new TerrainPatch.Header();
float zmax = -99999999.0f;
float zmin = 99999999.0f;
for (int j = patchY * 16; j < (patchY + 1) * 16; j++)
{
for (int i = patchX * 16; i < (patchX + 1) * 16; i++)
{
float val = heightmap[j * 256 + i];
if (val > zmax) zmax = val;
if (val < zmin) zmin = val;
}
}
header.DCOffset = zmin;
header.Range = (int)((zmax - zmin) + 1.0f);
return header;
}
public static TerrainPatch.Header DecodePatchHeader(BitPack bitpack)
{
TerrainPatch.Header header = new TerrainPatch.Header();
// Quantized word bits
header.QuantWBits = bitpack.UnpackBits(8);
if (header.QuantWBits == END_OF_PATCHES)
return header;
// DC offset
header.DCOffset = bitpack.UnpackFloat();
// Range
header.Range = bitpack.UnpackBits(16);
// Patch IDs (10 bits)
header.PatchIDs = bitpack.UnpackBits(10);
// Word bits
header.WordBits = (uint)((header.QuantWBits & 0x0f) + 2);
return header;
}
private static int EncodePatchHeader(BitPack output, TerrainPatch.Header header, int[] patch)
{
int temp;
int wbits = (header.QuantWBits & 0x0f) + 2;
uint maxWbits = (uint)wbits + 5;
uint minWbits = ((uint)wbits >> 1);
wbits = (int)minWbits;
for (int i = 0; i < patch.Length; i++)
{
temp = patch[i];
if (temp != 0)
{
// Get the absolute value
if (temp < 0) temp *= -1;
for (int j = (int)maxWbits; j > (int)minWbits; j--)
{
if ((temp & (1 << j)) != 0)
{
if (j > wbits) wbits = j;
break;
}
}
}
}
wbits += 1;
header.QuantWBits &= 0xf0;
if (wbits > 17 || wbits < 2)
{
Logger.Log("Bits needed per word in EncodePatchHeader() are outside the allowed range",
Helpers.LogLevel.Error);
}
header.QuantWBits |= (wbits - 2);
output.PackBits(header.QuantWBits, 8);
output.PackFloat(header.DCOffset);
output.PackBits(header.Range, 16);
output.PackBits(header.PatchIDs, 10);
return wbits;
}
private static void IDCTColumn16(float[] linein, float[] lineout, int column)
{
float total;
int usize;
for (int n = 0; n < 16; n++)
{
total = OO_SQRT2 * linein[column];
for (int u = 1; u < 16; u++)
{
usize = u * 16;
total += linein[usize + column] * CosineTable16[usize + n];
}
lineout[16 * n + column] = total;
}
}
private static void IDCTLine16(float[] linein, float[] lineout, int line)
{
const float oosob = 2.0f / 16.0f;
int lineSize = line * 16;
float total;
for (int n = 0; n < 16; n++)
{
total = OO_SQRT2 * linein[lineSize];
for (int u = 1; u < 16; u++)
{
total += linein[lineSize + u] * CosineTable16[u * 16 + n];
}
lineout[lineSize + n] = total * oosob;
}
}
private static void DCTLine16(float[] linein, float[] lineout, int line)
{
float total = 0.0f;
int lineSize = line * 16;
for (int n = 0; n < 16; n++)
{
total += linein[lineSize + n];
}
lineout[lineSize] = OO_SQRT2 * total;
for (int u = 1; u < 16; u++)
{
total = 0.0f;
for (int n = 0; n < 16; n++)
{
total += linein[lineSize + n] * CosineTable16[u * 16 + n];
}
lineout[lineSize + u] = total;
}
}
private static void DCTColumn16(float[] linein, int[] lineout, int column)
{
float total = 0.0f;
const float oosob = 2.0f / 16.0f;
for (int n = 0; n < 16; n++)
{
total += linein[16 * n + column];
}
lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]);
for (int u = 1; u < 16; u++)
{
total = 0.0f;
for (int n = 0; n < 16; n++)
{
total += linein[16 * n + column] * CosineTable16[u * 16 + n];
}
lineout[CopyMatrix16[16 * u + column]] = (int)(total * oosob * QuantizeTable16[16 * u + column]);
}
}
public static void DecodePatch(int[] patches, BitPack bitpack, TerrainPatch.Header header, int size)
{
int temp;
for (int n = 0; n < size * size; n++)
{
// ?
temp = bitpack.UnpackBits(1);
if (temp != 0)
{
// Value or EOB
temp = bitpack.UnpackBits(1);
if (temp != 0)
{
// Value
temp = bitpack.UnpackBits(1);
if (temp != 0)
{
// Negative
temp = bitpack.UnpackBits((int)header.WordBits);
patches[n] = temp * -1;
}
else
{
// Positive
temp = bitpack.UnpackBits((int)header.WordBits);
patches[n] = temp;
}
}
else
{
// Set the rest to zero
// TODO: This might not be necessary
for (int o = n; o < size * size; o++)
{
patches[o] = 0;
}
break;
}
}
else
{
patches[n] = 0;
}
}
}
private static void EncodePatch(BitPack output, int[] patch, int postquant, int wbits)
{
int temp;
bool eob;
if (postquant > 16 * 16 || postquant < 0)
{
Logger.Log("Postquant is outside the range of allowed values in EncodePatch()", Helpers.LogLevel.Error);
return;
}
if (postquant != 0) patch[16 * 16 - postquant] = 0;
for (int i = 0; i < 16 * 16; i++)
{
eob = false;
temp = patch[i];
if (temp == 0)
{
eob = true;
for (int j = i; j < 16 * 16 - postquant; j++)
{
if (patch[j] != 0)
{
eob = false;
break;
}
}
if (eob)
{
output.PackBits(ZERO_EOB, 2);
return;
}
else
{
output.PackBits(ZERO_CODE, 1);
}
}
else
{
if (temp < 0)
{
temp *= -1;
if (temp > (1 << wbits)) temp = (1 << wbits);
output.PackBits(NEGATIVE_VALUE, 3);
output.PackBits(temp, wbits);
}
else
{
if (temp > (1 << wbits)) temp = (1 << wbits);
output.PackBits(POSITIVE_VALUE, 3);
output.PackBits(temp, wbits);
}
}
}
}
public static float[] DecompressPatch(int[] patches, TerrainPatch.Header header, TerrainPatch.GroupHeader group)
{
float[] block = new float[group.PatchSize * group.PatchSize];
float[] output = new float[group.PatchSize * group.PatchSize];
int prequant = (header.QuantWBits >> 4) + 2;
int quantize = 1 << prequant;
float ooq = 1.0f / (float)quantize;
float mult = ooq * (float)header.Range;
float addval = mult * (float)(1 << (prequant - 1)) + header.DCOffset;
if (group.PatchSize == 16)
{
for (int n = 0; n < 16 * 16; n++)
{
block[n] = patches[CopyMatrix16[n]] * DequantizeTable16[n];
}
float[] ftemp = new float[16 * 16];
for (int o = 0; o < 16; o++)
IDCTColumn16(block, ftemp, o);
for (int o = 0; o < 16; o++)
IDCTLine16(ftemp, block, o);
}
else
{
for (int n = 0; n < 32 * 32; n++)
{
block[n] = patches[CopyMatrix32[n]] * DequantizeTable32[n];
}
Logger.Log("Implement IDCTPatchLarge", Helpers.LogLevel.Error);
}
for (int j = 0; j < block.Length; j++)
{
output[j] = block[j] * mult + addval;
}
return output;
}
private static int[] CompressPatch(float[] patchData, TerrainPatch.Header header, int prequant)
{
float[] block = new float[16 * 16];
int wordsize = prequant;
float oozrange = 1.0f / (float)header.Range;
float range = (float)(1 << prequant);
float premult = oozrange * range;
float sub = (float)(1 << (prequant - 1)) + header.DCOffset * premult;
header.QuantWBits = wordsize - 2;
header.QuantWBits |= (prequant - 2) << 4;
int k = 0;
for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 16; i++)
block[k++] = patchData[j * 16 + i] * premult - sub;
}
float[] ftemp = new float[16 * 16];
int[] itemp = new int[16 * 16];
for (int o = 0; o < 16; o++)
DCTLine16(block, ftemp, o);
for (int o = 0; o < 16; o++)
DCTColumn16(ftemp, itemp, o);
return itemp;
}
private static int[] CompressPatch(float[,] patchData, TerrainPatch.Header header, int prequant)
{
float[] block = new float[16 * 16];
int wordsize = prequant;
float oozrange = 1.0f / (float)header.Range;
float range = (float)(1 << prequant);
float premult = oozrange * range;
float sub = (float)(1 << (prequant - 1)) + header.DCOffset * premult;
header.QuantWBits = wordsize - 2;
header.QuantWBits |= (prequant - 2) << 4;
int k = 0;
for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 16; i++)
block[k++] = patchData[j, i] * premult - sub;
}
float[] ftemp = new float[16 * 16];
int[] itemp = new int[16 * 16];
for (int o = 0; o < 16; o++)
DCTLine16(block, ftemp, o);
for (int o = 0; o < 16; o++)
DCTColumn16(ftemp, itemp, o);
return itemp;
}
private static int[] CompressPatch(float[] heightmap, int patchX, int patchY, TerrainPatch.Header header, int prequant)
{
float[] block = new float[16 * 16];
int wordsize = prequant;
float oozrange = 1.0f / (float)header.Range;
float range = (float)(1 << prequant);
float premult = oozrange * range;
float sub = (float)(1 << (prequant - 1)) + header.DCOffset * premult;
header.QuantWBits = wordsize - 2;
header.QuantWBits |= (prequant - 2) << 4;
int k = 0;
for (int j = patchY * 16; j < (patchY + 1) * 16; j++)
{
for (int i = patchX * 16; i < (patchX + 1) * 16; i++)
block[k++] = heightmap[j * 256 + i] * premult - sub;
}
float[] ftemp = new float[16 * 16];
int[] itemp = new int[16 * 16];
for (int o = 0; o < 16; o++)
DCTLine16(block, ftemp, o);
for (int o = 0; o < 16; o++)
DCTColumn16(ftemp, itemp, o);
return itemp;
}
#region Initialization
private static void BuildDequantizeTable16()
{
for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 16; i++)
{
DequantizeTable16[j * 16 + i] = 1.0f + 2.0f * (float)(i + j);
}
}
}
private static void BuildQuantizeTable16()
{
for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 16; i++)
{
QuantizeTable16[j * 16 + i] = 1.0f / (1.0f + 2.0f * ((float)i + (float)j));
}
}
}
private static void SetupCosines16()
{
const float hposz = (float)Math.PI * 0.5f / 16.0f;
for (int u = 0; u < 16; u++)
{
for (int n = 0; n < 16; n++)
{
CosineTable16[u * 16 + n] = (float)Math.Cos((2.0f * (float)n + 1.0f) * (float)u * hposz);
}
}
}
private static void BuildCopyMatrix16()
{
bool diag = false;
bool right = true;
int i = 0;
int j = 0;
int count = 0;
while (i < 16 && j < 16)
{
CopyMatrix16[j * 16 + i] = count++;
if (!diag)
{
if (right)
{
if (i < 16 - 1) i++;
else j++;
right = false;
diag = true;
}
else
{
if (j < 16 - 1) j++;
else i++;
right = true;
diag = true;
}
}
else
{
if (right)
{
i++;
j--;
if (i == 16 - 1 || j == 0) diag = false;
}
else
{
i--;
j++;
if (j == 16 - 1 || i == 0) diag = false;
}
}
}
}
#endregion Initialization
}
}