From c5a7bf6601dfde518a008d45b4e4a8dc8de698bf Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Sat, 28 Mar 2015 08:30:52 -0700 Subject: [PATCH] varregion: add varregion and TerrainData use in LLClientView. Add sending multiple parcel patches and sending patches by avatar view distance. --- OpenSim/Framework/MapBlockData.cs | 18 + .../ClientStack/Linden/UDP/LLClientView.cs | 275 +++-- .../Framework/Scenes/TerrainCompressor.cs | 948 ++++++++++++++++++ 3 files changed, 1131 insertions(+), 110 deletions(-) create mode 100644 OpenSim/Region/Framework/Scenes/TerrainCompressor.cs diff --git a/OpenSim/Framework/MapBlockData.cs b/OpenSim/Framework/MapBlockData.cs index 2298ac57e2..4bee4995f3 100644 --- a/OpenSim/Framework/MapBlockData.cs +++ b/OpenSim/Framework/MapBlockData.cs @@ -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; + } } } diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index 849fec3cf7..4c0fba51bc 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -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 PacketHandlers = new Dictionary(); //Global/static handlers for all clients /// @@ -689,13 +690,37 @@ namespace OpenSim.Region.ClientStack.LindenUDP /// /// true if the handler was added. This is currently always the case. public bool AddLocalPacketHandler(PacketType packetType, PacketMethod handler, bool doAsync) + { + return AddLocalPacketHandler(packetType, handler, doAsync, false); + } + + /// + /// Add a handler for the given packet type. + /// + /// + /// + /// + /// 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. + /// + /// + /// 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. + /// + /// true if the handler was added. This is currently always the case. + 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 /// /// 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. /// /// heightmap 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 /// 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 } } - /// - /// Sends a set of four patches (x, x+1, ..., x+3) to the client - /// - /// heightmap - /// X coordinate for patches 0..12 - /// Y coordinate for patches 0..15 - 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); } } /// - /// 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. /// /// Patch coordinate (x) 0..15 /// Patch coordinate (y) 0..15 /// heightmap - 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 } } - /// - /// Munges heightfield into the LLUDP backed in restricted heightfield. - /// - /// float array in the base; Constants.RegionSize - /// float array in the base 256 - 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); } - } /// @@ -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 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); } diff --git a/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs b/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs new file mode 100644 index 0000000000..fc8f8cd2c5 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/TerrainCompressor.cs @@ -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); + } + + /// + /// Creates a LayerData packet for compressed land data given a full + /// simulator heightmap and an array of indices of patches to compress + /// + /// + /// Terrain data that can result in a meter square heightmap. + /// + /// + /// 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). + /// + /// + /// Array of indexes in the grid of patches. + /// + /// + /// + 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); + } + + /// + /// Add a patch of terrain to a BitPacker + /// + /// BitPacker to write the patch to + /// + /// Heightmap of the simulator. Presumed to be an sizeX*sizeY array. + /// + /// + /// X offset of the patch to create. + /// + /// + /// Y offset of the patch to create. + /// + /// + /// + 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 + } +}