diff options
author | Robert Adams | 2011-08-05 11:01:27 -0700 |
---|---|---|
committer | Mic Bowman | 2011-08-05 11:01:27 -0700 |
commit | 7640b5abf651e51ae2efd5f79f440768a595dbc9 (patch) | |
tree | 0b96cd6d2749f95888566f3e04f9798e04c21acd /OpenSim/Region/Physics/BulletSPlugin/BSScene.cs | |
parent | BulletSim: Update dlls (diff) | |
download | opensim-SC-7640b5abf651e51ae2efd5f79f440768a595dbc9.zip opensim-SC-7640b5abf651e51ae2efd5f79f440768a595dbc9.tar.gz opensim-SC-7640b5abf651e51ae2efd5f79f440768a595dbc9.tar.bz2 opensim-SC-7640b5abf651e51ae2efd5f79f440768a595dbc9.tar.xz |
BulletSim: Parameters settable from ini file. Linksets. Physical property value tuning
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/Physics/BulletSPlugin/BSScene.cs | 207 |
1 files changed, 116 insertions, 91 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs b/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs index 7c11f2b..de86d59 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs | |||
@@ -71,11 +71,17 @@ public class BSScene : PhysicsScene | |||
71 | private uint m_worldID; | 71 | private uint m_worldID; |
72 | public uint WorldID { get { return m_worldID; } } | 72 | public uint WorldID { get { return m_worldID; } } |
73 | 73 | ||
74 | private bool m_initialized = false; | ||
75 | |||
74 | public IMesher mesher; | 76 | public IMesher mesher; |
75 | public int meshLOD = 32; | 77 | private int m_meshLOD; |
78 | public int MeshLOD | ||
79 | { | ||
80 | get { return m_meshLOD; } | ||
81 | } | ||
76 | 82 | ||
77 | private int m_maxSubSteps = 10; | 83 | private int m_maxSubSteps; |
78 | private float m_fixedTimeStep = 1f / 60f; | 84 | private float m_fixedTimeStep; |
79 | private long m_simulationStep = 0; | 85 | private long m_simulationStep = 0; |
80 | public long SimulationStep { get { return m_simulationStep; } } | 86 | public long SimulationStep { get { return m_simulationStep; } } |
81 | 87 | ||
@@ -84,68 +90,65 @@ public class BSScene : PhysicsScene | |||
84 | private int m_simulationNowTime; | 90 | private int m_simulationNowTime; |
85 | public int SimulationNowTime { get { return m_simulationNowTime; } } | 91 | public int SimulationNowTime { get { return m_simulationNowTime; } } |
86 | 92 | ||
87 | private int m_maxCollisionsPerFrame = 2048; | 93 | private int m_maxCollisionsPerFrame; |
88 | private CollisionDesc[] m_collisionArray; | 94 | private CollisionDesc[] m_collisionArray; |
89 | private GCHandle m_collisionArrayPinnedHandle; | 95 | private GCHandle m_collisionArrayPinnedHandle; |
90 | 96 | ||
91 | private int m_maxUpdatesPerFrame = 2048; | 97 | private int m_maxUpdatesPerFrame; |
92 | private EntityProperties[] m_updateArray; | 98 | private EntityProperties[] m_updateArray; |
93 | private GCHandle m_updateArrayPinnedHandle; | 99 | private GCHandle m_updateArrayPinnedHandle; |
94 | 100 | ||
95 | private bool _meshSculptedPrim = true; // cause scuplted prims to get meshed | 101 | private bool _meshSculptedPrim = true; // cause scuplted prims to get meshed |
96 | private bool _forceSimplePrimMeshing = false; // if a cube or sphere, let Bullet do internal shapes | 102 | private bool _forceSimplePrimMeshing = false; // if a cube or sphere, let Bullet do internal shapes |
97 | public float maximumMassObject = 10000.01f; | ||
98 | 103 | ||
99 | public const uint TERRAIN_ID = 0; | 104 | public const uint TERRAIN_ID = 0; // OpenSim senses terrain with a localID of zero |
100 | public const uint GROUNDPLANE_ID = 1; | 105 | public const uint GROUNDPLANE_ID = 1; |
101 | 106 | ||
102 | public float DefaultFriction = 0.70f; | 107 | public ConfigurationParameters Params |
103 | public float DefaultDensity = 10.000006836f; // Aluminum g/cm3; TODO: compute based on object material | 108 | { |
104 | public Vector3 DefaultGravity = new Vector3(0, 0, -9.80665f); | 109 | get { return m_params[0]; } |
110 | } | ||
111 | public Vector3 DefaultGravity | ||
112 | { | ||
113 | get { return new Vector3(0f, 0f, Params.gravity); } | ||
114 | } | ||
115 | |||
116 | private float m_maximumObjectMass; | ||
117 | public float MaximumObjectMass | ||
118 | { | ||
119 | get { return m_maximumObjectMass; } | ||
120 | } | ||
105 | 121 | ||
106 | public delegate void TaintCallback(); | 122 | public delegate void TaintCallback(); |
107 | private List<TaintCallback> _taintedObjects; | 123 | private List<TaintCallback> _taintedObjects; |
108 | private Object _taintLock = new Object(); | 124 | private Object _taintLock = new Object(); |
109 | 125 | ||
110 | // A pointer to an instance if this structure is passed to the C++ code | 126 | // A pointer to an instance if this structure is passed to the C++ code |
111 | // Format of this structure must match the definition in the C++ code | 127 | ConfigurationParameters[] m_params; |
112 | private struct ConfigurationParameters | ||
113 | { | ||
114 | public float defaultFriction; | ||
115 | public float defaultDensity; | ||
116 | public float collisionMargin; | ||
117 | public float gravity; | ||
118 | |||
119 | public float linearDamping; | ||
120 | public float angularDamping; | ||
121 | public float deactivationTime; | ||
122 | public float linearSleepingThreshold; | ||
123 | public float angularSleepingThreshold; | ||
124 | |||
125 | public float terrainFriction; | ||
126 | public float terrainHitFriction; | ||
127 | public float terrainRestitution; | ||
128 | public float avatarFriction; | ||
129 | public float avatarCapsuleRadius; | ||
130 | public float avatarCapsuleHeight; | ||
131 | } | ||
132 | ConfigurationParameters m_params; | ||
133 | GCHandle m_paramsHandle; | 128 | GCHandle m_paramsHandle; |
134 | 129 | ||
135 | private BulletSimAPI.DebugLogCallback debugLogCallbackHandle; | 130 | private BulletSimAPI.DebugLogCallback debugLogCallbackHandle; |
136 | 131 | ||
137 | public BSScene(string identifier) | 132 | public BSScene(string identifier) |
138 | { | 133 | { |
134 | m_initialized = false; | ||
139 | } | 135 | } |
140 | 136 | ||
141 | public override void Initialise(IMesher meshmerizer, IConfigSource config) | 137 | public override void Initialise(IMesher meshmerizer, IConfigSource config) |
142 | { | 138 | { |
143 | m_params = new ConfigurationParameters(); | 139 | // Allocate pinned memory to pass parameters. |
140 | m_params = new ConfigurationParameters[1]; | ||
144 | m_paramsHandle = GCHandle.Alloc(m_params, GCHandleType.Pinned); | 141 | m_paramsHandle = GCHandle.Alloc(m_params, GCHandleType.Pinned); |
145 | 142 | ||
146 | // Set default values for physics parameters plus any overrides from the ini file | 143 | // Set default values for physics parameters plus any overrides from the ini file |
147 | GetInitialParameterValues(config); | 144 | GetInitialParameterValues(config); |
148 | 145 | ||
146 | // allocate more pinned memory close to the above in an attempt to get the memory all together | ||
147 | m_collisionArray = new CollisionDesc[m_maxCollisionsPerFrame]; | ||
148 | m_collisionArrayPinnedHandle = GCHandle.Alloc(m_collisionArray, GCHandleType.Pinned); | ||
149 | m_updateArray = new EntityProperties[m_maxUpdatesPerFrame]; | ||
150 | m_updateArrayPinnedHandle = GCHandle.Alloc(m_updateArray, GCHandleType.Pinned); | ||
151 | |||
149 | // if Debug, enable logging from the unmanaged code | 152 | // if Debug, enable logging from the unmanaged code |
150 | if (m_log.IsDebugEnabled) | 153 | if (m_log.IsDebugEnabled) |
151 | { | 154 | { |
@@ -160,68 +163,95 @@ public class BSScene : PhysicsScene | |||
160 | // The bounding box for the simulated world | 163 | // The bounding box for the simulated world |
161 | Vector3 worldExtent = new Vector3(Constants.RegionSize, Constants.RegionSize, 4096f); | 164 | Vector3 worldExtent = new Vector3(Constants.RegionSize, Constants.RegionSize, 4096f); |
162 | 165 | ||
163 | // Allocate pinned memory to pass back object property updates and collisions from simulation step | ||
164 | m_collisionArray = new CollisionDesc[m_maxCollisionsPerFrame]; | ||
165 | m_collisionArrayPinnedHandle = GCHandle.Alloc(m_collisionArray, GCHandleType.Pinned); | ||
166 | m_updateArray = new EntityProperties[m_maxUpdatesPerFrame]; | ||
167 | m_updateArrayPinnedHandle = GCHandle.Alloc(m_updateArray, GCHandleType.Pinned); | ||
168 | |||
169 | // m_log.DebugFormat("{0}: Initialize: Calling BulletSimAPI.Initialize.", LogHeader); | 166 | // m_log.DebugFormat("{0}: Initialize: Calling BulletSimAPI.Initialize.", LogHeader); |
170 | m_worldID = BulletSimAPI.Initialize(worldExtent, | 167 | m_worldID = BulletSimAPI.Initialize(worldExtent, m_paramsHandle.AddrOfPinnedObject(), |
171 | m_maxCollisionsPerFrame, m_collisionArrayPinnedHandle.AddrOfPinnedObject(), | 168 | m_maxCollisionsPerFrame, m_collisionArrayPinnedHandle.AddrOfPinnedObject(), |
172 | m_maxUpdatesPerFrame, m_updateArrayPinnedHandle.AddrOfPinnedObject()); | 169 | m_maxUpdatesPerFrame, m_updateArrayPinnedHandle.AddrOfPinnedObject()); |
170 | |||
171 | m_initialized = true; | ||
173 | } | 172 | } |
174 | 173 | ||
174 | // All default parameter values are set here. There should be no values set in the | ||
175 | // variable definitions. | ||
175 | private void GetInitialParameterValues(IConfigSource config) | 176 | private void GetInitialParameterValues(IConfigSource config) |
176 | { | 177 | { |
178 | ConfigurationParameters parms = new ConfigurationParameters(); | ||
179 | |||
177 | _meshSculptedPrim = true; // mesh sculpted prims | 180 | _meshSculptedPrim = true; // mesh sculpted prims |
178 | _forceSimplePrimMeshing = false; // use complex meshing if called for | 181 | _forceSimplePrimMeshing = false; // use complex meshing if called for |
179 | 182 | ||
180 | // Set the default values for the physics parameters | 183 | m_meshLOD = 32; |
181 | m_params.defaultFriction = 0.70f; | 184 | |
182 | m_params.defaultDensity = 10.000006836f; // Aluminum g/cm3 | 185 | m_maxSubSteps = 10; |
183 | m_params.collisionMargin = 0.0f; | 186 | m_fixedTimeStep = 1f / 60f; |
184 | m_params.gravity = -9.80665f; | 187 | m_maxCollisionsPerFrame = 2048; |
185 | 188 | m_maxUpdatesPerFrame = 2048; | |
186 | m_params.linearDamping = 0.1f; | 189 | m_maximumObjectMass = 10000.01f; |
187 | m_params.angularDamping = 0.85f; | 190 | |
188 | m_params.deactivationTime = 0.2f; | 191 | parms.defaultFriction = 0.70f; |
189 | m_params.linearSleepingThreshold = 0.8f; | 192 | parms.defaultDensity = 10.000006836f; // Aluminum g/cm3 |
190 | m_params.angularSleepingThreshold = 1.0f; | 193 | parms.defaultRestitution = 0f; |
191 | 194 | parms.collisionMargin = 0.0f; | |
192 | m_params.terrainFriction = 0.85f; | 195 | parms.gravity = -9.80665f; |
193 | m_params.terrainHitFriction = 0.8f; | 196 | |
194 | m_params.terrainRestitution = 0.2f; | 197 | parms.linearDamping = 0.0f; |
195 | m_params.avatarFriction = 0.85f; | 198 | parms.angularDamping = 0.0f; |
196 | m_params.avatarCapsuleRadius = 0.37f; | 199 | parms.deactivationTime = 0.2f; |
197 | m_params.avatarCapsuleHeight = 1.5f; // 2.140599f | 200 | parms.linearSleepingThreshold = 0.8f; |
201 | parms.angularSleepingThreshold = 1.0f; | ||
202 | parms.ccdMotionThreshold = 0.5f; // set to zero to disable | ||
203 | parms.ccdSweptSphereRadius = 0.2f; | ||
204 | |||
205 | parms.terrainFriction = 0.85f; | ||
206 | parms.terrainHitFriction = 0.8f; | ||
207 | parms.terrainRestitution = 0.2f; | ||
208 | parms.avatarFriction = 0.85f; | ||
209 | parms.avatarDensity = 60f; | ||
210 | parms.avatarCapsuleRadius = 0.37f; | ||
211 | parms.avatarCapsuleHeight = 1.5f; // 2.140599f | ||
198 | 212 | ||
199 | if (config != null) | 213 | if (config != null) |
200 | { | 214 | { |
201 | // If there are specifications in the ini file, use those values | 215 | // If there are specifications in the ini file, use those values |
216 | // WHEN ADDING OR UPDATING THIS SECTION, BE SURE TO ALSO UPDATE OpenSimDefaults.ini | ||
202 | IConfig pConfig = config.Configs["BulletSim"]; | 217 | IConfig pConfig = config.Configs["BulletSim"]; |
203 | if (pConfig != null) | 218 | if (pConfig != null) |
204 | { | 219 | { |
205 | _meshSculptedPrim = pConfig.GetBoolean("MeshSculptedPrim", true); | 220 | _meshSculptedPrim = pConfig.GetBoolean("MeshSculptedPrim", _meshSculptedPrim); |
206 | _forceSimplePrimMeshing = pConfig.GetBoolean("ForceSimplePrimMeshing", false); | 221 | _forceSimplePrimMeshing = pConfig.GetBoolean("ForceSimplePrimMeshing", _forceSimplePrimMeshing); |
207 | 222 | ||
208 | m_params.defaultFriction = pConfig.GetFloat("DefaultFriction", m_params.defaultFriction); | 223 | m_meshLOD = pConfig.GetInt("MeshLevelOfDetail", m_meshLOD); |
209 | m_params.defaultDensity = pConfig.GetFloat("DefaultDensity", m_params.defaultDensity); | 224 | |
210 | m_params.collisionMargin = pConfig.GetFloat("CollisionMargin", m_params.collisionMargin); | 225 | m_maxSubSteps = pConfig.GetInt("MaxSubSteps", m_maxSubSteps); |
211 | m_params.gravity = pConfig.GetFloat("Gravity", m_params.gravity); | 226 | m_fixedTimeStep = pConfig.GetFloat("FixedTimeStep", m_fixedTimeStep); |
212 | m_params.linearDamping = pConfig.GetFloat("LinearDamping", m_params.linearDamping); | 227 | m_maxCollisionsPerFrame = pConfig.GetInt("MaxCollisionsPerFrame", m_maxCollisionsPerFrame); |
213 | m_params.angularDamping = pConfig.GetFloat("AngularDamping", m_params.angularDamping); | 228 | m_maxUpdatesPerFrame = pConfig.GetInt("MaxUpdatesPerFrame", m_maxUpdatesPerFrame); |
214 | m_params.deactivationTime = pConfig.GetFloat("DeactivationTime", m_params.deactivationTime); | 229 | m_maximumObjectMass = pConfig.GetFloat("MaxObjectMass", m_maximumObjectMass); |
215 | m_params.linearSleepingThreshold = pConfig.GetFloat("LinearSleepingThreshold", m_params.linearSleepingThreshold); | 230 | |
216 | m_params.angularSleepingThreshold = pConfig.GetFloat("AngularSleepingThreshold", m_params.angularSleepingThreshold); | 231 | parms.defaultFriction = pConfig.GetFloat("DefaultFriction", parms.defaultFriction); |
217 | m_params.terrainFriction = pConfig.GetFloat("TerrainFriction", m_params.terrainFriction); | 232 | parms.defaultDensity = pConfig.GetFloat("DefaultDensity", parms.defaultDensity); |
218 | m_params.terrainHitFriction = pConfig.GetFloat("TerrainHitFriction", m_params.terrainHitFriction); | 233 | parms.defaultRestitution = pConfig.GetFloat("DefaultRestitution", parms.defaultRestitution); |
219 | m_params.terrainRestitution = pConfig.GetFloat("TerrainRestitution", m_params.terrainRestitution); | 234 | parms.collisionMargin = pConfig.GetFloat("CollisionMargin", parms.collisionMargin); |
220 | m_params.avatarFriction = pConfig.GetFloat("AvatarFriction", m_params.avatarFriction); | 235 | parms.gravity = pConfig.GetFloat("Gravity", parms.gravity); |
221 | m_params.avatarCapsuleRadius = pConfig.GetFloat("AvatarCapsuleRadius", m_params.avatarCapsuleRadius); | 236 | |
222 | m_params.avatarCapsuleHeight = pConfig.GetFloat("AvatarCapsuleHeight", m_params.avatarCapsuleHeight); | 237 | parms.linearDamping = pConfig.GetFloat("LinearDamping", parms.linearDamping); |
238 | parms.angularDamping = pConfig.GetFloat("AngularDamping", parms.angularDamping); | ||
239 | parms.deactivationTime = pConfig.GetFloat("DeactivationTime", parms.deactivationTime); | ||
240 | parms.linearSleepingThreshold = pConfig.GetFloat("LinearSleepingThreshold", parms.linearSleepingThreshold); | ||
241 | parms.angularSleepingThreshold = pConfig.GetFloat("AngularSleepingThreshold", parms.angularSleepingThreshold); | ||
242 | parms.ccdMotionThreshold = pConfig.GetFloat("CcdMotionThreshold", parms.ccdMotionThreshold); | ||
243 | parms.ccdSweptSphereRadius = pConfig.GetFloat("CcdSweptSphereRadius", parms.ccdSweptSphereRadius); | ||
244 | |||
245 | parms.terrainFriction = pConfig.GetFloat("TerrainFriction", parms.terrainFriction); | ||
246 | parms.terrainHitFriction = pConfig.GetFloat("TerrainHitFriction", parms.terrainHitFriction); | ||
247 | parms.terrainRestitution = pConfig.GetFloat("TerrainRestitution", parms.terrainRestitution); | ||
248 | parms.avatarFriction = pConfig.GetFloat("AvatarFriction", parms.avatarFriction); | ||
249 | parms.avatarDensity = pConfig.GetFloat("AvatarDensity", parms.avatarDensity); | ||
250 | parms.avatarCapsuleRadius = pConfig.GetFloat("AvatarCapsuleRadius", parms.avatarCapsuleRadius); | ||
251 | parms.avatarCapsuleHeight = pConfig.GetFloat("AvatarCapsuleHeight", parms.avatarCapsuleHeight); | ||
223 | } | 252 | } |
224 | } | 253 | } |
254 | m_params[0] = parms; | ||
225 | } | 255 | } |
226 | 256 | ||
227 | // Called directly from unmanaged code so don't do much | 257 | // Called directly from unmanaged code so don't do much |
@@ -282,20 +312,13 @@ public class BSScene : PhysicsScene | |||
282 | Vector3 size, Quaternion rotation, bool isPhysical, uint localID) | 312 | Vector3 size, Quaternion rotation, bool isPhysical, uint localID) |
283 | { | 313 | { |
284 | // m_log.DebugFormat("{0}: AddPrimShape2: {1}", LogHeader, primName); | 314 | // m_log.DebugFormat("{0}: AddPrimShape2: {1}", LogHeader, primName); |
285 | IMesh mesh = null; | 315 | BSPrim prim = new BSPrim(localID, primName, this, position, size, rotation, pbs, isPhysical); |
286 | if (NeedsMeshing(pbs)) | ||
287 | { | ||
288 | // if the prim is complex, create the mesh for it. | ||
289 | // If simple (box or sphere) leave 'mesh' null and physics will do a native shape. | ||
290 | mesh = mesher.CreateMesh(primName, pbs, size, this.meshLOD, isPhysical); | ||
291 | } | ||
292 | BSPrim prim = new BSPrim(localID, primName, this, position, size, rotation, mesh, pbs, isPhysical); | ||
293 | lock (m_prims) m_prims.Add(localID, prim); | 316 | lock (m_prims) m_prims.Add(localID, prim); |
294 | return prim; | 317 | return prim; |
295 | } | 318 | } |
296 | 319 | ||
297 | // This is a call from the simulator saying that some physical property has been updated. | 320 | // This is a call from the simulator saying that some physical property has been updated. |
298 | // The BulletS driver senses the changing of relevant properties so this taint | 321 | // The BulletSim driver senses the changing of relevant properties so this taint |
299 | // information call is not needed. | 322 | // information call is not needed. |
300 | public override void AddPhysicsActorTaint(PhysicsActor prim) { } | 323 | public override void AddPhysicsActorTaint(PhysicsActor prim) { } |
301 | 324 | ||
@@ -307,6 +330,9 @@ public class BSScene : PhysicsScene | |||
307 | int collidersCount; | 330 | int collidersCount; |
308 | IntPtr collidersPtr; | 331 | IntPtr collidersPtr; |
309 | 332 | ||
333 | // prevent simulation until we've been initialized | ||
334 | if (!m_initialized) return 10.0f; | ||
335 | |||
310 | // update the prim states while we know the physics engine is not busy | 336 | // update the prim states while we know the physics engine is not busy |
311 | ProcessTaints(); | 337 | ProcessTaints(); |
312 | 338 | ||
@@ -360,7 +386,7 @@ public class BSScene : PhysicsScene | |||
360 | } | 386 | } |
361 | } | 387 | } |
362 | 388 | ||
363 | // fps calculation wrong. This calculation always returns about 1 in normal operation. | 389 | // FIX THIS: fps calculation wrong. This calculation always returns about 1 in normal operation. |
364 | return timeStep / (numSubSteps * m_fixedTimeStep) * 1000f; | 390 | return timeStep / (numSubSteps * m_fixedTimeStep) * 1000f; |
365 | } | 391 | } |
366 | 392 | ||
@@ -369,8 +395,7 @@ public class BSScene : PhysicsScene | |||
369 | { | 395 | { |
370 | if (localID == TERRAIN_ID || localID == GROUNDPLANE_ID) | 396 | if (localID == TERRAIN_ID || localID == GROUNDPLANE_ID) |
371 | { | 397 | { |
372 | // we never send collisions to the terrain | 398 | return; // don't send collisions to the terrain |
373 | return; | ||
374 | } | 399 | } |
375 | 400 | ||
376 | ActorTypes type = ActorTypes.Prim; | 401 | ActorTypes type = ActorTypes.Prim; |
@@ -381,12 +406,12 @@ public class BSScene : PhysicsScene | |||
381 | 406 | ||
382 | BSPrim prim; | 407 | BSPrim prim; |
383 | if (m_prims.TryGetValue(localID, out prim)) { | 408 | if (m_prims.TryGetValue(localID, out prim)) { |
384 | prim.Collide(collidingWith, type, collidePoint, collideNormal, 0.01f); | 409 | prim.Collide(collidingWith, type, collidePoint, collideNormal, penitration); |
385 | return; | 410 | return; |
386 | } | 411 | } |
387 | BSCharacter actor; | 412 | BSCharacter actor; |
388 | if (m_avatars.TryGetValue(localID, out actor)) { | 413 | if (m_avatars.TryGetValue(localID, out actor)) { |
389 | actor.Collide(collidingWith, type, collidePoint, collideNormal, 0.01f); | 414 | actor.Collide(collidingWith, type, collidePoint, collideNormal, penitration); |
390 | return; | 415 | return; |
391 | } | 416 | } |
392 | return; | 417 | return; |
@@ -448,7 +473,7 @@ public class BSScene : PhysicsScene | |||
448 | 473 | ||
449 | if (pbs.SculptEntry && !_meshSculptedPrim) | 474 | if (pbs.SculptEntry && !_meshSculptedPrim) |
450 | { | 475 | { |
451 | // m_log.DebugFormat("{0}: NeedsMeshing: scultpy mesh", LogHeader); | 476 | // Render sculpties as boxes |
452 | return false; | 477 | return false; |
453 | } | 478 | } |
454 | 479 | ||