aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/Physics
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/Physics')
-rwxr-xr-xOpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs103
-rwxr-xr-xOpenSim/Region/Physics/BulletSPlugin/BSParam.cs15
-rwxr-xr-xOpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs22
-rw-r--r--OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs5
-rwxr-xr-xOpenSim/Region/Physics/BulletSPlugin/BulletSimTODO.txt3
5 files changed, 123 insertions, 25 deletions
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs b/OpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs
index 8416740..bd5ee0b1 100755
--- a/OpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs
@@ -40,10 +40,16 @@ public class BSActorAvatarMove : BSActor
40{ 40{
41 BSVMotor m_velocityMotor; 41 BSVMotor m_velocityMotor;
42 42
43 // Set to true if we think we're going up stairs.
44 // This state is remembered because collisions will turn on and off as we go up stairs.
45 int m_walkingUpStairs;
46 float m_lastStepUp;
47
43 public BSActorAvatarMove(BSScene physicsScene, BSPhysObject pObj, string actorName) 48 public BSActorAvatarMove(BSScene physicsScene, BSPhysObject pObj, string actorName)
44 : base(physicsScene, pObj, actorName) 49 : base(physicsScene, pObj, actorName)
45 { 50 {
46 m_velocityMotor = null; 51 m_velocityMotor = null;
52 m_walkingUpStairs = 0;
47 m_physicsScene.DetailLog("{0},BSActorAvatarMove,constructor", m_controllingPrim.LocalID); 53 m_physicsScene.DetailLog("{0},BSActorAvatarMove,constructor", m_controllingPrim.LocalID);
48 } 54 }
49 55
@@ -119,6 +125,8 @@ public class BSActorAvatarMove : BSActor
119 SetVelocityAndTarget(m_controllingPrim.RawVelocity, m_controllingPrim.TargetVelocity, true /* inTaintTime */); 125 SetVelocityAndTarget(m_controllingPrim.RawVelocity, m_controllingPrim.TargetVelocity, true /* inTaintTime */);
120 126
121 m_physicsScene.BeforeStep += Mover; 127 m_physicsScene.BeforeStep += Mover;
128
129 m_walkingUpStairs = 0;
122 } 130 }
123 } 131 }
124 132
@@ -216,8 +224,6 @@ public class BSActorAvatarMove : BSActor
216 // 'stepVelocity' is now the speed we'd like the avatar to move in. Turn that into an instantanous force. 224 // 'stepVelocity' is now the speed we'd like the avatar to move in. Turn that into an instantanous force.
217 OMV.Vector3 moveForce = (stepVelocity - m_controllingPrim.RawVelocity) * m_controllingPrim.Mass; 225 OMV.Vector3 moveForce = (stepVelocity - m_controllingPrim.RawVelocity) * m_controllingPrim.Mass;
218 226
219 // Should we check for move force being small and forcing velocity to zero?
220
221 // Add special movement force to allow avatars to walk up stepped surfaces. 227 // Add special movement force to allow avatars to walk up stepped surfaces.
222 moveForce += WalkUpStairs(); 228 moveForce += WalkUpStairs();
223 229
@@ -233,24 +239,33 @@ public class BSActorAvatarMove : BSActor
233 { 239 {
234 OMV.Vector3 ret = OMV.Vector3.Zero; 240 OMV.Vector3 ret = OMV.Vector3.Zero;
235 241
242 m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs,IsColliding={1},flying={2},targSpeed={3},collisions={4},avHeight={5}",
243 m_controllingPrim.LocalID, m_controllingPrim.IsColliding, m_controllingPrim.Flying,
244 m_controllingPrim.TargetVelocitySpeed, m_controllingPrim.CollisionsLastTick.Count, m_controllingPrim.Size.Z);
236 // This test is done if moving forward, not flying and is colliding with something. 245 // This test is done if moving forward, not flying and is colliding with something.
237 // DetailLog("{0},BSCharacter.WalkUpStairs,IsColliding={1},flying={2},targSpeed={3},collisions={4}", 246 // Check for stairs climbing if colliding, not flying and moving forward
238 // LocalID, IsColliding, Flying, TargetSpeed, CollisionsLastTick.Count); 247 if ( m_controllingPrim.IsColliding
239 if (m_controllingPrim.IsColliding && !m_controllingPrim.Flying && m_controllingPrim.TargetVelocitySpeed > 0.1f /* && ForwardSpeed < 0.1f */) 248 && !m_controllingPrim.Flying
249 && m_controllingPrim.TargetVelocitySpeed > 0.1f )
240 { 250 {
241 // The range near the character's feet where we will consider stairs 251 // The range near the character's feet where we will consider stairs
242 float nearFeetHeightMin = m_controllingPrim.RawPosition.Z - (m_controllingPrim.Size.Z / 2f) + 0.05f; 252 // float nearFeetHeightMin = m_controllingPrim.RawPosition.Z - (m_controllingPrim.Size.Z / 2f) + 0.05f;
253 // Note: there is a problem with the computation of the capsule height. Thus RawPosition is off
254 // from the height. Revisit size and this computation when height is scaled properly.
255 float nearFeetHeightMin = m_controllingPrim.RawPosition.Z - (m_controllingPrim.Size.Z / 2f) - 0.05f;
243 float nearFeetHeightMax = nearFeetHeightMin + BSParam.AvatarStepHeight; 256 float nearFeetHeightMax = nearFeetHeightMin + BSParam.AvatarStepHeight;
244 257
245 // Look for a collision point that is near the character's feet and is oriented the same as the charactor is 258 // Look for a collision point that is near the character's feet and is oriented the same as the charactor is.
259 // Find the highest 'good' collision.
260 OMV.Vector3 highestTouchPosition = OMV.Vector3.Zero;
246 foreach (KeyValuePair<uint, ContactPoint> kvp in m_controllingPrim.CollisionsLastTick.m_objCollisionList) 261 foreach (KeyValuePair<uint, ContactPoint> kvp in m_controllingPrim.CollisionsLastTick.m_objCollisionList)
247 { 262 {
248 // Don't care about collisions with the terrain 263 // Don't care about collisions with the terrain
249 if (kvp.Key > m_physicsScene.TerrainManager.HighestTerrainID) 264 if (kvp.Key > m_physicsScene.TerrainManager.HighestTerrainID)
250 { 265 {
251 OMV.Vector3 touchPosition = kvp.Value.Position; 266 OMV.Vector3 touchPosition = kvp.Value.Position;
252 // DetailLog("{0},BSCharacter.WalkUpStairs,min={1},max={2},touch={3}", 267 m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs,min={1},max={2},touch={3}",
253 // LocalID, nearFeetHeightMin, nearFeetHeightMax, touchPosition); 268 m_controllingPrim.LocalID, nearFeetHeightMin, nearFeetHeightMax, touchPosition);
254 if (touchPosition.Z >= nearFeetHeightMin && touchPosition.Z <= nearFeetHeightMax) 269 if (touchPosition.Z >= nearFeetHeightMin && touchPosition.Z <= nearFeetHeightMax)
255 { 270 {
256 // This contact is within the 'near the feet' range. 271 // This contact is within the 'near the feet' range.
@@ -261,24 +276,76 @@ public class BSActorAvatarMove : BSActor
261 float diff = Math.Abs(OMV.Vector3.Distance(directionFacing, touchNormal)); 276 float diff = Math.Abs(OMV.Vector3.Distance(directionFacing, touchNormal));
262 if (diff < BSParam.AvatarStepApproachFactor) 277 if (diff < BSParam.AvatarStepApproachFactor)
263 { 278 {
264 // Found the stairs contact point. Push up a little to raise the character. 279 if (highestTouchPosition.Z < touchPosition.Z)
265 float upForce = (touchPosition.Z - nearFeetHeightMin) * m_controllingPrim.Mass * BSParam.AvatarStepForceFactor; 280 highestTouchPosition = touchPosition;
266 ret = new OMV.Vector3(0f, 0f, upForce);
267
268 // Also move the avatar up for the new height
269 OMV.Vector3 displacement = new OMV.Vector3(0f, 0f, BSParam.AvatarStepHeight / 2f);
270 m_controllingPrim.ForcePosition = m_controllingPrim.RawPosition + displacement;
271 } 281 }
272 m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs,touchPos={1},nearFeetMin={2},faceDir={3},norm={4},diff={5},ret={6}",
273 m_controllingPrim.LocalID, touchPosition, nearFeetHeightMin, directionFacing, touchNormal, diff, ret);
274 } 282 }
275 } 283 }
276 } 284 }
285 m_walkingUpStairs = 0;
286 // If there is a good step sensing, move the avatar over the step.
287 if (highestTouchPosition != OMV.Vector3.Zero)
288 {
289 // Remember that we are going up stairs. This is needed because collisions
290 // will stop when we move up so this smoothes out that effect.
291 m_walkingUpStairs = BSParam.AvatarStepSmoothingSteps;
292
293 m_lastStepUp = highestTouchPosition.Z - nearFeetHeightMin;
294 ret = ComputeStairCorrection(m_lastStepUp);
295 m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs,touchPos={1},nearFeetMin={2},ret={3}",
296 m_controllingPrim.LocalID, highestTouchPosition, nearFeetHeightMin, ret);
297 }
298 }
299 else
300 {
301 // If we used to be going up stairs but are not now, smooth the case where collision goes away while
302 // we are bouncing up the stairs.
303 if (m_walkingUpStairs > 0)
304 {
305 m_walkingUpStairs--;
306 ret = ComputeStairCorrection(m_lastStepUp);
307 }
277 } 308 }
278 309
279 return ret; 310 return ret;
280 } 311 }
281 312
313 private OMV.Vector3 ComputeStairCorrection(float stepUp)
314 {
315 OMV.Vector3 ret = OMV.Vector3.Zero;
316 OMV.Vector3 displacement = OMV.Vector3.Zero;
317
318 if (stepUp > 0f)
319 {
320 // Found the stairs contact point. Push up a little to raise the character.
321 if (BSParam.AvatarStepForceFactor > 0f)
322 {
323 float upForce = stepUp * m_controllingPrim.Mass * BSParam.AvatarStepForceFactor;
324 ret = new OMV.Vector3(0f, 0f, upForce);
325 }
326
327 // Also move the avatar up for the new height
328 if (BSParam.AvatarStepUpCorrectionFactor > 0f)
329 {
330 // Move the avatar up related to the height of the collision
331 displacement = new OMV.Vector3(0f, 0f, stepUp * BSParam.AvatarStepUpCorrectionFactor);
332 m_controllingPrim.ForcePosition = m_controllingPrim.RawPosition + displacement;
333 }
334 else
335 {
336 if (BSParam.AvatarStepUpCorrectionFactor < 0f)
337 {
338 // Move the avatar up about the specified step height
339 displacement = new OMV.Vector3(0f, 0f, BSParam.AvatarStepHeight);
340 m_controllingPrim.ForcePosition = m_controllingPrim.RawPosition + displacement;
341 }
342 }
343 m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs.ComputeStairCorrection,disp={1},force={2}",
344 m_controllingPrim.LocalID, displacement, ret);
345
346 }
347 return ret;
348 }
282} 349}
283} 350}
284 351
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs
index 06df85e..980d405 100755
--- a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs
@@ -128,6 +128,8 @@ public static class BSParam
128 public static float AvatarStepHeight { get; private set; } 128 public static float AvatarStepHeight { get; private set; }
129 public static float AvatarStepApproachFactor { get; private set; } 129 public static float AvatarStepApproachFactor { get; private set; }
130 public static float AvatarStepForceFactor { get; private set; } 130 public static float AvatarStepForceFactor { get; private set; }
131 public static float AvatarStepUpCorrectionFactor { get; private set; }
132 public static int AvatarStepSmoothingSteps { get; private set; }
131 133
132 // Vehicle parameters 134 // Vehicle parameters
133 public static float VehicleMaxLinearVelocity { get; private set; } 135 public static float VehicleMaxLinearVelocity { get; private set; }
@@ -234,6 +236,7 @@ public static class BSParam
234 objectSet = pObjSetter; 236 objectSet = pObjSetter;
235 } 237 }
236 /* Wish I could simplify using this definition but CLR doesn't store references so closure around delegates of references won't work 238 /* Wish I could simplify using this definition but CLR doesn't store references so closure around delegates of references won't work
239 * TODO: Maybe use reflection and the name of the variable to create a reference for the getter/setter.
237 public ParameterDefn(string pName, string pDesc, T pDefault, ref T loc) 240 public ParameterDefn(string pName, string pDesc, T pDefault, ref T loc)
238 : base(pName, pDesc) 241 : base(pName, pDesc)
239 { 242 {
@@ -561,7 +564,7 @@ public static class BSParam
561 (s) => { return AvatarBelowGroundUpCorrectionMeters; }, 564 (s) => { return AvatarBelowGroundUpCorrectionMeters; },
562 (s,v) => { AvatarBelowGroundUpCorrectionMeters = v; } ), 565 (s,v) => { AvatarBelowGroundUpCorrectionMeters = v; } ),
563 new ParameterDefn<float>("AvatarStepHeight", "Height of a step obstacle to consider step correction", 566 new ParameterDefn<float>("AvatarStepHeight", "Height of a step obstacle to consider step correction",
564 0.3f, 567 0.6f,
565 (s) => { return AvatarStepHeight; }, 568 (s) => { return AvatarStepHeight; },
566 (s,v) => { AvatarStepHeight = v; } ), 569 (s,v) => { AvatarStepHeight = v; } ),
567 new ParameterDefn<float>("AvatarStepApproachFactor", "Factor to control angle of approach to step (0=straight on)", 570 new ParameterDefn<float>("AvatarStepApproachFactor", "Factor to control angle of approach to step (0=straight on)",
@@ -569,9 +572,17 @@ public static class BSParam
569 (s) => { return AvatarStepApproachFactor; }, 572 (s) => { return AvatarStepApproachFactor; },
570 (s,v) => { AvatarStepApproachFactor = v; } ), 573 (s,v) => { AvatarStepApproachFactor = v; } ),
571 new ParameterDefn<float>("AvatarStepForceFactor", "Controls the amount of force up applied to step up onto a step", 574 new ParameterDefn<float>("AvatarStepForceFactor", "Controls the amount of force up applied to step up onto a step",
572 2.0f, 575 1.0f,
573 (s) => { return AvatarStepForceFactor; }, 576 (s) => { return AvatarStepForceFactor; },
574 (s,v) => { AvatarStepForceFactor = v; } ), 577 (s,v) => { AvatarStepForceFactor = v; } ),
578 new ParameterDefn<float>("AvatarStepUpCorrectionFactor", "Multiplied by height of step collision to create up movement at step",
579 1.0f,
580 (s) => { return AvatarStepUpCorrectionFactor; },
581 (s,v) => { AvatarStepUpCorrectionFactor = v; } ),
582 new ParameterDefn<int>("AvatarStepSmoothingSteps", "Number of frames after a step collision that we continue walking up stairs",
583 2,
584 (s) => { return AvatarStepSmoothingSteps; },
585 (s,v) => { AvatarStepSmoothingSteps = v; } ),
575 586
576 new ParameterDefn<float>("VehicleMaxLinearVelocity", "Maximum velocity magnitude that can be assigned to a vehicle", 587 new ParameterDefn<float>("VehicleMaxLinearVelocity", "Maximum velocity magnitude that can be assigned to a vehicle",
577 1000.0f, 588 1000.0f,
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs
index 98eb4ca..309d004 100755
--- a/OpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs
@@ -96,7 +96,7 @@ public abstract class BSPhysObject : PhysicsActor
96 SetMaterial((int)MaterialAttributes.Material.Wood); 96 SetMaterial((int)MaterialAttributes.Material.Wood);
97 97
98 CollisionCollection = new CollisionEventUpdate(); 98 CollisionCollection = new CollisionEventUpdate();
99 CollisionsLastTick = CollisionCollection; 99 CollisionsLastReported = CollisionCollection;
100 SubscribedEventsMs = 0; 100 SubscribedEventsMs = 0;
101 CollidingStep = 0; 101 CollidingStep = 0;
102 CollidingGroundStep = 0; 102 CollidingGroundStep = 0;
@@ -368,11 +368,14 @@ public abstract class BSPhysObject : PhysicsActor
368 } 368 }
369 } 369 }
370 370
371 // The collisions that have been collected this tick 371 // The collisions that have been collected for the next collision reporting (throttled by subscription)
372 protected CollisionEventUpdate CollisionCollection; 372 protected CollisionEventUpdate CollisionCollection;
373 // Remember collisions from last tick for fancy collision based actions 373 // This is the collision collection last reported to the Simulator.
374 public CollisionEventUpdate CollisionsLastReported;
375 // Remember the collisions recorded in the last tick for fancy collision checking
374 // (like a BSCharacter walking up stairs). 376 // (like a BSCharacter walking up stairs).
375 public CollisionEventUpdate CollisionsLastTick; 377 public CollisionEventUpdate CollisionsLastTick;
378 private long CollisionsLastTickStep = -1;
376 379
377 // The simulation step is telling this object about a collision. 380 // The simulation step is telling this object about a collision.
378 // Return 'true' if a collision was processed and should be sent up. 381 // Return 'true' if a collision was processed and should be sent up.
@@ -399,6 +402,15 @@ public abstract class BSPhysObject : PhysicsActor
399 // For movement tests, remember if we are colliding with an object that is moving. 402 // For movement tests, remember if we are colliding with an object that is moving.
400 ColliderIsMoving = collidee != null ? (collidee.RawVelocity != OMV.Vector3.Zero) : false; 403 ColliderIsMoving = collidee != null ? (collidee.RawVelocity != OMV.Vector3.Zero) : false;
401 404
405 // Make a collection of the collisions that happened the last simulation tick.
406 // This is different than the collection created for sending up to the simulator as it is cleared every tick.
407 if (CollisionsLastTickStep != PhysicsScene.SimulationStep)
408 {
409 CollisionsLastTick = new CollisionEventUpdate();
410 CollisionsLastTickStep = PhysicsScene.SimulationStep;
411 }
412 CollisionsLastTick.AddCollider(collidingWith, new ContactPoint(contactPoint, contactNormal, pentrationDepth));
413
402 // If someone has subscribed for collision events log the collision so it will be reported up 414 // If someone has subscribed for collision events log the collision so it will be reported up
403 if (SubscribedEvents()) { 415 if (SubscribedEvents()) {
404 CollisionCollection.AddCollider(collidingWith, new ContactPoint(contactPoint, contactNormal, pentrationDepth)); 416 CollisionCollection.AddCollider(collidingWith, new ContactPoint(contactPoint, contactNormal, pentrationDepth));
@@ -419,7 +431,7 @@ public abstract class BSPhysObject : PhysicsActor
419 bool ret = true; 431 bool ret = true;
420 432
421 // If the 'no collision' call, force it to happen right now so quick collision_end 433 // If the 'no collision' call, force it to happen right now so quick collision_end
422 bool force = (CollisionCollection.Count == 0 && CollisionsLastTick.Count != 0); 434 bool force = (CollisionCollection.Count == 0 && CollisionsLastReported.Count != 0);
423 435
424 // throttle the collisions to the number of milliseconds specified in the subscription 436 // throttle the collisions to the number of milliseconds specified in the subscription
425 if (force || (PhysicsScene.SimulationNowTime >= NextCollisionOkTime)) 437 if (force || (PhysicsScene.SimulationNowTime >= NextCollisionOkTime))
@@ -438,7 +450,7 @@ public abstract class BSPhysObject : PhysicsActor
438 base.SendCollisionUpdate(CollisionCollection); 450 base.SendCollisionUpdate(CollisionCollection);
439 451
440 // Remember the collisions from this tick for some collision specific processing. 452 // Remember the collisions from this tick for some collision specific processing.
441 CollisionsLastTick = CollisionCollection; 453 CollisionsLastReported = CollisionCollection;
442 454
443 // The CollisionCollection instance is passed around in the simulator. 455 // The CollisionCollection instance is passed around in the simulator.
444 // Make sure we don't have a handle to that one and that a new one is used for next time. 456 // Make sure we don't have a handle to that one and that a new one is used for next time.
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs
index 3423d2e..4bc266b 100644
--- a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs
+++ b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs
@@ -69,12 +69,17 @@ public class BSPrim : BSPhysObject
69 69
70 private int CrossingFailures { get; set; } 70 private int CrossingFailures { get; set; }
71 71
72 // Keep a handle to the vehicle actor so it is easy to set parameters on same.
72 public BSDynamics VehicleActor; 73 public BSDynamics VehicleActor;
73 public const string VehicleActorName = "BasicVehicle"; 74 public const string VehicleActorName = "BasicVehicle";
74 75
76 // Parameters for the hover actor
75 public const string HoverActorName = "HoverActor"; 77 public const string HoverActorName = "HoverActor";
78 // Parameters for the axis lock actor
76 public const String LockedAxisActorName = "BSPrim.LockedAxis"; 79 public const String LockedAxisActorName = "BSPrim.LockedAxis";
80 // Parameters for the move to target actor
77 public const string MoveToTargetActorName = "MoveToTargetActor"; 81 public const string MoveToTargetActorName = "MoveToTargetActor";
82 // Parameters for the setForce and setTorque actors
78 public const string SetForceActorName = "SetForceActor"; 83 public const string SetForceActorName = "SetForceActor";
79 public const string SetTorqueActorName = "SetTorqueActor"; 84 public const string SetTorqueActorName = "SetTorqueActor";
80 85
diff --git a/OpenSim/Region/Physics/BulletSPlugin/BulletSimTODO.txt b/OpenSim/Region/Physics/BulletSPlugin/BulletSimTODO.txt
index a0131c7..1284ae7 100755
--- a/OpenSim/Region/Physics/BulletSPlugin/BulletSimTODO.txt
+++ b/OpenSim/Region/Physics/BulletSPlugin/BulletSimTODO.txt
@@ -42,6 +42,8 @@ One sided meshes? Should terrain be built into a closed shape?
42 42
43VEHICLES TODO LIST: 43VEHICLES TODO LIST:
44================================================= 44=================================================
45UBit improvements to remove rubber-banding of avatars sitting on vehicle child prims:
46 https://github.com/UbitUmarov/Ubit-opensim
45Border crossing with linked vehicle causes crash 47Border crossing with linked vehicle causes crash
46 20121129.1411: editting/moving phys object across region boundries causes crash 48 20121129.1411: editting/moving phys object across region boundries causes crash
47 getPos-> btRigidBody::upcast -> getBodyType -> BOOM 49 getPos-> btRigidBody::upcast -> getBodyType -> BOOM
@@ -167,6 +169,7 @@ Eliminate collisions between objects in a linkset. (LinksetConstraint)
167 169
168MORE 170MORE
169====================================================== 171======================================================
172Compute avatar size and scale correctly. Now it is a bit off from the capsule size.
170Create tests for different interface components 173Create tests for different interface components
171 Have test objects/scripts measure themselves and turn color if correct/bad 174 Have test objects/scripts measure themselves and turn color if correct/bad
172 Test functions in SL and calibrate correctness there 175 Test functions in SL and calibrate correctness there