diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/Physics/BulletSPlugin/BSScene.cs | 137 |
1 files changed, 106 insertions, 31 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs b/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs index 417cb5f..8773485 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs | |||
@@ -29,12 +29,14 @@ using System.Collections.Generic; | |||
29 | using System.Runtime.InteropServices; | 29 | using System.Runtime.InteropServices; |
30 | using System.Text; | 30 | using System.Text; |
31 | using System.Threading; | 31 | using System.Threading; |
32 | using Nini.Config; | ||
33 | using log4net; | ||
34 | using OpenSim.Framework; | 32 | using OpenSim.Framework; |
33 | using OpenSim.Region.Framework; | ||
34 | using OpenSim.Region.CoreModules; | ||
35 | using Logging = OpenSim.Region.CoreModules.Framework.Statistics.Logging; | ||
35 | using OpenSim.Region.Physics.Manager; | 36 | using OpenSim.Region.Physics.Manager; |
37 | using Nini.Config; | ||
38 | using log4net; | ||
36 | using OpenMetaverse; | 39 | using OpenMetaverse; |
37 | using OpenSim.Region.Framework; | ||
38 | 40 | ||
39 | // TODOs for BulletSim (for BSScene, BSPrim, BSCharacter and BulletSim) | 41 | // TODOs for BulletSim (for BSScene, BSPrim, BSCharacter and BulletSim) |
40 | // Debug linkset | 42 | // Debug linkset |
@@ -44,15 +46,17 @@ using OpenSim.Region.Framework; | |||
44 | // Compute physics FPS reasonably | 46 | // Compute physics FPS reasonably |
45 | // Based on material, set density and friction | 47 | // Based on material, set density and friction |
46 | // More efficient memory usage when passing hull information from BSPrim to BulletSim | 48 | // More efficient memory usage when passing hull information from BSPrim to BulletSim |
49 | // Move all logic out of the C++ code and into the C# code for easier future modifications. | ||
47 | // Four states of prim: Physical, regular, phantom and selected. Are we modeling these correctly? | 50 | // Four states of prim: Physical, regular, phantom and selected. Are we modeling these correctly? |
48 | // In SL one can set both physical and phantom (gravity, does not effect others, makes collisions with ground) | 51 | // In SL one can set both physical and phantom (gravity, does not effect others, makes collisions with ground) |
49 | // At the moment, physical and phantom causes object to drop through the terrain | 52 | // At the moment, physical and phantom causes object to drop through the terrain |
50 | // Physical phantom objects and related typing (collision options ) | 53 | // Physical phantom objects and related typing (collision options ) |
54 | // Use collision masks for collision with terrain and phantom objects | ||
51 | // Check out llVolumeDetect. Must do something for that. | 55 | // Check out llVolumeDetect. Must do something for that. |
52 | // Should prim.link() and prim.delink() membership checking happen at taint time? | 56 | // Should prim.link() and prim.delink() membership checking happen at taint time? |
57 | // changing the position and orientation of a linked prim must rebuild the constraint with the root. | ||
53 | // Mesh sharing. Use meshHash to tell if we already have a hull of that shape and only create once | 58 | // Mesh sharing. Use meshHash to tell if we already have a hull of that shape and only create once |
54 | // Do attachments need to be handled separately? Need collision events. Do not collide with VolumeDetect | 59 | // Do attachments need to be handled separately? Need collision events. Do not collide with VolumeDetect |
55 | // Use collision masks for collision with terrain and phantom objects | ||
56 | // Implement the genCollisions feature in BulletSim::SetObjectProperties (don't pass up unneeded collisions) | 60 | // Implement the genCollisions feature in BulletSim::SetObjectProperties (don't pass up unneeded collisions) |
57 | // Implement LockAngularMotion | 61 | // Implement LockAngularMotion |
58 | // Decide if clearing forces is the right thing to do when setting position (BulletSim::SetObjectTranslation) | 62 | // Decide if clearing forces is the right thing to do when setting position (BulletSim::SetObjectTranslation) |
@@ -60,9 +64,6 @@ using OpenSim.Region.Framework; | |||
60 | // Remove mesh and Hull stuff. Use mesh passed to bullet and use convexdecom from bullet. | 64 | // Remove mesh and Hull stuff. Use mesh passed to bullet and use convexdecom from bullet. |
61 | // Add PID movement operations. What does ScenePresence.MoveToTarget do? | 65 | // Add PID movement operations. What does ScenePresence.MoveToTarget do? |
62 | // Check terrain size. 128 or 127? | 66 | // Check terrain size. 128 or 127? |
63 | // Multiple contact points on collision? | ||
64 | // See code in ode::near... calls to collision_accounting_events() | ||
65 | // (This might not be a problem. ODE collects all the collisions with one object in one tick.) | ||
66 | // Raycast | 67 | // Raycast |
67 | // | 68 | // |
68 | namespace OpenSim.Region.Physics.BulletSPlugin | 69 | namespace OpenSim.Region.Physics.BulletSPlugin |
@@ -72,6 +73,8 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
72 | private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); | 73 | private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); |
73 | private static readonly string LogHeader = "[BULLETS SCENE]"; | 74 | private static readonly string LogHeader = "[BULLETS SCENE]"; |
74 | 75 | ||
76 | private void DebugLog(string mm, params Object[] xx) { if (shouldDebugLog) m_log.DebugFormat(mm, xx); } | ||
77 | |||
75 | public string BulletSimVersion = "?"; | 78 | public string BulletSimVersion = "?"; |
76 | 79 | ||
77 | private Dictionary<uint, BSCharacter> m_avatars = new Dictionary<uint, BSCharacter>(); | 80 | private Dictionary<uint, BSCharacter> m_avatars = new Dictionary<uint, BSCharacter>(); |
@@ -105,6 +108,8 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
105 | private long m_simulationStep = 0; | 108 | private long m_simulationStep = 0; |
106 | public long SimulationStep { get { return m_simulationStep; } } | 109 | public long SimulationStep { get { return m_simulationStep; } } |
107 | 110 | ||
111 | public float LastSimulatedTimestep { get; private set; } | ||
112 | |||
108 | // A value of the time now so all the collision and update routines do not have to get their own | 113 | // A value of the time now so all the collision and update routines do not have to get their own |
109 | // Set to 'now' just before all the prims and actors are called for collisions and updates | 114 | // Set to 'now' just before all the prims and actors are called for collisions and updates |
110 | private int m_simulationNowTime; | 115 | private int m_simulationNowTime; |
@@ -121,6 +126,9 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
121 | private bool _meshSculptedPrim = true; // cause scuplted prims to get meshed | 126 | private bool _meshSculptedPrim = true; // cause scuplted prims to get meshed |
122 | private bool _forceSimplePrimMeshing = false; // if a cube or sphere, let Bullet do internal shapes | 127 | private bool _forceSimplePrimMeshing = false; // if a cube or sphere, let Bullet do internal shapes |
123 | 128 | ||
129 | public float PID_D { get; private set; } // derivative | ||
130 | public float PID_P { get; private set; } // proportional | ||
131 | |||
124 | public const uint TERRAIN_ID = 0; // OpenSim senses terrain with a localID of zero | 132 | public const uint TERRAIN_ID = 0; // OpenSim senses terrain with a localID of zero |
125 | public const uint GROUNDPLANE_ID = 1; | 133 | public const uint GROUNDPLANE_ID = 1; |
126 | 134 | ||
@@ -147,8 +155,20 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
147 | ConfigurationParameters[] m_params; | 155 | ConfigurationParameters[] m_params; |
148 | GCHandle m_paramsHandle; | 156 | GCHandle m_paramsHandle; |
149 | 157 | ||
158 | public bool shouldDebugLog { get; private set; } | ||
159 | |||
150 | private BulletSimAPI.DebugLogCallback m_DebugLogCallbackHandle; | 160 | private BulletSimAPI.DebugLogCallback m_DebugLogCallbackHandle; |
151 | 161 | ||
162 | // Sometimes you just have to log everything. | ||
163 | public Logging.LogWriter PhysicsLogging; | ||
164 | private bool m_physicsLoggingEnabled; | ||
165 | private string m_physicsLoggingDir; | ||
166 | private string m_physicsLoggingPrefix; | ||
167 | private int m_physicsLoggingFileMinutes; | ||
168 | |||
169 | private bool m_vehicleLoggingEnabled; | ||
170 | public bool VehicleLoggingEnabled { get { return m_vehicleLoggingEnabled; } } | ||
171 | |||
152 | public BSScene(string identifier) | 172 | public BSScene(string identifier) |
153 | { | 173 | { |
154 | m_initialized = false; | 174 | m_initialized = false; |
@@ -169,17 +189,32 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
169 | m_updateArray = new EntityProperties[m_maxUpdatesPerFrame]; | 189 | m_updateArray = new EntityProperties[m_maxUpdatesPerFrame]; |
170 | m_updateArrayPinnedHandle = GCHandle.Alloc(m_updateArray, GCHandleType.Pinned); | 190 | m_updateArrayPinnedHandle = GCHandle.Alloc(m_updateArray, GCHandleType.Pinned); |
171 | 191 | ||
192 | // Enable very detailed logging. | ||
193 | // By creating an empty logger when not logging, the log message invocation code | ||
194 | // can be left in and every call doesn't have to check for null. | ||
195 | if (m_physicsLoggingEnabled) | ||
196 | { | ||
197 | PhysicsLogging = new Logging.LogWriter(m_physicsLoggingDir, m_physicsLoggingPrefix, m_physicsLoggingFileMinutes); | ||
198 | } | ||
199 | else | ||
200 | { | ||
201 | PhysicsLogging = new Logging.LogWriter(); | ||
202 | } | ||
203 | |||
172 | // Get the version of the DLL | 204 | // Get the version of the DLL |
173 | // TODO: this doesn't work yet. Something wrong with marshaling the returned string. | 205 | // TODO: this doesn't work yet. Something wrong with marshaling the returned string. |
174 | // BulletSimVersion = BulletSimAPI.GetVersion(); | 206 | // BulletSimVersion = BulletSimAPI.GetVersion(); |
175 | // m_log.WarnFormat("{0}: BulletSim.dll version='{1}'", LogHeader, BulletSimVersion); | 207 | // m_log.WarnFormat("{0}: BulletSim.dll version='{1}'", LogHeader, BulletSimVersion); |
176 | 208 | ||
177 | // if Debug, enable logging from the unmanaged code | 209 | // if Debug, enable logging from the unmanaged code |
178 | if (m_log.IsDebugEnabled) | 210 | if (m_log.IsDebugEnabled || PhysicsLogging.Enabled) |
179 | { | 211 | { |
180 | m_log.DebugFormat("{0}: Initialize: Setting debug callback for unmanaged code", LogHeader); | 212 | m_log.DebugFormat("{0}: Initialize: Setting debug callback for unmanaged code", LogHeader); |
181 | // the handle is saved to it doesn't get freed after this call | 213 | if (PhysicsLogging.Enabled) |
182 | m_DebugLogCallbackHandle = new BulletSimAPI.DebugLogCallback(BulletLogger); | 214 | m_DebugLogCallbackHandle = new BulletSimAPI.DebugLogCallback(BulletLoggerPhysLog); |
215 | else | ||
216 | m_DebugLogCallbackHandle = new BulletSimAPI.DebugLogCallback(BulletLogger); | ||
217 | // the handle is saved in a variable to make sure it doesn't get freed after this call | ||
183 | BulletSimAPI.SetDebugLogCallback(m_DebugLogCallbackHandle); | 218 | BulletSimAPI.SetDebugLogCallback(m_DebugLogCallbackHandle); |
184 | } | 219 | } |
185 | 220 | ||
@@ -209,6 +244,7 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
209 | m_meshLOD = 8f; | 244 | m_meshLOD = 8f; |
210 | m_sculptLOD = 32f; | 245 | m_sculptLOD = 32f; |
211 | 246 | ||
247 | shouldDebugLog = false; | ||
212 | m_detailedStatsStep = 0; // disabled | 248 | m_detailedStatsStep = 0; // disabled |
213 | 249 | ||
214 | m_maxSubSteps = 10; | 250 | m_maxSubSteps = 10; |
@@ -217,6 +253,9 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
217 | m_maxUpdatesPerFrame = 2048; | 253 | m_maxUpdatesPerFrame = 2048; |
218 | m_maximumObjectMass = 10000.01f; | 254 | m_maximumObjectMass = 10000.01f; |
219 | 255 | ||
256 | PID_D = 2200f; | ||
257 | PID_P = 900f; | ||
258 | |||
220 | parms.defaultFriction = 0.5f; | 259 | parms.defaultFriction = 0.5f; |
221 | parms.defaultDensity = 10.000006836f; // Aluminum g/cm3 | 260 | parms.defaultDensity = 10.000006836f; // Aluminum g/cm3 |
222 | parms.defaultRestitution = 0f; | 261 | parms.defaultRestitution = 0f; |
@@ -261,7 +300,9 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
261 | _meshSculptedPrim = pConfig.GetBoolean("MeshSculptedPrim", _meshSculptedPrim); | 300 | _meshSculptedPrim = pConfig.GetBoolean("MeshSculptedPrim", _meshSculptedPrim); |
262 | _forceSimplePrimMeshing = pConfig.GetBoolean("ForceSimplePrimMeshing", _forceSimplePrimMeshing); | 301 | _forceSimplePrimMeshing = pConfig.GetBoolean("ForceSimplePrimMeshing", _forceSimplePrimMeshing); |
263 | 302 | ||
303 | shouldDebugLog = pConfig.GetBoolean("ShouldDebugLog", shouldDebugLog); | ||
264 | m_detailedStatsStep = pConfig.GetInt("DetailedStatsStep", m_detailedStatsStep); | 304 | m_detailedStatsStep = pConfig.GetInt("DetailedStatsStep", m_detailedStatsStep); |
305 | |||
265 | m_meshLOD = pConfig.GetFloat("MeshLevelOfDetail", m_meshLOD); | 306 | m_meshLOD = pConfig.GetFloat("MeshLevelOfDetail", m_meshLOD); |
266 | m_sculptLOD = pConfig.GetFloat("SculptLevelOfDetail", m_sculptLOD); | 307 | m_sculptLOD = pConfig.GetFloat("SculptLevelOfDetail", m_sculptLOD); |
267 | 308 | ||
@@ -271,6 +312,9 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
271 | m_maxUpdatesPerFrame = pConfig.GetInt("MaxUpdatesPerFrame", m_maxUpdatesPerFrame); | 312 | m_maxUpdatesPerFrame = pConfig.GetInt("MaxUpdatesPerFrame", m_maxUpdatesPerFrame); |
272 | m_maximumObjectMass = pConfig.GetFloat("MaxObjectMass", m_maximumObjectMass); | 313 | m_maximumObjectMass = pConfig.GetFloat("MaxObjectMass", m_maximumObjectMass); |
273 | 314 | ||
315 | PID_D = pConfig.GetFloat("PIDDerivative", PID_D); | ||
316 | PID_P = pConfig.GetFloat("PIDProportional", PID_P); | ||
317 | |||
274 | parms.defaultFriction = pConfig.GetFloat("DefaultFriction", parms.defaultFriction); | 318 | parms.defaultFriction = pConfig.GetFloat("DefaultFriction", parms.defaultFriction); |
275 | parms.defaultDensity = pConfig.GetFloat("DefaultDensity", parms.defaultDensity); | 319 | parms.defaultDensity = pConfig.GetFloat("DefaultDensity", parms.defaultDensity); |
276 | parms.defaultRestitution = pConfig.GetFloat("DefaultRestitution", parms.defaultRestitution); | 320 | parms.defaultRestitution = pConfig.GetFloat("DefaultRestitution", parms.defaultRestitution); |
@@ -303,6 +347,14 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
303 | parms.shouldSplitSimulationIslands = ParamBoolean(pConfig, "ShouldSplitSimulationIslands", parms.shouldSplitSimulationIslands); | 347 | parms.shouldSplitSimulationIslands = ParamBoolean(pConfig, "ShouldSplitSimulationIslands", parms.shouldSplitSimulationIslands); |
304 | parms.shouldEnableFrictionCaching = ParamBoolean(pConfig, "ShouldEnableFrictionCaching", parms.shouldEnableFrictionCaching); | 348 | parms.shouldEnableFrictionCaching = ParamBoolean(pConfig, "ShouldEnableFrictionCaching", parms.shouldEnableFrictionCaching); |
305 | parms.numberOfSolverIterations = pConfig.GetFloat("NumberOfSolverIterations", parms.numberOfSolverIterations); | 349 | parms.numberOfSolverIterations = pConfig.GetFloat("NumberOfSolverIterations", parms.numberOfSolverIterations); |
350 | |||
351 | // Very detailed logging for physics debugging | ||
352 | m_physicsLoggingEnabled = pConfig.GetBoolean("PhysicsLoggingEnabled", false); | ||
353 | m_physicsLoggingDir = pConfig.GetString("PhysicsLoggingDir", "."); | ||
354 | m_physicsLoggingPrefix = pConfig.GetString("PhysicsLoggingPrefix", "physics-"); | ||
355 | m_physicsLoggingFileMinutes = pConfig.GetInt("PhysicsLoggingFileMinutes", 5); | ||
356 | // Very detailed logging for vehicle debugging | ||
357 | m_vehicleLoggingEnabled = pConfig.GetBoolean("VehicleLoggingEnabled", false); | ||
306 | } | 358 | } |
307 | } | 359 | } |
308 | m_params[0] = parms; | 360 | m_params[0] = parms; |
@@ -323,12 +375,17 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
323 | return ret; | 375 | return ret; |
324 | } | 376 | } |
325 | 377 | ||
326 | |||
327 | // Called directly from unmanaged code so don't do much | 378 | // Called directly from unmanaged code so don't do much |
328 | private void BulletLogger(string msg) | 379 | private void BulletLogger(string msg) |
329 | { | 380 | { |
330 | m_log.Debug("[BULLETS UNMANAGED]:" + msg); | 381 | m_log.Debug("[BULLETS UNMANAGED]:" + msg); |
331 | } | 382 | } |
383 | |||
384 | // Called directly from unmanaged code so don't do much | ||
385 | private void BulletLoggerPhysLog(string msg) | ||
386 | { | ||
387 | PhysicsLogging.Write("[BULLETS UNMANAGED]:" + msg); | ||
388 | } | ||
332 | 389 | ||
333 | public override PhysicsActor AddAvatar(string avName, Vector3 position, Vector3 size, bool isFlying) | 390 | public override PhysicsActor AddAvatar(string avName, Vector3 position, Vector3 size, bool isFlying) |
334 | { | 391 | { |
@@ -347,34 +404,42 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
347 | public override void RemoveAvatar(PhysicsActor actor) | 404 | public override void RemoveAvatar(PhysicsActor actor) |
348 | { | 405 | { |
349 | // m_log.DebugFormat("{0}: RemoveAvatar", LogHeader); | 406 | // m_log.DebugFormat("{0}: RemoveAvatar", LogHeader); |
350 | if (actor is BSCharacter) | 407 | BSCharacter bsactor = actor as BSCharacter; |
351 | { | 408 | if (bsactor != null) |
352 | ((BSCharacter)actor).Destroy(); | ||
353 | } | ||
354 | try | ||
355 | { | 409 | { |
356 | lock (m_avatars) m_avatars.Remove(actor.LocalID); | 410 | try |
357 | } | 411 | { |
358 | catch (Exception e) | 412 | lock (m_avatars) m_avatars.Remove(actor.LocalID); |
359 | { | 413 | } |
360 | m_log.WarnFormat("{0}: Attempt to remove avatar that is not in physics scene: {1}", LogHeader, e); | 414 | catch (Exception e) |
415 | { | ||
416 | m_log.WarnFormat("{0}: Attempt to remove avatar that is not in physics scene: {1}", LogHeader, e); | ||
417 | } | ||
418 | bsactor.Destroy(); | ||
419 | // bsactor.dispose(); | ||
361 | } | 420 | } |
362 | } | 421 | } |
363 | 422 | ||
364 | public override void RemovePrim(PhysicsActor prim) | 423 | public override void RemovePrim(PhysicsActor prim) |
365 | { | 424 | { |
366 | // m_log.DebugFormat("{0}: RemovePrim", LogHeader); | 425 | BSPrim bsprim = prim as BSPrim; |
367 | if (prim is BSPrim) | 426 | if (bsprim != null) |
368 | { | ||
369 | ((BSPrim)prim).Destroy(); | ||
370 | } | ||
371 | try | ||
372 | { | 427 | { |
373 | lock (m_prims) m_prims.Remove(prim.LocalID); | 428 | m_log.DebugFormat("{0}: RemovePrim. id={1}/{2}", LogHeader, bsprim.Name, bsprim.LocalID); |
429 | try | ||
430 | { | ||
431 | lock (m_prims) m_prims.Remove(bsprim.LocalID); | ||
432 | } | ||
433 | catch (Exception e) | ||
434 | { | ||
435 | m_log.ErrorFormat("{0}: Attempt to remove prim that is not in physics scene: {1}", LogHeader, e); | ||
436 | } | ||
437 | bsprim.Destroy(); | ||
438 | // bsprim.dispose(); | ||
374 | } | 439 | } |
375 | catch (Exception e) | 440 | else |
376 | { | 441 | { |
377 | m_log.WarnFormat("{0}: Attempt to remove prim that is not in physics scene: {1}", LogHeader, e); | 442 | m_log.ErrorFormat("{0}: Attempt to remove prim that is not a BSPrim type.", LogHeader); |
378 | } | 443 | } |
379 | } | 444 | } |
380 | 445 | ||
@@ -400,6 +465,8 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
400 | int collidersCount; | 465 | int collidersCount; |
401 | IntPtr collidersPtr; | 466 | IntPtr collidersPtr; |
402 | 467 | ||
468 | LastSimulatedTimestep = timeStep; | ||
469 | |||
403 | // prevent simulation until we've been initialized | 470 | // prevent simulation until we've been initialized |
404 | if (!m_initialized) return 10.0f; | 471 | if (!m_initialized) return 10.0f; |
405 | 472 | ||
@@ -459,7 +526,6 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
459 | for (int ii = 0; ii < updatedEntityCount; ii++) | 526 | for (int ii = 0; ii < updatedEntityCount; ii++) |
460 | { | 527 | { |
461 | EntityProperties entprop = m_updateArray[ii]; | 528 | EntityProperties entprop = m_updateArray[ii]; |
462 | // m_log.DebugFormat("{0}: entprop[{1}]: id={2}, pos={3}", LogHeader, ii, entprop.ID, entprop.Position); | ||
463 | BSPrim prim; | 529 | BSPrim prim; |
464 | if (m_prims.TryGetValue(entprop.ID, out prim)) | 530 | if (m_prims.TryGetValue(entprop.ID, out prim)) |
465 | { | 531 | { |
@@ -532,8 +598,17 @@ public class BSScene : PhysicsScene, IPhysicsParameters | |||
532 | }); | 598 | }); |
533 | } | 599 | } |
534 | 600 | ||
601 | // Someday we will have complex terrain with caves and tunnels | ||
602 | // For the moment, it's flat and convex | ||
603 | public float GetTerrainHeightAtXYZ(Vector3 loc) | ||
604 | { | ||
605 | return GetTerrainHeightAtXY(loc.X, loc.Y); | ||
606 | } | ||
607 | |||
535 | public float GetTerrainHeightAtXY(float tX, float tY) | 608 | public float GetTerrainHeightAtXY(float tX, float tY) |
536 | { | 609 | { |
610 | if (tX < 0 || tX >= Constants.RegionSize || tY < 0 || tY >= Constants.RegionSize) | ||
611 | return 30; | ||
537 | return m_heightMap[((int)tX) * Constants.RegionSize + ((int)tY)]; | 612 | return m_heightMap[((int)tX) * Constants.RegionSize + ((int)tY)]; |
538 | } | 613 | } |
539 | 614 | ||