clockwerk-opensim – Blame information for rev 1

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