aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region
diff options
context:
space:
mode:
authorTeravus Ovares2007-11-16 08:53:37 +0000
committerTeravus Ovares2007-11-16 08:53:37 +0000
commitb63076c303c380ac119cb608499b9ac54d524f05 (patch)
treea40395caef9d3b9bd0a24773e9b65bc10928fd3e /OpenSim/Region
parent* Fixed object edit movements causing full object updates instead of terse ob... (diff)
downloadopensim-SC-b63076c303c380ac119cb608499b9ac54d524f05.zip
opensim-SC-b63076c303c380ac119cb608499b9ac54d524f05.tar.gz
opensim-SC-b63076c303c380ac119cb608499b9ac54d524f05.tar.bz2
opensim-SC-b63076c303c380ac119cb608499b9ac54d524f05.tar.xz
* ODE step two on the way to separate dynamic space allocation ( One more to go )
Diffstat (limited to 'OpenSim/Region')
-rw-r--r--OpenSim/Region/Environment/Scenes/SceneObjectPart.cs54
-rw-r--r--OpenSim/Region/Physics/Manager/PhysicsActor.cs14
-rw-r--r--OpenSim/Region/Physics/OdePlugin/OdePlugin.cs164
3 files changed, 176 insertions, 56 deletions
diff --git a/OpenSim/Region/Environment/Scenes/SceneObjectPart.cs b/OpenSim/Region/Environment/Scenes/SceneObjectPart.cs
index 727ebb0..20c8517 100644
--- a/OpenSim/Region/Environment/Scenes/SceneObjectPart.cs
+++ b/OpenSim/Region/Environment/Scenes/SceneObjectPart.cs
@@ -806,6 +806,7 @@ namespace OpenSim.Region.Environment.Scenes
806 bool IsTemporary = false; 806 bool IsTemporary = false;
807 bool IsPhantom = false; 807 bool IsPhantom = false;
808 bool CastsShadows = false; 808 bool CastsShadows = false;
809 bool wasUsingPhysics = ((ObjectFlags & (uint)LLObject.ObjectFlags.Physics) != 0);
809 //bool IsLocked = false; 810 //bool IsLocked = false;
810 int i = 0; 811 int i = 0;
811 812
@@ -829,19 +830,24 @@ namespace OpenSim.Region.Environment.Scenes
829 if (UsePhysics ) 830 if (UsePhysics )
830 { 831 {
831 AddFlag(LLObject.ObjectFlags.Physics); 832 AddFlag(LLObject.ObjectFlags.Physics);
832 if (PhysActor != null) 833 if (!wasUsingPhysics)
833 PhysActor.OnRequestTerseUpdate += PhysicsRequestingTerseUpdate; 834 {
835 doPhysicsPropertyUpdate(UsePhysics);
836 }
834 837
835 } 838 }
836 else 839 else
837 { 840 {
838 if (m_parentGroup.m_scene.m_physicalPrim) 841 RemFlag(LLObject.ObjectFlags.Physics);
842 if (wasUsingPhysics)
839 { 843 {
840 RemFlag(LLObject.ObjectFlags.Physics); 844 doPhysicsPropertyUpdate(UsePhysics);
841 if (PhysActor != null)
842 PhysActor.OnRequestTerseUpdate -= PhysicsRequestingTerseUpdate;
843 } 845 }
844 } 846 }
847
848
849
850
845 851
846 if (IsPhantom) 852 if (IsPhantom)
847 { 853 {
@@ -884,6 +890,35 @@ namespace OpenSim.Region.Environment.Scenes
884// System.Console.WriteLine("Update: PHY:" + UsePhysics.ToString() + ", T:" + IsTemporary.ToString() + ", PHA:" + IsPhantom.ToString() + " S:" + CastsShadows.ToString()); 890// System.Console.WriteLine("Update: PHY:" + UsePhysics.ToString() + ", T:" + IsTemporary.ToString() + ", PHA:" + IsPhantom.ToString() + " S:" + CastsShadows.ToString());
885 ScheduleFullUpdate(); 891 ScheduleFullUpdate();
886 } 892 }
893 private void doPhysicsPropertyUpdate(bool UsePhysics)
894 {
895 if (PhysActor != null)
896 {
897 if (PhysActor.IsPhysical)
898 {
899 PhysActor.OnRequestTerseUpdate -= PhysicsRequestingTerseUpdate;
900 PhysActor.OnOutOfBounds -= PhysicsOutOfBounds;
901 }
902 m_parentGroup.m_scene.PhysScene.RemovePrim(PhysActor);
903 /// that's not wholesome. Had to make m_scene public
904 PhysActor = null;
905
906 PhysActor = m_parentGroup.m_scene.PhysScene.AddPrimShape(
907 Name,
908 Shape,
909 new PhysicsVector(AbsolutePosition.X, AbsolutePosition.Y,
910 AbsolutePosition.Z),
911 new PhysicsVector(Scale.X, Scale.Y, Scale.Z),
912 new Quaternion(RotationOffset.W, RotationOffset.X,
913 RotationOffset.Y, RotationOffset.Z), UsePhysics);
914 if (UsePhysics)
915 {
916 PhysActor.OnRequestTerseUpdate += PhysicsRequestingTerseUpdate;
917 PhysActor.OnOutOfBounds += PhysicsOutOfBounds;
918 }
919 }
920
921 }
887 922
888 public void UpdateExtraParam(ushort type, bool inUse, byte[] data) 923 public void UpdateExtraParam(ushort type, bool inUse, byte[] data)
889 { 924 {
@@ -1130,6 +1165,13 @@ namespace OpenSim.Region.Environment.Scenes
1130 } 1165 }
1131 #endregion 1166 #endregion
1132 1167
1168 public void PhysicsOutOfBounds(PhysicsVector pos)
1169 {
1170 OpenSim.Framework.Console.MainLog.Instance.Verbose("PHYSICS", "Physical Object went out of bounds.");
1171 doPhysicsPropertyUpdate(false);
1172 ScheduleFullUpdate();
1173 }
1174
1133 1175
1134 public virtual void OnGrab(LLVector3 offsetPos, IClientAPI remoteClient) 1176 public virtual void OnGrab(LLVector3 offsetPos, IClientAPI remoteClient)
1135 { 1177 {
diff --git a/OpenSim/Region/Physics/Manager/PhysicsActor.cs b/OpenSim/Region/Physics/Manager/PhysicsActor.cs
index 84b451f..cdb5ae2 100644
--- a/OpenSim/Region/Physics/Manager/PhysicsActor.cs
+++ b/OpenSim/Region/Physics/Manager/PhysicsActor.cs
@@ -99,6 +99,7 @@ namespace OpenSim.Region.Physics.Manager
99 { 99 {
100 public delegate void RequestTerseUpdate(); 100 public delegate void RequestTerseUpdate();
101 public delegate void CollisionUpdate(EventArgs e); 101 public delegate void CollisionUpdate(EventArgs e);
102 public delegate void OutOfBounds(PhysicsVector pos);
102 103
103#pragma warning disable 67 104#pragma warning disable 67
104 public event PositionUpdate OnPositionUpdate; 105 public event PositionUpdate OnPositionUpdate;
@@ -106,6 +107,7 @@ namespace OpenSim.Region.Physics.Manager
106 public event OrientationUpdate OnOrientationUpdate; 107 public event OrientationUpdate OnOrientationUpdate;
107 public event RequestTerseUpdate OnRequestTerseUpdate; 108 public event RequestTerseUpdate OnRequestTerseUpdate;
108 public event CollisionUpdate OnCollisionUpdate; 109 public event CollisionUpdate OnCollisionUpdate;
110 public event OutOfBounds OnOutOfBounds;
109#pragma warning restore 67 111#pragma warning restore 67
110 112
111 public static PhysicsActor Null 113 public static PhysicsActor Null
@@ -131,6 +133,18 @@ namespace OpenSim.Region.Physics.Manager
131 } 133 }
132 134
133 } 135 }
136 public virtual void RaiseOutOfBounds(PhysicsVector pos)
137 {
138 // Make a temporary copy of the event to avoid possibility of
139 // a race condition if the last subscriber unsubscribes
140 // immediately after the null check and before the event is raised.
141 OutOfBounds handler = OnOutOfBounds;
142 if (handler != null)
143 {
144 OnOutOfBounds(pos);
145 }
146
147 }
134 public virtual void SendCollisionUpdate(EventArgs e) 148 public virtual void SendCollisionUpdate(EventArgs e)
135 { 149 {
136 CollisionUpdate handler = OnCollisionUpdate; 150 CollisionUpdate handler = OnCollisionUpdate;
diff --git a/OpenSim/Region/Physics/OdePlugin/OdePlugin.cs b/OpenSim/Region/Physics/OdePlugin/OdePlugin.cs
index 948b2d9..bf9daa3 100644
--- a/OpenSim/Region/Physics/OdePlugin/OdePlugin.cs
+++ b/OpenSim/Region/Physics/OdePlugin/OdePlugin.cs
@@ -382,7 +382,25 @@ namespace OpenSim.Region.Physics.OdePlugin
382 p = (OdePrim) prim; 382 p = (OdePrim) prim;
383 p.disableBody(); 383 p.disableBody();
384 } 384 }
385 385 // we don't want to remove the main space
386 if (((OdePrim)prim).m_targetSpace != space && ((OdePrim)prim).IsPhysical == false)
387 {
388 // If the geometry is in the targetspace, remove it from the target space
389 if (d.SpaceQuery(((OdePrim)prim).m_targetSpace, ((OdePrim)prim).prim_geom))
390 {
391 d.SpaceRemove(space, ((OdePrim)prim).prim_geom);
392 }
393
394 //If there are no more geometries in the sub-space, we don't need it in the main space anymore
395 if (d.SpaceGetNumGeoms(((OdePrim)prim).m_targetSpace) == 0)
396 {
397 d.SpaceRemove(space, ((OdePrim)prim).m_targetSpace);
398 // free up memory used by the space.
399 d.SpaceDestroy(((OdePrim)prim).m_targetSpace);
400 resetSpaceArrayItemToZero(calculateSpaceArrayItemFromPos(((OdePrim)prim).Position));
401 }
402 }
403
386 d.GeomDestroy(((OdePrim)prim).prim_geom); 404 d.GeomDestroy(((OdePrim)prim).prim_geom);
387 405
388 _prims.Remove((OdePrim)prim); 406 _prims.Remove((OdePrim)prim);
@@ -390,20 +408,74 @@ namespace OpenSim.Region.Physics.OdePlugin
390 } 408 }
391 } 409 }
392 } 410 }
411 public void resetSpaceArrayItemToZero(IntPtr space)
412 {
413 for (int i = 0; i < staticPrimspace.Length; i++)
414 {
415 if (staticPrimspace[i] == space)
416 staticPrimspace[i] = IntPtr.Zero;
417 }
418 }
419 public void resetSpaceArrayItemToZero(int arrayitem)
420 {
421 staticPrimspace[arrayitem] = IntPtr.Zero;
422 }
393 423
394 public IntPtr recalculateSpaceForGeom(IntPtr geom, PhysicsVector pos) 424 public IntPtr recalculateSpaceForGeom(IntPtr geom, PhysicsVector pos, IntPtr currentspace)
395 { 425 {
396 //Todo recalculate space the prim is in. 426 //Todo recalculate space the prim is in.
427 // Called from setting the Position and Size of an ODEPrim so
428 // it's already in locked space.
397 429
430 // we don't want to remove the main space
431 // we don't need to test physical here because this function should
432 // never be called if the prim is physical(active)
433 if (currentspace != space)
434 {
435 if (d.SpaceQuery(currentspace, geom))
436 {
437 d.SpaceRemove(space, geom);
438 }
398 439
399 return space; 440 //If there are no more geometries in the sub-space, we don't need it in the main space anymore
441 if (d.SpaceGetNumGeoms(currentspace) == 0)
442 {
443 d.SpaceRemove(space, currentspace);
444 // free up memory used by the space.
445 d.SpaceDestroy(currentspace);
446 resetSpaceArrayItemToZero(currentspace);
447 }
448 }
449
450
451 // The routines in the Position and Size sections do the 'inserting' into the space,
452 // so all we have to do is make sure that the space that we're putting the prim into
453 // is in the 'main' space.
454 int iprimspaceArrItem = calculateSpaceArrayItemFromPos(pos);
455 IntPtr newspace = calculateSpaceForGeom(pos);
456
457 if (newspace == IntPtr.Zero)
458 newspace = createprimspace(iprimspaceArrItem);
459
460 return newspace;
461 }
462
463 public IntPtr createprimspace(int iprimspaceArrItem) {
464 // creating a new space for prim and inserting it into main space.
465 staticPrimspace[iprimspaceArrItem] = d.HashSpaceCreate(IntPtr.Zero);
466 d.SpaceAdd(space, staticPrimspace[iprimspaceArrItem]);
467 return staticPrimspace[iprimspaceArrItem];
400 } 468 }
401 469
402 public IntPtr calculateSpaceForNewGeom(PhysicsVector pos) 470 public IntPtr calculateSpaceForGeom(PhysicsVector pos)
403 { 471 {
404 472
405 return space; 473 return space;
406 } 474 }
475 public int calculateSpaceArrayItemFromPos(PhysicsVector pos)
476 {
477 return 0;
478 }
407 479
408 private PhysicsActor AddPrim(String name, PhysicsVector position, PhysicsVector size, Quaternion rotation, 480 private PhysicsActor AddPrim(String name, PhysicsVector position, PhysicsVector size, Quaternion rotation,
409 IMesh mesh, PrimitiveBaseShape pbs, bool isphysical) 481 IMesh mesh, PrimitiveBaseShape pbs, bool isphysical)
@@ -423,33 +495,36 @@ namespace OpenSim.Region.Physics.OdePlugin
423 rot.y = rotation.y; 495 rot.y = rotation.y;
424 rot.z = rotation.z; 496 rot.z = rotation.z;
425 497
426 IntPtr targetspace = calculateSpaceForNewGeom(pos); 498
499 int iprimspaceArrItem = calculateSpaceArrayItemFromPos(pos);
500 IntPtr targetspace = calculateSpaceForGeom(pos);
501
502 if (targetspace == IntPtr.Zero)
503 targetspace = createprimspace(iprimspaceArrItem);
504
427 OdePrim newPrim; 505 OdePrim newPrim;
428 lock (OdeLock) 506 lock (OdeLock)
429 { 507 {
430 newPrim = new OdePrim(name, this, targetspace, pos, siz, rot, mesh, pbs, isphysical); 508 newPrim = new OdePrim(name, this, targetspace, pos, siz, rot, mesh, pbs, isphysical);
431 } 509 }
432 _prims.Add(newPrim); 510 _prims.Add(newPrim);
511 OpenSim.Framework.Console.MainLog.Instance.Verbose("PHYSICS", "Added Object");
433 return newPrim; 512 return newPrim;
434 } 513 }
435 514
436 public void addActivePrim(OdePrim activatePrim) 515 public void addActivePrim(OdePrim activatePrim)
437 { 516 {
438 // adds active prim.. (ones that should be iterated over in collisions_optimized 517 // adds active prim.. (ones that should be iterated over in collisions_optimized
439 lock (OdeLock) 518
440 {
441 _activeprims.Add(activatePrim); 519 _activeprims.Add(activatePrim);
442 } 520
443 } 521 }
444 public void remActivePrim(OdePrim deactivatePrim) 522 public void remActivePrim(OdePrim deactivatePrim)
445 { 523 {
446 lock (OdeLock) 524
447 { 525 _activeprims.Remove(deactivatePrim);
448 lock (_activeprims) 526
449 { 527
450 _activeprims.Remove(deactivatePrim);
451 }
452 }
453 } 528 }
454 public int TriArrayCallback(IntPtr trimesh, IntPtr refObject, int[] triangleIndex, int triCount) 529 public int TriArrayCallback(IntPtr trimesh, IntPtr refObject, int[] triangleIndex, int triCount)
455 { 530 {
@@ -541,6 +616,7 @@ namespace OpenSim.Region.Physics.OdePlugin
541 616
542 public override void Simulate(float timeStep) 617 public override void Simulate(float timeStep)
543 { 618 {
619
544 step_time += timeStep; 620 step_time += timeStep;
545 lock (OdeLock) 621 lock (OdeLock)
546 { 622 {
@@ -548,21 +624,7 @@ namespace OpenSim.Region.Physics.OdePlugin
548 { 624 {
549 Console.WriteLine("RENDER: frame"); 625 Console.WriteLine("RENDER: frame");
550 } 626 }
551 foreach (OdePrim p in _prims) 627
552 {
553 if (_characters.Count > 0 && RENDER_FLAG)
554 {
555 Vector3 rx, ry, rz;
556 p.Orientation.ToAxes(out rx, out ry, out rz);
557 Console.WriteLine("RENDER: block; " + p.Size.X + ", " + p.Size.Y + ", " + p.Size.Z + "; " +
558 " 0, 0, 1; " + //shape, size, color
559 (p.Position.X - 128.0f) + ", " + (p.Position.Y - 128.0f) + ", " +
560 (p.Position.Z - 33.0f) + "; " + // position
561 rx.x + "," + ry.x + "," + rz.x + ", " + // rotation
562 rx.y + "," + ry.y + "," + rz.y + ", " +
563 rx.z + "," + ry.z + "," + rz.z);
564 }
565 }
566 628
567 629
568 // If We're loaded down by something else, 630 // If We're loaded down by something else,
@@ -570,6 +632,8 @@ namespace OpenSim.Region.Physics.OdePlugin
570 // skip a few frames to catch up gracefully. 632 // skip a few frames to catch up gracefully.
571 // without shooting the physicsactors all over the place 633 // without shooting the physicsactors all over the place
572 634
635
636
573 if (step_time >= m_SkipFramesAtms) 637 if (step_time >= m_SkipFramesAtms)
574 { 638 {
575 // Instead of trying to catch up, it'll do one physics frame only 639 // Instead of trying to catch up, it'll do one physics frame only
@@ -580,10 +644,12 @@ namespace OpenSim.Region.Physics.OdePlugin
580 { 644 {
581 m_physicsiterations = 10; 645 m_physicsiterations = 10;
582 } 646 }
647
583 // Process 10 frames if the sim is running normal.. 648 // Process 10 frames if the sim is running normal..
584 // process 5 frames if the sim is running slow 649 // process 5 frames if the sim is running slow
585 d.WorldSetQuickStepNumIterations(world, m_physicsiterations); 650 d.WorldSetQuickStepNumIterations(world, m_physicsiterations);
586 651
652
587 int i = 0; 653 int i = 0;
588 while (step_time > 0.0f) 654 while (step_time > 0.0f)
589 { 655 {
@@ -640,11 +706,13 @@ namespace OpenSim.Region.Physics.OdePlugin
640 } 706 }
641 if (timeStep < 0.2f) 707 if (timeStep < 0.2f)
642 { 708 {
709 OdePrim outofBoundsPrim = null;
643 foreach (OdePrim actor in _activeprims) 710 foreach (OdePrim actor in _activeprims)
644 { 711 {
645 if (actor.IsPhysical && (d.BodyIsEnabled(actor.Body) || !actor._zeroFlag)) 712 if (actor.IsPhysical && (d.BodyIsEnabled(actor.Body) || !actor._zeroFlag))
646 { 713 {
647 actor.UpdatePositionAndVelocity(); 714 actor.UpdatePositionAndVelocity();
715
648 } 716 }
649 } 717 }
650 } 718 }
@@ -716,6 +784,7 @@ namespace OpenSim.Region.Physics.OdePlugin
716 { 784 {
717 } 785 }
718 } 786 }
787 # region ODE Actors
719 788
720 public class OdeCharacter : PhysicsActor 789 public class OdeCharacter : PhysicsActor
721 { 790 {
@@ -1185,13 +1254,14 @@ namespace OpenSim.Region.Physics.OdePlugin
1185 private IMesh _mesh; 1254 private IMesh _mesh;
1186 private PrimitiveBaseShape _pbs; 1255 private PrimitiveBaseShape _pbs;
1187 private OdeScene _parent_scene; 1256 private OdeScene _parent_scene;
1188 private IntPtr m_targetSpace = (IntPtr)0; 1257 public IntPtr m_targetSpace = (IntPtr)0;
1189 public IntPtr prim_geom; 1258 public IntPtr prim_geom;
1190 public IntPtr _triMeshData; 1259 public IntPtr _triMeshData;
1191 private bool iscolliding = false; 1260 private bool iscolliding = false;
1192 private bool m_isphysical = false; 1261 private bool m_isphysical = false;
1193 private bool m_throttleUpdates = false; 1262 private bool m_throttleUpdates = false;
1194 private int throttleCounter = 0; 1263 private int throttleCounter = 0;
1264 public bool outofBounds = false;
1195 1265
1196 public bool _zeroFlag = false; 1266 public bool _zeroFlag = false;
1197 private bool m_lastUpdateSent = false; 1267 private bool m_lastUpdateSent = false;
@@ -1402,7 +1472,9 @@ namespace OpenSim.Region.Physics.OdePlugin
1402 1472
1403 public override PhysicsVector Position 1473 public override PhysicsVector Position
1404 { 1474 {
1405 get { return _position;} 1475 get {return _position; }
1476
1477
1406 set 1478 set
1407 { 1479 {
1408 _position = value; 1480 _position = value;
@@ -1421,9 +1493,9 @@ namespace OpenSim.Region.Physics.OdePlugin
1421 else 1493 else
1422 { 1494 {
1423 d.GeomSetPosition(prim_geom, _position.X, _position.Y, _position.Z); 1495 d.GeomSetPosition(prim_geom, _position.X, _position.Y, _position.Z);
1424 1496 m_targetSpace = _parent_scene.recalculateSpaceForGeom(prim_geom, _position, m_targetSpace);
1425 } 1497 }
1426 m_targetSpace = _parent_scene.recalculateSpaceForGeom(prim_geom, _position); 1498
1427 } 1499 }
1428 } 1500 }
1429 } 1501 }
@@ -1449,6 +1521,9 @@ namespace OpenSim.Region.Physics.OdePlugin
1449 disableBody(); 1521 disableBody();
1450 } 1522 }
1451 d.GeomDestroy(prim_geom); 1523 d.GeomDestroy(prim_geom);
1524
1525 // Recalculate which space this geometry should be in.
1526 m_targetSpace = _parent_scene.recalculateSpaceForGeom(prim_geom, _position, m_targetSpace);
1452 // Construction of new prim 1527 // Construction of new prim
1453 if (this._parent_scene.needsMeshing(_pbs)) 1528 if (this._parent_scene.needsMeshing(_pbs))
1454 { 1529 {
@@ -1478,7 +1553,7 @@ namespace OpenSim.Region.Physics.OdePlugin
1478 1553
1479 } 1554 }
1480 1555
1481 m_targetSpace = _parent_scene.recalculateSpaceForGeom(prim_geom, _position); 1556
1482 _parent_scene.geom_name_map[prim_geom] = oldname; 1557 _parent_scene.geom_name_map[prim_geom] = oldname;
1483 1558
1484 } 1559 }
@@ -1639,20 +1714,7 @@ namespace OpenSim.Region.Physics.OdePlugin
1639 // Disables the prim's movement physics.... 1714 // Disables the prim's movement physics....
1640 // It's a hack and will generate a console message if it fails. 1715 // It's a hack and will generate a console message if it fails.
1641 1716
1642 try 1717
1643 {
1644 disableBody();
1645
1646 }
1647 catch (System.Exception e)
1648 {
1649 if (Body != (IntPtr)0)
1650 {
1651 d.BodyDestroy(Body);
1652 Body = (IntPtr)0;
1653
1654 }
1655 }
1656 1718
1657 1719
1658 IsPhysical = false; 1720 IsPhysical = false;
@@ -1662,10 +1724,11 @@ namespace OpenSim.Region.Physics.OdePlugin
1662 m_rotationalVelocity.X = 0; 1724 m_rotationalVelocity.X = 0;
1663 m_rotationalVelocity.Y = 0; 1725 m_rotationalVelocity.Y = 0;
1664 m_rotationalVelocity.Z = 0; 1726 m_rotationalVelocity.Z = 0;
1665 base.RequestPhysicsterseUpdate(); 1727 //base.RequestPhysicsterseUpdate();
1666 m_throttleUpdates = false; 1728 m_throttleUpdates = false;
1667 throttleCounter = 0; 1729 throttleCounter = 0;
1668 _zeroFlag = true; 1730 _zeroFlag = true;
1731 outofBounds = true;
1669 } 1732 }
1670 1733
1671 if ((Math.Abs(m_lastposition.X - l_position.X) < 0.02) 1734 if ((Math.Abs(m_lastposition.X - l_position.X) < 0.02)
@@ -1755,3 +1818,4 @@ namespace OpenSim.Region.Physics.OdePlugin
1755 } 1818 }
1756 } 1819 }
1757} 1820}
1821 #endregion \ No newline at end of file