varregion: add varregion and TerrainData use in LLClientView.

Add sending multiple parcel patches and sending patches by avatar
view distance.
avinationmerge
Robert Adams 2015-03-28 08:30:52 -07:00
parent 08c72a8dc1
commit c5a7bf6601
3 changed files with 1131 additions and 110 deletions

View File

@ -27,6 +27,7 @@
using System;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
namespace OpenSim.Framework
{
@ -40,9 +41,26 @@ namespace OpenSim.Framework
public byte WaterHeight;
public ushort X;
public ushort Y;
public ushort SizeX;
public ushort SizeY;
public MapBlockData()
{
}
public OSDMap ToOSD()
{
OSDMap map = new OSDMap();
map["X"] = X;
map["Y"] = Y;
map["SizeX"] = SizeX;
map["SizeY"] = SizeY;
map["Name"] = Name;
map["Access"] = Access;
map["RegionFlags"] = RegionFlags;
map["WaterHeight"] = WaterHeight;
map["MapImageID"] = MapImageId;
return map;
}
}
}

View File

@ -312,6 +312,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
private const float m_sunPainDaHalfOrbitalCutoff = 4.712388980384689858f;
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static string LogHeader = "[LLCLIENTVIEW]";
protected static Dictionary<PacketType, PacketMethod> PacketHandlers = new Dictionary<PacketType, PacketMethod>(); //Global/static handlers for all clients
/// <summary>
@ -689,13 +690,37 @@ namespace OpenSim.Region.ClientStack.LindenUDP
/// </param>
/// <returns>true if the handler was added. This is currently always the case.</returns>
public bool AddLocalPacketHandler(PacketType packetType, PacketMethod handler, bool doAsync)
{
return AddLocalPacketHandler(packetType, handler, doAsync, false);
}
/// <summary>
/// Add a handler for the given packet type.
/// </summary>
/// <param name="packetType"></param>
/// <param name="handler"></param>
/// <param name="doAsync">
/// If true, when the packet is received handle it on a different thread. Whether this is given direct to
/// a threadpool thread or placed in a queue depends on the inEngine parameter.
/// </param>
/// <param name="inEngine">
/// If async is false then this parameter is ignored.
/// If async is true and inEngine is false, then the packet is sent directly to a
/// threadpool thread.
/// If async is true and inEngine is true, then the packet is sent to the IncomingPacketAsyncHandlingEngine.
/// This may result in slower handling but reduces the risk of overloading the simulator when there are many
/// simultaneous async requests.
/// </param>
/// <returns>true if the handler was added. This is currently always the case.</returns>
public bool AddLocalPacketHandler(PacketType packetType, PacketMethod handler, bool doAsync, bool inEngine)
{
bool result = false;
lock (m_packetHandlers)
{
if (!m_packetHandlers.ContainsKey(packetType))
{
m_packetHandlers.Add(packetType, new PacketProcessor() { method = handler, Async = doAsync });
m_packetHandlers.Add(
packetType, new PacketProcessor() { method = handler, Async = doAsync });
result = true;
}
}
@ -1174,11 +1199,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP
/// <summary>
/// Send the region heightmap to the client
/// This method is only called when not doing intellegent terrain patch sending and
/// is only called when the scene presence is initially created and sends all of the
/// region's patches to the client.
/// </summary>
/// <param name="map">heightmap</param>
public virtual void SendLayerData(float[] map)
{
Util.FireAndForget(DoSendLayerData, map);
Util.FireAndForget(DoSendLayerData, m_scene.Heightmap.GetTerrainData());
// Send it sync, and async. It's not that much data
// and it improves user experience just so much!
@ -1191,15 +1219,16 @@ namespace OpenSim.Region.ClientStack.LindenUDP
/// <param name="o"></param>
private void DoSendLayerData(object o)
{
float[] map = LLHeightFieldMoronize((float[])o);
TerrainData map = (TerrainData)o;
try
{
// Send LayerData in typerwriter pattern
for (int y = 0; y < 16; y++)
{
for (int x = 0; x < 16; x+=4)
for (int x = 0; x < 16; x++)
{
SendLayerPacket(x, y, map);
SendLayerData(x, y, map);
}
}
}
@ -1209,77 +1238,95 @@ namespace OpenSim.Region.ClientStack.LindenUDP
}
}
/// <summary>
/// Sends a set of four patches (x, x+1, ..., x+3) to the client
/// </summary>
/// <param name="map">heightmap</param>
/// <param name="px">X coordinate for patches 0..12</param>
/// <param name="py">Y coordinate for patches 0..15</param>
private void SendLayerPacket(int x, int y, float[] map)
// Legacy form of invocation that passes around a bare data array.
// Just ignore what was passed and use the real terrain info that is part of the scene.
// As a HORRIBLE kludge in an attempt to not change the definition of IClientAPI,
// there is a special form for specifying multiple terrain patches to send.
// The form is to pass 'px' as negative the number of patches to send and to
// pass the float array as pairs of patch X and Y coordinates. So, passing 'px'
// as -2 and map= [3, 5, 8, 4] would mean to send two terrain heightmap patches
// and the patches to send are <3,5> and <8,4>.
public void SendLayerData(int px, int py, float[] map)
{
int[] patches = new int[4];
patches[0] = x + 0 + y * 16;
patches[1] = x + 1 + y * 16;
patches[2] = x + 2 + y * 16;
patches[3] = x + 3 + y * 16;
float[] heightmap = (map.Length == 65536) ?
map :
LLHeightFieldMoronize(map);
try
if (px >= 0)
{
Packet layerpack = TerrainCompressor.CreateLandPacket(heightmap, patches);
OutPacket(layerpack, ThrottleOutPacketType.Land);
SendLayerData(px, py, m_scene.Heightmap.GetTerrainData());
}
catch
else
{
for (int px = x ; px < x + 4 ; px++)
SendLayerData(px, y, map);
int numPatches = -px;
int[] xPatches = new int[numPatches];
int[] yPatches = new int[numPatches];
for (int pp = 0; pp < numPatches; pp++)
{
xPatches[pp] = (int)map[pp * 2];
yPatches[pp] = (int)map[pp * 2 + 1];
}
// DebugSendingPatches("SendLayerData", xPatches, yPatches);
SendLayerData(xPatches, yPatches, m_scene.Heightmap.GetTerrainData());
}
}
private void DebugSendingPatches(string pWho, int[] pX, int[] pY)
{
if (m_log.IsDebugEnabled)
{
int numPatches = pX.Length;
string Xs = "";
string Ys = "";
for (int pp = 0; pp < numPatches; pp++)
{
Xs += String.Format("{0}", (int)pX[pp]) + ",";
Ys += String.Format("{0}", (int)pY[pp]) + ",";
}
m_log.DebugFormat("{0} {1}: numPatches={2}, X={3}, Y={4}", LogHeader, pWho, numPatches, Xs, Ys);
}
}
/// <summary>
/// Sends a specified patch to a client
/// Sends a terrain packet for the point specified.
/// This is a legacy call that has refarbed the terrain into a flat map of floats.
/// We just use the terrain from the region we know about.
/// </summary>
/// <param name="px">Patch coordinate (x) 0..15</param>
/// <param name="py">Patch coordinate (y) 0..15</param>
/// <param name="map">heightmap</param>
public void SendLayerData(int px, int py, float[] map)
public void SendLayerData(int px, int py, TerrainData terrData)
{
int[] xPatches = new[] { px };
int[] yPatches = new[] { py };
SendLayerData(xPatches, yPatches, terrData);
}
private void SendLayerData(int[] px, int[] py, TerrainData terrData)
{
try
{
int[] patches = new int[] { py * 16 + px };
float[] heightmap = (map.Length == 65536) ?
map :
LLHeightFieldMoronize(map);
LayerDataPacket layerpack = TerrainCompressor.CreateLandPacket(heightmap, patches);
// When a user edits the terrain, so much data is sent, the data queues up fast and presents a sub optimal editing experience.
// To alleviate this issue, when the user edits the terrain, we start skipping the queues until they're done editing the terrain.
// We also make them unreliable because it's extremely likely that multiple packets will be sent for a terrain patch area
// invalidating previous packets for that area.
// It's possible for an editing user to flood themselves with edited packets but the majority of use cases are such that only a
// tiny percentage of users will be editing the terrain. Other, non-editing users will see the edits much slower.
// One last note on this topic, by the time users are going to be editing the terrain, it's extremely likely that the sim will
// have rezzed already and therefore this is not likely going to cause any additional issues with lost packets, objects or terrain
// patches.
// m_justEditedTerrain is volatile, so test once and duplicate two affected statements so we only have one cache miss.
if (m_justEditedTerrain)
/* test code using the terrain compressor in libOpenMetaverse
int[] patchInd = new int[1];
patchInd[0] = px + (py * Constants.TerrainPatchSize);
LayerDataPacket layerpack = TerrainCompressor.CreateLandPacket(terrData.GetFloatsSerialized(), patchInd);
*/
// Many, many patches could have been passed to us. Since the patches will be compressed
// into variable sized blocks, we cannot pre-compute how many will fit into one
// packet. While some fancy packing algorithm is possible, 4 seems to always fit.
int PatchesAssumedToFit = 4;
for (int pcnt = 0; pcnt < px.Length; pcnt += PatchesAssumedToFit)
{
layerpack.Header.Reliable = false;
OutPacket(layerpack,
ThrottleOutPacketType.Unknown );
}
else
{
layerpack.Header.Reliable = true;
OutPacket(layerpack,
ThrottleOutPacketType.Task);
int remaining = Math.Min(px.Length - pcnt, PatchesAssumedToFit);
int[] xPatches = new int[remaining];
int[] yPatches = new int[remaining];
for (int ii = 0; ii < remaining; ii++)
{
xPatches[ii] = px[pcnt + ii];
yPatches[ii] = py[pcnt + ii];
}
LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLandPacket(terrData, xPatches, yPatches);
// DebugSendingPatches("SendLayerDataInternal", xPatches, yPatches);
SendTheLayerPacket(layerpack);
}
}
catch (Exception e)
@ -1288,36 +1335,34 @@ namespace OpenSim.Region.ClientStack.LindenUDP
}
}
/// <summary>
/// Munges heightfield into the LLUDP backed in restricted heightfield.
/// </summary>
/// <param name="map">float array in the base; Constants.RegionSize</param>
/// <returns>float array in the base 256</returns>
internal float[] LLHeightFieldMoronize(float[] map)
// When a user edits the terrain, so much data is sent, the data queues up fast and presents a
// sub optimal editing experience. To alleviate this issue, when the user edits the terrain, we
// start skipping the queues until they're done editing the terrain. We also make them
// unreliable because it's extremely likely that multiple packets will be sent for a terrain patch
// area invalidating previous packets for that area.
// It's possible for an editing user to flood themselves with edited packets but the majority
// of use cases are such that only a tiny percentage of users will be editing the terrain.
// Other, non-editing users will see the edits much slower.
// One last note on this topic, by the time users are going to be editing the terrain, it's
// extremely likely that the sim will have rezzed already and therefore this is not likely going
// to cause any additional issues with lost packets, objects or terrain patches.
// m_justEditedTerrain is volatile, so test once and duplicate two affected statements so we
// only have one cache miss.
private void SendTheLayerPacket(LayerDataPacket layerpack)
{
if (map.Length == 65536)
return map;
if (m_justEditedTerrain)
{
layerpack.Header.Reliable = false;
OutPacket(layerpack, ThrottleOutPacketType.Unknown );
}
else
{
float[] returnmap = new float[65536];
if (map.Length < 65535)
{
// rebase the vector stride to 256
for (int i = 0; i < Constants.RegionSize; i++)
Array.Copy(map, i * (int)Constants.RegionSize, returnmap, i * 256, (int)Constants.RegionSize);
}
else
{
for (int i = 0; i < 256; i++)
Array.Copy(map, i * (int)Constants.RegionSize, returnmap, i * 256, 256);
}
//Array.Copy(map,0,returnmap,0,(map.Length < 65536)? map.Length : 65536);
return returnmap;
layerpack.Header.Reliable = true;
OutPacket(layerpack, ThrottleOutPacketType.Land);
}
}
/// <summary>
@ -1346,21 +1391,22 @@ namespace OpenSim.Region.ClientStack.LindenUDP
{
Vector2[] windSpeeds = (Vector2[])o;
TerrainPatch[] patches = new TerrainPatch[2];
patches[0] = new TerrainPatch();
patches[0].Data = new float[16 * 16];
patches[1] = new TerrainPatch();
patches[1].Data = new float[16 * 16];
patches[0] = new TerrainPatch { Data = new float[16 * 16] };
patches[1] = new TerrainPatch { Data = new float[16 * 16] };
for (int y = 0; y < 16; y++)
for (int x = 0; x < 16 * 16; x++)
{
for (int x = 0; x < 16; x++)
{
patches[0].Data[y * 16 + x] = windSpeeds[y * 16 + x].X;
patches[1].Data[y * 16 + x] = windSpeeds[y * 16 + x].Y;
}
patches[0].Data[x] = windSpeeds[x].X;
patches[1].Data[x] = windSpeeds[x].Y;
}
LayerDataPacket layerpack = TerrainCompressor.CreateLayerDataPacket(patches, TerrainPatch.LayerType.Wind);
byte layerType = (byte)TerrainPatch.LayerType.Wind;
if (m_scene.RegionInfo.RegionSizeX > Constants.RegionSize || m_scene.RegionInfo.RegionSizeY > Constants.RegionSize)
layerType = (byte)TerrainPatch.LayerType.WindExtended;
// LayerDataPacket layerpack = TerrainCompressor.CreateLayerDataPacket(patches, (TerrainPatch.LayerType)layerType);
LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLayerDataPacket(patches, layerType,
(int)m_scene.RegionInfo.RegionSizeX, (int)m_scene.RegionInfo.RegionSizeY);
layerpack.Header.Zerocoded = true;
OutPacket(layerpack, ThrottleOutPacketType.Wind);
}
@ -1384,7 +1430,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP
}
}
LayerDataPacket layerpack = TerrainCompressor.CreateLayerDataPacket(patches, TerrainPatch.LayerType.Cloud);
byte layerType = (byte)TerrainPatch.LayerType.Cloud;
if (m_scene.RegionInfo.RegionSizeX > Constants.RegionSize || m_scene.RegionInfo.RegionSizeY > Constants.RegionSize)
layerType = (byte)TerrainPatch.LayerType.CloudExtended;
// LayerDataPacket layerpack = TerrainCompressor.CreateLayerDataPacket(patches, (TerrainPatch.LayerType)layerType);
LayerDataPacket layerpack = OpenSimTerrainCompressor.CreateLayerDataPacket(patches, layerType,
(int)m_scene.RegionInfo.RegionSizeX, (int)m_scene.RegionInfo.RegionSizeY);
layerpack.Header.Zerocoded = true;
OutPacket(layerpack, ThrottleOutPacketType.Cloud);
}
@ -1489,10 +1541,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
mapReply.Data[i].Access = mapBlocks2[i].Access;
mapReply.Data[i].Agents = mapBlocks2[i].Agents;
// TODO: hookup varregion sim size here
mapReply.Size[i] = new MapBlockReplyPacket.SizeBlock();
mapReply.Size[i].SizeX = 256;
mapReply.Size[i].SizeY = 256;
mapReply.Size[i].SizeX = mapBlocks2[i].SizeX;
mapReply.Size[i].SizeY = mapBlocks2[i].SizeY;
}
OutPacket(mapReply, ThrottleOutPacketType.Land);
}
@ -1657,15 +1708,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP
public void SendKillObject(List<uint> localIDs)
{
// foreach (uint id in localIDs)
// m_log.DebugFormat("[CLIENT]: Sending KillObjectPacket to {0} for {1} in {2}", Name, id, regionHandle);
// remove pending entities
lock (m_entityProps.SyncRoot)
m_entityProps.Remove(localIDs);
lock (m_entityUpdates.SyncRoot)
m_entityUpdates.Remove(localIDs);
KillObjectPacket kill = (KillObjectPacket)PacketPool.Instance.GetPacket(PacketType.KillObject);
// TODO: don't create new blocks if recycling an old packet
kill.ObjectData = new KillObjectPacket.ObjectDataBlock[localIDs.Count];
@ -9087,6 +9129,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP
TeleportLocationRequest handlerTeleportLocationRequest = OnTeleportLocationRequest;
if (handlerTeleportLocationRequest != null)
{
// Adjust teleport location to base of a larger region if requested to teleport to a sub-region
uint locX, locY;
Util.RegionHandleToWorldLoc(tpLocReq.Info.RegionHandle, out locX, out locY);
if ((locX >= m_scene.RegionInfo.WorldLocX)
&& (locX < (m_scene.RegionInfo.WorldLocX + m_scene.RegionInfo.RegionSizeX))
&& (locY >= m_scene.RegionInfo.WorldLocY)
&& (locY < (m_scene.RegionInfo.WorldLocY + m_scene.RegionInfo.RegionSizeY)) )
{
tpLocReq.Info.RegionHandle = m_scene.RegionInfo.RegionHandle;
tpLocReq.Info.Position.X += locX - m_scene.RegionInfo.WorldLocX;
tpLocReq.Info.Position.Y += locY - m_scene.RegionInfo.WorldLocY;
}
handlerTeleportLocationRequest(this, tpLocReq.Info.RegionHandle, tpLocReq.Info.Position,
tpLocReq.Info.LookAt, 16);
}

View File

@ -0,0 +1,948 @@
/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* 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.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project 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 DEVELOPERS ``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 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.
*/
/* Freely adapted from the Aurora version of the terrain compressor.
* Copyright (c) Contributors, http://aurora-sim.org/, http://opensimulator.org/
*/
using System;
using System.Reflection;
using log4net;
using OpenSim.Framework;
using OpenSim.Region.Framework;
using OpenSim.Region.Framework.Scenes;
using OpenMetaverse;
using OpenMetaverse.Packets;
namespace OpenSim.Region.ClientStack.LindenUDP
{
public static class OpenSimTerrainCompressor
{
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
#pragma warning disable 414
private static string LogHeader = "[TERRAIN COMPRESSOR]";
#pragma warning restore 414
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[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
private static readonly float[] DequantizeTable32 =
new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
private static readonly float[] CosineTable16 = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
//private static readonly float[] CosineTable32 = new float[Constants.TerrainPatchSize * Constants.TerrainPatchSize];
private static readonly int[] CopyMatrix16 = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
private static readonly int[] CopyMatrix32 = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
private static readonly float[] QuantizeTable16 =
new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
static OpenSimTerrainCompressor()
{
// Initialize the decompression tables
BuildDequantizeTable16();
SetupCosines16();
BuildCopyMatrix16();
BuildQuantizeTable16();
}
// Used to send cloud and wind patches
public static LayerDataPacket CreateLayerDataPacket(TerrainPatch[] patches, byte type, int pRegionSizeX,
int pRegionSizeY)
{
LayerDataPacket layer = new LayerDataPacket {LayerID = {Type = type}};
TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader
{Stride = STRIDE, PatchSize = Constants.TerrainPatchSize};
// Should be enough to fit even the most poorly packed data
byte[] data = new byte[patches.Length*Constants.TerrainPatchSize*Constants.TerrainPatchSize*2];
BitPack bitpack = new BitPack(data, 0);
bitpack.PackBits(header.Stride, 16);
bitpack.PackBits(header.PatchSize, 8);
bitpack.PackBits(type, 8);
foreach (TerrainPatch t in patches)
CreatePatch(bitpack, t.Data, t.X, t.Y, pRegionSizeX, pRegionSizeY);
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;
}
// Create a land packet for a single patch.
public static LayerDataPacket CreateLandPacket(TerrainData terrData, int patchX, int patchY)
{
int[] xPieces = new int[1];
int[] yPieces = new int[1];
xPieces[0] = patchX; // patch X dimension
yPieces[0] = patchY;
return CreateLandPacket(terrData, xPieces, yPieces);
}
public static LayerDataPacket CreateLandPacket(TerrainData terrData, int[] xPieces, int[] yPieces)
{
byte landPacketType = (byte)TerrainPatch.LayerType.Land;
if (terrData.SizeX > Constants.RegionSize || terrData.SizeY > Constants.RegionSize)
{
landPacketType = (byte)TerrainPatch.LayerType.LandExtended;
}
return CreateLandPacket(terrData, xPieces, yPieces, landPacketType);
}
/// <summary>
/// Creates a LayerData packet for compressed land data given a full
/// simulator heightmap and an array of indices of patches to compress
/// </summary>
/// <param name="terrData">
/// Terrain data that can result in a meter square heightmap.
/// </param>
/// <param name="x">
/// Array of indexes in the grid of patches
/// for this simulator.
/// If creating a packet for multiple patches, there will be entries in
/// both the X and Y arrays for each of the patches.
/// For example if patches 1 and 17 are to be sent,
/// x[] = {1,1} and y[] = {0,1} which specifies the patches at
/// indexes <1,0> and <1,1> (presuming the terrain size is 16x16 patches).
/// </param>
/// <param name="y">
/// Array of indexes in the grid of patches.
/// </param>
/// <param name="type"></param>
/// <returns></returns>
public static LayerDataPacket CreateLandPacket(TerrainData terrData, int[] x, int[] y, byte type)
{
LayerDataPacket layer = new LayerDataPacket {LayerID = {Type = type}};
TerrainPatch.GroupHeader header = new TerrainPatch.GroupHeader
{Stride = STRIDE, PatchSize = Constants.TerrainPatchSize};
byte[] data = new byte[x.Length * Constants.TerrainPatchSize * Constants.TerrainPatchSize * 2];
BitPack bitpack = new BitPack(data, 0);
bitpack.PackBits(header.Stride, 16);
bitpack.PackBits(header.PatchSize, 8);
bitpack.PackBits(type, 8);
for (int i = 0; i < x.Length; i++)
CreatePatchFromHeightmap(bitpack, terrData, x[i], y[i]);
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;
}
// Unused: left for historical reference.
public static void CreatePatch(BitPack output, float[] patchData, int x, int y, int pRegionSizeX, int pRegionSizeY)
{
TerrainPatch.Header header = PrescanPatch(patchData);
header.QuantWBits = 136;
if (pRegionSizeX > Constants.RegionSize || pRegionSizeY > Constants.RegionSize)
{
header.PatchIDs = (y & 0xFFFF);
header.PatchIDs += (x << 16);
}
else
{
header.PatchIDs = (y & 0x1F);
header.PatchIDs += (x << 5);
}
// NOTE: No idea what prequant and postquant should be or what they do
int wbits;
int[] patch = CompressPatch(patchData, header, 10, out wbits);
wbits = EncodePatchHeader(output, header, patch, Constants.RegionSize, Constants.RegionSize, wbits);
EncodePatch(output, patch, 0, wbits);
}
/// <summary>
/// Add a patch of terrain to a BitPacker
/// </summary>
/// <param name="output">BitPacker to write the patch to</param>
/// <param name="heightmap">
/// Heightmap of the simulator. Presumed to be an sizeX*sizeY array.
/// </param>
/// <param name="patchX">
/// X offset of the patch to create.
/// </param>
/// <param name="patchY">
/// Y offset of the patch to create.
/// </param>
/// <param name="pRegionSizeX"></param>
/// <param name="pRegionSizeY"></param>
public static void CreatePatchFromHeightmap(BitPack output, TerrainData terrData, int patchX, int patchY)
{
TerrainPatch.Header header = PrescanPatch(terrData, patchX, patchY);
header.QuantWBits = 136;
// If larger than legacy region size, pack patch X and Y info differently.
if (terrData.SizeX > Constants.RegionSize || terrData.SizeY > Constants.RegionSize)
{
header.PatchIDs = (patchY & 0xFFFF);
header.PatchIDs += (patchX << 16);
}
else
{
header.PatchIDs = (patchY & 0x1F);
header.PatchIDs += (patchX << 5);
}
// m_log.DebugFormat("{0} CreatePatchFromHeightmap. patchX={1}, patchY={2}, DCOffset={3}, range={4}",
// LogHeader, patchX, patchY, header.DCOffset, header.Range);
// NOTE: No idea what prequant and postquant should be or what they do
int wbits;
int[] patch = CompressPatch(terrData, patchX, patchY, header, 10, out wbits);
wbits = EncodePatchHeader(output, header, patch, (uint)terrData.SizeX, (uint)terrData.SizeY, wbits);
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 i = 0; i < Constants.TerrainPatchSize*Constants.TerrainPatchSize; i++)
{
float val = patch[i];
if (val > zmax) zmax = val;
if (val < zmin) zmin = val;
}
header.DCOffset = zmin;
header.Range = (int) ((zmax - zmin) + 1.0f);
return header;
}
// Scan the height info we're returning and return a patch packet header for this patch.
private static TerrainPatch.Header PrescanPatch(TerrainData terrData, int patchX, int patchY)
{
TerrainPatch.Header header = new TerrainPatch.Header();
float zmax = -99999999.0f;
float zmin = 99999999.0f;
for (int j = patchY*Constants.TerrainPatchSize; j < (patchY + 1)*Constants.TerrainPatchSize; j++)
{
for (int i = patchX*Constants.TerrainPatchSize; i < (patchX + 1)*Constants.TerrainPatchSize; i++)
{
float val = terrData[i, j];
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 {QuantWBits = bitpack.UnpackBits(8)};
// Quantized word bits
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, uint pRegionSizeX,
uint pRegionSizeY, int wbits)
{
/*
int temp;
int wbits = (header.QuantWBits & 0x0f) + 2;
uint maxWbits = (uint)wbits + 5;
uint minWbits = ((uint)wbits >> 1);
int wbitsMaxValue;
*/
// goal is to determ minimum number of bits to use so all data fits
/*
wbits = (int)minWbits;
wbitsMaxValue = (1 << wbits);
for (int i = 0; i < patch.Length; i++)
{
temp = patch[i];
if (temp != 0)
{
// Get the absolute value
if (temp < 0) temp *= -1;
no coments..
for (int j = (int)maxWbits; j > (int)minWbits; j--)
{
if ((temp & (1 << j)) != 0)
{
if (j > wbits) wbits = j;
break;
}
}
while (temp > wbitsMaxValue)
{
wbits++;
if (wbits == maxWbits)
goto Done;
wbitsMaxValue = 1 << wbits;
}
}
}
Done:
// wbits += 1;
*/
// better check
if (wbits > 17)
wbits = 16;
else if (wbits < 3)
wbits = 3;
header.QuantWBits &= 0xf0;
header.QuantWBits |= (wbits - 2);
output.PackBits(header.QuantWBits, 8);
output.PackFloat(header.DCOffset);
output.PackBits(header.Range, 16);
if (pRegionSizeX > Constants.RegionSize || pRegionSizeY > Constants.RegionSize)
output.PackBits(header.PatchIDs, 32);
else
output.PackBits(header.PatchIDs, 10);
return wbits;
}
private static void IDCTColumn16(float[] linein, float[] lineout, int column)
{
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
float total = OO_SQRT2*linein[column];
for (int u = 1; u < Constants.TerrainPatchSize; u++)
{
int usize = u*Constants.TerrainPatchSize;
total += linein[usize + column]*CosineTable16[usize + n];
}
lineout[Constants.TerrainPatchSize*n + column] = total;
}
}
private static void IDCTLine16(float[] linein, float[] lineout, int line)
{
const float oosob = 2.0f/Constants.TerrainPatchSize;
int lineSize = line*Constants.TerrainPatchSize;
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
float total = OO_SQRT2*linein[lineSize];
for (int u = 1; u < Constants.TerrainPatchSize; u++)
{
total += linein[lineSize + u]*CosineTable16[u*Constants.TerrainPatchSize + n];
}
lineout[lineSize + n] = total*oosob;
}
}
/*
private static void DCTLine16(float[] linein, float[] lineout, int line)
{
float total = 0.0f;
int lineSize = line * Constants.TerrainPatchSize;
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
total += linein[lineSize + n];
}
lineout[lineSize] = OO_SQRT2 * total;
int uptr = 0;
for (int u = 1; u < Constants.TerrainPatchSize; u++)
{
total = 0.0f;
uptr += Constants.TerrainPatchSize;
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
total += linein[lineSize + n] * CosineTable16[uptr + n];
}
lineout[lineSize + u] = total;
}
}
*/
private static void DCTLine16(float[] linein, float[] lineout, int line)
{
// outputs transpose data (lines exchanged with coluns )
// so to save a bit of cpu when doing coluns
float total = 0.0f;
int lineSize = line*Constants.TerrainPatchSize;
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
total += linein[lineSize + n];
}
lineout[line] = OO_SQRT2*total;
for (int u = Constants.TerrainPatchSize;
u < Constants.TerrainPatchSize*Constants.TerrainPatchSize;
u += Constants.TerrainPatchSize)
{
total = 0.0f;
for (int ptrn = lineSize, ptru = u; ptrn < lineSize + Constants.TerrainPatchSize; ptrn++,ptru++)
{
total += linein[ptrn]*CosineTable16[ptru];
}
lineout[line + u] = total;
}
}
/*
private static void DCTColumn16(float[] linein, int[] lineout, int column)
{
float total = 0.0f;
// const float oosob = 2.0f / Constants.TerrainPatchSize;
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
total += linein[Constants.TerrainPatchSize * n + column];
}
// lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]);
lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * QuantizeTable16[column]);
for (int uptr = Constants.TerrainPatchSize; uptr < Constants.TerrainPatchSize * Constants.TerrainPatchSize; uptr += Constants.TerrainPatchSize)
{
total = 0.0f;
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
total += linein[Constants.TerrainPatchSize * n + column] * CosineTable16[uptr + n];
}
// lineout[CopyMatrix16[Constants.TerrainPatchSize * u + column]] = (int)(total * oosob * QuantizeTable16[Constants.TerrainPatchSize * u + column]);
lineout[CopyMatrix16[uptr + column]] = (int)(total * QuantizeTable16[uptr + column]);
}
}
private static void DCTColumn16(float[] linein, int[] lineout, int column)
{
// input columns are in fact stored in lines now
float total = 0.0f;
// const float oosob = 2.0f / Constants.TerrainPatchSize;
int inlinesptr = Constants.TerrainPatchSize*column;
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
total += linein[inlinesptr + n];
}
// lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]);
lineout[CopyMatrix16[column]] = (int) (OO_SQRT2*total*QuantizeTable16[column]);
for (int uptr = Constants.TerrainPatchSize;
uptr < Constants.TerrainPatchSize*Constants.TerrainPatchSize;
uptr += Constants.TerrainPatchSize)
{
total = 0.0f;
for (int n = inlinesptr, ptru = uptr; n < inlinesptr + Constants.TerrainPatchSize; n++, ptru++)
{
total += linein[n]*CosineTable16[ptru];
}
// lineout[CopyMatrix16[Constants.TerrainPatchSize * u + column]] = (int)(total * oosob * QuantizeTable16[Constants.TerrainPatchSize * u + column]);
lineout[CopyMatrix16[uptr + column]] = (int) (total*QuantizeTable16[uptr + column]);
}
}
*/
private static int DCTColumn16Wbits(float[] linein, int[] lineout, int column, int wbits, int maxwbits)
{
// input columns are in fact stored in lines now
bool dowbits = wbits != maxwbits;
int wbitsMaxValue = 1 << wbits;
float total = 0.0f;
// const float oosob = 2.0f / Constants.TerrainPatchSize;
int inlinesptr = Constants.TerrainPatchSize*column;
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
total += linein[inlinesptr + n];
}
// lineout[CopyMatrix16[column]] = (int)(OO_SQRT2 * total * oosob * QuantizeTable16[column]);
int tmp = (int) (OO_SQRT2*total*QuantizeTable16[column]);
lineout[CopyMatrix16[column]] = tmp;
if (dowbits)
{
if (tmp < 0) tmp *= -1;
while (tmp > wbitsMaxValue)
{
wbits++;
wbitsMaxValue = 1 << wbits;
if (wbits == maxwbits)
{
dowbits = false;
break;
}
}
}
for (int uptr = Constants.TerrainPatchSize;
uptr < Constants.TerrainPatchSize*Constants.TerrainPatchSize;
uptr += Constants.TerrainPatchSize)
{
total = 0.0f;
for (int n = inlinesptr, ptru = uptr; n < inlinesptr + Constants.TerrainPatchSize; n++, ptru++)
{
total += linein[n]*CosineTable16[ptru];
}
tmp = (int) (total*QuantizeTable16[uptr + column]);
lineout[CopyMatrix16[uptr + column]] = tmp;
if (dowbits)
{
if (tmp < 0) tmp *= -1;
while (tmp > wbitsMaxValue)
{
wbits++;
wbitsMaxValue = 1 << wbits;
if (wbits == maxwbits)
{
dowbits = false;
break;
}
}
}
}
return wbits;
}
public static void DecodePatch(int[] patches, BitPack bitpack, TerrainPatch.Header header, int size)
{
for (int n = 0; n < size*size; n++)
{
// ?
int 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 maxwbitssize = (1 << wbits) - 1;
if (postquant > Constants.TerrainPatchSize*Constants.TerrainPatchSize || postquant < 0)
{
Logger.Log("Postquant is outside the range of allowed values in EncodePatch()", Helpers.LogLevel.Error);
return;
}
if (postquant != 0) patch[Constants.TerrainPatchSize*Constants.TerrainPatchSize - postquant] = 0;
for (int i = 0; i < Constants.TerrainPatchSize*Constants.TerrainPatchSize; i++)
{
int temp = patch[i];
if (temp == 0)
{
bool eob = true;
for (int j = i; j < Constants.TerrainPatchSize*Constants.TerrainPatchSize - postquant; j++)
{
if (patch[j] != 0)
{
eob = false;
break;
}
}
if (eob)
{
output.PackBits(ZERO_EOB, 2);
return;
}
output.PackBits(ZERO_CODE, 1);
}
else
{
if (temp < 0)
{
temp *= -1;
if (temp > maxwbitssize) temp = maxwbitssize;
output.PackBits(NEGATIVE_VALUE, 3);
output.PackBits(temp, wbits);
}
else
{
if (temp > maxwbitssize) temp = maxwbitssize;
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/quantize;
float mult = ooq*header.Range;
float addval = mult*(1 << (prequant - 1)) + header.DCOffset;
if (group.PatchSize == Constants.TerrainPatchSize)
{
for (int n = 0; n < Constants.TerrainPatchSize*Constants.TerrainPatchSize; n++)
{
block[n] = patches[CopyMatrix16[n]]*DequantizeTable16[n];
}
float[] ftemp = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
for (int o = 0; o < Constants.TerrainPatchSize; o++)
IDCTColumn16(block, ftemp, o);
for (int o = 0; o < Constants.TerrainPatchSize; o++)
IDCTLine16(ftemp, block, o);
}
else
{
for (int n = 0; n < Constants.TerrainPatchSize*2*Constants.TerrainPatchSize*2; 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, out int wbits)
{
float[] block = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
int wordsize = (prequant - 2) & 0x0f;
float oozrange = 1.0f/header.Range;
float range = (1 << prequant);
float premult = oozrange*range;
float sub = (1 << (prequant - 1)) + header.DCOffset*premult;
header.QuantWBits = wordsize;
header.QuantWBits |= wordsize << 4;
int k = 0;
for (int j = 0; j < Constants.TerrainPatchSize; j++)
{
for (int i = 0; i < Constants.TerrainPatchSize; i++)
block[k++] = patchData[j*Constants.TerrainPatchSize + i]*premult - sub;
}
float[] ftemp = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
int[] itemp = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
int maxWbits = prequant + 5;
wbits = (prequant >> 1);
for (int o = 0; o < Constants.TerrainPatchSize; o++)
DCTLine16(block, ftemp, o);
for (int o = 0; o < Constants.TerrainPatchSize; o++)
wbits = DCTColumn16Wbits(ftemp, itemp, o, wbits, maxWbits);
return itemp;
}
private static int[] CompressPatch(float[,] patchData, TerrainPatch.Header header, int prequant, out int wbits)
{
float[] block = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
float oozrange = 1.0f/header.Range;
float range = (1 << prequant);
float premult = oozrange*range;
float sub = (1 << (prequant - 1)) + header.DCOffset*premult;
int wordsize = (prequant - 2) & 0x0f;
header.QuantWBits = wordsize;
header.QuantWBits |= wordsize << 4;
int k = 0;
for (int j = 0; j < Constants.TerrainPatchSize; j++)
{
for (int i = 0; i < Constants.TerrainPatchSize; i++)
block[k++] = patchData[j, i]*premult - sub;
}
float[] ftemp = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
int[] itemp = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
int maxWbits = prequant + 5;
wbits = (prequant >> 1);
for (int o = 0; o < Constants.TerrainPatchSize; o++)
DCTLine16(block, ftemp, o);
for (int o = 0; o < Constants.TerrainPatchSize; o++)
wbits = DCTColumn16Wbits(ftemp, itemp, o, wbits, maxWbits);
return itemp;
}
private static int[] CompressPatch(TerrainData terrData, int patchX, int patchY, TerrainPatch.Header header,
int prequant, out int wbits)
{
float[] block = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
int wordsize = prequant;
float oozrange = 1.0f/header.Range;
float range = (1 << prequant);
float premult = oozrange*range;
float sub = (1 << (prequant - 1)) + header.DCOffset*premult;
header.QuantWBits = wordsize - 2;
header.QuantWBits |= (prequant - 2) << 4;
int k = 0;
int yPatchLimit = patchY >= (terrData.SizeY / Constants.TerrainPatchSize) ?
(terrData.SizeY - Constants.TerrainPatchSize) / Constants.TerrainPatchSize : patchY;
yPatchLimit = (yPatchLimit + 1) * Constants.TerrainPatchSize;
int xPatchLimit = patchX >= (terrData.SizeX / Constants.TerrainPatchSize) ?
(terrData.SizeX - Constants.TerrainPatchSize) / Constants.TerrainPatchSize : patchX;
xPatchLimit = (xPatchLimit + 1) * Constants.TerrainPatchSize;
for (int yy = patchY * Constants.TerrainPatchSize; yy < yPatchLimit; yy++)
{
for (int xx = patchX * Constants.TerrainPatchSize; xx < xPatchLimit; xx++)
{
block[k++] = terrData[xx, yy] * premult - sub;
}
}
float[] ftemp = new float[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
int[] itemp = new int[Constants.TerrainPatchSize*Constants.TerrainPatchSize];
int maxWbits = prequant + 5;
wbits = (prequant >> 1);
for (int o = 0; o < Constants.TerrainPatchSize; o++)
DCTLine16(block, ftemp, o);
for (int o = 0; o < Constants.TerrainPatchSize; o++)
wbits = DCTColumn16Wbits(ftemp, itemp, o, wbits, maxWbits);
return itemp;
}
#region Initialization
private static void BuildDequantizeTable16()
{
for (int j = 0; j < Constants.TerrainPatchSize; j++)
{
for (int i = 0; i < Constants.TerrainPatchSize; i++)
{
DequantizeTable16[j*Constants.TerrainPatchSize + i] = 1.0f + 2.0f*(i + j);
}
}
}
private static void BuildQuantizeTable16()
{
const float oosob = 2.0f/Constants.TerrainPatchSize;
for (int j = 0; j < Constants.TerrainPatchSize; j++)
{
for (int i = 0; i < Constants.TerrainPatchSize; i++)
{
// QuantizeTable16[j * Constants.TerrainPatchSize + i] = 1.0f / (1.0f + 2.0f * ((float)i + (float)j));
QuantizeTable16[j*Constants.TerrainPatchSize + i] = oosob/(1.0f + 2.0f*(i + (float) j));
}
}
}
private static void SetupCosines16()
{
const float hposz = (float) Math.PI*0.5f/Constants.TerrainPatchSize;
for (int u = 0; u < Constants.TerrainPatchSize; u++)
{
for (int n = 0; n < Constants.TerrainPatchSize; n++)
{
CosineTable16[u*Constants.TerrainPatchSize + n] = (float) Math.Cos((2.0f*n + 1.0f)*u*hposz);
}
}
}
private static void BuildCopyMatrix16()
{
bool diag = false;
bool right = true;
int i = 0;
int j = 0;
int count = 0;
while (i < Constants.TerrainPatchSize && j < Constants.TerrainPatchSize)
{
CopyMatrix16[j*Constants.TerrainPatchSize + i] = count++;
if (!diag)
{
if (right)
{
if (i < Constants.TerrainPatchSize - 1) i++;
else j++;
right = false;
diag = true;
}
else
{
if (j < Constants.TerrainPatchSize - 1) j++;
else i++;
right = true;
diag = true;
}
}
else
{
if (right)
{
i++;
j--;
if (i == Constants.TerrainPatchSize - 1 || j == 0) diag = false;
}
else
{
i--;
j++;
if (j == Constants.TerrainPatchSize - 1 || i == 0) diag = false;
}
}
}
}
#endregion Initialization
}
}