diff --git a/OpenSim/Framework/TerrainData.cs b/OpenSim/Framework/TerrainData.cs index 33582499e2..da135ad9f7 100644 --- a/OpenSim/Framework/TerrainData.cs +++ b/OpenSim/Framework/TerrainData.cs @@ -74,9 +74,12 @@ namespace OpenSim.Framework private float[,] m_heightmap; // Remember subregions of the heightmap that has changed. - private BitArray m_taint; + private TerrainTaintsArray m_taints; private readonly int m_taintSizeX; private readonly int m_taintSizeY; + private readonly int m_mapStride; + private readonly int m_mapPatchsStride; + // legacy CompressionFactor public float CompressionFactor { get; private set; } @@ -107,7 +110,7 @@ namespace OpenSim.Framework { m_heightmap[x, y] = value; int yy = y / Constants.TerrainPatchSize; - m_taint[x / Constants.TerrainPatchSize + yy * m_taintSizeX] = true; + m_taints.Set(x / Constants.TerrainPatchSize + yy * m_taintSizeX, true); } } } @@ -118,19 +121,24 @@ namespace OpenSim.Framework set { this[x, y] = value; } } + public TerrainTaintsArray GetTaints() + { + return m_taints; + } + public void ClearTaint() { - m_taint.SetAll(false); + m_taints.SetAll(false); } public void TaintAllTerrain() { - m_taint.SetAll(true); + m_taints.SetAll(true); } private void SetAllTaint(bool setting) { - m_taint.SetAll(setting); + m_taints.SetAll(setting); } public void ClearLand() @@ -145,6 +153,12 @@ namespace OpenSim.Framework m_heightmap[xx, yy] = pHeight; } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public bool IsTainted() + { + return m_taints.IsTaited(); + } + // Return 'true' of the patch that contains these region coordinates has been modified. // Note that checking the taint clears it. // There is existing code that relies on this feature. @@ -153,59 +167,51 @@ namespace OpenSim.Framework { yy /= Constants.TerrainPatchSize; int indx = xx / Constants.TerrainPatchSize + yy * m_taintSizeX; - bool ret = m_taint[indx]; - if (ret && clearOnTest) - m_taint[indx] = false; - return ret; + return m_taints.Get(indx, clearOnTest); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool IsTaintedAt(int xx, int yy) { yy /= Constants.TerrainPatchSize; - return m_taint[xx / Constants.TerrainPatchSize + yy * m_taintSizeX]; + return m_taints.Get(xx / Constants.TerrainPatchSize + yy * m_taintSizeX); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool IsTaintedAtPatch(int xx, int yy, bool clearOnTest) { int indx = xx + yy * m_taintSizeX; - bool ret = m_taint[indx]; - if (ret && clearOnTest) - m_taint[indx] = false; - return ret; + return m_taints.Get(indx, clearOnTest); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool IsTaintedAtPatch(int xx, int yy) { - return m_taint[xx + yy * m_taintSizeX]; + return m_taints.Get(xx + yy * m_taintSizeX); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool IsTaintedAtPatch(int indx, bool clearOnTest) { - bool ret = m_taint[indx]; - if (ret && clearOnTest) - m_taint[indx] = false; - return ret; + return m_taints.Get(indx, clearOnTest); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool IsTaintedAtPatchWithClear(int indx) { - if(m_taint[indx]) - { - m_taint[indx] = false; - return true; - } - return false; + return m_taints.GetAndClear(indx); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool IsTaintedAtPatch(int indx) { - return m_taint[indx]; + return m_taints.Get(indx); + } + + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public int GetAndClearNextTaint(int startIndex) + { + return m_taints.GetAndClearNextTrue(startIndex); } // TerrainData.GetDatabaseBlob // The user wants something to store in the database. @@ -233,6 +239,7 @@ namespace OpenSim.Framework { TerrainData ret = new TerrainData(SizeX, SizeY, SizeZ); ret.m_heightmap = (float[,])this.m_heightmap.Clone(); + return ret; } @@ -266,14 +273,12 @@ namespace OpenSim.Framework zmax = float.MinValue; zmin = float.MaxValue; - int stride = m_heightmap.GetLength(1); - int mstride = 16 * stride; - int mpy = 16 * py; + int mpy = Constants.TerrainPatchSize * py; fixed (float* map = m_heightmap) { - float* p = map + px * mstride; - float* pend = p + mstride; + float* p = map + px * m_mapPatchsStride; + float* pend = p + m_mapPatchsStride; while (p < pend) { float* yt = p + mpy; @@ -287,7 +292,7 @@ namespace OpenSim.Framework zmin = val; yt++; } - p += stride; + p += m_mapStride; } } } @@ -295,12 +300,9 @@ namespace OpenSim.Framework public unsafe void GetPatchBlock(float* block, int px, int py, float sub, float premult) { int k = 0; - int stride = m_heightmap.GetLength(1); - int mstride = 16 * stride; - - int startX = px * mstride; - int endX = startX + mstride; - int mpy = 16 * py; + int startX = px * m_mapPatchsStride; + int endX = startX + m_mapPatchsStride; + int mpy = py * Constants.TerrainPatchSize; fixed (float* map = m_heightmap) { float* yp = map + mpy; @@ -308,7 +310,7 @@ namespace OpenSim.Framework while (yp < yend) { - for (int x = startX; x < endX; x += stride) + for (int x = startX; x < endX; x += m_mapStride) { block[k++] = (yp[x] - sub) * premult; } @@ -362,9 +364,13 @@ namespace OpenSim.Framework m_taintSizeX = SizeX / Constants.TerrainPatchSize; m_taintSizeY = SizeY / Constants.TerrainPatchSize; + m_mapStride = SizeY; + m_mapPatchsStride = m_mapStride * Constants.TerrainPatchSize; + SizeZ = (int)Constants.RegionHeight; CompressionFactor = 100.0f; + m_heightmap = new float[SizeX, SizeY]; for (int ii = 0; ii < SizeX; ii++) { @@ -376,7 +382,7 @@ namespace OpenSim.Framework } // m_log.DebugFormat("{0} new by doubles. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ); - m_taint = new BitArray(m_taintSizeX * m_taintSizeY, false); + m_taints = new TerrainTaintsArray(m_taintSizeX * m_taintSizeY); } // Create underlying structures but don't initialize the heightmap assuming the caller will immediately do that @@ -387,9 +393,12 @@ namespace OpenSim.Framework SizeZ = pZ; m_taintSizeX = SizeX / Constants.TerrainPatchSize; m_taintSizeY = SizeY / Constants.TerrainPatchSize; + m_mapStride = SizeY; + m_mapPatchsStride = m_mapStride * Constants.TerrainPatchSize; + CompressionFactor = 100.0f; m_heightmap = new float[SizeX, SizeY]; - m_taint = new BitArray(m_taintSizeX * m_taintSizeY, false); + m_taints = new TerrainTaintsArray(m_taintSizeX * m_taintSizeY); // m_log.DebugFormat("{0} new by dimensions. sizeX={1}, sizeY={2}, sizeZ={3}", LogHeader, SizeX, SizeY, SizeZ); ClearLand(0f); diff --git a/OpenSim/Framework/TerrainTaintsArray.cs b/OpenSim/Framework/TerrainTaintsArray.cs new file mode 100644 index 0000000000..c763e50404 --- /dev/null +++ b/OpenSim/Framework/TerrainTaintsArray.cs @@ -0,0 +1,312 @@ +/* + * 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. + */ + +using System.Runtime.CompilerServices; +using System.Threading; + +namespace OpenSim.Framework +{ + public class TerrainTaintsArray + { + public const int VectorNumberBits = 32; + public const int VectorNumberBitsLog2 = 5; + public const int FALSEWORD = 0; + public const int TRUEWORD = ~FALSEWORD; + + private int[] m_data; + private readonly int m_nbits; + + private volatile int m_ntainted; + + private object m_mainlock = new object(); + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TerrainTaintsArray(int lenght) : this(lenght, false) { } + + public TerrainTaintsArray(int lenght, bool preset) + { + m_nbits = lenght; + int nInts = calclen(m_nbits); + + m_data = new int[nInts]; + if (preset) + { + for (int i = 0; i < m_data.Length; i++) + m_data[i] = TRUEWORD; + m_ntainted = m_nbits; + } + else + m_ntainted = 0; + } + + public int Length + { + get + { + return m_nbits; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int TaitedCount() + { + return m_ntainted; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsTaited() + { + return m_ntainted > 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Get(int bitindex) + { + int indexh = bitindex >> VectorNumberBitsLog2; + int mask = 1 << (bitindex & (VectorNumberBits - 1)); + return (m_data[indexh] & mask) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Get(int bitindex, bool clear) + { + int indexh = bitindex >> VectorNumberBitsLog2; + int mask = 1 << (bitindex & (VectorNumberBits - 1)); + + lock (m_mainlock) + { + if ((m_data[indexh] & mask) != 0) + { + if (clear) + { + m_data[indexh] ^= mask; + --m_ntainted; + } + return true; + } + } + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe bool GetAndClear(int bitindex) + { + int indexh = bitindex >> VectorNumberBitsLog2; + int mask = 1 << (bitindex & (VectorNumberBits - 1)); + lock (m_mainlock) + { + if ((m_data[indexh] & mask) != 0) + { + m_data[indexh] ^= mask; + --m_ntainted; + return true; + } + } + return false; + } + + public void Set(int bitindex, bool val) + { + int indexh = bitindex >> VectorNumberBitsLog2; + int mask = 1 << (bitindex & (VectorNumberBits - 1)); + lock (m_mainlock) + { + if (val) + { + if ((m_data[indexh] & mask) == 0) + { + m_data[indexh] |= mask; + ++m_ntainted; + } + } + else + { + if ((m_data[indexh] & mask) != 0) + { + m_data[indexh] ^= mask; + --m_ntainted; + } + } + } + } + + public bool this[int bitindex] + { + get + { + return Get(bitindex); + } + set + { + int indexh = bitindex >> VectorNumberBitsLog2; + int mask = 1 << (bitindex & (VectorNumberBits - 1)); + lock (m_mainlock) + { + if (value) + { + if ((m_data[indexh] & mask) == 0) + { + m_data[indexh] |= mask; + ++m_ntainted; + } + } + else + { + if ((m_data[indexh] & mask) != 0) + { + m_data[indexh] ^= mask; + --m_ntainted; + } + } + } + } + } + + public void SetAll(bool val) + { + lock (m_mainlock) + { + if (val) + { + for (int i = 0; i < m_data.Length; ++i) + m_data[i] = TRUEWORD; + m_ntainted = m_nbits; + + } + else + { + for (int i = 0; i < m_data.Length; ++i) + m_data[i] = 0; + m_ntainted = 0; + } + } + } + + public bool IsVectorOfFalse(int vectorIndex) + { + return m_data[vectorIndex] == 0; + } + + public bool IsVectorOfBitFalse(int bitindex) + { + return m_data[(bitindex >> VectorNumberBitsLog2)] == 0; + } + + + public bool IsVectorTrue(int vectorIndex) + { + return m_data[vectorIndex] == unchecked(((int)0xffffffff)); + } + + public bool IsVectorOfBitTrue(int bitindex) + { + return m_data[(bitindex >> VectorNumberBitsLog2)] == unchecked(((int)0xffffffff)); + } + + public void Or(TerrainTaintsArray other) + { + if (m_nbits != other.m_nbits) + return; + lock (m_mainlock) + { + lock (other.m_mainlock) + { + for (int i = 0; i < m_data.Length; ++i) + { + int tr = other.m_data[i]; + if (tr == 0) + continue; + + int tt = m_data[i] | tr; + tr ^= tt; + if (tr != 0) + { + m_data[i] = tt; + for (int j = 1; j != 0; j <<= 1) + { + if ((tr & j) != 0) + ++m_ntainted; + } + } + } + } + } + } + + public int GetNextTrue(int startBitIndex) + { + if (startBitIndex < 0 || startBitIndex >= m_nbits) + return -1; + int indexh = startBitIndex >> VectorNumberBitsLog2; + for (; indexh < m_data.Length; ++indexh) + { + int cur = m_data[indexh]; + if (cur == 0) + continue; + for (int j = 0; j < VectorNumberBits; ++j) + { + if ((cur & (1 << j)) != 0) + return (indexh << VectorNumberBitsLog2) | j; + } + } + return -1; + } + + public int GetAndClearNextTrue(int startBitIndex) + { + if (m_ntainted <= 0 || startBitIndex < 0 || startBitIndex >= m_nbits) + return -1; + int indexh = startBitIndex >> VectorNumberBitsLog2; + lock (m_mainlock) + { + for (; indexh < m_data.Length; ++indexh) + { + int cur = m_data[indexh]; + if (cur == 0) + continue; + for (int j = 0; j < VectorNumberBits; ++j) + { + int mask = (1 << j); + if ((cur & mask) != 0) + { + m_data[indexh] ^= mask; + --m_ntainted; + return (indexh << VectorNumberBitsLog2) | j; + } + } + } + } + return -1; + } + + + private int calclen(int bitsLen) + { + return bitsLen > 0 ? ((bitsLen - 1) >> VectorNumberBitsLog2) + 1 : 0; + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs index c5918ad2d1..8532aad93a 100755 --- a/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs +++ b/OpenSim/Region/CoreModules/World/Terrain/TerrainModule.cs @@ -99,8 +99,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain // patch packet is queued to the client, the bit for that patch is set to 'false'. private class PatchUpdates { - private BitArray updated; // for each patch, whether it needs to be sent to this client - private int updateCount; // number of patches that need to be sent + private TerrainTaintsArray taints; // for each patch, whether it needs to be sent to this client public ScenePresence Presence; // a reference to the client to send to public bool sendAll; public int sendAllcurrentX; @@ -112,8 +111,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { xsize = terrData.SizeX / Constants.TerrainPatchSize; ysize = terrData.SizeY / Constants.TerrainPatchSize; - updated = new BitArray(xsize * ysize, true); - updateCount = xsize * ysize; + taints = new TerrainTaintsArray(xsize * ysize, true); Presence = pPresence; // Initially, send all patches to the client sendAll = true; @@ -125,8 +123,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain { xsize = terrData.SizeX / Constants.TerrainPatchSize; ysize = terrData.SizeY / Constants.TerrainPatchSize; - updated = new BitArray(xsize * ysize, true); - updateCount = defaultState ? xsize * ysize : 0; + taints = new TerrainTaintsArray(xsize * ysize, true); Presence = pPresence; sendAll = defaultState; sendAllcurrentX = 0; @@ -137,7 +134,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool HasUpdates() { - return (updateCount > 0); + return (taints.IsTaited()); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] @@ -149,99 +146,57 @@ namespace OpenSim.Region.CoreModules.World.Terrain [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool GetByPatch(int patchX, int patchY) { - return updated[patchX + xsize * patchY]; + return taints[patchX + xsize * patchY]; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool GetByPatch(int indx) { - return updated[indx]; + return taints[indx]; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool GetByPatchAndClear(int patchX, int patchY) { - int indx = patchX + xsize * patchY; - if(updated[indx]) - { - updated[indx] = false; - --updateCount; - return true; - } - return false; + return (taints.GetAndClear(patchX + xsize * patchY)); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void SetByPatch(int patchX, int patchY, bool state) { int indx = patchX + xsize * patchY; - bool prevState = updated[indx]; - updated[indx] = state; - if (state) - { - if (!prevState) - ++updateCount; - } - else - { - if (prevState) - --updateCount; - } + taints.Set(indx, state); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void SetTrueByPatch(int patchX, int patchY) { int indx = patchX + xsize * patchY; - if (!updated[indx]) - { - updated[indx] = true; - ++updateCount; - } + taints.Set(indx, true); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void SetTrueByPatch(int indx) { - if (!updated[indx]) - { - updated[indx] = true; - ++updateCount; - } + taints.Set(indx, true); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void SetFalseByPatch(int patchX, int patchY) { int indx = patchX + xsize * patchY; - if (updated[indx]) - { - updated[indx] = false; - --updateCount; - } + taints.Set(indx, false); } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public void SetFalseByPatch(int indx) { - if (updated[indx]) - { - updated[indx] = false; - --updateCount; - } + taints.Set(indx, true); } public void SetAll(bool state) { - updated.SetAll(state); - - if (state) - { - sendAll = true; - updateCount = xsize * ysize; - } - else updateCount = 0; - + taints.SetAll(state); sendAllcurrentX = 0; sendAllcurrentY = 0; } @@ -249,21 +204,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain // Logically OR's the terrain data's patch taint map into this client's update map. public void SetAll(TerrainData terrData) { - if (xsize != (terrData.SizeX / Constants.TerrainPatchSize) - || ysize != (terrData.SizeY / Constants.TerrainPatchSize)) - { - throw new Exception( - String.Format("{0} PatchUpdates.SetAll: patch array not same size as terrain. arr=<{1},{2}>, terr=<{3},{4}>", - LogHeader, xsize, ysize, - terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize) - ); - } - - for (int indx = 0; indx < updated.Length; ++indx) - { - if (terrData.IsTaintedAtPatch(indx)) - SetTrueByPatch(indx); - } + taints.Or(terrData.GetTaints()); } } @@ -940,8 +881,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// doing it async, since currently this is 2 heavy for heartbeat private void EventManager_TerrainCheckUpdates() { - Util.FireAndForget( - EventManager_TerrainCheckUpdatesAsync); + Util.FireAndForget(EventManager_TerrainCheckUpdatesAsync); } object TerrainCheckUpdatesLock = new object(); @@ -954,17 +894,14 @@ namespace OpenSim.Region.CoreModules.World.Terrain TerrainData terrData = m_channel.GetTerrainData(); bool shouldTaint = false; - int sx = terrData.SizeX / Constants.TerrainPatchSize; - for (int y = 0, py = 0; y < terrData.SizeY / Constants.TerrainPatchSize; y++, py += sx) + if(terrData.IsTainted()) { - for (int x = 0; x < sx; x++) + int nextChanged = 0; + while((nextChanged = terrData.GetAndClearNextTaint(nextChanged)) >= 0) { - if (terrData.IsTaintedAtPatchWithClear(x + py)) - { - // Found a patch that was modified. Push this flag into the clients. - SendToClients(terrData, x, y); - shouldTaint = true; - } + SendToClients(terrData, nextChanged); + ++nextChanged; + shouldTaint = true; } } @@ -1145,7 +1082,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain /// A copy of the terrain as a 1D float array of size w*h /// x patch coords /// y patch coords - private void SendToClients(TerrainData terrData, int px, int py) + private void SendToClients(TerrainData terrData, int patchIndex) { if (m_sendTerrainUpdatesByViewDistance) { @@ -1160,7 +1097,7 @@ namespace OpenSim.Region.CoreModules.World.Terrain thisClientUpdates = new PatchUpdates(terrData, presence, false); m_perClientPatchUpdates.Add(presence.UUID, thisClientUpdates); } - thisClientUpdates.SetTrueByPatch(px, py); + thisClientUpdates.SetTrueByPatch(patchIndex); } ); } @@ -1170,6 +1107,9 @@ namespace OpenSim.Region.CoreModules.World.Terrain // Legacy update sending where the update is sent out as soon as noticed // We know the actual terrain data that is passed is ignored so this passes a dummy heightmap. //float[] heightMap = terrData.GetFloatsSerialized(); + int sx = terrData.SizeX / Constants.TerrainPatchSize; + int py = patchIndex / sx; + int px = patchIndex - py * sx; int[] map = new int[]{px, py}; m_scene.ForEachClient( delegate (IClientAPI controller)