Files
libremetaverse/libsecondlife-cs/TerrainManager.cs
John Hurliman 5c32edac19 * Added basic sim heightmap tracking (disabled by default) to TerrainManager
* Added TerrainHeightAtPoint() to TerrainManager

git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@975 52acb1d6-8a22-11de-b505-999d5b087335
2007-02-12 10:34:42 +00:00

586 lines
19 KiB
C#

/*
* Copyright (c) 2007, Second Life Reverse Engineering Team
* 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 Second Life Reverse Engineering Team 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 System.Collections.Generic;
using libsecondlife.Packets;
namespace libsecondlife
{
public class TerrainManager
{
public enum LayerType : byte
{
Land = 0x4C,
Water = 0x57,
Wind = 0x37,
Cloud = 0x38
}
public struct GroupHeader
{
public int Stride;
public int PatchSize;
public LayerType Type;
}
public struct PatchHeader
{
public float DCOffset;
public int Range;
public int QuantWBits;
public int PatchIDs;
public uint WordBits;
}
public class Patch
{
public float[] Heightmap;
}
/// <summary>
///
/// </summary>
/// <param name="simulator"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="data"></param>
public delegate void LandPatchCallback(Simulator simulator, int x, int y, int width, float[] data);
/// <summary>
///
/// </summary>
public event LandPatchCallback OnLandPatch;
private const byte END_OF_PATCHES = 97;
private const int PATCHES_PER_EDGE = 16;
private const float OO_SQRT2 = 0.7071067811865475244008443621049f;
private SecondLife Client;
private Dictionary<ulong, Patch[]> SimPatches = new Dictionary<ulong, Patch[]>();
private float[] DequantizeTable16 = new float[16 * 16];
private float[] DequantizeTable32 = new float[32 * 32];
private float[] ICosineTable16 = new float[16 * 16];
private float[] ICosineTable32 = new float[32 * 32];
private int[] DeCopyMatrix16 = new int[16 * 16];
private int[] DeCopyMatrix32 = new int[32 * 32];
/// <summary>
///
/// </summary>
/// <param name="client"></param>
public TerrainManager(SecondLife client)
{
Client = client;
// Initialize the decompression tables
BuildDequantizeTable16();
BuildDequantizeTable32();
SetupICosines16();
SetupICosines32();
BuildDecopyMatrix16();
BuildDecopyMatrix32();
Client.Network.RegisterCallback(PacketType.LayerData, new NetworkManager.PacketCallback(LayerDataHandler));
}
/// <summary>
/// Retrieve the terrain height at a given coordinate
/// </summary>
/// <param name="regionHandle">The region that the point of interest is in</param>
/// <param name="x">Sim X coordinate, valid range is from 0 to 255</param>
/// <param name="y">Sim Y coordinate, valid range is from 0 to 255</param>
/// <param name="height">The terrain height at the given point if the
/// lookup was successful, otherwise 0.0f</param>
/// <returns>True if the lookup was successful, otherwise false</returns>
public bool TerrainHeightAtPoint(ulong regionHandle, int x, int y, out float height)
{
if (x > 0 && x < 256 && y > 0 && y < 256)
{
lock (SimPatches)
{
if (SimPatches.ContainsKey(regionHandle))
{
int patchX = (int)Math.DivRem(x, 16, out x);
int patchY = (int)Math.DivRem(y, 16, out y);
if (SimPatches[regionHandle][patchY * 16 + patchX] != null)
{
height = SimPatches[regionHandle][patchY * 16 + patchX].Heightmap[y * 16 + x];
return true;
}
}
}
}
height = 0.0f;
return false;
}
private 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 void BuildDequantizeTable32()
{
for (int j = 0; j < 32; j++)
{
for (int i = 0; i < 32; i++)
{
DequantizeTable32[j * 32 + i] = 1.0f + 2.0f * (float)(i + j);
}
}
}
private void SetupICosines16()
{
const float hposz = (float)Math.PI * 0.5f / 16.0f;
for (int u = 0; u < 16; u++)
{
for (int n = 0; n < 16; n++)
{
ICosineTable16[u * 16 + n] = (float)Math.Cos((2.0f * (float)n + 1.0f) * (float)u * hposz);
}
}
}
private void SetupICosines32()
{
const float hposz = (float)Math.PI * 0.5f / 32.0f;
for (int u = 0; u < 32; u++)
{
for (int n = 0; n < 32; n++)
{
ICosineTable32[u * 32 + n] = (float)Math.Cos((2.0f * (float)n + 1.0f) * (float)u * hposz);
}
}
}
private void BuildDecopyMatrix16()
{
bool diag = false;
bool right = true;
int i = 0;
int j = 0;
int count = 0;
while (i < 16 && j < 16)
{
DeCopyMatrix16[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;
}
}
}
}
private void BuildDecopyMatrix32()
{
bool diag = false;
bool right = true;
int i = 0;
int j = 0;
int count = 0;
while (i < 32 && j < 32)
{
DeCopyMatrix32[j * 32 + i] = count++;
if (!diag)
{
if (right)
{
if (i < 32 - 1) i++;
else j++;
right = false;
diag = true;
}
else
{
if (j < 32 - 1) j++;
else i++;
right = true;
diag = true;
}
}
else
{
if (right)
{
i++;
j--;
if (i == 32 - 1 || j == 0) diag = false;
}
else
{
i--;
j++;
if (j == 32 - 1 || i == 0) diag = false;
}
}
}
}
private PatchHeader DecodePatchHeader(BitPack bitpack)
{
PatchHeader header = new PatchHeader();
// 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 & 0xf) + 2);
return header;
}
private 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] * ICosineTable16[usize + n];
}
lineout[16 * n + column] = total;
}
}
private void IDCTColumn32(float[] linein, float[] lineout, int column)
{
float total;
int usize;
for (int n = 0; n < 32; n++)
{
total = OO_SQRT2 * linein[column];
for (int u = 1; u < 32; u++)
{
usize = u * 32;
total += linein[usize + column] * ICosineTable32[usize + n];
}
lineout[32 * n + column] = total;
}
}
private 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] * ICosineTable16[u * 16 + n];
}
lineout[lineSize + n] = total * oosob;
}
}
private void IDCTLine32(float[] linein, float[] lineout, int line)
{
const float oosob = 2.0f / 32.0f;
int lineSize = line * 32;
float total;
for (int n = 0; n < 32; n++)
{
total = OO_SQRT2 * linein[lineSize];
for (int u = 1; u < 32; u++)
{
total += linein[lineSize + u] * ICosineTable32[u * 32 + n];
}
lineout[lineSize + n] = total * oosob;
}
}
private void DecodePatch(int[] patches, BitPack bitpack, PatchHeader 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 float[] DecompressPatch(int[] patches, PatchHeader header, 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[DeCopyMatrix16[n]] * DequantizeTable16[n];
}
float[] ftemp = new float[32 * 32];
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[DeCopyMatrix32[n]] * DequantizeTable32[n];
}
//IDCTPatchLarge(block);
Client.Log("Implement IDCTPatchLarge", Helpers.LogLevel.Warning);
}
for (int j = 0; j < block.Length; j++)
{
output[j] = block[j] * mult + addval;
}
return output;
}
private void DecompressLand(Simulator simulator, BitPack bitpack, GroupHeader group)
{
int x;
int y;
int[] patches = new int[32 * 32];
int count = 0;
//group.Stride = 256;
while (true)
{
PatchHeader header = DecodePatchHeader(bitpack);
if (header.QuantWBits == END_OF_PATCHES)
break;
x = header.PatchIDs >> 5;
y = header.PatchIDs & 0x1F;
if (x >= PATCHES_PER_EDGE || y >= PATCHES_PER_EDGE)
{
Client.Log("Invalid LayerData land packet, x = " + x + ", y = " + y + ", dc_offset = " +
header.DCOffset + ", range = " + header.Range + ", quant_wbits = " + header.QuantWBits +
", patchids = " + header.PatchIDs + ", count = " + count, Helpers.LogLevel.Warning);
return;
}
// Decode this patch
DecodePatch(patches, bitpack, header, group.PatchSize);
// Decompress this patch
float[] heightmap = DecompressPatch(patches, header, group);
count++;
if (OnLandPatch != null)
{
try { OnLandPatch(simulator, x, y, group.PatchSize, heightmap); }
catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); }
if (Client.Settings.STORE_LAND_PATCHES)
{
lock (SimPatches)
{
if (!SimPatches.ContainsKey(simulator.Handle))
SimPatches.Add(simulator.Handle, new Patch[16 * 16]);
SimPatches[simulator.Handle][y * 16 + x] = new Patch();
SimPatches[simulator.Handle][y * 16 + x].Heightmap = heightmap;
}
}
}
}
}
private void DecompressWind(Simulator simulator, BitPack bitpack, GroupHeader group)
{
;
}
private void DecompressCloud(Simulator simulator, BitPack bitpack, GroupHeader group)
{
;
}
private void LayerDataHandler(Packet packet, Simulator simulator)
{
LayerDataPacket layer = (LayerDataPacket)packet;
BitPack bitpack = new BitPack(layer.LayerData.Data, 0);
GroupHeader header = new GroupHeader();
LayerType type = (LayerType)layer.LayerID.Type;
// Stride
header.Stride = bitpack.UnpackBits(16);
// Patch size
header.PatchSize = bitpack.UnpackBits(8);
// Layer type
header.Type = (LayerType)bitpack.UnpackBits(8);
if (type != header.Type)
Client.DebugLog("LayerData: LayerID.Type " + type.ToString() + " does not match decoded type " +
header.Type.ToString());
switch (type)
{
case LayerType.Land:
if (OnLandPatch != null) DecompressLand(simulator, bitpack, header);
break;
case LayerType.Water:
Client.Log("Got a Water LayerData packet, implement me!", Helpers.LogLevel.Info);
break;
case LayerType.Wind:
DecompressWind(simulator, bitpack, header);
break;
case LayerType.Cloud:
DecompressCloud(simulator, bitpack, header);
break;
default:
Client.Log("Unrecognized LayerData type " + type.ToString(), Helpers.LogLevel.Warning);
break;
}
}
}
}