BSTerrainManager.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyrightD
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Text;
  30. using OpenSim.Framework;
  31. using OpenSim.Region.Framework;
  32. using OpenSim.Region.PhysicsModules.SharedBase;
  33. using Nini.Config;
  34. using log4net;
  35. using OpenMetaverse;
  36. namespace OpenSim.Region.PhysicsModule.BulletS
  37. {
  38. // The physical implementation of the terrain is wrapped in this class.
  39. public abstract class BSTerrainPhys : IDisposable
  40. {
  41. public enum TerrainImplementation
  42. {
  43. Heightmap = 0,
  44. Mesh = 1
  45. }
  46. protected BSScene m_physicsScene { get; private set; }
  47. // Base of the region in world coordinates. Coordinates inside the region are relative to this.
  48. public Vector3 TerrainBase { get; private set; }
  49. public uint ID { get; private set; }
  50. public BSTerrainPhys(BSScene physicsScene, Vector3 regionBase, uint id)
  51. {
  52. m_physicsScene = physicsScene;
  53. TerrainBase = regionBase;
  54. ID = id;
  55. }
  56. public abstract void Dispose();
  57. public abstract float GetTerrainHeightAtXYZ(Vector3 pos);
  58. public abstract float GetWaterLevelAtXYZ(Vector3 pos);
  59. }
  60. // ==========================================================================================
  61. public sealed class BSTerrainManager : IDisposable
  62. {
  63. static string LogHeader = "[BULLETSIM TERRAIN MANAGER]";
  64. // These height values are fractional so the odd values will be
  65. // noticable when debugging.
  66. public const float HEIGHT_INITIALIZATION = 24.987f;
  67. public const float HEIGHT_INITIAL_LASTHEIGHT = 24.876f;
  68. public const float HEIGHT_GETHEIGHT_RET = 24.765f;
  69. public const float WATER_HEIGHT_GETHEIGHT_RET = 19.998f;
  70. // If the min and max height are equal, we reduce the min by this
  71. // amount to make sure that a bounding box is built for the terrain.
  72. public const float HEIGHT_EQUAL_FUDGE = 0.2f;
  73. // Until the whole simulator is changed to pass us the region size, we rely on constants.
  74. public Vector3 DefaultRegionSize = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight);
  75. // The scene that I am part of
  76. private BSScene m_physicsScene { get; set; }
  77. // The ground plane created to keep thing from falling to infinity.
  78. private BulletBody m_groundPlane;
  79. // If doing mega-regions, if we're region zero we will be managing multiple
  80. // region terrains since region zero does the physics for the whole mega-region.
  81. private Dictionary<Vector3, BSTerrainPhys> m_terrains;
  82. // Flags used to know when to recalculate the height.
  83. private bool m_terrainModified = false;
  84. // If we are doing mega-regions, terrains are added from TERRAIN_ID to m_terrainCount.
  85. // This is incremented before assigning to new region so it is the last ID allocated.
  86. private uint m_terrainCount = BSScene.CHILDTERRAIN_ID - 1;
  87. public uint HighestTerrainID { get {return m_terrainCount; } }
  88. // If doing mega-regions, this holds our offset from region zero of
  89. // the mega-regions. "parentScene" points to the PhysicsScene of region zero.
  90. private Vector3 m_worldOffset;
  91. // If the parent region (region 0), this is the extent of the combined regions
  92. // relative to the origin of region zero
  93. private Vector3 m_worldMax;
  94. private PhysicsScene MegaRegionParentPhysicsScene { get; set; }
  95. public BSTerrainManager(BSScene physicsScene, Vector3 regionSize)
  96. {
  97. m_physicsScene = physicsScene;
  98. DefaultRegionSize = regionSize;
  99. m_terrains = new Dictionary<Vector3,BSTerrainPhys>();
  100. // Assume one region of default size
  101. m_worldOffset = Vector3.Zero;
  102. m_worldMax = new Vector3(DefaultRegionSize);
  103. MegaRegionParentPhysicsScene = null;
  104. }
  105. public void Dispose()
  106. {
  107. ReleaseGroundPlaneAndTerrain();
  108. }
  109. // Create the initial instance of terrain and the underlying ground plane.
  110. // This is called from the initialization routine so we presume it is
  111. // safe to call Bullet in real time. We hope no one is moving prims around yet.
  112. public void CreateInitialGroundPlaneAndTerrain()
  113. {
  114. DetailLog("{0},BSTerrainManager.CreateInitialGroundPlaneAndTerrain,region={1}", BSScene.DetailLogZero, m_physicsScene.RegionName);
  115. // The ground plane is here to catch things that are trying to drop to negative infinity
  116. BulletShape groundPlaneShape = m_physicsScene.PE.CreateGroundPlaneShape(BSScene.GROUNDPLANE_ID, 1f, BSParam.TerrainCollisionMargin);
  117. Vector3 groundPlaneAltitude = new Vector3(0f, 0f, BSParam.TerrainGroundPlane);
  118. m_groundPlane = m_physicsScene.PE.CreateBodyWithDefaultMotionState(groundPlaneShape,
  119. BSScene.GROUNDPLANE_ID, groundPlaneAltitude, Quaternion.Identity);
  120. // Everything collides with the ground plane.
  121. m_groundPlane.collisionType = CollisionType.Groundplane;
  122. m_physicsScene.PE.AddObjectToWorld(m_physicsScene.World, m_groundPlane);
  123. m_physicsScene.PE.UpdateSingleAabb(m_physicsScene.World, m_groundPlane);
  124. // Ground plane does not move
  125. m_physicsScene.PE.ForceActivationState(m_groundPlane, ActivationState.DISABLE_SIMULATION);
  126. BSTerrainPhys initialTerrain = new BSTerrainHeightmap(m_physicsScene, Vector3.Zero, BSScene.TERRAIN_ID, DefaultRegionSize);
  127. lock (m_terrains)
  128. {
  129. // Build an initial terrain and put it in the world. This quickly gets replaced by the real region terrain.
  130. m_terrains.Add(Vector3.Zero, initialTerrain);
  131. }
  132. }
  133. // Release all the terrain structures we might have allocated
  134. public void ReleaseGroundPlaneAndTerrain()
  135. {
  136. DetailLog("{0},BSTerrainManager.ReleaseGroundPlaneAndTerrain,region={1}", BSScene.DetailLogZero, m_physicsScene.RegionName);
  137. if (m_groundPlane.HasPhysicalBody)
  138. {
  139. if (m_physicsScene.PE.RemoveObjectFromWorld(m_physicsScene.World, m_groundPlane))
  140. {
  141. m_physicsScene.PE.DestroyObject(m_physicsScene.World, m_groundPlane);
  142. }
  143. m_groundPlane.Clear();
  144. }
  145. ReleaseTerrain();
  146. }
  147. // Release all the terrain we have allocated
  148. public void ReleaseTerrain()
  149. {
  150. lock (m_terrains)
  151. {
  152. foreach (KeyValuePair<Vector3, BSTerrainPhys> kvp in m_terrains)
  153. {
  154. kvp.Value.Dispose();
  155. }
  156. m_terrains.Clear();
  157. }
  158. }
  159. // The simulator wants to set a new heightmap for the terrain.
  160. public void SetTerrain(float[] heightMap) {
  161. float[] localHeightMap = heightMap;
  162. // If there are multiple requests for changes to the same terrain between ticks,
  163. // only do that last one.
  164. m_physicsScene.PostTaintObject("TerrainManager.SetTerrain-"+ m_worldOffset.ToString(), 0, delegate()
  165. {
  166. if (m_worldOffset != Vector3.Zero && MegaRegionParentPhysicsScene != null)
  167. {
  168. // If a child of a mega-region, we shouldn't have any terrain allocated for us
  169. ReleaseGroundPlaneAndTerrain();
  170. // If doing the mega-prim stuff and we are the child of the zero region,
  171. // the terrain is added to our parent
  172. if (MegaRegionParentPhysicsScene is BSScene)
  173. {
  174. DetailLog("{0},SetTerrain.ToParent,offset={1},worldMax={2}", BSScene.DetailLogZero, m_worldOffset, m_worldMax);
  175. ((BSScene)MegaRegionParentPhysicsScene).TerrainManager.AddMegaRegionChildTerrain(
  176. BSScene.CHILDTERRAIN_ID, localHeightMap, m_worldOffset, m_worldOffset + DefaultRegionSize);
  177. }
  178. }
  179. else
  180. {
  181. // If not doing the mega-prim thing, just change the terrain
  182. DetailLog("{0},SetTerrain.Existing", BSScene.DetailLogZero);
  183. UpdateTerrain(BSScene.TERRAIN_ID, localHeightMap, m_worldOffset, m_worldOffset + DefaultRegionSize);
  184. }
  185. });
  186. }
  187. // Another region is calling this region and passing a terrain.
  188. // A region that is not the mega-region root will pass its terrain to the root region so the root region
  189. // physics engine will have all the terrains.
  190. private void AddMegaRegionChildTerrain(uint id, float[] heightMap, Vector3 minCoords, Vector3 maxCoords)
  191. {
  192. // Since we are called by another region's thread, the action must be rescheduled onto our processing thread.
  193. m_physicsScene.PostTaintObject("TerrainManager.AddMegaRegionChild" + minCoords.ToString(), id, delegate()
  194. {
  195. UpdateTerrain(id, heightMap, minCoords, maxCoords);
  196. });
  197. }
  198. // If called for terrain has has not been previously allocated, a new terrain will be built
  199. // based on the passed information. The 'id' should be either the terrain id or
  200. // BSScene.CHILDTERRAIN_ID. If the latter, a new child terrain ID will be allocated and used.
  201. // The latter feature is for creating child terrains for mega-regions.
  202. // If there is an existing terrain body, a new
  203. // terrain shape is created and added to the body.
  204. // This call is most often used to update the heightMap and parameters of the terrain.
  205. // (The above does suggest that some simplification/refactoring is in order.)
  206. // Called during taint-time.
  207. private void UpdateTerrain(uint id, float[] heightMap, Vector3 minCoords, Vector3 maxCoords)
  208. {
  209. // Find high and low points of passed heightmap.
  210. // The min and max passed in is usually the area objects can be in (maximum
  211. // object height, for instance). The terrain wants the bounding box for the
  212. // terrain so replace passed min and max Z with the actual terrain min/max Z.
  213. float minZ = float.MaxValue;
  214. float maxZ = float.MinValue;
  215. foreach (float height in heightMap)
  216. {
  217. if (height < minZ) minZ = height;
  218. if (height > maxZ) maxZ = height;
  219. }
  220. if (minZ == maxZ)
  221. {
  222. // If min and max are the same, reduce min a little bit so a good bounding box is created.
  223. minZ -= BSTerrainManager.HEIGHT_EQUAL_FUDGE;
  224. }
  225. minCoords.Z = minZ;
  226. maxCoords.Z = maxZ;
  227. DetailLog("{0},BSTerrainManager.UpdateTerrain,call,id={1},minC={2},maxC={3}",
  228. BSScene.DetailLogZero, id, minCoords, maxCoords);
  229. Vector3 terrainRegionBase = new Vector3(minCoords.X, minCoords.Y, 0f);
  230. lock (m_terrains)
  231. {
  232. BSTerrainPhys terrainPhys;
  233. if (m_terrains.TryGetValue(terrainRegionBase, out terrainPhys))
  234. {
  235. // There is already a terrain in this spot. Free the old and build the new.
  236. DetailLog("{0},BSTerrainManager.UpdateTerrain:UpdateExisting,call,id={1},base={2},minC={3},maxC={4}",
  237. BSScene.DetailLogZero, id, terrainRegionBase, minCoords, maxCoords);
  238. // Remove old terrain from the collection
  239. m_terrains.Remove(terrainRegionBase);
  240. // Release any physical memory it may be using.
  241. terrainPhys.Dispose();
  242. if (MegaRegionParentPhysicsScene == null)
  243. {
  244. // This terrain is not part of the mega-region scheme. Create vanilla terrain.
  245. BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords);
  246. m_terrains.Add(terrainRegionBase, newTerrainPhys);
  247. m_terrainModified = true;
  248. }
  249. else
  250. {
  251. // It's possible that Combine() was called after this code was queued.
  252. // If we are a child of combined regions, we don't create any terrain for us.
  253. DetailLog("{0},BSTerrainManager.UpdateTerrain:AmACombineChild,taint", BSScene.DetailLogZero);
  254. // Get rid of any terrain that may have been allocated for us.
  255. ReleaseGroundPlaneAndTerrain();
  256. // I hate doing this, but just bail
  257. return;
  258. }
  259. }
  260. else
  261. {
  262. // We don't know about this terrain so either we are creating a new terrain or
  263. // our mega-prim child is giving us a new terrain to add to the phys world
  264. // if this is a child terrain, calculate a unique terrain id
  265. uint newTerrainID = id;
  266. if (newTerrainID >= BSScene.CHILDTERRAIN_ID)
  267. newTerrainID = ++m_terrainCount;
  268. DetailLog("{0},BSTerrainManager.UpdateTerrain:NewTerrain,taint,newID={1},minCoord={2},maxCoord={3}",
  269. BSScene.DetailLogZero, newTerrainID, minCoords, maxCoords);
  270. BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords);
  271. m_terrains.Add(terrainRegionBase, newTerrainPhys);
  272. m_terrainModified = true;
  273. }
  274. }
  275. }
  276. // TODO: redo terrain implementation selection to allow other base types than heightMap.
  277. private BSTerrainPhys BuildPhysicalTerrain(Vector3 terrainRegionBase, uint id, float[] heightMap, Vector3 minCoords, Vector3 maxCoords)
  278. {
  279. m_physicsScene.Logger.DebugFormat("{0} Terrain for {1}/{2} created with {3}",
  280. LogHeader, m_physicsScene.RegionName, terrainRegionBase,
  281. (BSTerrainPhys.TerrainImplementation)BSParam.TerrainImplementation);
  282. BSTerrainPhys newTerrainPhys = null;
  283. switch ((int)BSParam.TerrainImplementation)
  284. {
  285. case (int)BSTerrainPhys.TerrainImplementation.Heightmap:
  286. newTerrainPhys = new BSTerrainHeightmap(m_physicsScene, terrainRegionBase, id,
  287. heightMap, minCoords, maxCoords);
  288. break;
  289. case (int)BSTerrainPhys.TerrainImplementation.Mesh:
  290. newTerrainPhys = new BSTerrainMesh(m_physicsScene, terrainRegionBase, id,
  291. heightMap, minCoords, maxCoords);
  292. break;
  293. default:
  294. m_physicsScene.Logger.ErrorFormat("{0} Bad terrain implementation specified. Type={1}/{2},Region={3}/{4}",
  295. LogHeader,
  296. (int)BSParam.TerrainImplementation,
  297. BSParam.TerrainImplementation,
  298. m_physicsScene.RegionName, terrainRegionBase);
  299. break;
  300. }
  301. return newTerrainPhys;
  302. }
  303. // Return 'true' of this position is somewhere in known physical terrain space
  304. public bool IsWithinKnownTerrain(Vector3 pos)
  305. {
  306. Vector3 terrainBaseXYZ;
  307. BSTerrainPhys physTerrain;
  308. return GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ);
  309. }
  310. // Return a new position that is over known terrain if the position is outside our terrain.
  311. public Vector3 ClampPositionIntoKnownTerrain(Vector3 pPos)
  312. {
  313. float edgeEpsilon = 0.1f;
  314. Vector3 ret = pPos;
  315. // First, base addresses are never negative so correct for that possible problem.
  316. if (ret.X < 0f || ret.Y < 0f)
  317. {
  318. ret.X = Math.Clamp(ret.X, 0f, 1000000f);
  319. ret.Y = Math.Clamp(ret.Y, 0f, 1000000f);
  320. DetailLog("{0},BSTerrainManager.ClampPositionToKnownTerrain,zeroingNegXorY,oldPos={1},newPos={2}",
  321. BSScene.DetailLogZero, pPos, ret);
  322. }
  323. // Can't do this function if we don't know about any terrain.
  324. if (m_terrains.Count == 0)
  325. return ret;
  326. int loopPrevention = 10;
  327. Vector3 terrainBaseXYZ;
  328. BSTerrainPhys physTerrain;
  329. while (!GetTerrainPhysicalAtXYZ(ret, out physTerrain, out terrainBaseXYZ))
  330. {
  331. // The passed position is not within a known terrain area.
  332. // NOTE that GetTerrainPhysicalAtXYZ will set 'terrainBaseXYZ' to the base of the unfound region.
  333. // Must be off the top of a region. Find an adjacent region to move into.
  334. // The returned terrain is always 'lower'. That is, closer to <0,0>.
  335. Vector3 adjacentTerrainBase = FindAdjacentTerrainBase(terrainBaseXYZ);
  336. if (adjacentTerrainBase.X < terrainBaseXYZ.X)
  337. {
  338. // moving down into a new region in the X dimension. New position will be the max in the new base.
  339. ret.X = adjacentTerrainBase.X + DefaultRegionSize.X - edgeEpsilon;
  340. }
  341. if (adjacentTerrainBase.Y < terrainBaseXYZ.Y)
  342. {
  343. // moving down into a new region in the X dimension. New position will be the max in the new base.
  344. ret.Y = adjacentTerrainBase.Y + DefaultRegionSize.Y - edgeEpsilon;
  345. }
  346. DetailLog("{0},BSTerrainManager.ClampPositionToKnownTerrain,findingAdjacentRegion,adjacentRegBase={1},oldPos={2},newPos={3}",
  347. BSScene.DetailLogZero, adjacentTerrainBase, pPos, ret);
  348. if (loopPrevention-- < 0f)
  349. {
  350. // The 'while' is a little dangerous so this prevents looping forever if the
  351. // mapping of the terrains ever gets messed up (like nothing at <0,0>) or
  352. // the list of terrains is in transition.
  353. DetailLog("{0},BSTerrainManager.ClampPositionToKnownTerrain,suppressingFindAdjacentRegionLoop", BSScene.DetailLogZero);
  354. break;
  355. }
  356. }
  357. return ret;
  358. }
  359. // Given an X and Y, find the height of the terrain.
  360. // Since we could be handling multiple terrains for a mega-region,
  361. // the base of the region is calcuated assuming all regions are
  362. // the same size and that is the default.
  363. // Once the heightMapInfo is found, we have all the information to
  364. // compute the offset into the array.
  365. private float lastHeightTX = 999999f;
  366. private float lastHeightTY = 999999f;
  367. private float lastHeight = HEIGHT_INITIAL_LASTHEIGHT;
  368. public float GetTerrainHeightAtXYZ(Vector3 pos)
  369. {
  370. float tX = pos.X;
  371. float tY = pos.Y;
  372. // You'd be surprized at the number of times this routine is called
  373. // with the same parameters as last time.
  374. if (!m_terrainModified && (lastHeightTX == tX) && (lastHeightTY == tY))
  375. return lastHeight;
  376. m_terrainModified = false;
  377. lastHeightTX = tX;
  378. lastHeightTY = tY;
  379. float ret = HEIGHT_GETHEIGHT_RET;
  380. Vector3 terrainBaseXYZ;
  381. BSTerrainPhys physTerrain;
  382. if (GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ))
  383. {
  384. ret = physTerrain.GetTerrainHeightAtXYZ(pos - terrainBaseXYZ);
  385. }
  386. else
  387. {
  388. m_physicsScene.Logger.ErrorFormat("{0} GetTerrainHeightAtXY: terrain not found: region={1}, x={2}, y={3}",
  389. LogHeader, m_physicsScene.RegionName, tX, tY);
  390. DetailLog("{0},BSTerrainManager.GetTerrainHeightAtXYZ,terrainNotFound,pos={1},base={2}",
  391. BSScene.DetailLogZero, pos, terrainBaseXYZ);
  392. }
  393. lastHeight = ret;
  394. return ret;
  395. }
  396. public float GetWaterLevelAtXYZ(Vector3 pos)
  397. {
  398. float ret = WATER_HEIGHT_GETHEIGHT_RET;
  399. Vector3 terrainBaseXYZ;
  400. BSTerrainPhys physTerrain;
  401. if (GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ))
  402. {
  403. ret = physTerrain.GetWaterLevelAtXYZ(pos);
  404. }
  405. else
  406. {
  407. m_physicsScene.Logger.ErrorFormat("{0} GetWaterHeightAtXY: terrain not found: pos={1}, terrainBase={2}, height={3}",
  408. LogHeader, m_physicsScene.RegionName, pos, terrainBaseXYZ, ret);
  409. }
  410. return ret;
  411. }
  412. // Given an address, return 'true' of there is a description of that terrain and output
  413. // the descriptor class and the 'base' fo the addresses therein.
  414. private bool GetTerrainPhysicalAtXYZ(Vector3 pos, out BSTerrainPhys outPhysTerrain, out Vector3 outTerrainBase)
  415. {
  416. bool ret = false;
  417. Vector3 terrainBaseXYZ = Vector3.Zero;
  418. if (pos.X < 0f || pos.Y < 0f)
  419. {
  420. // We don't handle negative addresses so just make up a base that will not be found.
  421. terrainBaseXYZ = new Vector3(-DefaultRegionSize.X, -DefaultRegionSize.Y, 0f);
  422. }
  423. else
  424. {
  425. int offsetX = ((int)(pos.X / (int)DefaultRegionSize.X)) * (int)DefaultRegionSize.X;
  426. int offsetY = ((int)(pos.Y / (int)DefaultRegionSize.Y)) * (int)DefaultRegionSize.Y;
  427. terrainBaseXYZ = new Vector3(offsetX, offsetY, 0f);
  428. }
  429. BSTerrainPhys physTerrain = null;
  430. lock (m_terrains)
  431. {
  432. ret = m_terrains.TryGetValue(terrainBaseXYZ, out physTerrain);
  433. }
  434. outTerrainBase = terrainBaseXYZ;
  435. outPhysTerrain = physTerrain;
  436. return ret;
  437. }
  438. // Given a terrain base, return a terrain base for a terrain that is closer to <0,0> than
  439. // this one. Usually used to return an out of bounds object to a known place.
  440. private Vector3 FindAdjacentTerrainBase(Vector3 pTerrainBase)
  441. {
  442. Vector3 ret = pTerrainBase;
  443. // Can't do this function if we don't know about any terrain.
  444. if (m_terrains.Count == 0)
  445. return ret;
  446. // Just some sanity
  447. ret.X = Math.Clamp(ret.X, 0f, 1000000f);
  448. ret.Y = Math.Clamp(ret.Y, 0f, 1000000f);
  449. ret.Z = 0f;
  450. lock (m_terrains)
  451. {
  452. // Once down to the <0,0> region, we have to be done.
  453. while (ret.X > 0f || ret.Y > 0f)
  454. {
  455. if (ret.X > 0f)
  456. {
  457. ret.X = Math.Max(0f, ret.X - DefaultRegionSize.X);
  458. DetailLog("{0},BSTerrainManager.FindAdjacentTerrainBase,reducingX,terrainBase={1}", BSScene.DetailLogZero, ret);
  459. if (m_terrains.ContainsKey(ret))
  460. break;
  461. }
  462. if (ret.Y > 0f)
  463. {
  464. ret.Y = Math.Max(0f, ret.Y - DefaultRegionSize.Y);
  465. DetailLog("{0},BSTerrainManager.FindAdjacentTerrainBase,reducingY,terrainBase={1}", BSScene.DetailLogZero, ret);
  466. if (m_terrains.ContainsKey(ret))
  467. break;
  468. }
  469. }
  470. }
  471. return ret;
  472. }
  473. // Although no one seems to check this, I do support combining.
  474. public bool SupportsCombining()
  475. {
  476. return true;
  477. }
  478. // This routine is called two ways:
  479. // One with 'offset' and 'pScene' zero and null but 'extents' giving the maximum
  480. // extent of the combined regions. This is to inform the parent of the size
  481. // of the combined regions.
  482. // and one with 'offset' as the offset of the child region to the base region,
  483. // 'pScene' pointing to the parent and 'extents' of zero. This informs the
  484. // child of its relative base and new parent.
  485. public void Combine(PhysicsScene pScene, Vector3 offset, Vector3 extents)
  486. {
  487. m_worldOffset = offset;
  488. m_worldMax = extents;
  489. MegaRegionParentPhysicsScene = pScene;
  490. if (pScene != null)
  491. {
  492. // We are a child.
  493. // We want m_worldMax to be the highest coordinate of our piece of terrain.
  494. m_worldMax = offset + DefaultRegionSize;
  495. }
  496. DetailLog("{0},BSTerrainManager.Combine,offset={1},extents={2},wOffset={3},wMax={4}",
  497. BSScene.DetailLogZero, offset, extents, m_worldOffset, m_worldMax);
  498. }
  499. // Unhook all the combining that I know about.
  500. public void UnCombine(PhysicsScene pScene)
  501. {
  502. // Just like ODE, we don't do anything yet.
  503. DetailLog("{0},BSTerrainManager.UnCombine", BSScene.DetailLogZero);
  504. }
  505. private void DetailLog(string msg, params Object[] args)
  506. {
  507. m_physicsScene.PhysicsLogging.Write(msg, args);
  508. }
  509. }
  510. }