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