aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs')
-rw-r--r--OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs461
1 files changed, 461 insertions, 0 deletions
diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs
new file mode 100644
index 0000000..66d62f0
--- /dev/null
+++ b/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs
@@ -0,0 +1,461 @@
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 */
27using System;
28using System.Collections.Generic;
29using System.Text;
30
31using OpenSim.Framework;
32using OpenSim.Region.Framework;
33using OpenSim.Region.CoreModules;
34using OpenSim.Region.Physics.Manager;
35
36using Nini.Config;
37using log4net;
38
39using OpenMetaverse;
40
41namespace OpenSim.Region.Physics.BulletSNPlugin
42{
43
44// The physical implementation of the terrain is wrapped in this class.
45public abstract class BSTerrainPhys : IDisposable
46{
47 public enum TerrainImplementation
48 {
49 Heightmap = 0,
50 Mesh = 1
51 }
52
53 public BSScene 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 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// ==========================================================================================
70public 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 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)
115 {
116 PhysicsScene = physicsScene;
117 m_terrains = new Dictionary<Vector3,BSTerrainPhys>();
118
119 // Assume one region of default size
120 m_worldOffset = Vector3.Zero;
121 m_worldMax = new Vector3(DefaultRegionSize);
122 MegaRegionParentPhysicsScene = null;
123 }
124
125 public void Dispose()
126 {
127 ReleaseGroundPlaneAndTerrain();
128 }
129
130 // Create the initial instance of terrain and the underlying ground plane.
131 // This is called from the initialization routine so we presume it is
132 // safe to call Bullet in real time. We hope no one is moving prims around yet.
133 public void CreateInitialGroundPlaneAndTerrain()
134 {
135 // The ground plane is here to catch things that are trying to drop to negative infinity
136 BulletShape groundPlaneShape = new BulletShape(
137 BulletSimAPI.CreateGroundPlaneShape2(BSScene.GROUNDPLANE_ID, 1f,
138 BSParam.TerrainCollisionMargin),
139 BSPhysicsShapeType.SHAPE_GROUNDPLANE);
140 m_groundPlane = new BulletBody(BSScene.GROUNDPLANE_ID,
141 BulletSimAPI.CreateBodyWithDefaultMotionState2(groundPlaneShape.ptr, BSScene.GROUNDPLANE_ID,
142 Vector3.Zero, Quaternion.Identity));
143 BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, m_groundPlane.ptr, Vector3.Zero, Quaternion.Identity);
144 BulletSimAPI.UpdateSingleAabb2(PhysicsScene.World.ptr, m_groundPlane.ptr);
145 // Ground plane does not move
146 BulletSimAPI.ForceActivationState2(m_groundPlane.ptr, ActivationState.DISABLE_SIMULATION);
147 // Everything collides with the ground plane.
148 m_groundPlane.collisionType = CollisionType.Groundplane;
149 m_groundPlane.ApplyCollisionMask();
150
151 // Build an initial terrain and put it in the world. This quickly gets replaced by the real region terrain.
152 BSTerrainPhys initialTerrain = new BSTerrainHeightmap(PhysicsScene, Vector3.Zero, BSScene.TERRAIN_ID, DefaultRegionSize);
153 m_terrains.Add(Vector3.Zero, initialTerrain);
154 }
155
156 // Release all the terrain structures we might have allocated
157 public void ReleaseGroundPlaneAndTerrain()
158 {
159 if (m_groundPlane.HasPhysicalBody)
160 {
161 if (BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, m_groundPlane.ptr))
162 {
163 BulletSimAPI.DestroyObject2(PhysicsScene.World.ptr, m_groundPlane.ptr);
164 }
165 m_groundPlane.Clear();
166 }
167
168 ReleaseTerrain();
169 }
170
171 // Release all the terrain we have allocated
172 public void ReleaseTerrain()
173 {
174 lock (m_terrains)
175 {
176 foreach (KeyValuePair<Vector3, BSTerrainPhys> kvp in m_terrains)
177 {
178 kvp.Value.Dispose();
179 }
180 m_terrains.Clear();
181 }
182 }
183
184 // The simulator wants to set a new heightmap for the terrain.
185 public void SetTerrain(float[] heightMap) {
186 float[] localHeightMap = heightMap;
187 // If there are multiple requests for changes to the same terrain between ticks,
188 // only do that last one.
189 PhysicsScene.PostTaintObject("TerrainManager.SetTerrain-"+ m_worldOffset.ToString(), 0, delegate()
190 {
191 if (m_worldOffset != Vector3.Zero && MegaRegionParentPhysicsScene != null)
192 {
193 // If a child of a mega-region, we shouldn't have any terrain allocated for us
194 ReleaseGroundPlaneAndTerrain();
195 // If doing the mega-prim stuff and we are the child of the zero region,
196 // the terrain is added to our parent
197 if (MegaRegionParentPhysicsScene is BSScene)
198 {
199 DetailLog("{0},SetTerrain.ToParent,offset={1},worldMax={2}",
200 BSScene.DetailLogZero, m_worldOffset, m_worldMax);
201 ((BSScene)MegaRegionParentPhysicsScene).TerrainManager.UpdateTerrain(
202 BSScene.CHILDTERRAIN_ID, localHeightMap,
203 m_worldOffset, m_worldOffset + DefaultRegionSize, true);
204 }
205 }
206 else
207 {
208 // If not doing the mega-prim thing, just change the terrain
209 DetailLog("{0},SetTerrain.Existing", BSScene.DetailLogZero);
210
211 UpdateTerrain(BSScene.TERRAIN_ID, localHeightMap,
212 m_worldOffset, m_worldOffset + DefaultRegionSize, true);
213 }
214 });
215 }
216
217 // If called with no mapInfo for the terrain, this will create a new mapInfo and terrain
218 // based on the passed information. The 'id' should be either the terrain id or
219 // BSScene.CHILDTERRAIN_ID. If the latter, a new child terrain ID will be allocated and used.
220 // The latter feature is for creating child terrains for mega-regions.
221 // If called with a mapInfo in m_heightMaps and there is an existing terrain body, a new
222 // terrain shape is created and added to the body.
223 // This call is most often used to update the heightMap and parameters of the terrain.
224 // (The above does suggest that some simplification/refactoring is in order.)
225 // Called during taint-time.
226 private void UpdateTerrain(uint id, float[] heightMap,
227 Vector3 minCoords, Vector3 maxCoords, bool inTaintTime)
228 {
229 DetailLog("{0},BSTerrainManager.UpdateTerrain,call,minC={1},maxC={2},inTaintTime={3}",
230 BSScene.DetailLogZero, minCoords, maxCoords, inTaintTime);
231
232 // Find high and low points of passed heightmap.
233 // The min and max passed in is usually the area objects can be in (maximum
234 // object height, for instance). The terrain wants the bounding box for the
235 // terrain so replace passed min and max Z with the actual terrain min/max Z.
236 float minZ = float.MaxValue;
237 float maxZ = float.MinValue;
238 foreach (float height in heightMap)
239 {
240 if (height < minZ) minZ = height;
241 if (height > maxZ) maxZ = height;
242 }
243 if (minZ == maxZ)
244 {
245 // If min and max are the same, reduce min a little bit so a good bounding box is created.
246 minZ -= BSTerrainManager.HEIGHT_EQUAL_FUDGE;
247 }
248 minCoords.Z = minZ;
249 maxCoords.Z = maxZ;
250
251 Vector3 terrainRegionBase = new Vector3(minCoords.X, minCoords.Y, 0f);
252
253 lock (m_terrains)
254 {
255 BSTerrainPhys terrainPhys;
256 if (m_terrains.TryGetValue(terrainRegionBase, out terrainPhys))
257 {
258 // There is already a terrain in this spot. Free the old and build the new.
259 DetailLog("{0},UpdateTerrain:UpdateExisting,call,id={1},base={2},minC={3},maxC={4}",
260 BSScene.DetailLogZero, id, terrainRegionBase, minCoords, minCoords);
261
262 // Remove old terrain from the collection
263 m_terrains.Remove(terrainRegionBase);
264 // Release any physical memory it may be using.
265 terrainPhys.Dispose();
266
267 if (MegaRegionParentPhysicsScene == null)
268 {
269 BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords);
270 m_terrains.Add(terrainRegionBase, newTerrainPhys);
271
272 m_terrainModified = true;
273 }
274 else
275 {
276 // It's possible that Combine() was called after this code was queued.
277 // If we are a child of combined regions, we don't create any terrain for us.
278 DetailLog("{0},BSTerrainManager.UpdateTerrain:AmACombineChild,taint", BSScene.DetailLogZero);
279
280 // Get rid of any terrain that may have been allocated for us.
281 ReleaseGroundPlaneAndTerrain();
282
283 // I hate doing this, but just bail
284 return;
285 }
286 }
287 else
288 {
289 // We don't know about this terrain so either we are creating a new terrain or
290 // our mega-prim child is giving us a new terrain to add to the phys world
291
292 // if this is a child terrain, calculate a unique terrain id
293 uint newTerrainID = id;
294 if (newTerrainID >= BSScene.CHILDTERRAIN_ID)
295 newTerrainID = ++m_terrainCount;
296
297 DetailLog("{0},UpdateTerrain:NewTerrain,taint,newID={1},minCoord={2},maxCoord={3}",
298 BSScene.DetailLogZero, newTerrainID, minCoords, minCoords);
299 BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords);
300 m_terrains.Add(terrainRegionBase, newTerrainPhys);
301
302 m_terrainModified = true;
303 }
304 }
305 }
306
307 // TODO: redo terrain implementation selection to allow other base types than heightMap.
308 private BSTerrainPhys BuildPhysicalTerrain(Vector3 terrainRegionBase, uint id, float[] heightMap, Vector3 minCoords, Vector3 maxCoords)
309 {
310 PhysicsScene.Logger.DebugFormat("{0} Terrain for {1}/{2} created with {3}",
311 LogHeader, PhysicsScene.RegionName, terrainRegionBase,
312 (BSTerrainPhys.TerrainImplementation)BSParam.TerrainImplementation);
313 BSTerrainPhys newTerrainPhys = null;
314 switch ((int)BSParam.TerrainImplementation)
315 {
316 case (int)BSTerrainPhys.TerrainImplementation.Heightmap:
317 newTerrainPhys = new BSTerrainHeightmap(PhysicsScene, terrainRegionBase, id,
318 heightMap, minCoords, maxCoords);
319 break;
320 case (int)BSTerrainPhys.TerrainImplementation.Mesh:
321 newTerrainPhys = new BSTerrainMesh(PhysicsScene, terrainRegionBase, id,
322 heightMap, minCoords, maxCoords);
323 break;
324 default:
325 PhysicsScene.Logger.ErrorFormat("{0} Bad terrain implementation specified. Type={1}/{2},Region={3}/{4}",
326 LogHeader,
327 (int)BSParam.TerrainImplementation,
328 BSParam.TerrainImplementation,
329 PhysicsScene.RegionName, terrainRegionBase);
330 break;
331 }
332 return newTerrainPhys;
333 }
334
335 // Return 'true' of this position is somewhere in known physical terrain space
336 public bool IsWithinKnownTerrain(Vector3 pos)
337 {
338 Vector3 terrainBaseXYZ;
339 BSTerrainPhys physTerrain;
340 return GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ);
341 }
342
343 // Given an X and Y, find the height of the terrain.
344 // Since we could be handling multiple terrains for a mega-region,
345 // the base of the region is calcuated assuming all regions are
346 // the same size and that is the default.
347 // Once the heightMapInfo is found, we have all the information to
348 // compute the offset into the array.
349 private float lastHeightTX = 999999f;
350 private float lastHeightTY = 999999f;
351 private float lastHeight = HEIGHT_INITIAL_LASTHEIGHT;
352 public float GetTerrainHeightAtXYZ(Vector3 pos)
353 {
354 float tX = pos.X;
355 float tY = pos.Y;
356 // You'd be surprized at the number of times this routine is called
357 // with the same parameters as last time.
358 if (!m_terrainModified && (lastHeightTX == tX) && (lastHeightTY == tY))
359 return lastHeight;
360 m_terrainModified = false;
361
362 lastHeightTX = tX;
363 lastHeightTY = tY;
364 float ret = HEIGHT_GETHEIGHT_RET;
365
366 Vector3 terrainBaseXYZ;
367 BSTerrainPhys physTerrain;
368 if (GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ))
369 {
370 ret = physTerrain.GetTerrainHeightAtXYZ(pos - terrainBaseXYZ);
371 }
372 else
373 {
374 PhysicsScene.Logger.ErrorFormat("{0} GetTerrainHeightAtXY: terrain not found: region={1}, x={2}, y={3}",
375 LogHeader, PhysicsScene.RegionName, tX, tY);
376 DetailLog("{0},BSTerrainManager.GetTerrainHeightAtXYZ,terrainNotFound,pos={1},base={2}",
377 BSScene.DetailLogZero, pos, terrainBaseXYZ);
378 }
379
380 lastHeight = ret;
381 return ret;
382 }
383
384 public float GetWaterLevelAtXYZ(Vector3 pos)
385 {
386 float ret = WATER_HEIGHT_GETHEIGHT_RET;
387
388 Vector3 terrainBaseXYZ;
389 BSTerrainPhys physTerrain;
390 if (GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ))
391 {
392 ret = physTerrain.GetWaterLevelAtXYZ(pos);
393 }
394 else
395 {
396 PhysicsScene.Logger.ErrorFormat("{0} GetWaterHeightAtXY: terrain not found: pos={1}, terrainBase={2}, height={3}",
397 LogHeader, PhysicsScene.RegionName, pos, terrainBaseXYZ, ret);
398 }
399 return ret;
400 }
401
402 // Given an address, return 'true' of there is a description of that terrain and output
403 // the descriptor class and the 'base' fo the addresses therein.
404 private bool GetTerrainPhysicalAtXYZ(Vector3 pos, out BSTerrainPhys outPhysTerrain, out Vector3 outTerrainBase)
405 {
406 int offsetX = ((int)(pos.X / (int)DefaultRegionSize.X)) * (int)DefaultRegionSize.X;
407 int offsetY = ((int)(pos.Y / (int)DefaultRegionSize.Y)) * (int)DefaultRegionSize.Y;
408 Vector3 terrainBaseXYZ = new Vector3(offsetX, offsetY, 0f);
409
410 BSTerrainPhys physTerrain = null;
411 lock (m_terrains)
412 {
413 m_terrains.TryGetValue(terrainBaseXYZ, out physTerrain);
414 }
415 outTerrainBase = terrainBaseXYZ;
416 outPhysTerrain = physTerrain;
417 return (physTerrain != null);
418 }
419
420 // Although no one seems to check this, I do support combining.
421 public bool SupportsCombining()
422 {
423 return true;
424 }
425
426 // This routine is called two ways:
427 // One with 'offset' and 'pScene' zero and null but 'extents' giving the maximum
428 // extent of the combined regions. This is to inform the parent of the size
429 // of the combined regions.
430 // and one with 'offset' as the offset of the child region to the base region,
431 // 'pScene' pointing to the parent and 'extents' of zero. This informs the
432 // child of its relative base and new parent.
433 public void Combine(PhysicsScene pScene, Vector3 offset, Vector3 extents)
434 {
435 m_worldOffset = offset;
436 m_worldMax = extents;
437 MegaRegionParentPhysicsScene = pScene;
438 if (pScene != null)
439 {
440 // We are a child.
441 // We want m_worldMax to be the highest coordinate of our piece of terrain.
442 m_worldMax = offset + DefaultRegionSize;
443 }
444 DetailLog("{0},BSTerrainManager.Combine,offset={1},extents={2},wOffset={3},wMax={4}",
445 BSScene.DetailLogZero, offset, extents, m_worldOffset, m_worldMax);
446 }
447
448 // Unhook all the combining that I know about.
449 public void UnCombine(PhysicsScene pScene)
450 {
451 // Just like ODE, we don't do anything yet.
452 DetailLog("{0},BSTerrainManager.UnCombine", BSScene.DetailLogZero);
453 }
454
455
456 private void DetailLog(string msg, params Object[] args)
457 {
458 PhysicsScene.PhysicsLogging.Write(msg, args);
459 }
460}
461}