From eb0687f5af127ad6195b95965ce31346f2bc0a24 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Wed, 8 May 2013 06:02:12 -0700 Subject: [PATCH] vh: update BulletSim (OpenSim/Region/Physics/BulletSPlugin and DLL/SO) to ac6dcd35fb77f118fc6c3d72cb029591306c7e99 (Mon May 6 21:10:02 2013 -0400) on top of 0.7.5-postfixes. --- .../Physics/BulletSPlugin/BSAPIUnman.cs | 207 ++- .../Region/Physics/BulletSPlugin/BSAPIXNA.cs | 1466 ++++++++++++----- .../BulletSPlugin/BSActorAvatarMove.cs | 351 ++++ .../Physics/BulletSPlugin/BSActorHover.cs | 173 ++ .../Physics/BulletSPlugin/BSActorLockAxis.cs | 187 +++ .../BulletSPlugin/BSActorMoveToTarget.cs | 157 ++ .../Physics/BulletSPlugin/BSActorSetForce.cs | 137 ++ .../Physics/BulletSPlugin/BSActorSetTorque.cs | 138 ++ .../Region/Physics/BulletSPlugin/BSActors.cs | 160 ++ .../Physics/BulletSPlugin/BSApiTemplate.cs | 167 +- .../Physics/BulletSPlugin/BSCharacter.cs | 383 ++--- .../Physics/BulletSPlugin/BSConstraint.cs | 2 + .../Physics/BulletSPlugin/BSConstraint6Dof.cs | 16 + .../BulletSPlugin/BSConstraintCollection.cs | 13 +- .../BulletSPlugin/BSConstraintHinge.cs | 2 +- .../Physics/BulletSPlugin/BSDynamics.cs | 842 ++++++---- .../Region/Physics/BulletSPlugin/BSLinkset.cs | 77 +- .../BulletSPlugin/BSLinksetCompound.cs | 390 +++-- .../BulletSPlugin/BSLinksetConstraints.cs | 60 +- .../Physics/BulletSPlugin/BSMaterials.cs | 3 + .../Region/Physics/BulletSPlugin/BSMotors.cs | 234 ++- .../Region/Physics/BulletSPlugin/BSParam.cs | 911 ++++++---- .../Physics/BulletSPlugin/BSPhysObject.cs | 357 ++-- .../Region/Physics/BulletSPlugin/BSPlugin.cs | 2 +- .../Region/Physics/BulletSPlugin/BSPrim.cs | 903 +++++----- .../Physics/BulletSPlugin/BSPrimDisplaced.cs | 165 ++ .../Physics/BulletSPlugin/BSPrimLinkable.cs | 192 +++ .../Region/Physics/BulletSPlugin/BSScene.cs | 163 +- .../BulletSPlugin/BSShapeCollection.cs | 943 ++--------- .../Region/Physics/BulletSPlugin/BSShapes.cs | 1035 ++++++++++-- .../BulletSPlugin/BSTerrainHeightmap.cs | 30 +- .../Physics/BulletSPlugin/BSTerrainManager.cs | 212 ++- .../Physics/BulletSPlugin/BSTerrainMesh.cs | 258 ++- .../Physics/BulletSPlugin/BulletSimData.cs | 54 +- .../Physics/BulletSPlugin/BulletSimTODO.txt | 184 ++- .../BulletSPlugin/Properties/AssemblyInfo.cs | 4 +- bin/lib32/BulletSim.dll | Bin 551936 -> 1010176 bytes bin/lib32/libBulletSim.dylib | Bin 0 -> 1181656 bytes bin/lib32/libBulletSim.so | Bin 1719480 -> 2096576 bytes bin/lib64/BulletSim.dll | Bin 700928 -> 1142272 bytes bin/lib64/libBulletSim.so | Bin 1857745 -> 2265980 bytes 41 files changed, 7244 insertions(+), 3334 deletions(-) create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSActorHover.cs create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSActorLockAxis.cs create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSActorMoveToTarget.cs create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSActorSetForce.cs create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSActorSetTorque.cs create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSActors.cs create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSPrimDisplaced.cs create mode 100755 OpenSim/Region/Physics/BulletSPlugin/BSPrimLinkable.cs create mode 100755 bin/lib32/libBulletSim.dylib diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSAPIUnman.cs b/OpenSim/Region/Physics/BulletSPlugin/BSAPIUnman.cs index 8c6e7d6bd5..231f0f8197 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSAPIUnman.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSAPIUnman.cs @@ -75,11 +75,11 @@ private sealed class BulletBodyUnman : BulletBody private sealed class BulletShapeUnman : BulletShape { public IntPtr ptr; - public BulletShapeUnman(IntPtr xx, BSPhysicsShapeType typ) + public BulletShapeUnman(IntPtr xx, BSPhysicsShapeType typ) : base() { ptr = xx; - type = typ; + shapeType = typ; } public override bool HasPhysicalShape { @@ -91,7 +91,7 @@ private sealed class BulletShapeUnman : BulletShape } public override BulletShape Clone() { - return new BulletShapeUnman(ptr, type); + return new BulletShapeUnman(ptr, shapeType); } public override bool ReferenceSame(BulletShape other) { @@ -166,7 +166,7 @@ public override BulletWorld Initialize(Vector3 maxPosition, ConfigurationParamet // If Debug logging level, enable logging from the unmanaged code m_DebugLogCallbackHandle = null; - if (BSScene.m_log.IsDebugEnabled || PhysicsScene.PhysicsLogging.Enabled) + if (BSScene.m_log.IsDebugEnabled && PhysicsScene.PhysicsLogging.Enabled) { BSScene.m_log.DebugFormat("{0}: Initialize: Setting debug callback for unmanaged code", BSScene.LogHeader); if (PhysicsScene.PhysicsLogging.Enabled) @@ -202,7 +202,7 @@ private void BulletLoggerPhysLog(string msg) } public override int PhysicsStep(BulletWorld world, float timeStep, int maxSubSteps, float fixedTimeStep, - out int updatedEntityCount, out int collidersCount) + out int updatedEntityCount, out int collidersCount) { BulletWorldUnman worldu = world as BulletWorldUnman; return BSAPICPP.PhysicsStep2(worldu.ptr, timeStep, maxSubSteps, fixedTimeStep, out updatedEntityCount, out collidersCount); @@ -212,6 +212,19 @@ public override void Shutdown(BulletWorld world) { BulletWorldUnman worldu = world as BulletWorldUnman; BSAPICPP.Shutdown2(worldu.ptr); + + if (m_paramsHandle.IsAllocated) + { + m_paramsHandle.Free(); + } + if (m_collisionArrayPinnedHandle.IsAllocated) + { + m_collisionArrayPinnedHandle.Free(); + } + if (m_updateArrayPinnedHandle.IsAllocated) + { + m_updateArrayPinnedHandle.Free(); + } } public override bool PushUpdate(BulletBody obj) @@ -242,19 +255,38 @@ public override BulletShape CreateHullShape(BulletWorld world, int hullCount, fl { BulletWorldUnman worldu = world as BulletWorldUnman; return new BulletShapeUnman( - BSAPICPP.CreateHullShape2(worldu.ptr, hullCount, hulls), + BSAPICPP.CreateHullShape2(worldu.ptr, hullCount, hulls), BSPhysicsShapeType.SHAPE_HULL); } -public override BulletShape BuildHullShapeFromMesh(BulletWorld world, BulletShape meshShape) +public override BulletShape BuildHullShapeFromMesh(BulletWorld world, BulletShape meshShape, HACDParams parms) { BulletWorldUnman worldu = world as BulletWorldUnman; BulletShapeUnman shapeu = meshShape as BulletShapeUnman; return new BulletShapeUnman( - BSAPICPP.BuildHullShapeFromMesh2(worldu.ptr, shapeu.ptr), + BSAPICPP.BuildHullShapeFromMesh2(worldu.ptr, shapeu.ptr, parms), BSPhysicsShapeType.SHAPE_HULL); } +public override BulletShape BuildConvexHullShapeFromMesh(BulletWorld world, BulletShape meshShape) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BulletShapeUnman shapeu = meshShape as BulletShapeUnman; + return new BulletShapeUnman( + BSAPICPP.BuildConvexHullShapeFromMesh2(worldu.ptr, shapeu.ptr), + BSPhysicsShapeType.SHAPE_CONVEXHULL); +} + +public override BulletShape CreateConvexHullShape(BulletWorld world, + int indicesCount, int[] indices, + int verticesCount, float[] vertices) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + return new BulletShapeUnman( + BSAPICPP.CreateConvexHullShape2(worldu.ptr, indicesCount, indices, verticesCount, vertices), + BSPhysicsShapeType.SHAPE_CONVEXHULL); +} + public override BulletShape BuildNativeShape(BulletWorld world, ShapeData shapeData) { BulletWorldUnman worldu = world as BulletWorldUnman; @@ -273,7 +305,7 @@ public override void SetShapeCollisionMargin(BulletShape shape, float margin) { BulletShapeUnman shapeu = shape as BulletShapeUnman; if (shapeu != null && shapeu.HasPhysicalShape) - BSAPICPP.SetShapeCollisionMargin2(shapeu.ptr, margin); + BSAPICPP.SetShapeCollisionMargin(shapeu.ptr, margin); } public override BulletShape BuildCapsuleShape(BulletWorld world, float radius, float height, Vector3 scale) @@ -327,6 +359,12 @@ public override void RemoveChildShapeFromCompoundShape(BulletShape shape, Bullet BSAPICPP.RemoveChildShapeFromCompoundShape2(shapeu.ptr, removeShapeu.ptr); } +public override void UpdateChildTransform(BulletShape pShape, int childIndex, Vector3 pos, Quaternion rot, bool shouldRecalculateLocalAabb) +{ + BulletShapeUnman shapeu = pShape as BulletShapeUnman; + BSAPICPP.UpdateChildTransform2(shapeu.ptr, childIndex, pos, rot, shouldRecalculateLocalAabb); +} + public override void RecalculateCompoundShapeLocalAabb(BulletShape shape) { BulletShapeUnman shapeu = shape as BulletShapeUnman; @@ -337,7 +375,7 @@ public override BulletShape DuplicateCollisionShape(BulletWorld world, BulletSha { BulletWorldUnman worldu = world as BulletWorldUnman; BulletShapeUnman srcShapeu = srcShape as BulletShapeUnman; - return new BulletShapeUnman(BSAPICPP.DuplicateCollisionShape2(worldu.ptr, srcShapeu.ptr, id), srcShape.type); + return new BulletShapeUnman(BSAPICPP.DuplicateCollisionShape2(worldu.ptr, srcShapeu.ptr, id), srcShape.shapeType); } public override bool DeleteCollisionShape(BulletWorld world, BulletShape shape) @@ -419,6 +457,28 @@ public override BulletConstraint Create6DofConstraintToPoint(BulletWorld world, joinPoint, useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); } +public override BulletConstraint Create6DofConstraintFixed(BulletWorld world, BulletBody obj1, + Vector3 frameInBloc, Quaternion frameInBrot, + bool useLinearReferenceFrameB, bool disableCollisionsBetweenLinkedBodies) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BulletBodyUnman bodyu1 = obj1 as BulletBodyUnman; + return new BulletConstraintUnman(BSAPICPP.Create6DofConstraintFixed2(worldu.ptr, bodyu1.ptr, + frameInBloc, frameInBrot, useLinearReferenceFrameB, disableCollisionsBetweenLinkedBodies)); +} + +public override BulletConstraint Create6DofSpringConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 frame1loc, Quaternion frame1rot, + Vector3 frame2loc, Quaternion frame2rot, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BulletBodyUnman bodyu1 = obj1 as BulletBodyUnman; + BulletBodyUnman bodyu2 = obj2 as BulletBodyUnman; + return new BulletConstraintUnman(BSAPICPP.Create6DofSpringConstraint2(worldu.ptr, bodyu1.ptr, bodyu2.ptr, frame1loc, frame1rot, + frame2loc, frame2rot, useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); +} + public override BulletConstraint CreateHingeConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, Vector3 pivotinA, Vector3 pivotinB, Vector3 axisInA, Vector3 axisInB, @@ -431,6 +491,52 @@ public override BulletConstraint CreateHingeConstraint(BulletWorld world, Bullet pivotinA, pivotinB, axisInA, axisInB, useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); } +public override BulletConstraint CreateSliderConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 frame1loc, Quaternion frame1rot, + Vector3 frame2loc, Quaternion frame2rot, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BulletBodyUnman bodyu1 = obj1 as BulletBodyUnman; + BulletBodyUnman bodyu2 = obj2 as BulletBodyUnman; + return new BulletConstraintUnman(BSAPICPP.CreateSliderConstraint2(worldu.ptr, bodyu1.ptr, bodyu2.ptr, frame1loc, frame1rot, + frame2loc, frame2rot, useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); +} + +public override BulletConstraint CreateConeTwistConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 frame1loc, Quaternion frame1rot, + Vector3 frame2loc, Quaternion frame2rot, + bool disableCollisionsBetweenLinkedBodies) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BulletBodyUnman bodyu1 = obj1 as BulletBodyUnman; + BulletBodyUnman bodyu2 = obj2 as BulletBodyUnman; + return new BulletConstraintUnman(BSAPICPP.CreateConeTwistConstraint2(worldu.ptr, bodyu1.ptr, bodyu2.ptr, frame1loc, frame1rot, + frame2loc, frame2rot, disableCollisionsBetweenLinkedBodies)); +} + +public override BulletConstraint CreateGearConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 axisInA, Vector3 axisInB, + float ratio, bool disableCollisionsBetweenLinkedBodies) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BulletBodyUnman bodyu1 = obj1 as BulletBodyUnman; + BulletBodyUnman bodyu2 = obj2 as BulletBodyUnman; + return new BulletConstraintUnman(BSAPICPP.CreateGearConstraint2(worldu.ptr, bodyu1.ptr, bodyu2.ptr, axisInA, axisInB, + ratio, disableCollisionsBetweenLinkedBodies)); +} + +public override BulletConstraint CreatePoint2PointConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 pivotInA, Vector3 pivotInB, + bool disableCollisionsBetweenLinkedBodies) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BulletBodyUnman bodyu1 = obj1 as BulletBodyUnman; + BulletBodyUnman bodyu2 = obj2 as BulletBodyUnman; + return new BulletConstraintUnman(BSAPICPP.CreatePoint2PointConstraint2(worldu.ptr, bodyu1.ptr, bodyu2.ptr, pivotInA, pivotInB, + disableCollisionsBetweenLinkedBodies)); +} + public override void SetConstraintEnable(BulletConstraint constrain, float numericTrueFalse) { BulletConstraintUnman constrainu = constrain as BulletConstraintUnman; @@ -530,12 +636,12 @@ public override void SetForceUpdateAllAabbs(BulletWorld world, bool force) // btDynamicsWorld entries public override bool AddObjectToWorld(BulletWorld world, BulletBody obj) { - // Bullet resets several variables when an object is added to the world. - // Gravity is reset to world default depending on the static/dynamic - // type. Of course, the collision flags in the broadphase proxy are initialized to default. BulletWorldUnman worldu = world as BulletWorldUnman; BulletBodyUnman bodyu = obj as BulletBodyUnman; + // Bullet resets several variables when an object is added to the world. + // Gravity is reset to world default depending on the static/dynamic + // type. Of course, the collision flags in the broadphase proxy are initialized to default. Vector3 origGrav = BSAPICPP.GetGravity2(bodyu.ptr); bool ret = BSAPICPP.AddObjectToWorld2(worldu.ptr, bodyu.ptr); @@ -921,6 +1027,7 @@ public override void SetCenterOfMassByPosRot(BulletBody obj, Vector3 pos, Quater } // Add a force to the object as if its mass is one. +// Deep down in Bullet: m_totalForce += force*m_linearFactor; public override void ApplyCentralForce(BulletBody obj, Vector3 force) { BulletBodyUnman bodyu = obj as BulletBodyUnman; @@ -964,6 +1071,7 @@ public override void SetSleepingThresholds(BulletBody obj, float lin_threshold, BSAPICPP.SetSleepingThresholds2(bodyu.ptr, lin_threshold, ang_threshold); } +// Deep down in Bullet: m_totalTorque += torque*m_angularFactor; public override void ApplyTorque(BulletBody obj, Vector3 torque) { BulletBodyUnman bodyu = obj as BulletBodyUnman; @@ -971,6 +1079,8 @@ public override void ApplyTorque(BulletBody obj, Vector3 torque) } // Apply force at the given point. Will add torque to the object. +// Deep down in Bullet: applyCentralForce(force); +// applyTorque(rel_pos.cross(force*m_linearFactor)); public override void ApplyForce(BulletBody obj, Vector3 force, Vector3 pos) { BulletBodyUnman bodyu = obj as BulletBodyUnman; @@ -978,6 +1088,7 @@ public override void ApplyForce(BulletBody obj, Vector3 force, Vector3 pos) } // Apply impulse to the object. Same as "ApplycentralForce" but force scaled by object's mass. +// Deep down in Bullet: m_linearVelocity += impulse *m_linearFactor * m_inverseMass; public override void ApplyCentralImpulse(BulletBody obj, Vector3 imp) { BulletBodyUnman bodyu = obj as BulletBodyUnman; @@ -985,6 +1096,7 @@ public override void ApplyCentralImpulse(BulletBody obj, Vector3 imp) } // Apply impulse to the object's torque. Force is scaled by object's mass. +// Deep down in Bullet: m_angularVelocity += m_invInertiaTensorWorld * torque * m_angularFactor; public override void ApplyTorqueImpulse(BulletBody obj, Vector3 imp) { BulletBodyUnman bodyu = obj as BulletBodyUnman; @@ -992,6 +1104,8 @@ public override void ApplyTorqueImpulse(BulletBody obj, Vector3 imp) } // Apply impulse at the point given. For is scaled by object's mass and effects both linear and angular forces. +// Deep down in Bullet: applyCentralImpulse(impulse); +// applyTorqueImpulse(rel_pos.cross(impulse*m_linearFactor)); public override void ApplyImpulse(BulletBody obj, Vector3 imp, Vector3 pos) { BulletBodyUnman bodyu = obj as BulletBodyUnman; @@ -1259,6 +1373,16 @@ public override void DumpPhysicsStatistics(BulletWorld world) BulletWorldUnman worldu = world as BulletWorldUnman; BSAPICPP.DumpPhysicsStatistics2(worldu.ptr); } +public override void ResetBroadphasePool(BulletWorld world) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BSAPICPP.ResetBroadphasePool(worldu.ptr); +} +public override void ResetConstraintSolver(BulletWorld world) +{ + BulletWorldUnman worldu = world as BulletWorldUnman; + BSAPICPP.ResetConstraintSolver(worldu.ptr); +} // ===================================================================================== // ===================================================================================== @@ -1306,7 +1430,15 @@ public static extern IntPtr CreateHullShape2(IntPtr world, int hullCount, [MarshalAs(UnmanagedType.LPArray)] float[] hulls); [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] -public static extern IntPtr BuildHullShapeFromMesh2(IntPtr world, IntPtr meshShape); +public static extern IntPtr BuildHullShapeFromMesh2(IntPtr world, IntPtr meshShape, HACDParams parms); + +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern IntPtr BuildConvexHullShapeFromMesh2(IntPtr world, IntPtr meshShape); + +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern IntPtr CreateConvexHullShape2(IntPtr world, + int indicesCount, [MarshalAs(UnmanagedType.LPArray)] int[] indices, + int verticesCount, [MarshalAs(UnmanagedType.LPArray)] float[] vertices ); [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern IntPtr BuildNativeShape2(IntPtr world, ShapeData shapeData); @@ -1315,7 +1447,7 @@ public static extern IntPtr BuildNativeShape2(IntPtr world, ShapeData shapeData) public static extern bool IsNativeShape2(IntPtr shape); [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] -public static extern void SetShapeCollisionMargin2(IntPtr shape, float margin); +public static extern void SetShapeCollisionMargin(IntPtr shape, float margin); [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern IntPtr BuildCapsuleShape2(IntPtr world, float radius, float height, Vector3 scale); @@ -1338,6 +1470,9 @@ public static extern IntPtr RemoveChildShapeFromCompoundShapeIndex2(IntPtr cShap [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern void RemoveChildShapeFromCompoundShape2(IntPtr cShape, IntPtr removeShape); +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern void UpdateChildTransform2(IntPtr pShape, int childIndex, Vector3 pos, Quaternion rot, bool shouldRecalculateLocalAabb); + [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern void RecalculateCompoundShapeLocalAabb2(IntPtr cShape); @@ -1368,7 +1503,7 @@ public static extern void DestroyObject2(IntPtr sim, IntPtr obj); public static extern IntPtr CreateGroundPlaneShape2(uint id, float height, float collisionMargin); [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] -public static extern IntPtr CreateTerrainShape2(uint id, Vector3 size, float minHeight, float maxHeight, +public static extern IntPtr CreateTerrainShape2(uint id, Vector3 size, float minHeight, float maxHeight, [MarshalAs(UnmanagedType.LPArray)] float[] heightMap, float scaleFactor, float collisionMargin); @@ -1385,12 +1520,46 @@ public static extern IntPtr Create6DofConstraintToPoint2(IntPtr world, IntPtr ob Vector3 joinPoint, bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies); +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern IntPtr Create6DofConstraintFixed2(IntPtr world, IntPtr obj1, + Vector3 frameInBloc, Quaternion frameInBrot, + bool useLinearReferenceFrameB, bool disableCollisionsBetweenLinkedBodies); + +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern IntPtr Create6DofSpringConstraint2(IntPtr world, IntPtr obj1, IntPtr obj2, + Vector3 frame1loc, Quaternion frame1rot, + Vector3 frame2loc, Quaternion frame2rot, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies); + [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern IntPtr CreateHingeConstraint2(IntPtr world, IntPtr obj1, IntPtr obj2, Vector3 pivotinA, Vector3 pivotinB, Vector3 axisInA, Vector3 axisInB, bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies); +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern IntPtr CreateSliderConstraint2(IntPtr world, IntPtr obj1, IntPtr obj2, + Vector3 frameInAloc, Quaternion frameInArot, + Vector3 frameInBloc, Quaternion frameInBrot, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies); + +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern IntPtr CreateConeTwistConstraint2(IntPtr world, IntPtr obj1, IntPtr obj2, + Vector3 frameInAloc, Quaternion frameInArot, + Vector3 frameInBloc, Quaternion frameInBrot, + bool disableCollisionsBetweenLinkedBodies); + +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern IntPtr CreateGearConstraint2(IntPtr world, IntPtr obj1, IntPtr obj2, + Vector3 axisInA, Vector3 axisInB, + float ratio, bool disableCollisionsBetweenLinkedBodies); + +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern IntPtr CreatePoint2PointConstraint2(IntPtr world, IntPtr obj1, IntPtr obj2, + Vector3 pivotInA, Vector3 pivotInB, + bool disableCollisionsBetweenLinkedBodies); + + [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern void SetConstraintEnable2(IntPtr constrain, float numericTrueFalse); @@ -1832,6 +2001,12 @@ public static extern void DumpAllInfo2(IntPtr sim); [DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] public static extern void DumpPhysicsStatistics2(IntPtr sim); +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern void ResetBroadphasePool(IntPtr sim); + +[DllImport("BulletSim", CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] +public static extern void ResetConstraintSolver(IntPtr sim); + } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSAPIXNA.cs b/OpenSim/Region/Physics/BulletSPlugin/BSAPIXNA.cs index 30a7bee667..59780ae8ee 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSAPIXNA.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSAPIXNA.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Text; using OpenSim.Framework; @@ -80,11 +81,11 @@ private sealed class BulletBodyXNA : BulletBody private sealed class BulletShapeXNA : BulletShape { public CollisionShape shape; - public BulletShapeXNA(CollisionShape xx, BSPhysicsShapeType typ) + public BulletShapeXNA(CollisionShape xx, BSPhysicsShapeType typ) : base() { shape = xx; - type = typ; + shapeType = typ; } public override bool HasPhysicalShape { @@ -96,7 +97,7 @@ private sealed class BulletShapeXNA : BulletShape } public override BulletShape Clone() { - return new BulletShapeXNA(shape, type); + return new BulletShapeXNA(shape, shapeType); } public override bool ReferenceSame(BulletShape other) { @@ -129,6 +130,14 @@ private sealed class BulletConstraintXNA : BulletConstraint get { return "XNAConstraint"; } } } + internal int m_maxCollisions; + internal CollisionDesc[] UpdatedCollisions; + internal int LastCollisionDesc = 0; + internal int m_maxUpdatesPerFrame; + internal int LastEntityProperty = 0; + + internal EntityProperties[] UpdatedObjects; + internal Dictionary specialCollisionObjects; private static int m_collisionsThisFrame; private BSScene PhysicsScene { get; set; } @@ -142,111 +151,127 @@ private sealed class BulletConstraintXNA : BulletConstraint } /// - /// + /// /// /// /// public override bool RemoveObjectFromWorld(BulletWorld pWorld, BulletBody pBody) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - world.RemoveRigidBody(body); + CollisionObject collisionObject = ((BulletBodyXNA)pBody).body; + if (body != null) + world.RemoveRigidBody(body); + else if (collisionObject != null) + world.RemoveCollisionObject(collisionObject); + else + return false; return true; } - public override bool AddConstraintToWorld(BulletWorld world, BulletConstraint constrain, bool disableCollisionsBetweenLinkedObjects) + public override bool AddConstraintToWorld(BulletWorld pWorld, BulletConstraint pConstraint, bool pDisableCollisionsBetweenLinkedObjects) { - /* TODO */ - return false; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + TypedConstraint constraint = (pConstraint as BulletConstraintXNA).constrain; + world.AddConstraint(constraint, pDisableCollisionsBetweenLinkedObjects); + + return true; + } - public override bool RemoveConstraintFromWorld(BulletWorld world, BulletConstraint constrain) + public override bool RemoveConstraintFromWorld(BulletWorld pWorld, BulletConstraint pConstraint) { - /* TODO */ - return false; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + TypedConstraint constraint = (pConstraint as BulletConstraintXNA).constrain; + world.RemoveConstraint(constraint); + return true; } - public override void SetRestitution(BulletBody pBody, float pRestitution) + public override void SetRestitution(BulletBody pCollisionObject, float pRestitution) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.SetRestitution(pRestitution); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + collisionObject.SetRestitution(pRestitution); } public override int GetShapeType(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return (int)shape.GetShapeType(); } public override void SetMargin(BulletShape pShape, float pMargin) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; shape.SetMargin(pMargin); } public override float GetMargin(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.GetMargin(); } public override void SetLocalScaling(BulletShape pShape, Vector3 pScale) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; IndexedVector3 vec = new IndexedVector3(pScale.X, pScale.Y, pScale.Z); shape.SetLocalScaling(ref vec); } - public override void SetContactProcessingThreshold(BulletBody pBody, float contactprocessingthreshold) + public override void SetContactProcessingThreshold(BulletBody pCollisionObject, float contactprocessingthreshold) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.SetContactProcessingThreshold(contactprocessingthreshold); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + collisionObject.SetContactProcessingThreshold(contactprocessingthreshold); } - public override void SetCcdMotionThreshold(BulletBody pBody, float pccdMotionThreashold) + public override void SetCcdMotionThreshold(BulletBody pCollisionObject, float pccdMotionThreashold) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.SetCcdMotionThreshold(pccdMotionThreashold); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + collisionObject.SetCcdMotionThreshold(pccdMotionThreashold); } - public override void SetCcdSweptSphereRadius(BulletBody pBody, float pCcdSweptSphereRadius) + public override void SetCcdSweptSphereRadius(BulletBody pCollisionObject, float pCcdSweptSphereRadius) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.SetCcdSweptSphereRadius(pCcdSweptSphereRadius); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + collisionObject.SetCcdSweptSphereRadius(pCcdSweptSphereRadius); } public override void SetAngularFactorV(BulletBody pBody, Vector3 pAngularFactor) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; body.SetAngularFactor(new IndexedVector3(pAngularFactor.X, pAngularFactor.Y, pAngularFactor.Z)); } - public override CollisionFlags AddToCollisionFlags(BulletBody pBody, CollisionFlags pcollisionFlags) + public override CollisionFlags AddToCollisionFlags(BulletBody pCollisionObject, CollisionFlags pcollisionFlags) { - CollisionObject body = ((BulletBodyXNA)pBody).body; - CollisionFlags existingcollisionFlags = (CollisionFlags)(uint)body.GetCollisionFlags(); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + CollisionFlags existingcollisionFlags = (CollisionFlags)(uint)collisionObject.GetCollisionFlags(); existingcollisionFlags |= pcollisionFlags; - body.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags)(uint)existingcollisionFlags); + collisionObject.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags)(uint)existingcollisionFlags); return (CollisionFlags) (uint) existingcollisionFlags; } public override bool AddObjectToWorld(BulletWorld pWorld, BulletBody pBody) { + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + CollisionObject cbody = (pBody as BulletBodyXNA).body; + RigidBody rbody = cbody as RigidBody; + // Bullet resets several variables when an object is added to the world. In particular, // BulletXNA resets position and rotation. Gravity is also reset depending on the static/dynamic // type. Of course, the collision flags in the broadphase proxy are initialized to default. - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - - IndexedMatrix origPos = body.GetWorldTransform(); - IndexedVector3 origGrav = body.GetGravity(); - - //if (!(body.GetCollisionShape().GetShapeType() == BroadphaseNativeTypes.STATIC_PLANE_PROXYTYPE && body.GetCollisionShape().GetShapeType() == BroadphaseNativeTypes.TERRAIN_SHAPE_PROXYTYPE)) - - world.AddRigidBody(body); - - body.SetWorldTransform(origPos); - body.SetGravity(origGrav); + IndexedMatrix origPos = cbody.GetWorldTransform(); + if (rbody != null) + { + IndexedVector3 origGrav = rbody.GetGravity(); + world.AddRigidBody(rbody); + rbody.SetGravity(origGrav); + } + else + { + world.AddCollisionObject(cbody); + } + cbody.SetWorldTransform(origPos); pBody.ApplyCollisionMask(pWorld.physicsScene); @@ -255,99 +280,110 @@ private sealed class BulletConstraintXNA : BulletConstraint return true; } - public override void ForceActivationState(BulletBody pBody, ActivationState pActivationState) + public override void ForceActivationState(BulletBody pCollisionObject, ActivationState pActivationState) { - CollisionObject body = ((BulletBodyXNA)pBody).body; - body.ForceActivationState((BulletXNA.BulletCollision.ActivationState)(uint)pActivationState); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + collisionObject.ForceActivationState((BulletXNA.BulletCollision.ActivationState)(uint)pActivationState); } - public override void UpdateSingleAabb(BulletWorld pWorld, BulletBody pBody) + public override void UpdateSingleAabb(BulletWorld pWorld, BulletBody pCollisionObject) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - CollisionObject body = ((BulletBodyXNA)pBody).body; - world.UpdateSingleAabb(body); + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + world.UpdateSingleAabb(collisionObject); } - public override void UpdateAabbs(BulletWorld world) { /* TODO */ } - public override bool GetForceUpdateAllAabbs(BulletWorld world) { /* TODO */ return false; } - public override void SetForceUpdateAllAabbs(BulletWorld world, bool force) { /* TODO */ } + public override void UpdateAabbs(BulletWorld pWorld) { + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + world.UpdateAabbs(); + } + public override bool GetForceUpdateAllAabbs(BulletWorld pWorld) { + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + return world.GetForceUpdateAllAabbs(); - public override bool SetCollisionGroupMask(BulletBody pBody, uint pGroup, uint pMask) + } + public override void SetForceUpdateAllAabbs(BulletWorld pWorld, bool pForce) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.GetBroadphaseHandle().m_collisionFilterGroup = (BulletXNA.BulletCollision.CollisionFilterGroups) pGroup; - body.GetBroadphaseHandle().m_collisionFilterGroup = (BulletXNA.BulletCollision.CollisionFilterGroups) pGroup; - if ((uint) body.GetBroadphaseHandle().m_collisionFilterGroup == 0) + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + world.SetForceUpdateAllAabbs(pForce); + } + + public override bool SetCollisionGroupMask(BulletBody pCollisionObject, uint pGroup, uint pMask) + { + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + collisionObject.GetBroadphaseHandle().m_collisionFilterGroup = (BulletXNA.BulletCollision.CollisionFilterGroups) pGroup; + collisionObject.GetBroadphaseHandle().m_collisionFilterGroup = (BulletXNA.BulletCollision.CollisionFilterGroups) pGroup; + if ((uint) collisionObject.GetBroadphaseHandle().m_collisionFilterGroup == 0) return false; return true; } - public override void ClearAllForces(BulletBody pBody) + public override void ClearAllForces(BulletBody pCollisionObject) { - CollisionObject body = ((BulletBodyXNA)pBody).body; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; IndexedVector3 zeroVector = new IndexedVector3(0, 0, 0); - body.SetInterpolationLinearVelocity(ref zeroVector); - body.SetInterpolationAngularVelocity(ref zeroVector); - IndexedMatrix bodytransform = body.GetWorldTransform(); + collisionObject.SetInterpolationLinearVelocity(ref zeroVector); + collisionObject.SetInterpolationAngularVelocity(ref zeroVector); + IndexedMatrix bodytransform = collisionObject.GetWorldTransform(); - body.SetInterpolationWorldTransform(ref bodytransform); + collisionObject.SetInterpolationWorldTransform(ref bodytransform); - if (body is RigidBody) + if (collisionObject is RigidBody) { - RigidBody rigidbody = body as RigidBody; + RigidBody rigidbody = collisionObject as RigidBody; rigidbody.SetLinearVelocity(zeroVector); rigidbody.SetAngularVelocity(zeroVector); rigidbody.ClearForces(); } } - public override void SetInterpolationAngularVelocity(BulletBody pBody, Vector3 pVector3) + public override void SetInterpolationAngularVelocity(BulletBody pCollisionObject, Vector3 pVector3) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; IndexedVector3 vec = new IndexedVector3(pVector3.X, pVector3.Y, pVector3.Z); - body.SetInterpolationAngularVelocity(ref vec); + collisionObject.SetInterpolationAngularVelocity(ref vec); } public override void SetAngularVelocity(BulletBody pBody, Vector3 pVector3) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 vec = new IndexedVector3(pVector3.X, pVector3.Y, pVector3.Z); body.SetAngularVelocity(ref vec); } public override Vector3 GetTotalForce(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 iv3 = body.GetTotalForce(); return new Vector3(iv3.X, iv3.Y, iv3.Z); } public override Vector3 GetTotalTorque(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 iv3 = body.GetTotalTorque(); return new Vector3(iv3.X, iv3.Y, iv3.Z); } public override Vector3 GetInvInertiaDiagLocal(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 iv3 = body.GetInvInertiaDiagLocal(); return new Vector3(iv3.X, iv3.Y, iv3.Z); } public override void SetInvInertiaDiagLocal(BulletBody pBody, Vector3 inert) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 iv3 = new IndexedVector3(inert.X, inert.Y, inert.Z); body.SetInvInertiaDiagLocal(ref iv3); } public override void ApplyForce(BulletBody pBody, Vector3 force, Vector3 pos) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 forceiv3 = new IndexedVector3(force.X, force.Y, force.Z); IndexedVector3 posiv3 = new IndexedVector3(pos.X, pos.Y, pos.Z); body.ApplyForce(ref forceiv3, ref posiv3); } public override void ApplyImpulse(BulletBody pBody, Vector3 imp, Vector3 pos) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 impiv3 = new IndexedVector3(imp.X, imp.Y, imp.Z); IndexedVector3 posiv3 = new IndexedVector3(pos.X, pos.Y, pos.Z); body.ApplyImpulse(ref impiv3, ref posiv3); @@ -355,32 +391,32 @@ private sealed class BulletConstraintXNA : BulletConstraint public override void ClearForces(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; body.ClearForces(); } - public override void SetTranslation(BulletBody pBody, Vector3 _position, Quaternion _orientation) + public override void SetTranslation(BulletBody pCollisionObject, Vector3 _position, Quaternion _orientation) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; IndexedVector3 vposition = new IndexedVector3(_position.X, _position.Y, _position.Z); IndexedQuaternion vquaternion = new IndexedQuaternion(_orientation.X, _orientation.Y, _orientation.Z, _orientation.W); IndexedMatrix mat = IndexedMatrix.CreateFromQuaternion(vquaternion); mat._origin = vposition; - body.SetWorldTransform(mat); - + collisionObject.SetWorldTransform(mat); + } - public override Vector3 GetPosition(BulletBody pBody) + public override Vector3 GetPosition(BulletBody pCollisionObject) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - IndexedVector3 pos = body.GetInterpolationWorldTransform()._origin; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + IndexedVector3 pos = collisionObject.GetInterpolationWorldTransform()._origin; return new Vector3(pos.X, pos.Y, pos.Z); } public override Vector3 CalculateLocalInertia(BulletShape pShape, float pphysMass) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; IndexedVector3 inertia = IndexedVector3.Zero; shape.CalculateLocalInertia(pphysMass, out inertia); return new Vector3(inertia.X, inertia.Y, inertia.Z); @@ -388,81 +424,104 @@ private sealed class BulletConstraintXNA : BulletConstraint public override void SetMassProps(BulletBody pBody, float pphysMass, Vector3 plocalInertia) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - IndexedVector3 inertia = new IndexedVector3(plocalInertia.X, plocalInertia.Y, plocalInertia.Z); - body.SetMassProps(pphysMass, inertia); + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + if (body != null) // Can't set mass props on collision object. + { + IndexedVector3 inertia = new IndexedVector3(plocalInertia.X, plocalInertia.Y, plocalInertia.Z); + body.SetMassProps(pphysMass, inertia); + } } public override void SetObjectForce(BulletBody pBody, Vector3 _force) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 force = new IndexedVector3(_force.X, _force.Y, _force.Z); body.SetTotalForce(ref force); } - public override void SetFriction(BulletBody pBody, float _currentFriction) + public override void SetFriction(BulletBody pCollisionObject, float _currentFriction) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.SetFriction(_currentFriction); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + collisionObject.SetFriction(_currentFriction); } public override void SetLinearVelocity(BulletBody pBody, Vector3 _velocity) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 velocity = new IndexedVector3(_velocity.X, _velocity.Y, _velocity.Z); body.SetLinearVelocity(velocity); } - public override void Activate(BulletBody pBody, bool pforceactivation) + public override void Activate(BulletBody pCollisionObject, bool pforceactivation) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.Activate(pforceactivation); - + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + collisionObject.Activate(pforceactivation); + } - public override Quaternion GetOrientation(BulletBody pBody) + public override Quaternion GetOrientation(BulletBody pCollisionObject) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - IndexedQuaternion mat = body.GetInterpolationWorldTransform().GetRotation(); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + IndexedQuaternion mat = collisionObject.GetInterpolationWorldTransform().GetRotation(); return new Quaternion(mat.X, mat.Y, mat.Z, mat.W); } - public override CollisionFlags RemoveFromCollisionFlags(BulletBody pBody, CollisionFlags pcollisionFlags) + public override CollisionFlags RemoveFromCollisionFlags(BulletBody pCollisionObject, CollisionFlags pcollisionFlags) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - CollisionFlags existingcollisionFlags = (CollisionFlags)(uint)body.GetCollisionFlags(); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + CollisionFlags existingcollisionFlags = (CollisionFlags)(uint)collisionObject.GetCollisionFlags(); existingcollisionFlags &= ~pcollisionFlags; - body.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags)(uint)existingcollisionFlags); + collisionObject.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags)(uint)existingcollisionFlags); return (CollisionFlags)(uint)existingcollisionFlags; } - public override float GetCcdMotionThreshold(BulletBody obj) { /* TODO */ return 0f; } + public override float GetCcdMotionThreshold(BulletBody pCollisionObject) + { + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + return collisionObject.GetCcdSquareMotionThreshold(); + } - public override float GetCcdSweptSphereRadius(BulletBody obj) { /* TODO */ return 0f; } + public override float GetCcdSweptSphereRadius(BulletBody pCollisionObject) + { + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + return collisionObject.GetCcdSweptSphereRadius(); - public override IntPtr GetUserPointer(BulletBody obj) { /* TODO */ return IntPtr.Zero; } + } - public override void SetUserPointer(BulletBody obj, IntPtr val) { /* TODO */ } + public override IntPtr GetUserPointer(BulletBody pCollisionObject) + { + CollisionObject shape = (pCollisionObject as BulletBodyXNA).body; + return (IntPtr)shape.GetUserPointer(); + } + + public override void SetUserPointer(BulletBody pCollisionObject, IntPtr val) + { + CollisionObject shape = (pCollisionObject as BulletBodyXNA).body; + shape.SetUserPointer(val); + } public override void SetGravity(BulletBody pBody, Vector3 pGravity) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - IndexedVector3 gravity = new IndexedVector3(pGravity.X, pGravity.Y, pGravity.Z); - body.SetGravity(gravity); + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + if (body != null) // Can't set collisionobject.set gravity + { + IndexedVector3 gravity = new IndexedVector3(pGravity.X, pGravity.Y, pGravity.Z); + body.SetGravity(gravity); + } } public override bool DestroyConstraint(BulletWorld pWorld, BulletConstraint pConstraint) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - TypedConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + TypedConstraint constraint = (pConstraint as BulletConstraintXNA).constrain; world.RemoveConstraint(constraint); return true; } public override bool SetLinearLimits(BulletConstraint pConstraint, Vector3 low, Vector3 high) { - Generic6DofConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constraint = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; IndexedVector3 lowlimit = new IndexedVector3(low.X, low.Y, low.Z); IndexedVector3 highlimit = new IndexedVector3(high.X, high.Y, high.Z); constraint.SetLinearLowerLimit(lowlimit); @@ -472,7 +531,7 @@ private sealed class BulletConstraintXNA : BulletConstraint public override bool SetAngularLimits(BulletConstraint pConstraint, Vector3 low, Vector3 high) { - Generic6DofConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constraint = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; IndexedVector3 lowlimit = new IndexedVector3(low.X, low.Y, low.Z); IndexedVector3 highlimit = new IndexedVector3(high.X, high.Y, high.Z); constraint.SetAngularLowerLimit(lowlimit); @@ -482,31 +541,32 @@ private sealed class BulletConstraintXNA : BulletConstraint public override void SetConstraintNumSolverIterations(BulletConstraint pConstraint, float cnt) { - Generic6DofConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constraint = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; constraint.SetOverrideNumSolverIterations((int)cnt); } public override bool CalculateTransforms(BulletConstraint pConstraint) { - Generic6DofConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constraint = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; constraint.CalculateTransforms(); return true; } public override void SetConstraintEnable(BulletConstraint pConstraint, float p_2) { - Generic6DofConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constraint = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; constraint.SetEnabled((p_2 == 0) ? false : true); } - //BulletSimAPI.Create6DofConstraint(m_world.ptr, m_body1.ptr, m_body2.ptr,frame1, frame1rot,frame2, frame2rot,useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); - public override BulletConstraint Create6DofConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, Vector3 pframe1, Quaternion pframe1rot, Vector3 pframe2, Quaternion pframe2rot, bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) + public override BulletConstraint Create6DofConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, + Vector3 pframe1, Quaternion pframe1rot, Vector3 pframe2, Quaternion pframe2rot, + bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - RigidBody body1 = ((BulletBodyXNA)pBody1).rigidBody; - RigidBody body2 = ((BulletBodyXNA)pBody2).rigidBody; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody body1 = (pBody1 as BulletBodyXNA).rigidBody; + RigidBody body2 = (pBody2 as BulletBodyXNA).rigidBody; IndexedVector3 frame1v = new IndexedVector3(pframe1.X, pframe1.Y, pframe1.Z); IndexedQuaternion frame1rot = new IndexedQuaternion(pframe1rot.X, pframe1rot.Y, pframe1rot.Z, pframe1rot.W); IndexedMatrix frame1 = IndexedMatrix.CreateFromQuaternion(frame1rot); @@ -525,9 +585,26 @@ private sealed class BulletConstraintXNA : BulletConstraint return new BulletConstraintXNA(consttr); } - + public override BulletConstraint Create6DofConstraintFixed(BulletWorld pWorld, BulletBody pBody1, + Vector3 pframe1, Quaternion pframe1rot, + bool pUseLinearReferenceFrameB, bool pdisableCollisionsBetweenLinkedBodies) + { + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody body1 = (pBody1 as BulletBodyXNA).rigidBody; + IndexedVector3 frame1v = new IndexedVector3(pframe1.X, pframe1.Y, pframe1.Z); + IndexedQuaternion frame1rot = new IndexedQuaternion(pframe1rot.X, pframe1rot.Y, pframe1rot.Z, pframe1rot.W); + IndexedMatrix frame1 = IndexedMatrix.CreateFromQuaternion(frame1rot); + frame1._origin = frame1v; + + Generic6DofConstraint consttr = new Generic6DofConstraint(body1, ref frame1, pUseLinearReferenceFrameB); + consttr.CalculateTransforms(); + world.AddConstraint(consttr,pdisableCollisionsBetweenLinkedBodies); + + return new BulletConstraintXNA(consttr); + } + /// - /// + /// /// /// /// @@ -538,9 +615,9 @@ private sealed class BulletConstraintXNA : BulletConstraint /// public override BulletConstraint Create6DofConstraintToPoint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, Vector3 pjoinPoint, bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - RigidBody body1 = ((BulletBodyXNA)pBody1).rigidBody; - RigidBody body2 = ((BulletBodyXNA)pBody2).rigidBody; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody body1 = (pBody1 as BulletBodyXNA).rigidBody; + RigidBody body2 = (pBody2 as BulletBodyXNA).rigidBody; IndexedMatrix frame1 = new IndexedMatrix(IndexedBasisMatrix.Identity, new IndexedVector3(0, 0, 0)); IndexedMatrix frame2 = new IndexedMatrix(IndexedBasisMatrix.Identity, new IndexedVector3(0, 0, 0)); @@ -559,7 +636,7 @@ private sealed class BulletConstraintXNA : BulletConstraint //SetFrames(m_constraint.ptr, frameA, frameArot, frameB, frameBrot); public override bool SetFrames(BulletConstraint pConstraint, Vector3 pframe1, Quaternion pframe1rot, Vector3 pframe2, Quaternion pframe2rot) { - Generic6DofConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constraint = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; IndexedVector3 frame1v = new IndexedVector3(pframe1.X, pframe1.Y, pframe1.Z); IndexedQuaternion frame1rot = new IndexedQuaternion(pframe1rot.X, pframe1rot.Y, pframe1rot.Z, pframe1rot.W); IndexedMatrix frame1 = IndexedMatrix.CreateFromQuaternion(frame1rot); @@ -575,109 +652,110 @@ private sealed class BulletConstraintXNA : BulletConstraint public override Vector3 GetLinearVelocity(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 iv3 = body.GetLinearVelocity(); return new Vector3(iv3.X, iv3.Y, iv3.Z); } public override Vector3 GetAngularVelocity(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 iv3 = body.GetAngularVelocity(); return new Vector3(iv3.X, iv3.Y, iv3.Z); } public override Vector3 GetVelocityInLocalPoint(BulletBody pBody, Vector3 pos) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 posiv3 = new IndexedVector3(pos.X, pos.Y, pos.Z); IndexedVector3 iv3 = body.GetVelocityInLocalPoint(ref posiv3); return new Vector3(iv3.X, iv3.Y, iv3.Z); } - public override void Translate(BulletBody pBody, Vector3 trans) + public override void Translate(BulletBody pCollisionObject, Vector3 trans) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + collisionObject.Translate(new IndexedVector3(trans.X,trans.Y,trans.Z)); } public override void UpdateDeactivation(BulletBody pBody, float timeStep) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; body.UpdateDeactivation(timeStep); } public override bool WantsSleeping(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; return body.WantsSleeping(); } public override void SetAngularFactor(BulletBody pBody, float factor) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; body.SetAngularFactor(factor); } public override Vector3 GetAngularFactor(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 iv3 = body.GetAngularFactor(); return new Vector3(iv3.X, iv3.Y, iv3.Z); } - public override bool IsInWorld(BulletWorld pWorld, BulletBody pBody) + public override bool IsInWorld(BulletWorld pWorld, BulletBody pCollisionObject) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - CollisionObject body = ((BulletBodyXNA)pBody).body; - return world.IsInWorld(body); + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + return world.IsInWorld(collisionObject); } - public override void AddConstraintRef(BulletBody pBody, BulletConstraint pConstrain) + public override void AddConstraintRef(BulletBody pBody, BulletConstraint pConstraint) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - TypedConstraint constrain = ((BulletConstraintXNA)pConstrain).constrain; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + TypedConstraint constrain = (pConstraint as BulletConstraintXNA).constrain; body.AddConstraintRef(constrain); } - public override void RemoveConstraintRef(BulletBody pBody, BulletConstraint pConstrain) + public override void RemoveConstraintRef(BulletBody pBody, BulletConstraint pConstraint) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - TypedConstraint constrain = ((BulletConstraintXNA)pConstrain).constrain; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + TypedConstraint constrain = (pConstraint as BulletConstraintXNA).constrain; body.RemoveConstraintRef(constrain); } public override BulletConstraint GetConstraintRef(BulletBody pBody, int index) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; return new BulletConstraintXNA(body.GetConstraintRef(index)); } public override int GetNumConstraintRefs(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; return body.GetNumConstraintRefs(); } - public override void SetInterpolationLinearVelocity(BulletBody pBody, Vector3 VehicleVelocity) + public override void SetInterpolationLinearVelocity(BulletBody pCollisionObject, Vector3 VehicleVelocity) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; IndexedVector3 velocity = new IndexedVector3(VehicleVelocity.X, VehicleVelocity.Y, VehicleVelocity.Z); - body.SetInterpolationLinearVelocity(ref velocity); + collisionObject.SetInterpolationLinearVelocity(ref velocity); } public override bool UseFrameOffset(BulletConstraint pConstraint, float onOff) { - Generic6DofConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constraint = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; constraint.SetUseFrameOffset((onOff == 0) ? false : true); return true; } //SetBreakingImpulseThreshold(m_constraint.ptr, threshold); public override bool SetBreakingImpulseThreshold(BulletConstraint pConstraint, float threshold) { - Generic6DofConstraint constraint = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constraint = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; constraint.SetBreakingImpulseThreshold(threshold); return true; } //BulletSimAPI.SetAngularDamping(Prim.PhysBody.ptr, angularDamping); public override void SetAngularDamping(BulletBody pBody, float angularDamping) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; float lineardamping = body.GetLinearDamping(); body.SetDamping(lineardamping, angularDamping); @@ -685,163 +763,241 @@ private sealed class BulletConstraintXNA : BulletConstraint public override void UpdateInertiaTensor(BulletBody pBody) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.UpdateInertiaTensor(); + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + if (body != null) // can't update inertia tensor on CollisionObject + body.UpdateInertiaTensor(); } public override void RecalculateCompoundShapeLocalAabb(BulletShape pCompoundShape) { - CompoundShape shape = ((BulletShapeXNA)pCompoundShape).shape as CompoundShape; + CompoundShape shape = (pCompoundShape as BulletShapeXNA).shape as CompoundShape; shape.RecalculateLocalAabb(); } //BulletSimAPI.GetCollisionFlags(PhysBody.ptr) - public override CollisionFlags GetCollisionFlags(BulletBody pBody) + public override CollisionFlags GetCollisionFlags(BulletBody pCollisionObject) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - uint flags = (uint)body.GetCollisionFlags(); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + uint flags = (uint)collisionObject.GetCollisionFlags(); return (CollisionFlags) flags; } public override void SetDamping(BulletBody pBody, float pLinear, float pAngular) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; body.SetDamping(pLinear, pAngular); } //PhysBody.ptr, PhysicsScene.Params.deactivationTime); - public override void SetDeactivationTime(BulletBody pBody, float pDeactivationTime) + public override void SetDeactivationTime(BulletBody pCollisionObject, float pDeactivationTime) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.SetDeactivationTime(pDeactivationTime); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + collisionObject.SetDeactivationTime(pDeactivationTime); } //SetSleepingThresholds(PhysBody.ptr, PhysicsScene.Params.linearSleepingThreshold, PhysicsScene.Params.angularSleepingThreshold); public override void SetSleepingThresholds(BulletBody pBody, float plinearSleepingThreshold, float pangularSleepingThreshold) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; body.SetSleepingThresholds(plinearSleepingThreshold, pangularSleepingThreshold); } - public override CollisionObjectTypes GetBodyType(BulletBody pBody) + public override CollisionObjectTypes GetBodyType(BulletBody pCollisionObject) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - return (CollisionObjectTypes)(int) body.GetInternalType(); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + return (CollisionObjectTypes)(int) collisionObject.GetInternalType(); } - public override void ApplyGravity(BulletBody obj) { /* TODO */ } + public override void ApplyGravity(BulletBody pBody) + { - public override Vector3 GetGravity(BulletBody obj) { /* TODO */ return Vector3.Zero; } + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + body.ApplyGravity(); + } - public override void SetLinearDamping(BulletBody obj, float lin_damping) { /* TODO */ } + public override Vector3 GetGravity(BulletBody pBody) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + IndexedVector3 gravity = body.GetGravity(); + return new Vector3(gravity.X, gravity.Y, gravity.Z); + } - public override float GetLinearDamping(BulletBody obj) { /* TODO */ return 0f; } + public override void SetLinearDamping(BulletBody pBody, float lin_damping) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + float angularDamping = body.GetAngularDamping(); + body.SetDamping(lin_damping, angularDamping); + } - public override float GetAngularDamping(BulletBody obj) { /* TODO */ return 0f; } + public override float GetLinearDamping(BulletBody pBody) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + return body.GetLinearDamping(); + } - public override float GetLinearSleepingThreshold(BulletBody obj) { /* TODO */ return 0f; } + public override float GetAngularDamping(BulletBody pBody) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + return body.GetAngularDamping(); + } - public override void ApplyDamping(BulletBody obj, float timeStep) { /* TODO */ } + public override float GetLinearSleepingThreshold(BulletBody pBody) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + return body.GetLinearSleepingThreshold(); + } - public override Vector3 GetLinearFactor(BulletBody obj) { /* TODO */ return Vector3.Zero; } + public override void ApplyDamping(BulletBody pBody, float timeStep) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + body.ApplyDamping(timeStep); + } - public override void SetLinearFactor(BulletBody obj, Vector3 factor) { /* TODO */ } + public override Vector3 GetLinearFactor(BulletBody pBody) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + IndexedVector3 linearFactor = body.GetLinearFactor(); + return new Vector3(linearFactor.X, linearFactor.Y, linearFactor.Z); + } - public override void SetCenterOfMassByPosRot(BulletBody obj, Vector3 pos, Quaternion rot) { /* TODO */ } + public override void SetLinearFactor(BulletBody pBody, Vector3 factor) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + body.SetLinearFactor(new IndexedVector3(factor.X, factor.Y, factor.Z)); + } + + public override void SetCenterOfMassByPosRot(BulletBody pBody, Vector3 pos, Quaternion rot) + { + RigidBody body = (pBody as BulletBodyXNA).rigidBody; + IndexedQuaternion quat = new IndexedQuaternion(rot.X, rot.Y, rot.Z,rot.W); + IndexedMatrix mat = IndexedMatrix.CreateFromQuaternion(quat); + mat._origin = new IndexedVector3(pos.X, pos.Y, pos.Z); + body.SetCenterOfMassTransform( ref mat); + /* TODO: double check this */ + } //BulletSimAPI.ApplyCentralForce(PhysBody.ptr, fSum); public override void ApplyCentralForce(BulletBody pBody, Vector3 pfSum) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 fSum = new IndexedVector3(pfSum.X, pfSum.Y, pfSum.Z); body.ApplyCentralForce(ref fSum); } public override void ApplyCentralImpulse(BulletBody pBody, Vector3 pfSum) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 fSum = new IndexedVector3(pfSum.X, pfSum.Y, pfSum.Z); body.ApplyCentralImpulse(ref fSum); } public override void ApplyTorque(BulletBody pBody, Vector3 pfSum) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 fSum = new IndexedVector3(pfSum.X, pfSum.Y, pfSum.Z); body.ApplyTorque(ref fSum); } public override void ApplyTorqueImpulse(BulletBody pBody, Vector3 pfSum) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; + RigidBody body = (pBody as BulletBodyXNA).rigidBody; IndexedVector3 fSum = new IndexedVector3(pfSum.X, pfSum.Y, pfSum.Z); body.ApplyTorqueImpulse(ref fSum); } - public override void DumpRigidBody(BulletWorld p, BulletBody p_2) + public override void DestroyObject(BulletWorld pWorld, BulletBody pBody) { - //TODO: - } + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + CollisionObject co = (pBody as BulletBodyXNA).rigidBody; + RigidBody bo = co as RigidBody; + if (bo == null) + { - public override void DumpCollisionShape(BulletWorld p, BulletShape p_2) - { - //TODO: - } - public override void DumpConstraint(BulletWorld world, BulletConstraint constrain) - { - //TODO: - } + if (world.IsInWorld(co)) + { + world.RemoveCollisionObject(co); + } + } + else + { - public override void DumpActivationInfo(BulletWorld world) - { - //TODO: - } + if (world.IsInWorld(bo)) + { + world.RemoveRigidBody(bo); + } + } + if (co != null) + { + if (co.GetUserPointer() != null) + { + uint localId = (uint) co.GetUserPointer(); + if (specialCollisionObjects.ContainsKey(localId)) + { + specialCollisionObjects.Remove(localId); + } + } + } - public override void DumpAllInfo(BulletWorld world) - { - //TODO: - } - - public override void DumpPhysicsStatistics(BulletWorld world) - { - //TODO: - } - - public override void DestroyObject(BulletWorld p, BulletBody p_2) - { - //TODO: } public override void Shutdown(BulletWorld pWorld) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; world.Cleanup(); } - public override BulletShape DuplicateCollisionShape(BulletWorld sim, BulletShape srcShape, uint id) + public override BulletShape DuplicateCollisionShape(BulletWorld pWorld, BulletShape pShape, uint id) { - return null; + CollisionShape shape1 = (pShape as BulletShapeXNA).shape; + + // TODO: Turn this from a reference copy to a Value Copy. + BulletShapeXNA shape2 = new BulletShapeXNA(shape1, BSShapeTypeFromBroadPhaseNativeType(shape1.GetShapeType())); + + return shape2; } - public override bool DeleteCollisionShape(BulletWorld p, BulletShape p_2) + public override bool DeleteCollisionShape(BulletWorld pWorld, BulletShape pShape) { //TODO: return false; } //(sim.ptr, shape.ptr, prim.LocalID, prim.RawPosition, prim.RawOrientation); - + public override BulletBody CreateBodyFromShape(BulletWorld pWorld, BulletShape pShape, uint pLocalID, Vector3 pRawPosition, Quaternion pRawOrientation) { - CollisionWorld world = ((BulletWorldXNA)pWorld).world; + CollisionWorld world = (pWorld as BulletWorldXNA).world; IndexedMatrix mat = IndexedMatrix.CreateFromQuaternion(new IndexedQuaternion(pRawOrientation.X, pRawOrientation.Y, pRawOrientation.Z, pRawOrientation.W)); mat._origin = new IndexedVector3(pRawPosition.X, pRawPosition.Y, pRawPosition.Z); - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; //UpdateSingleAabb(world, shape); // TODO: Feed Update array into null - RigidBody body = new RigidBody(0,new SimMotionState(world,pLocalID,mat,null),shape,IndexedVector3.Zero); - + SimMotionState motionState = new SimMotionState(this, pLocalID, mat, null); + RigidBody body = new RigidBody(0,motionState,shape,IndexedVector3.Zero); + RigidBodyConstructionInfo constructionInfo = new RigidBodyConstructionInfo(0, motionState, shape, IndexedVector3.Zero) + { + m_mass = 0 + }; + /* + m_mass = mass; + m_motionState =motionState; + m_collisionShape = collisionShape; + m_localInertia = localInertia; + m_linearDamping = 0f; + m_angularDamping = 0f; + m_friction = 0.5f; + m_restitution = 0f; + m_linearSleepingThreshold = 0.8f; + m_angularSleepingThreshold = 1f; + m_additionalDamping = false; + m_additionalDampingFactor = 0.005f; + m_additionalLinearDampingThresholdSqr = 0.01f; + m_additionalAngularDampingThresholdSqr = 0.01f; + m_additionalAngularDampingFactor = 0.01f; + m_startWorldTransform = IndexedMatrix.Identity; + */ body.SetUserPointer(pLocalID); + return new BulletBodyXNA(pLocalID, body); } - + public override BulletBody CreateBodyWithDefaultMotionState( BulletShape pShape, uint pLocalID, Vector3 pRawPosition, Quaternion pRawOrientation) { @@ -850,7 +1006,7 @@ private sealed class BulletConstraintXNA : BulletConstraint pRawOrientation.Z, pRawOrientation.W)); mat._origin = new IndexedVector3(pRawPosition.X, pRawPosition.Y, pRawPosition.Z); - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; // TODO: Feed Update array into null RigidBody body = new RigidBody(0, new DefaultMotionState( mat, IndexedMatrix.Identity), shape, IndexedVector3.Zero); @@ -859,21 +1015,43 @@ private sealed class BulletConstraintXNA : BulletConstraint return new BulletBodyXNA(pLocalID, body); } //(m_mapInfo.terrainBody.ptr, CollisionFlags.CF_STATIC_OBJECT); - public override CollisionFlags SetCollisionFlags(BulletBody pBody, CollisionFlags collisionFlags) + public override CollisionFlags SetCollisionFlags(BulletBody pCollisionObject, CollisionFlags collisionFlags) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags) (uint) collisionFlags); - return (CollisionFlags)body.GetCollisionFlags(); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + collisionObject.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags) (uint) collisionFlags); + return (CollisionFlags)collisionObject.GetCollisionFlags(); } - public override Vector3 GetAnisotripicFriction(BulletConstraint pconstrain) { /* TODO */ return Vector3.Zero; } + public override Vector3 GetAnisotripicFriction(BulletConstraint pconstrain) + { + + /* TODO */ + return Vector3.Zero; + } public override Vector3 SetAnisotripicFriction(BulletConstraint pconstrain, Vector3 frict) { /* TODO */ return Vector3.Zero; } public override bool HasAnisotripicFriction(BulletConstraint pconstrain) { /* TODO */ return false; } public override float GetContactProcessingThreshold(BulletBody pBody) { /* TODO */ return 0f; } - public override bool IsStaticObject(BulletBody pBody) { /* TODO */ return false; } - public override bool IsKinematicObject(BulletBody pBody) { /* TODO */ return false; } - public override bool IsStaticOrKinematicObject(BulletBody pBody) { /* TODO */ return false; } - public override bool HasContactResponse(BulletBody pBody) { /* TODO */ return false; } + public override bool IsStaticObject(BulletBody pCollisionObject) + { + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + return collisionObject.IsStaticObject(); + + } + public override bool IsKinematicObject(BulletBody pCollisionObject) + { + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + return collisionObject.IsKinematicObject(); + } + public override bool IsStaticOrKinematicObject(BulletBody pCollisionObject) + { + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + return collisionObject.IsStaticOrKinematicObject(); + } + public override bool HasContactResponse(BulletBody pCollisionObject) + { + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + return collisionObject.HasContactResponse(); + } public override int GetActivationState(BulletBody pBody) { /* TODO */ return 0; } public override void SetActivationState(BulletBody pBody, int state) { /* TODO */ } public override float GetDeactivationTime(BulletBody pBody) { /* TODO */ return 0f; } @@ -884,15 +1062,15 @@ private sealed class BulletConstraintXNA : BulletConstraint public override float GetHitFraction(BulletBody pBody) { /* TODO */ return 0f; } //(m_mapInfo.terrainBody.ptr, PhysicsScene.Params.terrainHitFraction); - public override void SetHitFraction(BulletBody pBody, float pHitFraction) + public override void SetHitFraction(BulletBody pCollisionObject, float pHitFraction) { - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - body.SetHitFraction(pHitFraction); + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + collisionObject.SetHitFraction(pHitFraction); } //BuildCapsuleShape(physicsScene.World.ptr, 1f, 1f, prim.Scale); public override BulletShape BuildCapsuleShape(BulletWorld pWorld, float pRadius, float pHeight, Vector3 pScale) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; IndexedVector3 scale = new IndexedVector3(pScale.X, pScale.Y, pScale.Z); CapsuleShapeZ capsuleShapeZ = new CapsuleShapeZ(pRadius, pHeight); capsuleShapeZ.SetMargin(world.WorldSettings.Params.collisionMargin); @@ -906,19 +1084,29 @@ private sealed class BulletConstraintXNA : BulletConstraint int maxUpdates, ref EntityProperties[] updateArray ) { + + UpdatedObjects = updateArray; + UpdatedCollisions = collisionArray; /* TODO */ - return new BulletWorldXNA(1, null, null); + ConfigurationParameters[] configparms = new ConfigurationParameters[1]; + configparms[0] = parms; + Vector3 worldExtent = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight); + m_maxCollisions = maxCollisions; + m_maxUpdatesPerFrame = maxUpdates; + specialCollisionObjects = new Dictionary(); + + return new BulletWorldXNA(1, PhysicsScene, BSAPIXNA.Initialize2(worldExtent, configparms, maxCollisions, ref collisionArray, maxUpdates, ref updateArray, null)); } - private static object Initialize2(Vector3 worldExtent, + private static DiscreteDynamicsWorld Initialize2(Vector3 worldExtent, ConfigurationParameters[] o, - int mMaxCollisionsPerFrame, ref List collisionArray, - int mMaxUpdatesPerFrame, ref List updateArray, + int mMaxCollisionsPerFrame, ref CollisionDesc[] collisionArray, + int mMaxUpdatesPerFrame, ref EntityProperties[] updateArray, object mDebugLogCallbackHandle) { CollisionWorld.WorldData.ParamData p = new CollisionWorld.WorldData.ParamData(); - p.angularDamping = o[0].XangularDamping; + p.angularDamping = BSParam.AngularDamping; p.defaultFriction = o[0].defaultFriction; p.defaultFriction = o[0].defaultFriction; p.defaultDensity = o[0].defaultDensity; @@ -926,33 +1114,33 @@ private sealed class BulletConstraintXNA : BulletConstraint p.collisionMargin = o[0].collisionMargin; p.gravity = o[0].gravity; - p.linearDamping = o[0].XlinearDamping; - p.angularDamping = o[0].XangularDamping; - p.deactivationTime = o[0].XdeactivationTime; - p.linearSleepingThreshold = o[0].XlinearSleepingThreshold; - p.angularSleepingThreshold = o[0].XangularSleepingThreshold; - p.ccdMotionThreshold = o[0].XccdMotionThreshold; - p.ccdSweptSphereRadius = o[0].XccdSweptSphereRadius; - p.contactProcessingThreshold = o[0].XcontactProcessingThreshold; + p.linearDamping = BSParam.LinearDamping; + p.angularDamping = BSParam.AngularDamping; + p.deactivationTime = BSParam.DeactivationTime; + p.linearSleepingThreshold = BSParam.LinearSleepingThreshold; + p.angularSleepingThreshold = BSParam.AngularSleepingThreshold; + p.ccdMotionThreshold = BSParam.CcdMotionThreshold; + p.ccdSweptSphereRadius = BSParam.CcdSweptSphereRadius; + p.contactProcessingThreshold = BSParam.ContactProcessingThreshold; - p.terrainImplementation = o[0].XterrainImplementation; - p.terrainFriction = o[0].XterrainFriction; + p.terrainImplementation = BSParam.TerrainImplementation; + p.terrainFriction = BSParam.TerrainFriction; - p.terrainHitFraction = o[0].XterrainHitFraction; - p.terrainRestitution = o[0].XterrainRestitution; - p.terrainCollisionMargin = o[0].XterrainCollisionMargin; + p.terrainHitFraction = BSParam.TerrainHitFraction; + p.terrainRestitution = BSParam.TerrainRestitution; + p.terrainCollisionMargin = BSParam.TerrainCollisionMargin; + + p.avatarFriction = BSParam.AvatarFriction; + p.avatarStandingFriction = BSParam.AvatarStandingFriction; + p.avatarDensity = BSParam.AvatarDensity; + p.avatarRestitution = BSParam.AvatarRestitution; + p.avatarCapsuleWidth = BSParam.AvatarCapsuleWidth; + p.avatarCapsuleDepth = BSParam.AvatarCapsuleDepth; + p.avatarCapsuleHeight = BSParam.AvatarCapsuleHeight; + p.avatarContactProcessingThreshold = BSParam.AvatarContactProcessingThreshold; + + p.vehicleAngularDamping = BSParam.VehicleAngularDamping; - p.avatarFriction = o[0].XavatarFriction; - p.avatarStandingFriction = o[0].XavatarStandingFriction; - p.avatarDensity = o[0].XavatarDensity; - p.avatarRestitution = o[0].XavatarRestitution; - p.avatarCapsuleWidth = o[0].XavatarCapsuleWidth; - p.avatarCapsuleDepth = o[0].XavatarCapsuleDepth; - p.avatarCapsuleHeight = o[0].XavatarCapsuleHeight; - p.avatarContactProcessingThreshold = o[0].XavatarContactProcessingThreshold; - - p.vehicleAngularDamping = o[0].XvehicleAngularDamping; - p.maxPersistantManifoldPoolSize = o[0].maxPersistantManifoldPoolSize; p.maxCollisionAlgorithmPoolSize = o[0].maxCollisionAlgorithmPoolSize; p.shouldDisableContactPoolDynamicAllocation = o[0].shouldDisableContactPoolDynamicAllocation; @@ -962,17 +1150,17 @@ private sealed class BulletConstraintXNA : BulletConstraint p.shouldEnableFrictionCaching = o[0].shouldEnableFrictionCaching; p.numberOfSolverIterations = o[0].numberOfSolverIterations; - p.linksetImplementation = o[0].XlinksetImplementation; - p.linkConstraintUseFrameOffset = o[0].XlinkConstraintUseFrameOffset; - p.linkConstraintEnableTransMotor = o[0].XlinkConstraintEnableTransMotor; - p.linkConstraintTransMotorMaxVel = o[0].XlinkConstraintTransMotorMaxVel; - p.linkConstraintTransMotorMaxForce = o[0].XlinkConstraintTransMotorMaxForce; - p.linkConstraintERP = o[0].XlinkConstraintERP; - p.linkConstraintCFM = o[0].XlinkConstraintCFM; - p.linkConstraintSolverIterations = o[0].XlinkConstraintSolverIterations; - p.physicsLoggingFrames = o[0].XphysicsLoggingFrames; + p.linksetImplementation = BSParam.LinksetImplementation; + p.linkConstraintUseFrameOffset = BSParam.NumericBool(BSParam.LinkConstraintUseFrameOffset); + p.linkConstraintEnableTransMotor = BSParam.NumericBool(BSParam.LinkConstraintEnableTransMotor); + p.linkConstraintTransMotorMaxVel = BSParam.LinkConstraintTransMotorMaxVel; + p.linkConstraintTransMotorMaxForce = BSParam.LinkConstraintTransMotorMaxForce; + p.linkConstraintERP = BSParam.LinkConstraintERP; + p.linkConstraintCFM = BSParam.LinkConstraintCFM; + p.linkConstraintSolverIterations = BSParam.LinkConstraintSolverIterations; + p.physicsLoggingFrames = o[0].physicsLoggingFrames; DefaultCollisionConstructionInfo ccci = new DefaultCollisionConstructionInfo(); - + DefaultCollisionConfiguration cci = new DefaultCollisionConfiguration(); CollisionDispatcher m_dispatcher = new CollisionDispatcher(cci); @@ -993,8 +1181,10 @@ private sealed class BulletConstraintXNA : BulletConstraint SequentialImpulseConstraintSolver m_solver = new SequentialImpulseConstraintSolver(); DiscreteDynamicsWorld world = new DiscreteDynamicsWorld(m_dispatcher, m_broadphase, m_solver, cci); - world.UpdatedObjects = updateArray; - world.UpdatedCollisions = collisionArray; + + world.LastCollisionDesc = 0; + world.LastEntityProperty = 0; + world.WorldSettings.Params = p; world.SetForceUpdateAllAabbs(p.shouldForceUpdateAllAabbs != 0); world.GetSolverInfo().m_solverMode = SolverMode.SOLVER_USE_WARMSTARTING | SolverMode.SOLVER_SIMD; @@ -1028,7 +1218,7 @@ private sealed class BulletConstraintXNA : BulletConstraint world.GetSolverInfo().m_restingContactRestitutionThreshold = 2; world.SetForceUpdateAllAabbs(true); - + //BSParam.TerrainImplementation = 0; world.SetGravity(new IndexedVector3(0,0,p.gravity)); return world; @@ -1036,7 +1226,7 @@ private sealed class BulletConstraintXNA : BulletConstraint //m_constraint.ptr, ConstraintParams.BT_CONSTRAINT_STOP_CFM, cfm, ConstraintParamAxis.AXIS_ALL public override bool SetConstraintParam(BulletConstraint pConstraint, ConstraintParams paramIndex, float paramvalue, ConstraintParamAxis axis) { - Generic6DofConstraint constrain = ((BulletConstraintXNA)pConstraint).constrain as Generic6DofConstraint; + Generic6DofConstraint constrain = (pConstraint as BulletConstraintXNA).constrain as Generic6DofConstraint; if (axis == ConstraintParamAxis.AXIS_LINEAR_ALL || axis == ConstraintParamAxis.AXIS_ALL) { constrain.SetParam((BulletXNA.BulletDynamics.ConstraintParams) (int) paramIndex, paramvalue, 0); @@ -1059,7 +1249,8 @@ private sealed class BulletConstraintXNA : BulletConstraint public override bool PushUpdate(BulletBody pCollisionObject) { bool ret = false; - RigidBody rb = ((BulletBodyXNA)pCollisionObject).rigidBody; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + RigidBody rb = collisionObject as RigidBody; if (rb != null) { SimMotionState sms = rb.GetMotionState() as SimMotionState; @@ -1072,62 +1263,62 @@ private sealed class BulletConstraintXNA : BulletConstraint } } return ret; - + } public override float GetAngularMotionDisc(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.GetAngularMotionDisc(); } public override float GetContactBreakingThreshold(BulletShape pShape, float defaultFactor) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.GetContactBreakingThreshold(defaultFactor); } public override bool IsCompound(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.IsCompound(); } public override bool IsSoftBody(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.IsSoftBody(); } public override bool IsPolyhedral(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.IsPolyhedral(); } public override bool IsConvex2d(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.IsConvex2d(); } public override bool IsConvex(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.IsConvex(); } public override bool IsNonMoving(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.IsNonMoving(); } public override bool IsConcave(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.IsConcave(); } public override bool IsInfinite(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; return shape.IsInfinite(); } public override bool IsNativeShape(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; bool ret; switch (shape.GetShapeType()) { @@ -1144,38 +1335,59 @@ private sealed class BulletConstraintXNA : BulletConstraint return ret; } - public override void SetShapeCollisionMargin(BulletShape shape, float margin) { /* TODO */ } + public override void SetShapeCollisionMargin(BulletShape pShape, float pMargin) + { + CollisionShape shape = (pShape as BulletShapeXNA).shape; + shape.SetMargin(pMargin); + } //sim.ptr, shape.ptr,prim.LocalID, prim.RawPosition, prim.RawOrientation public override BulletBody CreateGhostFromShape(BulletWorld pWorld, BulletShape pShape, uint pLocalID, Vector3 pRawPosition, Quaternion pRawOrientation) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; IndexedMatrix bodyTransform = new IndexedMatrix(); bodyTransform._origin = new IndexedVector3(pRawPosition.X, pRawPosition.Y, pRawPosition.Z); bodyTransform.SetRotation(new IndexedQuaternion(pRawOrientation.X,pRawOrientation.Y,pRawOrientation.Z,pRawOrientation.W)); GhostObject gObj = new PairCachingGhostObject(); gObj.SetWorldTransform(bodyTransform); - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; gObj.SetCollisionShape(shape); gObj.SetUserPointer(pLocalID); + + if (specialCollisionObjects.ContainsKey(pLocalID)) + specialCollisionObjects[pLocalID] = gObj; + else + specialCollisionObjects.Add(pLocalID, gObj); + // TODO: Add to Special CollisionObjects! return new BulletBodyXNA(pLocalID, gObj); } - public override void SetCollisionShape(BulletWorld pWorld, BulletBody pObj, BulletShape pShape) + public override void SetCollisionShape(BulletWorld pWorld, BulletBody pCollisionObject, BulletShape pShape) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - CollisionObject obj = ((BulletBodyXNA)pObj).body; - CollisionShape shape = ((BulletShapeXNA)pShape).shape; - obj.SetCollisionShape(shape); - + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).body; + if (pShape == null) + { + collisionObject.SetCollisionShape(new EmptyShape()); + } + else + { + CollisionShape shape = (pShape as BulletShapeXNA).shape; + collisionObject.SetCollisionShape(shape); + } + } + public override BulletShape GetCollisionShape(BulletBody pCollisionObject) + { + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + CollisionShape shape = collisionObject.GetCollisionShape(); + return new BulletShapeXNA(shape, BSShapeTypeFromBroadPhaseNativeType(shape.GetShapeType())); } - public override BulletShape GetCollisionShape(BulletBody obj) { /* TODO */ return null; } //(PhysicsScene.World.ptr, nativeShapeData) public override BulletShape BuildNativeShape(BulletWorld pWorld, ShapeData pShapeData) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; CollisionShape shape = null; switch (pShapeData.Type) { @@ -1210,15 +1422,15 @@ private sealed class BulletConstraintXNA : BulletConstraint public override int GetNumberOfCompoundChildren(BulletShape pCompoundShape) { - CompoundShape compoundshape = ((BulletShapeXNA)pCompoundShape).shape as CompoundShape; + CompoundShape compoundshape = (pCompoundShape as BulletShapeXNA).shape as CompoundShape; return compoundshape.GetNumChildShapes(); } //LinksetRoot.PhysShape.ptr, newShape.ptr, displacementPos, displacementRot public override void AddChildShapeToCompoundShape(BulletShape pCShape, BulletShape paddShape, Vector3 displacementPos, Quaternion displacementRot) { IndexedMatrix relativeTransform = new IndexedMatrix(); - CompoundShape compoundshape = ((BulletShapeXNA)pCShape).shape as CompoundShape; - CollisionShape addshape = ((BulletShapeXNA)paddShape).shape; + CompoundShape compoundshape = (pCShape as BulletShapeXNA).shape as CompoundShape; + CollisionShape addshape = (paddShape as BulletShapeXNA).shape; relativeTransform._origin = new IndexedVector3(displacementPos.X, displacementPos.Y, displacementPos.Z); relativeTransform.SetRotation(new IndexedQuaternion(displacementRot.X,displacementRot.Y,displacementRot.Z,displacementRot.W)); @@ -1228,15 +1440,155 @@ private sealed class BulletConstraintXNA : BulletConstraint public override BulletShape RemoveChildShapeFromCompoundShapeIndex(BulletShape pCShape, int pii) { - CompoundShape compoundshape = ((BulletShapeXNA)pCShape).shape as CompoundShape; + CompoundShape compoundshape = (pCShape as BulletShapeXNA).shape as CompoundShape; CollisionShape ret = null; ret = compoundshape.GetChildShape(pii); compoundshape.RemoveChildShapeByIndex(pii); - return new BulletShapeXNA(ret, BSPhysicsShapeType.SHAPE_UNKNOWN); + return new BulletShapeXNA(ret, BSShapeTypeFromBroadPhaseNativeType(ret.GetShapeType())); + } + + public override BulletShape GetChildShapeFromCompoundShapeIndex(BulletShape cShape, int indx) { + + if (cShape == null) + return null; + CompoundShape compoundShape = (cShape as BulletShapeXNA).shape as CompoundShape; + CollisionShape shape = compoundShape.GetChildShape(indx); + BulletShape retShape = new BulletShapeXNA(shape, BSShapeTypeFromBroadPhaseNativeType(shape.GetShapeType())); + + + return retShape; + } + + public BSPhysicsShapeType BSShapeTypeFromBroadPhaseNativeType(BroadphaseNativeTypes pin) + { + BSPhysicsShapeType ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + switch (pin) + { + case BroadphaseNativeTypes.BOX_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_BOX; + break; + case BroadphaseNativeTypes.TRIANGLE_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + + case BroadphaseNativeTypes.TETRAHEDRAL_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.CONVEX_TRIANGLEMESH_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_MESH; + break; + case BroadphaseNativeTypes.CONVEX_HULL_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_HULL; + break; + case BroadphaseNativeTypes.CONVEX_POINT_CLOUD_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.CUSTOM_POLYHEDRAL_SHAPE_TYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + //implicit convex shapes + case BroadphaseNativeTypes.IMPLICIT_CONVEX_SHAPES_START_HERE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.SPHERE_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_SPHERE; + break; + case BroadphaseNativeTypes.MULTI_SPHERE_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.CAPSULE_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_CAPSULE; + break; + case BroadphaseNativeTypes.CONE_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_CONE; + break; + case BroadphaseNativeTypes.CONVEX_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.CYLINDER_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_CYLINDER; + break; + case BroadphaseNativeTypes.UNIFORM_SCALING_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.MINKOWSKI_SUM_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.MINKOWSKI_DIFFERENCE_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.BOX_2D_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.CONVEX_2D_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.CUSTOM_CONVEX_SHAPE_TYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + //concave shape + case BroadphaseNativeTypes.CONCAVE_SHAPES_START_HERE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + //keep all the convex shapetype below here, for the check IsConvexShape in broadphase proxy! + case BroadphaseNativeTypes.TRIANGLE_MESH_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_MESH; + break; + case BroadphaseNativeTypes.SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_MESH; + break; + ///used for demo integration FAST/Swift collision library and Bullet + case BroadphaseNativeTypes.FAST_CONCAVE_MESH_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_MESH; + break; + //terrain + case BroadphaseNativeTypes.TERRAIN_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_HEIGHTMAP; + break; + ///Used for GIMPACT Trimesh integration + case BroadphaseNativeTypes.GIMPACT_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_MESH; + break; + ///Multimaterial mesh + case BroadphaseNativeTypes.MULTIMATERIAL_TRIANGLE_MESH_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_MESH; + break; + + case BroadphaseNativeTypes.EMPTY_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.STATIC_PLANE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_GROUNDPLANE; + break; + case BroadphaseNativeTypes.CUSTOM_CONCAVE_SHAPE_TYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.CONCAVE_SHAPES_END_HERE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + + case BroadphaseNativeTypes.COMPOUND_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_COMPOUND; + break; + + case BroadphaseNativeTypes.SOFTBODY_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_MESH; + break; + case BroadphaseNativeTypes.HFFLUID_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.HFFLUID_BUOYANT_CONVEX_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + case BroadphaseNativeTypes.INVALID_SHAPE_PROXYTYPE: + ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + break; + } + return ret; } - public override BulletShape GetChildShapeFromCompoundShapeIndex(BulletShape cShape, int indx) { /* TODO */ return null; } public override void RemoveChildShapeFromCompoundShape(BulletShape cShape, BulletShape removeShape) { /* TODO */ } + public override void UpdateChildTransform(BulletShape pShape, int childIndex, Vector3 pos, Quaternion rot, bool shouldRecalculateLocalAabb) { /* TODO */ } public override BulletShape CreateGroundPlaneShape(uint pLocalId, float pheight, float pcollisionMargin) { @@ -1246,18 +1598,144 @@ private sealed class BulletConstraintXNA : BulletConstraint return new BulletShapeXNA(m_planeshape, BSPhysicsShapeType.SHAPE_GROUNDPLANE); } - public override BulletConstraint CreateHingeConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody ppBody2, Vector3 ppivotInA, Vector3 ppivotInB, Vector3 paxisInA, Vector3 paxisInB, bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) + public override BulletConstraint Create6DofSpringConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, + Vector3 pframe1, Quaternion pframe1rot, Vector3 pframe2, Quaternion pframe2rot, + bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) + + { + Generic6DofSpringConstraint constrain = null; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody body1 = (pBody1 as BulletBodyXNA).rigidBody; + RigidBody body2 = (pBody2 as BulletBodyXNA).rigidBody; + if (body1 != null && body2 != null) + { + IndexedVector3 frame1v = new IndexedVector3(pframe1.X, pframe1.Y, pframe1.Z); + IndexedQuaternion frame1rot = new IndexedQuaternion(pframe1rot.X, pframe1rot.Y, pframe1rot.Z, pframe1rot.W); + IndexedMatrix frame1 = IndexedMatrix.CreateFromQuaternion(frame1rot); + frame1._origin = frame1v; + + IndexedVector3 frame2v = new IndexedVector3(pframe2.X, pframe2.Y, pframe2.Z); + IndexedQuaternion frame2rot = new IndexedQuaternion(pframe2rot.X, pframe2rot.Y, pframe2rot.Z, pframe2rot.W); + IndexedMatrix frame2 = IndexedMatrix.CreateFromQuaternion(frame2rot); + frame2._origin = frame1v; + + constrain = new Generic6DofSpringConstraint(body1, body2, ref frame1, ref frame2, puseLinearReferenceFrameA); + world.AddConstraint(constrain, pdisableCollisionsBetweenLinkedBodies); + + constrain.CalculateTransforms(); + } + + return new BulletConstraintXNA(constrain); + } + + public override BulletConstraint CreateHingeConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, + Vector3 ppivotInA, Vector3 ppivotInB, Vector3 paxisInA, Vector3 paxisInB, + bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) { HingeConstraint constrain = null; - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - RigidBody rb1 = ((BulletBodyXNA)pBody1).rigidBody; - RigidBody rb2 = ((BulletBodyXNA)ppBody2).rigidBody; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody rb1 = (pBody1 as BulletBodyXNA).rigidBody; + RigidBody rb2 = (pBody2 as BulletBodyXNA).rigidBody; if (rb1 != null && rb2 != null) { IndexedVector3 pivotInA = new IndexedVector3(ppivotInA.X, ppivotInA.Y, ppivotInA.Z); IndexedVector3 pivotInB = new IndexedVector3(ppivotInB.X, ppivotInB.Y, ppivotInB.Z); IndexedVector3 axisInA = new IndexedVector3(paxisInA.X, paxisInA.Y, paxisInA.Z); IndexedVector3 axisInB = new IndexedVector3(paxisInB.X, paxisInB.Y, paxisInB.Z); + constrain = new HingeConstraint(rb1, rb2, ref pivotInA, ref pivotInB, ref axisInA, ref axisInB, puseLinearReferenceFrameA); + world.AddConstraint(constrain, pdisableCollisionsBetweenLinkedBodies); + } + return new BulletConstraintXNA(constrain); + } + + public override BulletConstraint CreateSliderConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, + Vector3 pframe1, Quaternion pframe1rot, + Vector3 pframe2, Quaternion pframe2rot, + bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) + { + SliderConstraint constrain = null; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody rb1 = (pBody1 as BulletBodyXNA).rigidBody; + RigidBody rb2 = (pBody2 as BulletBodyXNA).rigidBody; + if (rb1 != null && rb2 != null) + { + IndexedVector3 frame1v = new IndexedVector3(pframe1.X, pframe1.Y, pframe1.Z); + IndexedQuaternion frame1rot = new IndexedQuaternion(pframe1rot.X, pframe1rot.Y, pframe1rot.Z, pframe1rot.W); + IndexedMatrix frame1 = IndexedMatrix.CreateFromQuaternion(frame1rot); + frame1._origin = frame1v; + + IndexedVector3 frame2v = new IndexedVector3(pframe2.X, pframe2.Y, pframe2.Z); + IndexedQuaternion frame2rot = new IndexedQuaternion(pframe2rot.X, pframe2rot.Y, pframe2rot.Z, pframe2rot.W); + IndexedMatrix frame2 = IndexedMatrix.CreateFromQuaternion(frame2rot); + frame2._origin = frame1v; + + constrain = new SliderConstraint(rb1, rb2, ref frame1, ref frame2, puseLinearReferenceFrameA); + world.AddConstraint(constrain, pdisableCollisionsBetweenLinkedBodies); + } + return new BulletConstraintXNA(constrain); + } + + public override BulletConstraint CreateConeTwistConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, + Vector3 pframe1, Quaternion pframe1rot, + Vector3 pframe2, Quaternion pframe2rot, + bool pdisableCollisionsBetweenLinkedBodies) + { + ConeTwistConstraint constrain = null; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody rb1 = (pBody1 as BulletBodyXNA).rigidBody; + RigidBody rb2 = (pBody2 as BulletBodyXNA).rigidBody; + if (rb1 != null && rb2 != null) + { + IndexedVector3 frame1v = new IndexedVector3(pframe1.X, pframe1.Y, pframe1.Z); + IndexedQuaternion frame1rot = new IndexedQuaternion(pframe1rot.X, pframe1rot.Y, pframe1rot.Z, pframe1rot.W); + IndexedMatrix frame1 = IndexedMatrix.CreateFromQuaternion(frame1rot); + frame1._origin = frame1v; + + IndexedVector3 frame2v = new IndexedVector3(pframe2.X, pframe2.Y, pframe2.Z); + IndexedQuaternion frame2rot = new IndexedQuaternion(pframe2rot.X, pframe2rot.Y, pframe2rot.Z, pframe2rot.W); + IndexedMatrix frame2 = IndexedMatrix.CreateFromQuaternion(frame2rot); + frame2._origin = frame1v; + + constrain = new ConeTwistConstraint(rb1, rb2, ref frame1, ref frame2); + world.AddConstraint(constrain, pdisableCollisionsBetweenLinkedBodies); + } + return new BulletConstraintXNA(constrain); + } + + public override BulletConstraint CreateGearConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, + Vector3 paxisInA, Vector3 paxisInB, + float pratio, bool pdisableCollisionsBetweenLinkedBodies) + { + Generic6DofConstraint constrain = null; + /* BulletXNA does not have a gear constraint + GearConstraint constrain = null; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody rb1 = (pBody1 as BulletBodyXNA).rigidBody; + RigidBody rb2 = (pBody2 as BulletBodyXNA).rigidBody; + if (rb1 != null && rb2 != null) + { + IndexedVector3 axis1 = new IndexedVector3(paxisInA.X, paxisInA.Y, paxisInA.Z); + IndexedVector3 axis2 = new IndexedVector3(paxisInB.X, paxisInB.Y, paxisInB.Z); + constrain = new GearConstraint(rb1, rb2, ref axis1, ref axis2, pratio); + world.AddConstraint(constrain, pdisableCollisionsBetweenLinkedBodies); + } + */ + return new BulletConstraintXNA(constrain); + } + + public override BulletConstraint CreatePoint2PointConstraint(BulletWorld pWorld, BulletBody pBody1, BulletBody pBody2, + Vector3 ppivotInA, Vector3 ppivotInB, + bool pdisableCollisionsBetweenLinkedBodies) + { + Point2PointConstraint constrain = null; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + RigidBody rb1 = (pBody1 as BulletBodyXNA).rigidBody; + RigidBody rb2 = (pBody2 as BulletBodyXNA).rigidBody; + if (rb1 != null && rb2 != null) + { + IndexedVector3 pivotInA = new IndexedVector3(ppivotInA.X, ppivotInA.Y, ppivotInA.Z); + IndexedVector3 pivotInB = new IndexedVector3(ppivotInB.X, ppivotInB.Y, ppivotInB.Z); + constrain = new Point2PointConstraint(rb1, rb2, ref pivotInA, ref pivotInB); world.AddConstraint(constrain, pdisableCollisionsBetweenLinkedBodies); } return new BulletConstraintXNA(constrain); @@ -1265,9 +1743,9 @@ private sealed class BulletConstraintXNA : BulletConstraint public override BulletShape CreateHullShape(BulletWorld pWorld, int pHullCount, float[] pConvHulls) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; CompoundShape compoundshape = new CompoundShape(false); - + compoundshape.SetMargin(world.WorldSettings.Params.collisionMargin); int ii = 1; @@ -1283,7 +1761,7 @@ private sealed class BulletConstraintXNA : BulletConstraint int ender = ((ii + 4) + (vertexCount*3)); for (int iii = ii + 4; iii < ender; iii+=3) { - + virts.Add(new IndexedVector3(pConvHulls[iii], pConvHulls[iii + 1], pConvHulls[iii +2])); } ConvexHullShape convexShape = new ConvexHullShape(virts, vertexCount); @@ -1291,26 +1769,39 @@ private sealed class BulletConstraintXNA : BulletConstraint compoundshape.AddChildShape(ref childTrans, convexShape); ii += (vertexCount*3 + 4); } - + return new BulletShapeXNA(compoundshape, BSPhysicsShapeType.SHAPE_HULL); } - public override BulletShape BuildHullShapeFromMesh(BulletWorld world, BulletShape meshShape) { /* TODO */ return null; } + public override BulletShape BuildHullShapeFromMesh(BulletWorld world, BulletShape meshShape, HACDParams parms) + { + /* TODO */ return null; + } + + public override BulletShape BuildConvexHullShapeFromMesh(BulletWorld world, BulletShape meshShape) + { + /* TODO */ return null; + } + + public override BulletShape CreateConvexHullShape(BulletWorld pWorld, int pIndicesCount, int[] indices, int pVerticesCount, float[] verticesAsFloats) + { + /* TODO */ return null; + } public override BulletShape CreateMeshShape(BulletWorld pWorld, int pIndicesCount, int[] indices, int pVerticesCount, float[] verticesAsFloats) { //DumpRaw(indices,verticesAsFloats,pIndicesCount,pVerticesCount); - + for (int iter = 0; iter < pVerticesCount; iter++) { if (verticesAsFloats[iter] > 0 && verticesAsFloats[iter] < 0.0001) verticesAsFloats[iter] = 0; if (verticesAsFloats[iter] < 0 && verticesAsFloats[iter] > -0.0001) verticesAsFloats[iter] = 0; } - + ObjectArray indicesarr = new ObjectArray(indices); ObjectArray vertices = new ObjectArray(verticesAsFloats); DumpRaw(indicesarr,vertices,pIndicesCount,pVerticesCount); - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; IndexedMesh mesh = new IndexedMesh(); mesh.m_indexType = PHY_ScalarType.PHY_INTEGER; mesh.m_numTriangles = pIndicesCount/3; @@ -1320,7 +1811,7 @@ private sealed class BulletConstraintXNA : BulletConstraint mesh.m_vertexStride = 3; mesh.m_vertexType = PHY_ScalarType.PHY_FLOAT; mesh.m_triangleIndexStride = 3; - + TriangleIndexVertexArray tribuilder = new TriangleIndexVertexArray(); tribuilder.AddIndexedMesh(mesh, PHY_ScalarType.PHY_INTEGER); BvhTriangleMeshShape meshShape = new BvhTriangleMeshShape(tribuilder, true,true); @@ -1331,7 +1822,7 @@ private sealed class BulletConstraintXNA : BulletConstraint } public static void DumpRaw(ObjectArrayindices, ObjectArray vertices, int pIndicesCount,int pVerticesCount ) { - + String fileName = "objTest3.raw"; String completePath = System.IO.Path.Combine(Util.configDir(), fileName); StreamWriter sw = new StreamWriter(completePath); @@ -1357,7 +1848,7 @@ private sealed class BulletConstraintXNA : BulletConstraint string s = vertices[indices[i * 3]].ToString("0.0000"); s += " " + vertices[indices[i * 3 + 1]].ToString("0.0000"); s += " " + vertices[indices[i * 3 + 2]].ToString("0.0000"); - + sw.Write(s + "\n"); } @@ -1379,7 +1870,7 @@ private sealed class BulletConstraintXNA : BulletConstraint mesh.m_vertexStride = 3; mesh.m_vertexType = PHY_ScalarType.PHY_FLOAT; mesh.m_triangleIndexStride = 3; - + TriangleIndexVertexArray tribuilder = new TriangleIndexVertexArray(); tribuilder.AddIndexedMesh(mesh, PHY_ScalarType.PHY_INTEGER); @@ -1410,7 +1901,7 @@ private sealed class BulletConstraintXNA : BulletConstraint sw.Close(); } - public override BulletShape CreateTerrainShape(uint id, Vector3 size, float minHeight, float maxHeight, float[] heightMap, + public override BulletShape CreateTerrainShape(uint id, Vector3 size, float minHeight, float maxHeight, float[] heightMap, float scaleFactor, float collisionMargin) { const int upAxis = 2; @@ -1426,7 +1917,7 @@ private sealed class BulletConstraintXNA : BulletConstraint public override bool TranslationalLimitMotor(BulletConstraint pConstraint, float ponOff, float targetVelocity, float maxMotorForce) { - TypedConstraint tconstrain = ((BulletConstraintXNA)pConstraint).constrain; + TypedConstraint tconstrain = (pConstraint as BulletConstraintXNA).constrain; bool onOff = ponOff != 0; bool ret = false; @@ -1452,64 +1943,73 @@ private sealed class BulletConstraintXNA : BulletConstraint /* TODO */ updatedEntityCount = 0; collidersCount = 0; - return 1; + + + int ret = PhysicsStep2(world,timeStep,maxSubSteps,fixedTimeStep,out updatedEntityCount,out world.physicsScene.m_updateArray, out collidersCount, out world.physicsScene.m_collisionArray); + + return ret; } - private int PhysicsStep2(BulletWorld pWorld, float timeStep, int m_maxSubSteps, float m_fixedTimeStep, - out int updatedEntityCount, out List updatedEntities, - out int collidersCount, out Listcolliders) + private int PhysicsStep2(BulletWorld pWorld, float timeStep, int m_maxSubSteps, float m_fixedTimeStep, + out int updatedEntityCount, out EntityProperties[] updatedEntities, + out int collidersCount, out CollisionDesc[] colliders) { int epic = PhysicsStepint(pWorld, timeStep, m_maxSubSteps, m_fixedTimeStep, out updatedEntityCount, out updatedEntities, - out collidersCount, out colliders); + out collidersCount, out colliders, m_maxCollisions, m_maxUpdatesPerFrame); return epic; } - private static int PhysicsStepint(BulletWorld pWorld,float timeStep, int m_maxSubSteps, float m_fixedTimeStep, out int updatedEntityCount, out List updatedEntities, out int collidersCount, out List colliders) + private int PhysicsStepint(BulletWorld pWorld,float timeStep, int m_maxSubSteps, float m_fixedTimeStep, out int updatedEntityCount, + out EntityProperties[] updatedEntities, out int collidersCount, out CollisionDesc[] colliders, int maxCollisions, int maxUpdates) { int numSimSteps = 0; - + Array.Clear(UpdatedObjects, 0, UpdatedObjects.Length); + Array.Clear(UpdatedCollisions, 0, UpdatedCollisions.Length); + LastEntityProperty=0; - //if (updatedEntities is null) - // updatedEntities = new List(); - //if (colliders is null) - // colliders = new List(); - + + + + + LastCollisionDesc=0; + + updatedEntityCount = 0; + collidersCount = 0; + if (pWorld is BulletWorldXNA) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + world.LastCollisionDesc = 0; + world.LastEntityProperty = 0; numSimSteps = world.StepSimulation(timeStep, m_maxSubSteps, m_fixedTimeStep); int updates = 0; - updatedEntityCount = world.UpdatedObjects.Count; - updatedEntities = new List(world.UpdatedObjects); - updatedEntityCount = updatedEntities.Count; - world.UpdatedObjects.Clear(); - + PersistentManifold contactManifold; + CollisionObject objA; + CollisionObject objB; + ManifoldPoint manifoldPoint; + PairCachingGhostObject pairCachingGhostObject; - collidersCount = world.UpdatedCollisions.Count; - colliders = new List(world.UpdatedCollisions); - - world.UpdatedCollisions.Clear(); m_collisionsThisFrame = 0; int numManifolds = world.GetDispatcher().GetNumManifolds(); for (int j = 0; j < numManifolds; j++) { - PersistentManifold contactManifold = world.GetDispatcher().GetManifoldByIndexInternal(j); + contactManifold = world.GetDispatcher().GetManifoldByIndexInternal(j); int numContacts = contactManifold.GetNumContacts(); if (numContacts == 0) continue; - CollisionObject objA = contactManifold.GetBody0() as CollisionObject; - CollisionObject objB = contactManifold.GetBody1() as CollisionObject; + objA = contactManifold.GetBody0() as CollisionObject; + objB = contactManifold.GetBody1() as CollisionObject; - ManifoldPoint manifoldPoint = contactManifold.GetContactPoint(0); - IndexedVector3 contactPoint = manifoldPoint.GetPositionWorldOnB(); - IndexedVector3 contactNormal = -manifoldPoint.m_normalWorldOnB; // make relative to A + manifoldPoint = contactManifold.GetContactPoint(0); + //IndexedVector3 contactPoint = manifoldPoint.GetPositionWorldOnB(); + // IndexedVector3 contactNormal = -manifoldPoint.m_normalWorldOnB; // make relative to A - RecordCollision(world, objA, objB, contactPoint, contactNormal); + RecordCollision(this, objA, objB, manifoldPoint.GetPositionWorldOnB(), -manifoldPoint.m_normalWorldOnB, manifoldPoint.GetDistance()); m_collisionsThisFrame ++; if (m_collisionsThisFrame >= 9999999) break; @@ -1517,23 +2017,91 @@ private sealed class BulletConstraintXNA : BulletConstraint } + foreach (GhostObject ghostObject in specialCollisionObjects.Values) + { + pairCachingGhostObject = ghostObject as PairCachingGhostObject; + if (pairCachingGhostObject != null) + { + RecordGhostCollisions(pairCachingGhostObject); + } + + } + + + updatedEntityCount = LastEntityProperty; + updatedEntities = UpdatedObjects; + + collidersCount = LastCollisionDesc; + colliders = UpdatedCollisions; + } else { //if (updatedEntities is null) - updatedEntities = new List(); - updatedEntityCount = 0; - //if (colliders is null) - colliders = new List(); - collidersCount = 0; + //updatedEntities = new List(); + //updatedEntityCount = 0; + + + //collidersCount = 0; + + updatedEntities = new EntityProperties[0]; + + + colliders = new CollisionDesc[0]; + } return numSimSteps; } - - private static void RecordCollision(CollisionWorld world, CollisionObject objA, CollisionObject objB, IndexedVector3 contact, IndexedVector3 norm) + public void RecordGhostCollisions(PairCachingGhostObject obj) { - + IOverlappingPairCache cache = obj.GetOverlappingPairCache(); + ObjectArray pairs = cache.GetOverlappingPairArray(); + + DiscreteDynamicsWorld world = (PhysicsScene.World as BulletWorldXNA).world; + PersistentManifoldArray manifoldArray = new PersistentManifoldArray(); + BroadphasePair collisionPair; + PersistentManifold contactManifold; + + CollisionObject objA; + CollisionObject objB; + + ManifoldPoint pt; + + int numPairs = pairs.Count; + + for (int i = 0; i < numPairs; i++) + { + manifoldArray.Clear(); + if (LastCollisionDesc < UpdatedCollisions.Length) + break; + collisionPair = world.GetPairCache().FindPair(pairs[i].m_pProxy0, pairs[i].m_pProxy1); + if (collisionPair == null) + continue; + + collisionPair.m_algorithm.GetAllContactManifolds(manifoldArray); + for (int j = 0; j < manifoldArray.Count; j++) + { + contactManifold = manifoldArray[j]; + int numContacts = contactManifold.GetNumContacts(); + objA = contactManifold.GetBody0() as CollisionObject; + objB = contactManifold.GetBody1() as CollisionObject; + for (int p = 0; p < numContacts; p++) + { + pt = contactManifold.GetContactPoint(p); + if (pt.GetDistance() < 0.0f) + { + RecordCollision(this, objA, objB, pt.GetPositionWorldOnA(), -pt.m_normalWorldOnB,pt.GetDistance()); + break; + } + } + } + } + + } + private static void RecordCollision(BSAPIXNA world, CollisionObject objA, CollisionObject objB, IndexedVector3 contact, IndexedVector3 norm, float penetration) + { + IndexedVector3 contactNormal = norm; if ((objA.GetCollisionFlags() & BulletXNA.BulletCollision.CollisionFlags.BS_WANTS_COLLISIONS) == 0 && (objB.GetCollisionFlags() & BulletXNA.BulletCollision.CollisionFlags.BS_WANTS_COLLISIONS) == 0) @@ -1550,31 +2118,34 @@ private sealed class BulletConstraintXNA : BulletConstraint contactNormal = -contactNormal; } - ulong collisionID = ((ulong) idA << 32) | idB; + //ulong collisionID = ((ulong) idA << 32) | idB; - BulletXNA.CollisionDesc cDesc = new BulletXNA.CollisionDesc() + CollisionDesc cDesc = new CollisionDesc() { aID = idA, bID = idB, - point = contact, - normal = contactNormal + point = new Vector3(contact.X,contact.Y,contact.Z), + normal = new Vector3(contactNormal.X,contactNormal.Y,contactNormal.Z), + penetration = penetration + }; - world.UpdatedCollisions.Add(cDesc); + if (world.LastCollisionDesc < world.UpdatedCollisions.Length) + world.UpdatedCollisions[world.LastCollisionDesc++] = (cDesc); m_collisionsThisFrame++; } - private static EntityProperties GetDebugProperties(BulletWorld pWorld, BulletBody pBody) + private static EntityProperties GetDebugProperties(BulletWorld pWorld, BulletBody pCollisionObject) { EntityProperties ent = new EntityProperties(); - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; - RigidBody body = ((BulletBodyXNA)pBody).rigidBody; - IndexedMatrix transform = body.GetWorldTransform(); - IndexedVector3 LinearVelocity = body.GetInterpolationLinearVelocity(); - IndexedVector3 AngularVelocity = body.GetInterpolationAngularVelocity(); + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; + CollisionObject collisionObject = (pCollisionObject as BulletBodyXNA).rigidBody; + IndexedMatrix transform = collisionObject.GetWorldTransform(); + IndexedVector3 LinearVelocity = collisionObject.GetInterpolationLinearVelocity(); + IndexedVector3 AngularVelocity = collisionObject.GetInterpolationAngularVelocity(); IndexedQuaternion rotation = transform.GetRotation(); ent.Acceleration = Vector3.Zero; - ent.ID = (uint)body.GetUserPointer(); + ent.ID = (uint)collisionObject.GetUserPointer(); ent.Position = new Vector3(transform._origin.X,transform._origin.Y,transform._origin.Z); ent.Rotation = new Quaternion(rotation.X,rotation.Y,rotation.Z,rotation.W); ent.Velocity = new Vector3(LinearVelocity.X, LinearVelocity.Y, LinearVelocity.Z); @@ -1582,28 +2153,29 @@ private sealed class BulletConstraintXNA : BulletConstraint return ent; } - public override bool UpdateParameter(BulletWorld world, uint localID, String parm, float value) { /* TODO */ return false; } + public override bool UpdateParameter(BulletWorld world, uint localID, String parm, float value) { /* TODO */ + return false; } public override Vector3 GetLocalScaling(BulletShape pShape) { - CollisionShape shape = ((BulletShapeXNA)pShape).shape; + CollisionShape shape = (pShape as BulletShapeXNA).shape; IndexedVector3 scale = shape.GetLocalScaling(); return new Vector3(scale.X,scale.Y,scale.Z); } public bool RayCastGround(BulletWorld pWorld, Vector3 _RayOrigin, float pRayHeight, BulletBody NotMe) { - DiscreteDynamicsWorld world = ((BulletWorldXNA)pWorld).world; + DiscreteDynamicsWorld world = (pWorld as BulletWorldXNA).world; if (world != null) { if (NotMe is BulletBodyXNA && NotMe.HasPhysicalBody) { - CollisionObject AvoidBody = ((BulletBodyXNA)NotMe).body; - + CollisionObject AvoidBody = (NotMe as BulletBodyXNA).body; + IndexedVector3 rOrigin = new IndexedVector3(_RayOrigin.X, _RayOrigin.Y, _RayOrigin.Z); IndexedVector3 rEnd = new IndexedVector3(_RayOrigin.X, _RayOrigin.Y, _RayOrigin.Z - pRayHeight); using ( - ClosestNotMeRayResultCallback rayCallback = + ClosestNotMeRayResultCallback rayCallback = new ClosestNotMeRayResultCallback(rOrigin, rEnd, AvoidBody) ) { @@ -1619,4 +2191,130 @@ private sealed class BulletConstraintXNA : BulletConstraint return false; } } + + + + + public class SimMotionState : DefaultMotionState + { + public RigidBody Rigidbody; + public Vector3 ZeroVect; + + private IndexedMatrix m_xform; + + private EntityProperties m_properties; + private EntityProperties m_lastProperties; + private BSAPIXNA m_world; + + const float POSITION_TOLERANCE = 0.05f; + const float VELOCITY_TOLERANCE = 0.001f; + const float ROTATION_TOLERANCE = 0.01f; + const float ANGULARVELOCITY_TOLERANCE = 0.01f; + + public SimMotionState(BSAPIXNA pWorld, uint id, IndexedMatrix starTransform, object frameUpdates) + { + IndexedQuaternion OrientationQuaterion = starTransform.GetRotation(); + m_properties = new EntityProperties() + { + ID = id, + Position = new Vector3(starTransform._origin.X, starTransform._origin.Y,starTransform._origin.Z), + Rotation = new Quaternion(OrientationQuaterion.X,OrientationQuaterion.Y,OrientationQuaterion.Z,OrientationQuaterion.W) + }; + m_lastProperties = new EntityProperties() + { + ID = id, + Position = new Vector3(starTransform._origin.X, starTransform._origin.Y, starTransform._origin.Z), + Rotation = new Quaternion(OrientationQuaterion.X, OrientationQuaterion.Y, OrientationQuaterion.Z, OrientationQuaterion.W) + }; + m_world = pWorld; + m_xform = starTransform; + } + + public override void GetWorldTransform(out IndexedMatrix worldTrans) + { + worldTrans = m_xform; + } + + public override void SetWorldTransform(IndexedMatrix worldTrans) + { + SetWorldTransform(ref worldTrans); + } + + public override void SetWorldTransform(ref IndexedMatrix worldTrans) + { + SetWorldTransform(ref worldTrans, false); + } + public void SetWorldTransform(ref IndexedMatrix worldTrans, bool force) + { + m_xform = worldTrans; + // Put the new transform into m_properties + IndexedQuaternion OrientationQuaternion = m_xform.GetRotation(); + IndexedVector3 LinearVelocityVector = Rigidbody.GetLinearVelocity(); + IndexedVector3 AngularVelocityVector = Rigidbody.GetAngularVelocity(); + m_properties.Position = new Vector3(m_xform._origin.X, m_xform._origin.Y, m_xform._origin.Z); + m_properties.Rotation = new Quaternion(OrientationQuaternion.X, OrientationQuaternion.Y, + OrientationQuaternion.Z, OrientationQuaternion.W); + // A problem with stock Bullet is that we don't get an event when an object is deactivated. + // This means that the last non-zero values for linear and angular velocity + // are left in the viewer who does dead reconning and the objects look like + // they float off. + // BulletSim ships with a patch to Bullet which creates such an event. + m_properties.Velocity = new Vector3(LinearVelocityVector.X, LinearVelocityVector.Y, LinearVelocityVector.Z); + m_properties.RotationalVelocity = new Vector3(AngularVelocityVector.X, AngularVelocityVector.Y, AngularVelocityVector.Z); + + if (force + + || !AlmostEqual(ref m_lastProperties.Position, ref m_properties.Position, POSITION_TOLERANCE) + || !AlmostEqual(ref m_properties.Rotation, ref m_lastProperties.Rotation, ROTATION_TOLERANCE) + // If the Velocity and AngularVelocity are zero, most likely the object has + // been deactivated. If they both are zero and they have become zero recently, + // make sure a property update is sent so the zeros make it to the viewer. + || ((m_properties.Velocity == ZeroVect && m_properties.RotationalVelocity == ZeroVect) + && + (m_properties.Velocity != m_lastProperties.Velocity || + m_properties.RotationalVelocity != m_lastProperties.RotationalVelocity)) + // If Velocity and AngularVelocity are non-zero but have changed, send an update. + || !AlmostEqual(ref m_properties.Velocity, ref m_lastProperties.Velocity, VELOCITY_TOLERANCE) + || + !AlmostEqual(ref m_properties.RotationalVelocity, ref m_lastProperties.RotationalVelocity, + ANGULARVELOCITY_TOLERANCE) + ) + + + { + // Add this update to the list of updates for this frame. + m_lastProperties = m_properties; + if (m_world.LastEntityProperty < m_world.UpdatedObjects.Length) + m_world.UpdatedObjects[m_world.LastEntityProperty++]=(m_properties); + + //(*m_updatesThisFrame)[m_properties.ID] = &m_properties; + } + + + + + } + public override void SetRigidBody(RigidBody body) + { + Rigidbody = body; + } + internal static bool AlmostEqual(ref Vector3 v1, ref Vector3 v2, float nEpsilon) + { + return + (((v1.X - nEpsilon) < v2.X) && (v2.X < (v1.X + nEpsilon))) && + (((v1.Y - nEpsilon) < v2.Y) && (v2.Y < (v1.Y + nEpsilon))) && + (((v1.Z - nEpsilon) < v2.Z) && (v2.Z < (v1.Z + nEpsilon))); + } + + internal static bool AlmostEqual(ref Quaternion v1, ref Quaternion v2, float nEpsilon) + { + return + (((v1.X - nEpsilon) < v2.X) && (v2.X < (v1.X + nEpsilon))) && + (((v1.Y - nEpsilon) < v2.Y) && (v2.Y < (v1.Y + nEpsilon))) && + (((v1.Z - nEpsilon) < v2.Z) && (v2.Z < (v1.Z + nEpsilon))) && + (((v1.W - nEpsilon) < v2.W) && (v2.W < (v1.W + nEpsilon))); + } + + } } + diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs b/OpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs new file mode 100755 index 0000000000..ac8c30c9cd --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSActorAvatarMove.cs @@ -0,0 +1,351 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyrightD + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OpenSim.Region.Physics.Manager; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSActorAvatarMove : BSActor +{ + BSVMotor m_velocityMotor; + + // Set to true if we think we're going up stairs. + // This state is remembered because collisions will turn on and off as we go up stairs. + int m_walkingUpStairs; + float m_lastStepUp; + + public BSActorAvatarMove(BSScene physicsScene, BSPhysObject pObj, string actorName) + : base(physicsScene, pObj, actorName) + { + m_velocityMotor = null; + m_walkingUpStairs = 0; + m_physicsScene.DetailLog("{0},BSActorAvatarMove,constructor", m_controllingPrim.LocalID); + } + + // BSActor.isActive + public override bool isActive + { + get { return Enabled && m_controllingPrim.IsPhysicallyActive; } + } + + // Release any connections and resources used by the actor. + // BSActor.Dispose() + public override void Dispose() + { + Enabled = false; + } + + // Called when physical parameters (properties set in Bullet) need to be re-applied. + // Called at taint-time. + // BSActor.Refresh() + public override void Refresh() + { + m_physicsScene.DetailLog("{0},BSActorAvatarMove,refresh", m_controllingPrim.LocalID); + + // If the object is physically active, add the hoverer prestep action + if (isActive) + { + ActivateAvatarMove(); + } + else + { + DeactivateAvatarMove(); + } + } + + // The object's physical representation is being rebuilt so pick up any physical dependencies (constraints, ...). + // Register a prestep action to restore physical requirements before the next simulation step. + // Called at taint-time. + // BSActor.RemoveDependencies() + public override void RemoveDependencies() + { + // Nothing to do for the hoverer since it is all software at pre-step action time. + } + + // Usually called when target velocity changes to set the current velocity and the target + // into the movement motor. + public void SetVelocityAndTarget(OMV.Vector3 vel, OMV.Vector3 targ, bool inTaintTime) + { + m_physicsScene.TaintedObject(inTaintTime, "BSActorAvatarMove.setVelocityAndTarget", delegate() + { + if (m_velocityMotor != null) + { + m_velocityMotor.Reset(); + m_velocityMotor.SetTarget(targ); + m_velocityMotor.SetCurrent(vel); + m_velocityMotor.Enabled = true; + } + }); + } + + // If a hover motor has not been created, create one and start the hovering. + private void ActivateAvatarMove() + { + if (m_velocityMotor == null) + { + // Infinite decay and timescale values so motor only changes current to target values. + m_velocityMotor = new BSVMotor("BSCharacter.Velocity", + 0.2f, // time scale + BSMotor.Infinite, // decay time scale + 1f // efficiency + ); + // _velocityMotor.PhysicsScene = PhysicsScene; // DEBUG DEBUG so motor will output detail log messages. + SetVelocityAndTarget(m_controllingPrim.RawVelocity, m_controllingPrim.TargetVelocity, true /* inTaintTime */); + + m_physicsScene.BeforeStep += Mover; + + m_walkingUpStairs = 0; + } + } + + private void DeactivateAvatarMove() + { + if (m_velocityMotor != null) + { + m_physicsScene.BeforeStep -= Mover; + m_velocityMotor = null; + } + } + + // Called just before the simulation step. Update the vertical position for hoverness. + private void Mover(float timeStep) + { + // Don't do movement while the object is selected. + if (!isActive) + return; + + // TODO: Decide if the step parameters should be changed depending on the avatar's + // state (flying, colliding, ...). There is code in ODE to do this. + + // COMMENTARY: when the user is making the avatar walk, except for falling, the velocity + // specified for the avatar is the one that should be used. For falling, if the avatar + // is not flying and is not colliding then it is presumed to be falling and the Z + // component is not fooled with (thus allowing gravity to do its thing). + // When the avatar is standing, though, the user has specified a velocity of zero and + // the avatar should be standing. But if the avatar is pushed by something in the world + // (raising elevator platform, moving vehicle, ...) the avatar should be allowed to + // move. Thus, the velocity cannot be forced to zero. The problem is that small velocity + // errors can creap in and the avatar will slowly float off in some direction. + // So, the problem is that, when an avatar is standing, we cannot tell creaping error + // from real pushing. + // The code below uses whether the collider is static or moving to decide whether to zero motion. + + m_velocityMotor.Step(timeStep); + m_controllingPrim.IsStationary = false; + + // If we're not supposed to be moving, make sure things are zero. + if (m_velocityMotor.ErrorIsZero() && m_velocityMotor.TargetValue == OMV.Vector3.Zero) + { + // The avatar shouldn't be moving + m_velocityMotor.Zero(); + + if (m_controllingPrim.IsColliding) + { + // If we are colliding with a stationary object, presume we're standing and don't move around + if (!m_controllingPrim.ColliderIsMoving) + { + m_physicsScene.DetailLog("{0},BSCharacter.MoveMotor,collidingWithStationary,zeroingMotion", m_controllingPrim.LocalID); + m_controllingPrim.IsStationary = true; + m_controllingPrim.ZeroMotion(true /* inTaintTime */); + } + + // Standing has more friction on the ground + if (m_controllingPrim.Friction != BSParam.AvatarStandingFriction) + { + m_controllingPrim.Friction = BSParam.AvatarStandingFriction; + m_physicsScene.PE.SetFriction(m_controllingPrim.PhysBody, m_controllingPrim.Friction); + } + } + else + { + if (m_controllingPrim.Flying) + { + // Flying and not collising and velocity nearly zero. + m_controllingPrim.ZeroMotion(true /* inTaintTime */); + } + } + + m_physicsScene.DetailLog("{0},BSCharacter.MoveMotor,taint,stopping,target={1},colliding={2}", + m_controllingPrim.LocalID, m_velocityMotor.TargetValue, m_controllingPrim.IsColliding); + } + else + { + // Supposed to be moving. + OMV.Vector3 stepVelocity = m_velocityMotor.CurrentValue; + + if (m_controllingPrim.Friction != BSParam.AvatarFriction) + { + // Probably starting up walking. Set friction to moving friction. + m_controllingPrim.Friction = BSParam.AvatarFriction; + m_physicsScene.PE.SetFriction(m_controllingPrim.PhysBody, m_controllingPrim.Friction); + } + + // If falling, we keep the world's downward vector no matter what the other axis specify. + // The check for RawVelocity.Z < 0 makes jumping work (temporary upward force). + if (!m_controllingPrim.Flying && !m_controllingPrim.IsColliding) + { + if (m_controllingPrim.RawVelocity.Z < 0) + stepVelocity.Z = m_controllingPrim.RawVelocity.Z; + // DetailLog("{0},BSCharacter.MoveMotor,taint,overrideStepZWithWorldZ,stepVel={1}", LocalID, stepVelocity); + } + + // 'stepVelocity' is now the speed we'd like the avatar to move in. Turn that into an instantanous force. + OMV.Vector3 moveForce = (stepVelocity - m_controllingPrim.RawVelocity) * m_controllingPrim.Mass; + + // Add special movement force to allow avatars to walk up stepped surfaces. + moveForce += WalkUpStairs(); + + m_physicsScene.DetailLog("{0},BSCharacter.MoveMotor,move,stepVel={1},vel={2},mass={3},moveForce={4}", + m_controllingPrim.LocalID, stepVelocity, m_controllingPrim.RawVelocity, m_controllingPrim.Mass, moveForce); + m_physicsScene.PE.ApplyCentralImpulse(m_controllingPrim.PhysBody, moveForce); + } + } + + // Decide if the character is colliding with a low object and compute a force to pop the + // avatar up so it can walk up and over the low objects. + private OMV.Vector3 WalkUpStairs() + { + OMV.Vector3 ret = OMV.Vector3.Zero; + + m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs,IsColliding={1},flying={2},targSpeed={3},collisions={4},avHeight={5}", + m_controllingPrim.LocalID, m_controllingPrim.IsColliding, m_controllingPrim.Flying, + m_controllingPrim.TargetVelocitySpeed, m_controllingPrim.CollisionsLastTick.Count, m_controllingPrim.Size.Z); + // This test is done if moving forward, not flying and is colliding with something. + // Check for stairs climbing if colliding, not flying and moving forward + if ( m_controllingPrim.IsColliding + && !m_controllingPrim.Flying + && m_controllingPrim.TargetVelocitySpeed > 0.1f ) + { + // The range near the character's feet where we will consider stairs + // float nearFeetHeightMin = m_controllingPrim.RawPosition.Z - (m_controllingPrim.Size.Z / 2f) + 0.05f; + // Note: there is a problem with the computation of the capsule height. Thus RawPosition is off + // from the height. Revisit size and this computation when height is scaled properly. + float nearFeetHeightMin = m_controllingPrim.RawPosition.Z - (m_controllingPrim.Size.Z / 2f) - 0.05f; + float nearFeetHeightMax = nearFeetHeightMin + BSParam.AvatarStepHeight; + + // Look for a collision point that is near the character's feet and is oriented the same as the charactor is. + // Find the highest 'good' collision. + OMV.Vector3 highestTouchPosition = OMV.Vector3.Zero; + foreach (KeyValuePair kvp in m_controllingPrim.CollisionsLastTick.m_objCollisionList) + { + // Don't care about collisions with the terrain + if (kvp.Key > m_physicsScene.TerrainManager.HighestTerrainID) + { + OMV.Vector3 touchPosition = kvp.Value.Position; + m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs,min={1},max={2},touch={3}", + m_controllingPrim.LocalID, nearFeetHeightMin, nearFeetHeightMax, touchPosition); + if (touchPosition.Z >= nearFeetHeightMin && touchPosition.Z <= nearFeetHeightMax) + { + // This contact is within the 'near the feet' range. + // The normal should be our contact point to the object so it is pointing away + // thus the difference between our facing orientation and the normal should be small. + OMV.Vector3 directionFacing = OMV.Vector3.UnitX * m_controllingPrim.RawOrientation; + OMV.Vector3 touchNormal = OMV.Vector3.Normalize(kvp.Value.SurfaceNormal); + float diff = Math.Abs(OMV.Vector3.Distance(directionFacing, touchNormal)); + if (diff < BSParam.AvatarStepApproachFactor) + { + if (highestTouchPosition.Z < touchPosition.Z) + highestTouchPosition = touchPosition; + } + } + } + } + m_walkingUpStairs = 0; + // If there is a good step sensing, move the avatar over the step. + if (highestTouchPosition != OMV.Vector3.Zero) + { + // Remember that we are going up stairs. This is needed because collisions + // will stop when we move up so this smoothes out that effect. + m_walkingUpStairs = BSParam.AvatarStepSmoothingSteps; + + m_lastStepUp = highestTouchPosition.Z - nearFeetHeightMin; + ret = ComputeStairCorrection(m_lastStepUp); + m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs,touchPos={1},nearFeetMin={2},ret={3}", + m_controllingPrim.LocalID, highestTouchPosition, nearFeetHeightMin, ret); + } + } + else + { + // If we used to be going up stairs but are not now, smooth the case where collision goes away while + // we are bouncing up the stairs. + if (m_walkingUpStairs > 0) + { + m_walkingUpStairs--; + ret = ComputeStairCorrection(m_lastStepUp); + } + } + + return ret; + } + + private OMV.Vector3 ComputeStairCorrection(float stepUp) + { + OMV.Vector3 ret = OMV.Vector3.Zero; + OMV.Vector3 displacement = OMV.Vector3.Zero; + + if (stepUp > 0f) + { + // Found the stairs contact point. Push up a little to raise the character. + if (BSParam.AvatarStepForceFactor > 0f) + { + float upForce = stepUp * m_controllingPrim.Mass * BSParam.AvatarStepForceFactor; + ret = new OMV.Vector3(0f, 0f, upForce); + } + + // Also move the avatar up for the new height + if (BSParam.AvatarStepUpCorrectionFactor > 0f) + { + // Move the avatar up related to the height of the collision + displacement = new OMV.Vector3(0f, 0f, stepUp * BSParam.AvatarStepUpCorrectionFactor); + m_controllingPrim.ForcePosition = m_controllingPrim.RawPosition + displacement; + } + else + { + if (BSParam.AvatarStepUpCorrectionFactor < 0f) + { + // Move the avatar up about the specified step height + displacement = new OMV.Vector3(0f, 0f, BSParam.AvatarStepHeight); + m_controllingPrim.ForcePosition = m_controllingPrim.RawPosition + displacement; + } + } + m_physicsScene.DetailLog("{0},BSCharacter.WalkUpStairs.ComputeStairCorrection,disp={1},force={2}", + m_controllingPrim.LocalID, displacement, ret); + + } + return ret; + } +} +} + + diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSActorHover.cs b/OpenSim/Region/Physics/BulletSPlugin/BSActorHover.cs new file mode 100755 index 0000000000..8a79809fa5 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSActorHover.cs @@ -0,0 +1,173 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyrightD + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OpenSim.Region.Physics.Manager; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSActorHover : BSActor +{ + private BSFMotor m_hoverMotor; + + public BSActorHover(BSScene physicsScene, BSPhysObject pObj, string actorName) + : base(physicsScene, pObj, actorName) + { + m_hoverMotor = null; + m_physicsScene.DetailLog("{0},BSActorHover,constructor", m_controllingPrim.LocalID); + } + + // BSActor.isActive + public override bool isActive + { + get { return Enabled; } + } + + // Release any connections and resources used by the actor. + // BSActor.Dispose() + public override void Dispose() + { + Enabled = false; + } + + // Called when physical parameters (properties set in Bullet) need to be re-applied. + // Called at taint-time. + // BSActor.Refresh() + public override void Refresh() + { + m_physicsScene.DetailLog("{0},BSActorHover,refresh", m_controllingPrim.LocalID); + + // If not active any more, turn me off + if (!m_controllingPrim.HoverActive) + { + SetEnabled(false); + } + + // If the object is physically active, add the hoverer prestep action + if (isActive) + { + ActivateHover(); + } + else + { + DeactivateHover(); + } + } + + // The object's physical representation is being rebuilt so pick up any physical dependencies (constraints, ...). + // Register a prestep action to restore physical requirements before the next simulation step. + // Called at taint-time. + // BSActor.RemoveDependencies() + public override void RemoveDependencies() + { + // Nothing to do for the hoverer since it is all software at pre-step action time. + } + + // If a hover motor has not been created, create one and start the hovering. + private void ActivateHover() + { + if (m_hoverMotor == null) + { + // Turning the target on + m_hoverMotor = new BSFMotor("BSActorHover", + m_controllingPrim.HoverTau, // timeScale + BSMotor.Infinite, // decay time scale + 1f // efficiency + ); + m_hoverMotor.SetTarget(ComputeCurrentHoverHeight()); + m_hoverMotor.SetCurrent(m_controllingPrim.RawPosition.Z); + m_hoverMotor.PhysicsScene = m_physicsScene; // DEBUG DEBUG so motor will output detail log messages. + + m_physicsScene.BeforeStep += Hoverer; + } + } + + private void DeactivateHover() + { + if (m_hoverMotor != null) + { + m_physicsScene.BeforeStep -= Hoverer; + m_hoverMotor = null; + } + } + + // Called just before the simulation step. Update the vertical position for hoverness. + private void Hoverer(float timeStep) + { + // Don't do hovering while the object is selected. + if (!isActive) + return; + + m_hoverMotor.SetCurrent(m_controllingPrim.RawPosition.Z); + m_hoverMotor.SetTarget(ComputeCurrentHoverHeight()); + float targetHeight = m_hoverMotor.Step(timeStep); + + // 'targetHeight' is where we'd like the Z of the prim to be at this moment. + // Compute the amount of force to push us there. + float moveForce = (targetHeight - m_controllingPrim.RawPosition.Z) * m_controllingPrim.RawMass; + // Undo anything the object thinks it's doing at the moment + moveForce = -m_controllingPrim.RawVelocity.Z * m_controllingPrim.Mass; + + m_physicsScene.PE.ApplyCentralImpulse(m_controllingPrim.PhysBody, new OMV.Vector3(0f, 0f, moveForce)); + m_physicsScene.DetailLog("{0},BSPrim.Hover,move,targHt={1},moveForce={2},mass={3}", + m_controllingPrim.LocalID, targetHeight, moveForce, m_controllingPrim.RawMass); + } + + // Based on current position, determine what we should be hovering at now. + // Must recompute often. What if we walked offa cliff> + private float ComputeCurrentHoverHeight() + { + float ret = m_controllingPrim.HoverHeight; + float groundHeight = m_physicsScene.TerrainManager.GetTerrainHeightAtXYZ(m_controllingPrim.RawPosition); + + switch (m_controllingPrim.HoverType) + { + case PIDHoverType.Ground: + ret = groundHeight + m_controllingPrim.HoverHeight; + break; + case PIDHoverType.GroundAndWater: + float waterHeight = m_physicsScene.TerrainManager.GetWaterLevelAtXYZ(m_controllingPrim.RawPosition); + if (groundHeight > waterHeight) + { + ret = groundHeight + m_controllingPrim.HoverHeight; + } + else + { + ret = waterHeight + m_controllingPrim.HoverHeight; + } + break; + } + return ret; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSActorLockAxis.cs b/OpenSim/Region/Physics/BulletSPlugin/BSActorLockAxis.cs new file mode 100755 index 0000000000..8b0fdeb7f2 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSActorLockAxis.cs @@ -0,0 +1,187 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyrightD + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSActorLockAxis : BSActor +{ + BSConstraint LockAxisConstraint = null; + + public BSActorLockAxis(BSScene physicsScene, BSPhysObject pObj, string actorName) + : base(physicsScene, pObj, actorName) + { + m_physicsScene.DetailLog("{0},BSActorLockAxis,constructor", m_controllingPrim.LocalID); + LockAxisConstraint = null; + } + + // BSActor.isActive + public override bool isActive + { + get { return Enabled && m_controllingPrim.IsPhysicallyActive; } + } + + // Release any connections and resources used by the actor. + // BSActor.Dispose() + public override void Dispose() + { + RemoveAxisLockConstraint(); + } + + // Called when physical parameters (properties set in Bullet) need to be re-applied. + // Called at taint-time. + // BSActor.Refresh() + public override void Refresh() + { + m_physicsScene.DetailLog("{0},BSActorLockAxis,refresh,lockedAxis={1},enabled={2},pActive={3}", + m_controllingPrim.LocalID, m_controllingPrim.LockedAngularAxis, Enabled, m_controllingPrim.IsPhysicallyActive); + // If all the axis are free, we don't need to exist + if (m_controllingPrim.LockedAngularAxis == m_controllingPrim.LockedAxisFree) + { + Enabled = false; + } + + // If the object is physically active, add the axis locking constraint + if (isActive) + { + AddAxisLockConstraint(); + } + else + { + RemoveAxisLockConstraint(); + } + } + + // The object's physical representation is being rebuilt so pick up any physical dependencies (constraints, ...). + // Register a prestep action to restore physical requirements before the next simulation step. + // Called at taint-time. + // BSActor.RemoveDependencies() + public override void RemoveDependencies() + { + if (LockAxisConstraint != null) + { + // If a constraint is set up, remove it from the physical scene + RemoveAxisLockConstraint(); + // Schedule a call before the next simulation step to restore the constraint. + m_physicsScene.PostTaintObject("BSActorLockAxis:" + ActorName, m_controllingPrim.LocalID, delegate() + { + Refresh(); + }); + } + } + + private void AddAxisLockConstraint() + { + if (LockAxisConstraint == null) + { + // Lock that axis by creating a 6DOF constraint that has one end in the world and + // the other in the object. + // http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?p=20817 + // http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?p=26380 + + // Remove any existing axis constraint (just to be sure) + RemoveAxisLockConstraint(); + + BSConstraint6Dof axisConstrainer = new BSConstraint6Dof(m_physicsScene.World, m_controllingPrim.PhysBody, + OMV.Vector3.Zero, OMV.Quaternion.Identity, + false /* useLinearReferenceFrameB */, true /* disableCollisionsBetweenLinkedBodies */); + LockAxisConstraint = axisConstrainer; + m_physicsScene.Constraints.AddConstraint(LockAxisConstraint); + + // The constraint is tied to the world and oriented to the prim. + + // Free to move linearly in the region + OMV.Vector3 linearLow = OMV.Vector3.Zero; + OMV.Vector3 linearHigh = m_physicsScene.TerrainManager.DefaultRegionSize; + if (m_controllingPrim.LockedLinearAxis.X != BSPhysObject.FreeAxis) + { + linearLow.X = m_controllingPrim.RawPosition.X; + linearHigh.X = m_controllingPrim.RawPosition.X; + } + if (m_controllingPrim.LockedLinearAxis.Y != BSPhysObject.FreeAxis) + { + linearLow.Y = m_controllingPrim.RawPosition.Y; + linearHigh.Y = m_controllingPrim.RawPosition.Y; + } + if (m_controllingPrim.LockedLinearAxis.Z != BSPhysObject.FreeAxis) + { + linearLow.Z = m_controllingPrim.RawPosition.Z; + linearHigh.Z = m_controllingPrim.RawPosition.Z; + } + axisConstrainer.SetLinearLimits(linearLow, linearHigh); + + // Angular with some axis locked + float fPI = (float)Math.PI; + OMV.Vector3 angularLow = new OMV.Vector3(-fPI, -fPI, -fPI); + OMV.Vector3 angularHigh = new OMV.Vector3(fPI, fPI, fPI); + if (m_controllingPrim.LockedAngularAxis.X != BSPhysObject.FreeAxis) + { + angularLow.X = 0f; + angularHigh.X = 0f; + } + if (m_controllingPrim.LockedAngularAxis.Y != BSPhysObject.FreeAxis) + { + angularLow.Y = 0f; + angularHigh.Y = 0f; + } + if (m_controllingPrim.LockedAngularAxis.Z != BSPhysObject.FreeAxis) + { + angularLow.Z = 0f; + angularHigh.Z = 0f; + } + if (!axisConstrainer.SetAngularLimits(angularLow, angularHigh)) + { + m_physicsScene.DetailLog("{0},BSActorLockAxis.AddAxisLockConstraint,failedSetAngularLimits", m_controllingPrim.LocalID); + } + + m_physicsScene.DetailLog("{0},BSActorLockAxis.AddAxisLockConstraint,create,linLow={1},linHi={2},angLow={3},angHi={4}", + m_controllingPrim.LocalID, linearLow, linearHigh, angularLow, angularHigh); + + // Constants from one of the posts mentioned above and used in Bullet's ConstraintDemo. + axisConstrainer.TranslationalLimitMotor(true /* enable */, 5.0f, 0.1f); + + axisConstrainer.RecomputeConstraintVariables(m_controllingPrim.RawMass); + } + } + + private void RemoveAxisLockConstraint() + { + if (LockAxisConstraint != null) + { + m_physicsScene.Constraints.RemoveAndDestroyConstraint(LockAxisConstraint); + LockAxisConstraint = null; + m_physicsScene.DetailLog("{0},BSActorLockAxis.RemoveAxisLockConstraint,destroyingConstraint", m_controllingPrim.LocalID); + } + } +} +} diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSActorMoveToTarget.cs b/OpenSim/Region/Physics/BulletSPlugin/BSActorMoveToTarget.cs new file mode 100755 index 0000000000..75ff24ed0b --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSActorMoveToTarget.cs @@ -0,0 +1,157 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyrightD + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OpenSim.Region.Physics.Manager; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSActorMoveToTarget : BSActor +{ + private BSVMotor m_targetMotor; + + public BSActorMoveToTarget(BSScene physicsScene, BSPhysObject pObj, string actorName) + : base(physicsScene, pObj, actorName) + { + m_targetMotor = null; + m_physicsScene.DetailLog("{0},BSActorMoveToTarget,constructor", m_controllingPrim.LocalID); + } + + // BSActor.isActive + public override bool isActive + { + get { return Enabled; } + } + + // Release any connections and resources used by the actor. + // BSActor.Dispose() + public override void Dispose() + { + Enabled = false; + } + + // Called when physical parameters (properties set in Bullet) need to be re-applied. + // Called at taint-time. + // BSActor.Refresh() + public override void Refresh() + { + m_physicsScene.DetailLog("{0},BSActorMoveToTarget,refresh,enabled={1},active={2},target={3},tau={4}", + m_controllingPrim.LocalID, Enabled, m_controllingPrim.MoveToTargetActive, + m_controllingPrim.MoveToTargetTarget, m_controllingPrim.MoveToTargetTau ); + + // If not active any more... + if (!m_controllingPrim.MoveToTargetActive) + { + Enabled = false; + } + + if (isActive) + { + ActivateMoveToTarget(); + } + else + { + DeactivateMoveToTarget(); + } + } + + // The object's physical representation is being rebuilt so pick up any physical dependencies (constraints, ...). + // Register a prestep action to restore physical requirements before the next simulation step. + // Called at taint-time. + // BSActor.RemoveDependencies() + public override void RemoveDependencies() + { + // Nothing to do for the moveToTarget since it is all software at pre-step action time. + } + + // If a hover motor has not been created, create one and start the hovering. + private void ActivateMoveToTarget() + { + if (m_targetMotor == null) + { + // We're taking over after this. + m_controllingPrim.ZeroMotion(true); + + m_targetMotor = new BSVMotor("BSActorMoveToTargget.Activate", + m_controllingPrim.MoveToTargetTau, // timeScale + BSMotor.Infinite, // decay time scale + 1f // efficiency + ); + m_targetMotor.PhysicsScene = m_physicsScene; // DEBUG DEBUG so motor will output detail log messages. + m_targetMotor.SetTarget(m_controllingPrim.MoveToTargetTarget); + m_targetMotor.SetCurrent(m_controllingPrim.RawPosition); + + m_physicsScene.BeforeStep += Mover; + } + } + + private void DeactivateMoveToTarget() + { + if (m_targetMotor != null) + { + m_physicsScene.BeforeStep -= Mover; + m_targetMotor = null; + } + } + + // Called just before the simulation step. Update the vertical position for hoverness. + private void Mover(float timeStep) + { + // Don't do hovering while the object is selected. + if (!isActive) + return; + + OMV.Vector3 origPosition = m_controllingPrim.RawPosition; // DEBUG DEBUG (for printout below) + + // 'movePosition' is where we'd like the prim to be at this moment. + OMV.Vector3 movePosition = m_controllingPrim.RawPosition + m_targetMotor.Step(timeStep); + + // If we are very close to our target, turn off the movement motor. + if (m_targetMotor.ErrorIsZero()) + { + m_physicsScene.DetailLog("{0},BSActorMoveToTarget.Mover,zeroMovement,movePos={1},pos={2},mass={3}", + m_controllingPrim.LocalID, movePosition, m_controllingPrim.RawPosition, m_controllingPrim.Mass); + m_controllingPrim.ForcePosition = m_targetMotor.TargetValue; + // Setting the position does not cause the physics engine to generate a property update. Force it. + m_physicsScene.PE.PushUpdate(m_controllingPrim.PhysBody); + } + else + { + m_controllingPrim.ForcePosition = movePosition; + // Setting the position does not cause the physics engine to generate a property update. Force it. + m_physicsScene.PE.PushUpdate(m_controllingPrim.PhysBody); + } + m_physicsScene.DetailLog("{0},BSActorMoveToTarget.Mover,move,fromPos={1},movePos={2}", m_controllingPrim.LocalID, origPosition, movePosition); + } +} +} diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSActorSetForce.cs b/OpenSim/Region/Physics/BulletSPlugin/BSActorSetForce.cs new file mode 100755 index 0000000000..96fa0b6ce9 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSActorSetForce.cs @@ -0,0 +1,137 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyrightD + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OpenSim.Region.Physics.Manager; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSActorSetForce : BSActor +{ + BSFMotor m_forceMotor; + + public BSActorSetForce(BSScene physicsScene, BSPhysObject pObj, string actorName) + : base(physicsScene, pObj, actorName) + { + m_forceMotor = null; + m_physicsScene.DetailLog("{0},BSActorSetForce,constructor", m_controllingPrim.LocalID); + } + + // BSActor.isActive + public override bool isActive + { + get { return Enabled && m_controllingPrim.IsPhysicallyActive; } + } + + // Release any connections and resources used by the actor. + // BSActor.Dispose() + public override void Dispose() + { + Enabled = false; + } + + // Called when physical parameters (properties set in Bullet) need to be re-applied. + // Called at taint-time. + // BSActor.Refresh() + public override void Refresh() + { + m_physicsScene.DetailLog("{0},BSActorSetForce,refresh", m_controllingPrim.LocalID); + + // If not active any more, get rid of me (shouldn't ever happen, but just to be safe) + if (m_controllingPrim.RawForce == OMV.Vector3.Zero) + { + m_physicsScene.DetailLog("{0},BSActorSetForce,refresh,notSetForce,removing={1}", m_controllingPrim.LocalID, ActorName); + Enabled = false; + return; + } + + // If the object is physically active, add the hoverer prestep action + if (isActive) + { + ActivateSetForce(); + } + else + { + DeactivateSetForce(); + } + } + + // The object's physical representation is being rebuilt so pick up any physical dependencies (constraints, ...). + // Register a prestep action to restore physical requirements before the next simulation step. + // Called at taint-time. + // BSActor.RemoveDependencies() + public override void RemoveDependencies() + { + // Nothing to do for the hoverer since it is all software at pre-step action time. + } + + // If a hover motor has not been created, create one and start the hovering. + private void ActivateSetForce() + { + if (m_forceMotor == null) + { + // A fake motor that might be used someday + m_forceMotor = new BSFMotor("setForce", 1f, 1f, 1f); + + m_physicsScene.BeforeStep += Mover; + } + } + + private void DeactivateSetForce() + { + if (m_forceMotor != null) + { + m_physicsScene.BeforeStep -= Mover; + m_forceMotor = null; + } + } + + // Called just before the simulation step. Update the vertical position for hoverness. + private void Mover(float timeStep) + { + // Don't do force while the object is selected. + if (!isActive) + return; + + m_physicsScene.DetailLog("{0},BSActorSetForce,preStep,force={1}", m_controllingPrim.LocalID, m_controllingPrim.RawForce); + if (m_controllingPrim.PhysBody.HasPhysicalBody) + { + m_physicsScene.PE.ApplyCentralForce(m_controllingPrim.PhysBody, m_controllingPrim.RawForce); + m_controllingPrim.ActivateIfPhysical(false); + } + + // TODO: + } +} +} + diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSActorSetTorque.cs b/OpenSim/Region/Physics/BulletSPlugin/BSActorSetTorque.cs new file mode 100755 index 0000000000..65098e1641 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSActorSetTorque.cs @@ -0,0 +1,138 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyrightD + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OpenSim.Region.Physics.Manager; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSActorSetTorque : BSActor +{ + BSFMotor m_torqueMotor; + + public BSActorSetTorque(BSScene physicsScene, BSPhysObject pObj, string actorName) + : base(physicsScene, pObj, actorName) + { + m_torqueMotor = null; + m_physicsScene.DetailLog("{0},BSActorSetTorque,constructor", m_controllingPrim.LocalID); + } + + // BSActor.isActive + public override bool isActive + { + get { return Enabled && m_controllingPrim.IsPhysicallyActive; } + } + + // Release any connections and resources used by the actor. + // BSActor.Dispose() + public override void Dispose() + { + Enabled = false; + } + + // Called when physical parameters (properties set in Bullet) need to be re-applied. + // Called at taint-time. + // BSActor.Refresh() + public override void Refresh() + { + m_physicsScene.DetailLog("{0},BSActorSetTorque,refresh,torque={1}", m_controllingPrim.LocalID, m_controllingPrim.RawTorque); + + // If not active any more, get rid of me (shouldn't ever happen, but just to be safe) + if (m_controllingPrim.RawTorque == OMV.Vector3.Zero) + { + m_physicsScene.DetailLog("{0},BSActorSetTorque,refresh,notSetTorque,disabling={1}", m_controllingPrim.LocalID, ActorName); + Enabled = false; + return; + } + + // If the object is physically active, add the hoverer prestep action + if (isActive) + { + ActivateSetTorque(); + } + else + { + DeactivateSetTorque(); + } + } + + // The object's physical representation is being rebuilt so pick up any physical dependencies (constraints, ...). + // Register a prestep action to restore physical requirements before the next simulation step. + // Called at taint-time. + // BSActor.RemoveDependencies() + public override void RemoveDependencies() + { + // Nothing to do for the hoverer since it is all software at pre-step action time. + } + + // If a hover motor has not been created, create one and start the hovering. + private void ActivateSetTorque() + { + if (m_torqueMotor == null) + { + // A fake motor that might be used someday + m_torqueMotor = new BSFMotor("setTorque", 1f, 1f, 1f); + + m_physicsScene.BeforeStep += Mover; + } + } + + private void DeactivateSetTorque() + { + if (m_torqueMotor != null) + { + m_physicsScene.BeforeStep -= Mover; + m_torqueMotor = null; + } + } + + // Called just before the simulation step. Update the vertical position for hoverness. + private void Mover(float timeStep) + { + // Don't do force while the object is selected. + if (!isActive) + return; + + m_physicsScene.DetailLog("{0},BSActorSetTorque,preStep,force={1}", m_controllingPrim.LocalID, m_controllingPrim.RawTorque); + if (m_controllingPrim.PhysBody.HasPhysicalBody) + { + m_controllingPrim.AddAngularForce(m_controllingPrim.RawTorque, false, true); + m_controllingPrim.ActivateIfPhysical(false); + } + + // TODO: + } +} +} + + diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSActors.cs b/OpenSim/Region/Physics/BulletSPlugin/BSActors.cs new file mode 100755 index 0000000000..fff63e4eb2 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSActors.cs @@ -0,0 +1,160 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyrightD + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSActorCollection +{ + private BSScene m_physicsScene { get; set; } + private Dictionary m_actors; + + public BSActorCollection(BSScene physicsScene) + { + m_physicsScene = physicsScene; + m_actors = new Dictionary(); + } + public void Add(string name, BSActor actor) + { + lock (m_actors) + { + if (!m_actors.ContainsKey(name)) + { + m_actors[name] = actor; + } + } + } + public bool RemoveAndRelease(string name) + { + bool ret = false; + lock (m_actors) + { + if (m_actors.ContainsKey(name)) + { + BSActor beingRemoved = m_actors[name]; + m_actors.Remove(name); + beingRemoved.Dispose(); + ret = true; + } + } + return ret; + } + public void Clear() + { + lock (m_actors) + { + Release(); + m_actors.Clear(); + } + } + public void Dispose() + { + Clear(); + } + public bool HasActor(string name) + { + return m_actors.ContainsKey(name); + } + public bool TryGetActor(string actorName, out BSActor theActor) + { + return m_actors.TryGetValue(actorName, out theActor); + } + public void ForEachActor(Action act) + { + lock (m_actors) + { + foreach (KeyValuePair kvp in m_actors) + act(kvp.Value); + } + } + + public void Enable(bool enabl) + { + ForEachActor(a => a.SetEnabled(enabl)); + } + public void Release() + { + ForEachActor(a => a.Dispose()); + } + public void Refresh() + { + ForEachActor(a => a.Refresh()); + } + public void RemoveDependencies() + { + ForEachActor(a => a.RemoveDependencies()); + } +} + +// ============================================================================= +/// +/// Each physical object can have 'actors' who are pushing the object around. +/// This can be used for hover, locking axis, making vehicles, etc. +/// Each physical object can have multiple actors acting on it. +/// +/// An actor usually registers itself with physics scene events (pre-step action) +/// and modifies the parameters on the host physical object. +/// +public abstract class BSActor +{ + protected BSScene m_physicsScene { get; private set; } + protected BSPhysObject m_controllingPrim { get; private set; } + public virtual bool Enabled { get; set; } + public string ActorName { get; private set; } + + public BSActor(BSScene physicsScene, BSPhysObject pObj, string actorName) + { + m_physicsScene = physicsScene; + m_controllingPrim = pObj; + ActorName = actorName; + Enabled = true; + } + + // Return 'true' if activily updating the prim + public virtual bool isActive + { + get { return Enabled; } + } + + // Turn the actor on an off. Only used by ActorCollection to set all enabled/disabled. + // Anyone else should assign true/false to 'Enabled'. + public void SetEnabled(bool setEnabled) + { + Enabled = setEnabled; + } + // Release any connections and resources used by the actor. + public abstract void Dispose(); + // Called when physical parameters (properties set in Bullet) need to be re-applied. + public abstract void Refresh(); + // The object's physical representation is being rebuilt so pick up any physical dependencies (constraints, ...). + // Register a prestep action to restore physical requirements before the next simulation step. + public abstract void RemoveDependencies(); + +} +} diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSApiTemplate.cs b/OpenSim/Region/Physics/BulletSPlugin/BSApiTemplate.cs index 8ad78ca4b2..3378c932d3 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSApiTemplate.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSApiTemplate.cs @@ -6,7 +6,7 @@ * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyrightD + * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the @@ -70,6 +70,7 @@ public enum BSPhysicsShapeType SHAPE_COMPOUND = 22, SHAPE_HEIGHTMAP = 23, SHAPE_AVATAR = 24, + SHAPE_CONVEXHULL= 25, }; // The native shapes have predefined shape hash keys @@ -87,7 +88,7 @@ public enum FixedShapeKey : ulong [StructLayout(LayoutKind.Sequential)] public struct ShapeData { - public uint ID; + public UInt32 ID; public BSPhysicsShapeType Type; public Vector3 Position; public Quaternion Rotation; @@ -111,7 +112,7 @@ public struct ShapeData [StructLayout(LayoutKind.Sequential)] public struct SweepHit { - public uint ID; + public UInt32 ID; public float Fraction; public Vector3 Normal; public Vector3 Point; @@ -119,27 +120,47 @@ public struct SweepHit [StructLayout(LayoutKind.Sequential)] public struct RaycastHit { - public uint ID; + public UInt32 ID; public float Fraction; public Vector3 Normal; } [StructLayout(LayoutKind.Sequential)] public struct CollisionDesc { - public uint aID; - public uint bID; + public UInt32 aID; + public UInt32 bID; public Vector3 point; public Vector3 normal; + public float penetration; } [StructLayout(LayoutKind.Sequential)] public struct EntityProperties { - public uint ID; + public UInt32 ID; public Vector3 Position; public Quaternion Rotation; public Vector3 Velocity; public Vector3 Acceleration; public Vector3 RotationalVelocity; + + public override string ToString() + { + StringBuilder buff = new StringBuilder(); + buff.Append(""); + return buff.ToString(); + } } // Format of this structure must match the definition in the C++ code @@ -154,32 +175,6 @@ public struct ConfigurationParameters public float collisionMargin; public float gravity; - public float XlinearDamping; - public float XangularDamping; - public float XdeactivationTime; - public float XlinearSleepingThreshold; - public float XangularSleepingThreshold; - public float XccdMotionThreshold; - public float XccdSweptSphereRadius; - public float XcontactProcessingThreshold; - - public float XterrainImplementation; - public float XterrainFriction; - public float XterrainHitFraction; - public float XterrainRestitution; - public float XterrainCollisionMargin; - - public float XavatarFriction; - public float XavatarStandingFriction; - public float XavatarDensity; - public float XavatarRestitution; - public float XavatarCapsuleWidth; - public float XavatarCapsuleDepth; - public float XavatarCapsuleHeight; - public float XavatarContactProcessingThreshold; - - public float XvehicleAngularDamping; - public float maxPersistantManifoldPoolSize; public float maxCollisionAlgorithmPoolSize; public float shouldDisableContactPoolDynamicAllocation; @@ -188,22 +183,30 @@ public struct ConfigurationParameters public float shouldSplitSimulationIslands; public float shouldEnableFrictionCaching; public float numberOfSolverIterations; + public float useSingleSidedMeshes; + public float globalContactBreakingThreshold; - public float XlinksetImplementation; - public float XlinkConstraintUseFrameOffset; - public float XlinkConstraintEnableTransMotor; - public float XlinkConstraintTransMotorMaxVel; - public float XlinkConstraintTransMotorMaxForce; - public float XlinkConstraintERP; - public float XlinkConstraintCFM; - public float XlinkConstraintSolverIterations; - - public float XphysicsLoggingFrames; + public float physicsLoggingFrames; public const float numericTrue = 1f; public const float numericFalse = 0f; } +// Parameters passed for the conversion of a mesh to a hull using Bullet's HACD library. +[StructLayout(LayoutKind.Sequential)] +public struct HACDParams +{ + // usual default values + public float maxVerticesPerHull; // 100 + public float minClusters; // 2 + public float compacityWeight; // 0.1 + public float volumeWeight; // 0.0 + public float concavity; // 100 + public float addExtraDistPoints; // false + public float addNeighboursDistPoints; // false + public float addFacesPoints; // false + public float shouldAdjustCollisionMargin; // false +} // The states a bullet collision object can have public enum ActivationState : uint @@ -238,9 +241,10 @@ public enum CollisionFlags : uint CF_DISABLE_VISUALIZE_OBJECT = 1 << 5, CF_DISABLE_SPU_COLLISION_PROCESS = 1 << 6, // Following used by BulletSim to control collisions and updates - BS_SUBSCRIBE_COLLISION_EVENTS = 1 << 10, - BS_FLOATS_ON_WATER = 1 << 11, - BS_VEHICLE_COLLISIONS = 1 << 12, + BS_SUBSCRIBE_COLLISION_EVENTS = 1 << 10, // return collision events from unmanaged to managed + BS_FLOATS_ON_WATER = 1 << 11, // the object should float at water level + BS_VEHICLE_COLLISIONS = 1 << 12, // return collisions for vehicle ground checking + BS_RETURN_ROOT_COMPOUND_SHAPE = 1 << 13, // return the pos/rot of the root shape in a compound shape BS_NONE = 0, BS_ALL = 0xFFFFFFFF }; @@ -294,7 +298,7 @@ public abstract class BSAPITemplate { // Returns the name of the underlying Bullet engine public abstract string BulletEngineName { get; } -public abstract string BulletEngineVersion { get; protected set;} +public abstract string BulletEngineVersion { get; protected set;} // Initialization and simulation public abstract BulletWorld Initialize(Vector3 maxPosition, ConfigurationParameters parms, @@ -305,7 +309,7 @@ public abstract BulletWorld Initialize(Vector3 maxPosition, ConfigurationParamet public abstract int PhysicsStep(BulletWorld world, float timeStep, int maxSubSteps, float fixedTimeStep, out int updatedEntityCount, out int collidersCount); -public abstract bool UpdateParameter(BulletWorld world, uint localID, String parm, float value); +public abstract bool UpdateParameter(BulletWorld world, UInt32 localID, String parm, float value); public abstract void Shutdown(BulletWorld sim); @@ -320,7 +324,13 @@ public abstract BulletShape CreateMeshShape(BulletWorld world, public abstract BulletShape CreateHullShape(BulletWorld world, int hullCount, float[] hulls); -public abstract BulletShape BuildHullShapeFromMesh(BulletWorld world, BulletShape meshShape); +public abstract BulletShape BuildHullShapeFromMesh(BulletWorld world, BulletShape meshShape, HACDParams parms); + +public abstract BulletShape BuildConvexHullShapeFromMesh(BulletWorld world, BulletShape meshShape); + +public abstract BulletShape CreateConvexHullShape(BulletWorld world, + int indicesCount, int[] indices, + int verticesCount, float[] vertices ); public abstract BulletShape BuildNativeShape(BulletWorld world, ShapeData shapeData); @@ -342,26 +352,28 @@ public abstract BulletShape RemoveChildShapeFromCompoundShapeIndex(BulletShape c public abstract void RemoveChildShapeFromCompoundShape(BulletShape cShape, BulletShape removeShape); +public abstract void UpdateChildTransform(BulletShape pShape, int childIndex, Vector3 pos, Quaternion rot, bool shouldRecalculateLocalAabb); + public abstract void RecalculateCompoundShapeLocalAabb(BulletShape cShape); -public abstract BulletShape DuplicateCollisionShape(BulletWorld sim, BulletShape srcShape, uint id); +public abstract BulletShape DuplicateCollisionShape(BulletWorld sim, BulletShape srcShape, UInt32 id); public abstract bool DeleteCollisionShape(BulletWorld world, BulletShape shape); public abstract CollisionObjectTypes GetBodyType(BulletBody obj); -public abstract BulletBody CreateBodyFromShape(BulletWorld sim, BulletShape shape, uint id, Vector3 pos, Quaternion rot); +public abstract BulletBody CreateBodyFromShape(BulletWorld sim, BulletShape shape, UInt32 id, Vector3 pos, Quaternion rot); -public abstract BulletBody CreateBodyWithDefaultMotionState(BulletShape shape, uint id, Vector3 pos, Quaternion rot); +public abstract BulletBody CreateBodyWithDefaultMotionState(BulletShape shape, UInt32 id, Vector3 pos, Quaternion rot); -public abstract BulletBody CreateGhostFromShape(BulletWorld sim, BulletShape shape, uint id, Vector3 pos, Quaternion rot); +public abstract BulletBody CreateGhostFromShape(BulletWorld sim, BulletShape shape, UInt32 id, Vector3 pos, Quaternion rot); public abstract void DestroyObject(BulletWorld sim, BulletBody obj); // ===================================================================================== -public abstract BulletShape CreateGroundPlaneShape(uint id, float height, float collisionMargin); +public abstract BulletShape CreateGroundPlaneShape(UInt32 id, float height, float collisionMargin); -public abstract BulletShape CreateTerrainShape(uint id, Vector3 size, float minHeight, float maxHeight, float[] heightMap, +public abstract BulletShape CreateTerrainShape(UInt32 id, Vector3 size, float minHeight, float maxHeight, float[] heightMap, float scaleFactor, float collisionMargin); // ===================================================================================== @@ -375,11 +387,38 @@ public abstract BulletConstraint Create6DofConstraintToPoint(BulletWorld world, Vector3 joinPoint, bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies); +public abstract BulletConstraint Create6DofConstraintFixed(BulletWorld world, BulletBody obj1, + Vector3 frameInBloc, Quaternion frameInBrot, + bool useLinearReferenceFrameB, bool disableCollisionsBetweenLinkedBodies); + +public abstract BulletConstraint Create6DofSpringConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 frame1loc, Quaternion frame1rot, + Vector3 frame2loc, Quaternion frame2rot, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies); + public abstract BulletConstraint CreateHingeConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, Vector3 pivotinA, Vector3 pivotinB, Vector3 axisInA, Vector3 axisInB, bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies); +public abstract BulletConstraint CreateSliderConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 frameInAloc, Quaternion frameInArot, + Vector3 frameInBloc, Quaternion frameInBrot, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies); + +public abstract BulletConstraint CreateConeTwistConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 frameInAloc, Quaternion frameInArot, + Vector3 frameInBloc, Quaternion frameInBrot, + bool disableCollisionsBetweenLinkedBodies); + +public abstract BulletConstraint CreateGearConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 axisInA, Vector3 axisInB, + float ratio, bool disableCollisionsBetweenLinkedBodies); + +public abstract BulletConstraint CreatePoint2PointConstraint(BulletWorld world, BulletBody obj1, BulletBody obj2, + Vector3 pivotInA, Vector3 pivotInB, + bool disableCollisionsBetweenLinkedBodies); + public abstract void SetConstraintEnable(BulletConstraint constrain, float numericTrueFalse); public abstract void SetConstraintNumSolverIterations(BulletConstraint constrain, float iterations); @@ -607,7 +646,7 @@ public abstract BulletConstraint GetConstraintRef(BulletBody obj, int index); public abstract int GetNumConstraintRefs(BulletBody obj); -public abstract bool SetCollisionGroupMask(BulletBody body, uint filter, uint mask); +public abstract bool SetCollisionGroupMask(BulletBody body, UInt32 filter, UInt32 mask); // ===================================================================================== // btCollisionShape entries @@ -646,17 +685,21 @@ public abstract float GetMargin(BulletShape shape); // ===================================================================================== // Debugging -public abstract void DumpRigidBody(BulletWorld sim, BulletBody collisionObject); +public virtual void DumpRigidBody(BulletWorld sim, BulletBody collisionObject) { } -public abstract void DumpCollisionShape(BulletWorld sim, BulletShape collisionShape); +public virtual void DumpCollisionShape(BulletWorld sim, BulletShape collisionShape) { } -public abstract void DumpConstraint(BulletWorld sim, BulletConstraint constrain); +public virtual void DumpConstraint(BulletWorld sim, BulletConstraint constrain) { } -public abstract void DumpActivationInfo(BulletWorld sim); +public virtual void DumpActivationInfo(BulletWorld sim) { } -public abstract void DumpAllInfo(BulletWorld sim); +public virtual void DumpAllInfo(BulletWorld sim) { } -public abstract void DumpPhysicsStatistics(BulletWorld sim); +public virtual void DumpPhysicsStatistics(BulletWorld sim) { } + +public virtual void ResetBroadphasePool(BulletWorld sim) { } + +public virtual void ResetConstraintSolver(BulletWorld sim) { } }; } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs index 103d8fc1db..542f732365 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs @@ -45,11 +45,7 @@ public sealed class BSCharacter : BSPhysObject private bool _selected; private OMV.Vector3 _position; private float _mass; - private float _avatarDensity; private float _avatarVolume; - private OMV.Vector3 _force; - private OMV.Vector3 _velocity; - private OMV.Vector3 _torque; private float _collisionScore; private OMV.Vector3 _acceleration; private OMV.Quaternion _orientation; @@ -58,25 +54,17 @@ public sealed class BSCharacter : BSPhysObject private bool _flying; private bool _setAlwaysRun; private bool _throttleUpdates; - private bool _isColliding; - private bool _collidingObj; private bool _floatOnWater; private OMV.Vector3 _rotationalVelocity; private bool _kinematic; private float _buoyancy; - // The friction and velocity of the avatar is modified depending on whether walking or not. - private float _currentFriction; // the friction currently being used (changed by setVelocity). - - private BSVMotor _velocityMotor; + private BSActorAvatarMove m_moveActor; + private const string AvatarMoveActorName = "BSCharacter.AvatarMove"; private OMV.Vector3 _PIDTarget; private bool _usePID; private float _PIDTau; - private bool _useHoverPID; - private float _PIDHoverHeight; - private PIDHoverType _PIDHoverType; - private float _PIDHoverTao; public BSCharacter(uint localID, String avName, BSScene parent_scene, OMV.Vector3 pos, OMV.Vector3 size, bool isFlying) : base(parent_scene, localID, avName, "BSCharacter") @@ -86,10 +74,10 @@ public sealed class BSCharacter : BSPhysObject _flying = isFlying; _orientation = OMV.Quaternion.Identity; - _velocity = OMV.Vector3.Zero; + RawVelocity = OMV.Vector3.Zero; _buoyancy = ComputeBuoyancyFromFlying(isFlying); - _currentFriction = BSParam.AvatarStandingFriction; - _avatarDensity = BSParam.AvatarDensity; + Friction = BSParam.AvatarStandingFriction; + Density = BSParam.AvatarDensity / BSParam.DensityScaleFactor; // Old versions of ScenePresence passed only the height. If width and/or depth are zero, // replace with the default values. @@ -103,17 +91,22 @@ public sealed class BSCharacter : BSPhysObject // set _avatarVolume and _mass based on capsule size, _density and Scale ComputeAvatarVolumeAndMass(); - SetupMovementMotor(); + // The avatar's movement is controlled by this motor that speeds up and slows down + // the avatar seeking to reach the motor's target speed. + // This motor runs as a prestep action for the avatar so it will keep the avatar + // standing as well as moving. Destruction of the avatar will destroy the pre-step action. + m_moveActor = new BSActorAvatarMove(PhysScene, this, AvatarMoveActorName); + PhysicalActors.Add(AvatarMoveActorName, m_moveActor); DetailLog("{0},BSCharacter.create,call,size={1},scale={2},density={3},volume={4},mass={5}", - LocalID, _size, Scale, _avatarDensity, _avatarVolume, RawMass); + LocalID, _size, Scale, Density, _avatarVolume, RawMass); // do actual creation in taint time - PhysicsScene.TaintedObject("BSCharacter.create", delegate() + PhysScene.TaintedObject("BSCharacter.create", delegate() { DetailLog("{0},BSCharacter.create,taint", LocalID); // New body and shape into PhysBody and PhysShape - PhysicsScene.Shapes.GetBodyAndShape(true, PhysicsScene.World, this); + PhysScene.Shapes.GetBodyAndShape(true, PhysScene.World, this); SetPhysicalProperties(); }); @@ -126,114 +119,63 @@ public sealed class BSCharacter : BSPhysObject base.Destroy(); DetailLog("{0},BSCharacter.Destroy", LocalID); - PhysicsScene.TaintedObject("BSCharacter.destroy", delegate() + PhysScene.TaintedObject("BSCharacter.destroy", delegate() { - PhysicsScene.Shapes.DereferenceBody(PhysBody, true, null); + PhysScene.Shapes.DereferenceBody(PhysBody, null /* bodyCallback */); PhysBody.Clear(); - PhysicsScene.Shapes.DereferenceShape(PhysShape, true, null); - PhysShape.Clear(); + PhysShape.Dereference(PhysScene); + PhysShape = new BSShapeNull(); }); } private void SetPhysicalProperties() { - PhysicsScene.PE.RemoveObjectFromWorld(PhysicsScene.World, PhysBody); + PhysScene.PE.RemoveObjectFromWorld(PhysScene.World, PhysBody); ZeroMotion(true); ForcePosition = _position; - // Set the velocity and compute the proper friction - _velocityMotor.Reset(); - _velocityMotor.SetTarget(_velocity); - _velocityMotor.SetCurrent(_velocity); - ForceVelocity = _velocity; + // Set the velocity + if (m_moveActor != null) + m_moveActor.SetVelocityAndTarget(RawVelocity, RawVelocity, false); + + ForceVelocity = RawVelocity; // This will enable or disable the flying buoyancy of the avatar. // Needs to be reset especially when an avatar is recreated after crossing a region boundry. Flying = _flying; - PhysicsScene.PE.SetRestitution(PhysBody, BSParam.AvatarRestitution); - PhysicsScene.PE.SetMargin(PhysShape, PhysicsScene.Params.collisionMargin); - PhysicsScene.PE.SetLocalScaling(PhysShape, Scale); - PhysicsScene.PE.SetContactProcessingThreshold(PhysBody, BSParam.ContactProcessingThreshold); + PhysScene.PE.SetRestitution(PhysBody, BSParam.AvatarRestitution); + PhysScene.PE.SetMargin(PhysShape.physShapeInfo, PhysScene.Params.collisionMargin); + PhysScene.PE.SetLocalScaling(PhysShape.physShapeInfo, Scale); + PhysScene.PE.SetContactProcessingThreshold(PhysBody, BSParam.ContactProcessingThreshold); if (BSParam.CcdMotionThreshold > 0f) { - PhysicsScene.PE.SetCcdMotionThreshold(PhysBody, BSParam.CcdMotionThreshold); - PhysicsScene.PE.SetCcdSweptSphereRadius(PhysBody, BSParam.CcdSweptSphereRadius); + PhysScene.PE.SetCcdMotionThreshold(PhysBody, BSParam.CcdMotionThreshold); + PhysScene.PE.SetCcdSweptSphereRadius(PhysBody, BSParam.CcdSweptSphereRadius); } UpdatePhysicalMassProperties(RawMass, false); // Make so capsule does not fall over - PhysicsScene.PE.SetAngularFactorV(PhysBody, OMV.Vector3.Zero); + PhysScene.PE.SetAngularFactorV(PhysBody, OMV.Vector3.Zero); - PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.CF_CHARACTER_OBJECT); + // The avatar mover sets some parameters. + PhysicalActors.Refresh(); - PhysicsScene.PE.AddObjectToWorld(PhysicsScene.World, PhysBody); + PhysScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.CF_CHARACTER_OBJECT); + + PhysScene.PE.AddObjectToWorld(PhysScene.World, PhysBody); // PhysicsScene.PE.ForceActivationState(PhysBody, ActivationState.ACTIVE_TAG); - PhysicsScene.PE.ForceActivationState(PhysBody, ActivationState.DISABLE_DEACTIVATION); - PhysicsScene.PE.UpdateSingleAabb(PhysicsScene.World, PhysBody); + PhysScene.PE.ForceActivationState(PhysBody, ActivationState.DISABLE_DEACTIVATION); + PhysScene.PE.UpdateSingleAabb(PhysScene.World, PhysBody); // Do this after the object has been added to the world PhysBody.collisionType = CollisionType.Avatar; - PhysBody.ApplyCollisionMask(PhysicsScene); + PhysBody.ApplyCollisionMask(PhysScene); } - // The avatar's movement is controlled by this motor that speeds up and slows down - // the avatar seeking to reach the motor's target speed. - // This motor runs as a prestep action for the avatar so it will keep the avatar - // standing as well as moving. Destruction of the avatar will destroy the pre-step action. - private void SetupMovementMotor() - { - - // Someday, use a PID motor for asymmetric speed up and slow down - // _velocityMotor = new BSPIDVMotor("BSCharacter.Velocity", 3f, 5f, BSMotor.InfiniteVector, 1f); - - // Infinite decay and timescale values so motor only changes current to target values. - _velocityMotor = new BSVMotor("BSCharacter.Velocity", - 0.2f, // time scale - BSMotor.Infinite, // decay time scale - BSMotor.InfiniteVector, // friction timescale - 1f // efficiency - ); - // _velocityMotor.PhysicsScene = PhysicsScene; // DEBUG DEBUG so motor will output detail log messages. - - RegisterPreStepAction("BSCharactor.Movement", LocalID, delegate(float timeStep) - { - // TODO: Decide if the step parameters should be changed depending on the avatar's - // state (flying, colliding, ...). There is code in ODE to do this. - - OMV.Vector3 stepVelocity = _velocityMotor.Step(timeStep); - - // If falling, we keep the world's downward vector no matter what the other axis specify. - if (!Flying && !IsColliding) - { - stepVelocity.Z = _velocity.Z; - // DetailLog("{0},BSCharacter.MoveMotor,taint,overrideStepZWithWorldZ,stepVel={1}", LocalID, stepVelocity); - } - - // 'stepVelocity' is now the speed we'd like the avatar to move in. Turn that into an instantanous force. - OMV.Vector3 moveForce = (stepVelocity - _velocity) * Mass / PhysicsScene.LastTimeStep; - - /* - // If moveForce is very small, zero things so we don't keep sending microscopic updates to the user - float moveForceMagnitudeSquared = moveForce.LengthSquared(); - if (moveForceMagnitudeSquared < 0.0001) - { - DetailLog("{0},BSCharacter.MoveMotor,zeroMovement,stepVel={1},vel={2},mass={3},magSq={4},moveForce={5}", - LocalID, stepVelocity, _velocity, Mass, moveForceMagnitudeSquared, moveForce); - ForceVelocity = OMV.Vector3.Zero; - } - else - { - AddForce(moveForce, false, true); - } - */ - // DetailLog("{0},BSCharacter.MoveMotor,move,stepVel={1},vel={2},mass={3},moveForce={4}", LocalID, stepVelocity, _velocity, Mass, moveForce); - AddForce(moveForce, false, true); - }); - } public override void RequestPhysicsterseUpdate() { @@ -259,16 +201,16 @@ public sealed class BSCharacter : BSPhysObject Scale = ComputeAvatarScale(_size); ComputeAvatarVolumeAndMass(); DetailLog("{0},BSCharacter.setSize,call,size={1},scale={2},density={3},volume={4},mass={5}", - LocalID, _size, Scale, _avatarDensity, _avatarVolume, RawMass); + LocalID, _size, Scale, Density, _avatarVolume, RawMass); - PhysicsScene.TaintedObject("BSCharacter.setSize", delegate() + PhysScene.TaintedObject("BSCharacter.setSize", delegate() { - if (PhysBody.HasPhysicalBody && PhysShape.HasPhysicalShape) + if (PhysBody.HasPhysicalBody && PhysShape.physShapeInfo.HasPhysicalShape) { - PhysicsScene.PE.SetLocalScaling(PhysShape, Scale); + PhysScene.PE.SetLocalScaling(PhysShape.physShapeInfo, Scale); UpdatePhysicalMassProperties(RawMass, true); // Make sure this change appears as a property update event - PhysicsScene.PE.PushUpdate(PhysBody); + PhysScene.PE.PushUpdate(PhysBody); } }); @@ -279,11 +221,6 @@ public sealed class BSCharacter : BSPhysObject { set { BaseShape = value; } } - // I want the physics engine to make an avatar capsule - public override BSPhysicsShapeType PreferredPhysicalShape - { - get {return BSPhysicsShapeType.SHAPE_CAPSULE; } - } public override bool Grabbed { set { _grabbed = value; } @@ -291,6 +228,10 @@ public sealed class BSCharacter : BSPhysObject public override bool Selected { set { _selected = value; } } + public override bool IsSelected + { + get { return _selected; } + } public override void CrossingFailure() { return; } public override void link(PhysicsActor obj) { return; } public override void delink() { return; } @@ -301,29 +242,29 @@ public sealed class BSCharacter : BSPhysObject // Called at taint time! public override void ZeroMotion(bool inTaintTime) { - _velocity = OMV.Vector3.Zero; + RawVelocity = OMV.Vector3.Zero; _acceleration = OMV.Vector3.Zero; _rotationalVelocity = OMV.Vector3.Zero; // Zero some other properties directly into the physics engine - PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate() + PhysScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate() { if (PhysBody.HasPhysicalBody) - PhysicsScene.PE.ClearAllForces(PhysBody); + PhysScene.PE.ClearAllForces(PhysBody); }); } public override void ZeroAngularMotion(bool inTaintTime) { _rotationalVelocity = OMV.Vector3.Zero; - PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate() + PhysScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate() { if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.SetInterpolationAngularVelocity(PhysBody, OMV.Vector3.Zero); - PhysicsScene.PE.SetAngularVelocity(PhysBody, OMV.Vector3.Zero); + PhysScene.PE.SetInterpolationAngularVelocity(PhysBody, OMV.Vector3.Zero); + PhysScene.PE.SetAngularVelocity(PhysBody, OMV.Vector3.Zero); // The next also get rid of applied linear force but the linear velocity is untouched. - PhysicsScene.PE.ClearForces(PhysBody); + PhysScene.PE.ClearForces(PhysBody); } }); } @@ -344,25 +285,26 @@ public sealed class BSCharacter : BSPhysObject } set { _position = value; - PositionSanityCheck(); - PhysicsScene.TaintedObject("BSCharacter.setPosition", delegate() + PhysScene.TaintedObject("BSCharacter.setPosition", delegate() { DetailLog("{0},BSCharacter.SetPosition,taint,pos={1},orient={2}", LocalID, _position, _orientation); - if (PhysBody.HasPhysicalBody) - PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation); + PositionSanityCheck(); + ForcePosition = _position; }); } } public override OMV.Vector3 ForcePosition { get { - _position = PhysicsScene.PE.GetPosition(PhysBody); + _position = PhysScene.PE.GetPosition(PhysBody); return _position; } set { _position = value; - PositionSanityCheck(); - PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation); + if (PhysBody.HasPhysicalBody) + { + PhysScene.PE.SetTranslation(PhysBody, _position, _orientation); + } } } @@ -375,25 +317,27 @@ public sealed class BSCharacter : BSPhysObject bool ret = false; // TODO: check for out of bounds - if (!PhysicsScene.TerrainManager.IsWithinKnownTerrain(_position)) + if (!PhysScene.TerrainManager.IsWithinKnownTerrain(RawPosition)) { // The character is out of the known/simulated area. - // Upper levels of code will handle the transition to other areas so, for - // the time, we just ignore the position. - return ret; + // Force the avatar position to be within known. ScenePresence will use the position + // plus the velocity to decide if the avatar is moving out of the region. + RawPosition = PhysScene.TerrainManager.ClampPositionIntoKnownTerrain(RawPosition); + DetailLog("{0},BSCharacter.PositionSanityCheck,notWithinKnownTerrain,clampedPos={1}", LocalID, RawPosition); + return true; } // If below the ground, move the avatar up - float terrainHeight = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(_position); + float terrainHeight = PhysScene.TerrainManager.GetTerrainHeightAtXYZ(RawPosition); if (Position.Z < terrainHeight) { - DetailLog("{0},BSCharacter.PositionAdjustUnderGround,call,pos={1},terrain={2}", LocalID, _position, terrainHeight); - _position.Z = terrainHeight + 2.0f; + DetailLog("{0},BSCharacter.PositionSanityCheck,adjustForUnderGround,pos={1},terrain={2}", LocalID, _position, terrainHeight); + _position.Z = terrainHeight + BSParam.AvatarBelowGroundUpCorrectionMeters; ret = true; } if ((CurrentCollisionFlags & CollisionFlags.BS_FLOATS_ON_WATER) != 0) { - float waterHeight = PhysicsScene.TerrainManager.GetWaterLevelAtXYZ(_position); + float waterHeight = PhysScene.TerrainManager.GetWaterLevelAtXYZ(_position); if (Position.Z < waterHeight) { _position.Z = waterHeight; @@ -414,11 +358,10 @@ public sealed class BSCharacter : BSPhysObject { // The new position value must be pushed into the physics engine but we can't // just assign to "Position" because of potential call loops. - PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.PositionSanityCheck", delegate() + PhysScene.TaintedObject(inTaintTime, "BSCharacter.PositionSanityCheck", delegate() { DetailLog("{0},BSCharacter.PositionSanityCheck,taint,pos={1},orient={2}", LocalID, _position, _orientation); - if (PhysBody.HasPhysicalBody) - PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation); + ForcePosition = _position; }); ret = true; } @@ -428,25 +371,25 @@ public sealed class BSCharacter : BSPhysObject public override float Mass { get { return _mass; } } // used when we only want this prim's mass and not the linkset thing - public override float RawMass { + public override float RawMass { get {return _mass; } } public override void UpdatePhysicalMassProperties(float physMass, bool inWorld) { - OMV.Vector3 localInertia = PhysicsScene.PE.CalculateLocalInertia(PhysShape, physMass); - PhysicsScene.PE.SetMassProps(PhysBody, physMass, localInertia); + OMV.Vector3 localInertia = PhysScene.PE.CalculateLocalInertia(PhysShape.physShapeInfo, physMass); + PhysScene.PE.SetMassProps(PhysBody, physMass, localInertia); } public override OMV.Vector3 Force { - get { return _force; } + get { return RawForce; } set { - _force = value; + RawForce = value; // m_log.DebugFormat("{0}: Force = {1}", LogHeader, _force); - PhysicsScene.TaintedObject("BSCharacter.SetForce", delegate() + PhysScene.TaintedObject("BSCharacter.SetForce", delegate() { - DetailLog("{0},BSCharacter.setForce,taint,force={1}", LocalID, _force); + DetailLog("{0},BSCharacter.setForce,taint,force={1}", LocalID, RawForce); if (PhysBody.HasPhysicalBody) - PhysicsScene.PE.SetObjectForce(PhysBody, _force); + PhysScene.PE.SetObjectForce(PhysBody, RawForce); }); } } @@ -469,77 +412,49 @@ public sealed class BSCharacter : BSPhysObject { get { - return _velocityMotor.TargetValue; + return base.m_targetVelocity; } set { DetailLog("{0},BSCharacter.setTargetVelocity,call,vel={1}", LocalID, value); + m_targetVelocity = value; OMV.Vector3 targetVel = value; if (_setAlwaysRun) - targetVel *= BSParam.AvatarAlwaysRunFactor; + targetVel *= new OMV.Vector3(BSParam.AvatarAlwaysRunFactor, BSParam.AvatarAlwaysRunFactor, 0f); - PhysicsScene.TaintedObject("BSCharacter.setTargetVelocity", delegate() - { - _velocityMotor.Reset(); - _velocityMotor.SetTarget(targetVel); - _velocityMotor.SetCurrent(_velocity); - _velocityMotor.Enabled = true; - }); + if (m_moveActor != null) + m_moveActor.SetVelocityAndTarget(RawVelocity, targetVel, false /* inTaintTime */); } } // Directly setting velocity means this is what the user really wants now. public override OMV.Vector3 Velocity { - get { return _velocity; } + get { return RawVelocity; } set { - _velocity = value; - // m_log.DebugFormat("{0}: set velocity = {1}", LogHeader, _velocity); - PhysicsScene.TaintedObject("BSCharacter.setVelocity", delegate() + RawVelocity = value; + // m_log.DebugFormat("{0}: set velocity = {1}", LogHeader, RawVelocity); + PhysScene.TaintedObject("BSCharacter.setVelocity", delegate() { - _velocityMotor.Reset(); - _velocityMotor.SetCurrent(_velocity); - _velocityMotor.SetTarget(_velocity); - // Even though the motor is initialized, it's not used and the velocity goes straight into the avatar. - _velocityMotor.Enabled = false; + if (m_moveActor != null) + m_moveActor.SetVelocityAndTarget(RawVelocity, RawVelocity, true /* inTaintTime */); - DetailLog("{0},BSCharacter.setVelocity,taint,vel={1}", LocalID, _velocity); - ForceVelocity = _velocity; + DetailLog("{0},BSCharacter.setVelocity,taint,vel={1}", LocalID, RawVelocity); + ForceVelocity = RawVelocity; }); } } public override OMV.Vector3 ForceVelocity { - get { return _velocity; } + get { return RawVelocity; } set { - PhysicsScene.AssertInTaintTime("BSCharacter.ForceVelocity"); + PhysScene.AssertInTaintTime("BSCharacter.ForceVelocity"); - _velocity = value; - // Depending on whether the avatar is moving or not, change the friction - // to keep the avatar from slipping around - if (_velocity.Length() == 0) - { - if (_currentFriction != BSParam.AvatarStandingFriction) - { - _currentFriction = BSParam.AvatarStandingFriction; - if (PhysBody.HasPhysicalBody) - PhysicsScene.PE.SetFriction(PhysBody, _currentFriction); - } - } - else - { - if (_currentFriction != BSParam.AvatarFriction) - { - _currentFriction = BSParam.AvatarFriction; - if (PhysBody.HasPhysicalBody) - PhysicsScene.PE.SetFriction(PhysBody, _currentFriction); - } - } - - PhysicsScene.PE.SetLinearVelocity(PhysBody, _velocity); - PhysicsScene.PE.Activate(PhysBody, true); + RawVelocity = value; + PhysScene.PE.SetLinearVelocity(PhysBody, RawVelocity); + PhysScene.PE.Activate(PhysBody, true); } } public override OMV.Vector3 Torque { - get { return _torque; } - set { _torque = value; + get { return RawTorque; } + set { RawTorque = value; } } public override float CollisionScore { @@ -564,7 +479,7 @@ public sealed class BSCharacter : BSPhysObject if (_orientation != value) { _orientation = value; - PhysicsScene.TaintedObject("BSCharacter.setOrientation", delegate() + PhysScene.TaintedObject("BSCharacter.setOrientation", delegate() { ForceOrientation = _orientation; }); @@ -576,7 +491,7 @@ public sealed class BSCharacter : BSPhysObject { get { - _orientation = PhysicsScene.PE.GetOrientation(PhysBody); + _orientation = PhysScene.PE.GetOrientation(PhysBody); return _orientation; } set @@ -585,7 +500,7 @@ public sealed class BSCharacter : BSPhysObject if (PhysBody.HasPhysicalBody) { // _position = PhysicsScene.PE.GetPosition(BSBody); - PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation); + PhysScene.PE.SetTranslation(PhysBody, _position, _orientation); } } } @@ -605,6 +520,9 @@ public sealed class BSCharacter : BSPhysObject public override bool IsStatic { get { return false; } } + public override bool IsPhysicallyActive { + get { return true; } + } public override bool Flying { get { return _flying; } set { @@ -631,14 +549,14 @@ public sealed class BSCharacter : BSPhysObject public override bool FloatOnWater { set { _floatOnWater = value; - PhysicsScene.TaintedObject("BSCharacter.setFloatOnWater", delegate() + PhysScene.TaintedObject("BSCharacter.setFloatOnWater", delegate() { if (PhysBody.HasPhysicalBody) { if (_floatOnWater) - CurrentCollisionFlags = PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER); + CurrentCollisionFlags = PhysScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER); else - CurrentCollisionFlags = PhysicsScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER); + CurrentCollisionFlags = PhysScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER); } }); } @@ -659,7 +577,7 @@ public sealed class BSCharacter : BSPhysObject public override float Buoyancy { get { return _buoyancy; } set { _buoyancy = value; - PhysicsScene.TaintedObject("BSCharacter.setBuoyancy", delegate() + PhysScene.TaintedObject("BSCharacter.setBuoyancy", delegate() { DetailLog("{0},BSCharacter.setBuoyancy,taint,buoy={1}", LocalID, _buoyancy); ForceBuoyancy = _buoyancy; @@ -668,15 +586,16 @@ public sealed class BSCharacter : BSPhysObject } public override float ForceBuoyancy { get { return _buoyancy; } - set { - PhysicsScene.AssertInTaintTime("BSCharacter.ForceBuoyancy"); + set { + PhysScene.AssertInTaintTime("BSCharacter.ForceBuoyancy"); _buoyancy = value; DetailLog("{0},BSCharacter.setForceBuoyancy,taint,buoy={1}", LocalID, _buoyancy); // Buoyancy is faked by changing the gravity applied to the object - float grav = PhysicsScene.Params.gravity * (1f - _buoyancy); + float grav = BSParam.Gravity * (1f - _buoyancy); + Gravity = new OMV.Vector3(0f, 0f, grav); if (PhysBody.HasPhysicalBody) - PhysicsScene.PE.SetGravity(PhysBody, new OMV.Vector3(0f, 0f, grav)); + PhysScene.PE.SetGravity(PhysBody, Gravity); } } @@ -691,53 +610,25 @@ public sealed class BSCharacter : BSPhysObject set { _PIDTau = value; } } - // Used for llSetHoverHeight and maybe vehicle height - // Hover Height will override MoveTo target's Z - public override bool PIDHoverActive { - set { _useHoverPID = value; } - } - public override float PIDHoverHeight { - set { _PIDHoverHeight = value; } - } - public override PIDHoverType PIDHoverType { - set { _PIDHoverType = value; } - } - public override float PIDHoverTau { - set { _PIDHoverTao = value; } - } - - // For RotLookAt - public override OMV.Quaternion APIDTarget { set { return; } } - public override bool APIDActive { set { return; } } - public override float APIDStrength { set { return; } } - public override float APIDDamping { set { return; } } - public override void AddForce(OMV.Vector3 force, bool pushforce) { // Since this force is being applied in only one step, make this a force per second. - OMV.Vector3 addForce = force / PhysicsScene.LastTimeStep; + OMV.Vector3 addForce = force / PhysScene.LastTimeStep; AddForce(addForce, pushforce, false); } private void AddForce(OMV.Vector3 force, bool pushforce, bool inTaintTime) { if (force.IsFinite()) { - float magnitude = force.Length(); - if (magnitude > BSParam.MaxAddForceMagnitude) - { - // Force has a limit - force = force / magnitude * BSParam.MaxAddForceMagnitude; - } - - OMV.Vector3 addForce = force; + OMV.Vector3 addForce = Util.ClampV(force, BSParam.MaxAddForceMagnitude); // DetailLog("{0},BSCharacter.addForce,call,force={1}", LocalID, addForce); - PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.AddForce", delegate() + PhysScene.TaintedObject(inTaintTime, "BSCharacter.AddForce", delegate() { // Bullet adds this central force to the total force for this tick // DetailLog("{0},BSCharacter.addForce,taint,force={1}", LocalID, addForce); if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.ApplyCentralForce(PhysBody, addForce); + PhysScene.PE.ApplyCentralForce(PhysBody, addForce); } }); } @@ -748,7 +639,7 @@ public sealed class BSCharacter : BSPhysObject } } - public override void AddAngularForce(OMV.Vector3 force, bool pushforce) { + public override void AddAngularForce(OMV.Vector3 force, bool pushforce, bool inTaintTime) { } public override void SetMomentum(OMV.Vector3 momentum) { } @@ -756,7 +647,7 @@ public sealed class BSCharacter : BSPhysObject private OMV.Vector3 ComputeAvatarScale(OMV.Vector3 size) { OMV.Vector3 newScale; - + // Bullet's capsule total height is the "passed height + radius * 2"; // The base capsule is 1 diameter and 2 height (passed radius=0.5, passed height = 1) // The number we pass in for 'scaling' is the multiplier to get that base @@ -794,34 +685,48 @@ public sealed class BSCharacter : BSPhysObject * Math.Min(Size.X, Size.Y) / 2 * Size.Y / 2f // plus the volume of the capsule end caps ); - _mass = _avatarDensity * _avatarVolume; + _mass = Density * BSParam.DensityScaleFactor * _avatarVolume; } // The physics engine says that properties have updated. Update same and inform // the world that things have changed. public override void UpdateProperties(EntityProperties entprop) { - _position = entprop.Position; + // Don't change position if standing on a stationary object. + if (!IsStationary) + _position = entprop.Position; + _orientation = entprop.Rotation; - _velocity = entprop.Velocity; + + // Smooth velocity. OpenSimulator is VERY sensitive to changes in velocity of the avatar + // and will send agent updates to the clients if velocity changes by more than + // 0.001m/s. Bullet introduces a lot of jitter in the velocity which causes many + // extra updates. + if (!entprop.Velocity.ApproxEquals(RawVelocity, 0.1f)) + RawVelocity = entprop.Velocity; + _acceleration = entprop.Acceleration; _rotationalVelocity = entprop.RotationalVelocity; // Do some sanity checking for the avatar. Make sure it's above ground and inbounds. - PositionSanityCheck(true); + if (PositionSanityCheck(true)) + { + DetailLog("{0},BSCharacter.UpdateProperties,updatePosForSanity,pos={1}", LocalID, _position); + entprop.Position = _position; + } // remember the current and last set values LastEntityProperties = CurrentEntityProperties; CurrentEntityProperties = entprop; // Tell the linkset about value changes - Linkset.UpdateProperties(this, true); + // Linkset.UpdateProperties(UpdatedProperties.EntPropUpdates, this); // Avatars don't report their changes the usual way. Changes are checked for in the heartbeat loop. // base.RequestPhysicsterseUpdate(); DetailLog("{0},BSCharacter.UpdateProperties,call,pos={1},orient={2},vel={3},accel={4},rotVel={5}", - LocalID, _position, _orientation, _velocity, _acceleration, _rotationalVelocity); + LocalID, _position, _orientation, RawVelocity, _acceleration, _rotationalVelocity); } } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSConstraint.cs b/OpenSim/Region/Physics/BulletSPlugin/BSConstraint.cs index b813974b68..42b5c49857 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSConstraint.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSConstraint.cs @@ -85,7 +85,9 @@ public abstract class BSConstraint : IDisposable { bool ret = false; if (m_enabled) + { ret = PhysicsScene.PE.SetAngularLimits(m_constraint, low, high); + } return ret; } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSConstraint6Dof.cs b/OpenSim/Region/Physics/BulletSPlugin/BSConstraint6Dof.cs index ecb1b32884..d0949f5d95 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSConstraint6Dof.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSConstraint6Dof.cs @@ -57,6 +57,7 @@ public sealed class BSConstraint6Dof : BSConstraint obj1.ID, obj1.AddrString, obj2.ID, obj2.AddrString); } + // 6 Dof constraint based on a midpoint between the two constrained bodies public BSConstraint6Dof(BulletWorld world, BulletBody obj1, BulletBody obj2, Vector3 joinPoint, bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies) @@ -94,6 +95,21 @@ public sealed class BSConstraint6Dof : BSConstraint } } + // A 6 Dof constraint that is fixed in the world and constrained to a on-the-fly created static object + public BSConstraint6Dof(BulletWorld world, BulletBody obj1, Vector3 frameInBloc, Quaternion frameInBrot, + bool useLinearReferenceFrameB, bool disableCollisionsBetweenLinkedBodies) + : base(world) + { + m_body1 = obj1; + m_body2 = obj1; // Look out for confusion down the road + m_constraint = PhysicsScene.PE.Create6DofConstraintFixed(m_world, m_body1, + frameInBloc, frameInBrot, + useLinearReferenceFrameB, disableCollisionsBetweenLinkedBodies); + m_enabled = true; + world.physicsScene.DetailLog("{0},BS6DofConstraint,createFixed,wID={1},rID={2},rBody={3}", + BSScene.DetailLogZero, world.worldID, obj1.ID, obj1.AddrString); + } + public bool SetFrames(Vector3 frameA, Quaternion frameArot, Vector3 frameB, Quaternion frameBrot) { bool ret = false; diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSConstraintCollection.cs b/OpenSim/Region/Physics/BulletSPlugin/BSConstraintCollection.cs index 2aeff25035..5c8d94e07e 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSConstraintCollection.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSConstraintCollection.cs @@ -117,8 +117,7 @@ public sealed class BSConstraintCollection : IDisposable if (this.TryGetConstraint(body1, body2, out constrain)) { // remove the constraint from our collection - RemoveAndDestroyConstraint(constrain); - ret = true; + ret = RemoveAndDestroyConstraint(constrain); } } @@ -126,17 +125,19 @@ public sealed class BSConstraintCollection : IDisposable } // The constraint MUST exist in the collection + // Could be called if the constraint was previously removed. + // Return 'true' if the constraint was actually removed and disposed. public bool RemoveAndDestroyConstraint(BSConstraint constrain) { + bool removed = false; lock (m_constraints) { // remove the constraint from our collection - m_constraints.Remove(constrain); + removed = m_constraints.Remove(constrain); } - // tell the engine that all its structures need to be freed + // Dispose() is safe to call multiple times constrain.Dispose(); - // we destroyed something - return true; + return removed; } // Remove all constraints that reference the passed body. diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSConstraintHinge.cs b/OpenSim/Region/Physics/BulletSPlugin/BSConstraintHinge.cs index 7714a0352c..ed89f630e1 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSConstraintHinge.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSConstraintHinge.cs @@ -45,7 +45,7 @@ public sealed class BSConstraintHinge : BSConstraint m_body1 = obj1; m_body2 = obj2; m_constraint = PhysicsScene.PE.CreateHingeConstraint(world, obj1, obj2, - pivotInA, pivotInB, axisInA, axisInB, + pivotInA, pivotInB, axisInA, axisInB, useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies); m_enabled = true; } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs b/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs index 13c253963b..c16b7d3e6a 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSDynamics.cs @@ -35,17 +35,19 @@ using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using OpenMetaverse; +using OpenSim.Framework; using OpenSim.Region.Physics.Manager; namespace OpenSim.Region.Physics.BulletSPlugin { - public sealed class BSDynamics + public sealed class BSDynamics : BSActor { private static string LogHeader = "[BULLETSIM VEHICLE]"; - private BSScene PhysicsScene { get; set; } // the prim this dynamic controller belongs to - private BSPrim Prim { get; set; } + private BSPrim ControllingPrim { get; set; } + + private bool m_haveRegisteredForSceneEvents; // mass of the vehicle fetched each time we're calles private float m_vehicleMass; @@ -108,10 +110,9 @@ namespace OpenSim.Region.Physics.BulletSPlugin private float m_VhoverEfficiency = 0f; private float m_VhoverTimescale = 0f; private float m_VhoverTargetHeight = -1.0f; // if <0 then no hover, else its the current target height - private float m_VehicleBuoyancy = 0f; //KF: m_VehicleBuoyancy is set by VEHICLE_BUOYANCY for a vehicle. - // Modifies gravity. Slider between -1 (double-gravity) and 1 (full anti-gravity) - // KF: So far I have found no good method to combine a script-requested .Z velocity and gravity. - // Therefore only m_VehicleBuoyancy=1 (0g) will use the script-requested .Z velocity. + // Modifies gravity. Slider between -1 (double-gravity) and 1 (full anti-gravity) + private float m_VehicleBuoyancy = 0f; + private Vector3 m_VehicleGravity = Vector3.Zero; // Gravity computed when buoyancy set //Attractor properties private BSVMotor m_verticalAttractionMotor = new BSVMotor("VerticalAttraction"); @@ -124,22 +125,51 @@ namespace OpenSim.Region.Physics.BulletSPlugin static readonly float PIOverFour = ((float)Math.PI) / 4f; static readonly float PIOverTwo = ((float)Math.PI) / 2f; - public BSDynamics(BSScene myScene, BSPrim myPrim) + // For debugging, flags to turn on and off individual corrections. + public bool enableAngularVerticalAttraction; + public bool enableAngularDeflection; + public bool enableAngularBanking; + + public BSDynamics(BSScene myScene, BSPrim myPrim, string actorName) + : base(myScene, myPrim, actorName) { - PhysicsScene = myScene; - Prim = myPrim; + ControllingPrim = myPrim; Type = Vehicle.TYPE_NONE; + m_haveRegisteredForSceneEvents = false; + SetupVehicleDebugging(); + } + + // Stopgap debugging enablement. Allows source level debugging but still checking + // in changes by making enablement of debugging flags from INI file. + public void SetupVehicleDebugging() + { + enableAngularVerticalAttraction = true; + enableAngularDeflection = false; + enableAngularBanking = true; + if (BSParam.VehicleDebuggingEnable) + { + enableAngularVerticalAttraction = true; + enableAngularDeflection = false; + enableAngularBanking = false; + } } // Return 'true' if this vehicle is doing vehicle things public bool IsActive { - get { return Type != Vehicle.TYPE_NONE && Prim.IsPhysical; } + get { return (Type != Vehicle.TYPE_NONE && ControllingPrim.IsPhysicallyActive); } } - internal void ProcessFloatVehicleParam(Vehicle pParam, float pValue) + // Return 'true' if this a vehicle that should be sitting on the ground + public bool IsGroundVehicle { - VDetailLog("{0},ProcessFloatVehicleParam,param={1},val={2}", Prim.LocalID, pParam, pValue); + get { return (Type == Vehicle.TYPE_CAR || Type == Vehicle.TYPE_SLED); } + } + + #region Vehicle parameter setting + public void ProcessFloatVehicleParam(Vehicle pParam, float pValue) + { + VDetailLog("{0},ProcessFloatVehicleParam,param={1},val={2}", ControllingPrim.LocalID, pParam, pValue); switch (pParam) { case Vehicle.ANGULAR_DEFLECTION_EFFICIENCY: @@ -167,6 +197,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin break; case Vehicle.BUOYANCY: m_VehicleBuoyancy = ClampInRange(-1f, pValue, 1f); + m_VehicleGravity = ControllingPrim.ComputeGravity(m_VehicleBuoyancy); break; case Vehicle.HOVER_EFFICIENCY: m_VhoverEfficiency = ClampInRange(0f, pValue, 1f); @@ -204,15 +235,14 @@ namespace OpenSim.Region.Physics.BulletSPlugin // set all of the components to the same value case Vehicle.ANGULAR_FRICTION_TIMESCALE: m_angularFrictionTimescale = new Vector3(pValue, pValue, pValue); - m_angularMotor.FrictionTimescale = m_angularFrictionTimescale; break; case Vehicle.ANGULAR_MOTOR_DIRECTION: m_angularMotorDirection = new Vector3(pValue, pValue, pValue); + m_angularMotor.Zero(); m_angularMotor.SetTarget(m_angularMotorDirection); break; case Vehicle.LINEAR_FRICTION_TIMESCALE: m_linearFrictionTimescale = new Vector3(pValue, pValue, pValue); - m_linearMotor.FrictionTimescale = m_linearFrictionTimescale; break; case Vehicle.LINEAR_MOTOR_DIRECTION: m_linearMotorDirection = new Vector3(pValue, pValue, pValue); @@ -228,12 +258,11 @@ namespace OpenSim.Region.Physics.BulletSPlugin internal void ProcessVectorVehicleParam(Vehicle pParam, Vector3 pValue) { - VDetailLog("{0},ProcessVectorVehicleParam,param={1},val={2}", Prim.LocalID, pParam, pValue); + VDetailLog("{0},ProcessVectorVehicleParam,param={1},val={2}", ControllingPrim.LocalID, pParam, pValue); switch (pParam) { case Vehicle.ANGULAR_FRICTION_TIMESCALE: m_angularFrictionTimescale = new Vector3(pValue.X, pValue.Y, pValue.Z); - m_angularMotor.FrictionTimescale = m_angularFrictionTimescale; break; case Vehicle.ANGULAR_MOTOR_DIRECTION: // Limit requested angular speed to 2 rps= 4 pi rads/sec @@ -241,11 +270,11 @@ namespace OpenSim.Region.Physics.BulletSPlugin pValue.Y = ClampInRange(-12.56f, pValue.Y, 12.56f); pValue.Z = ClampInRange(-12.56f, pValue.Z, 12.56f); m_angularMotorDirection = new Vector3(pValue.X, pValue.Y, pValue.Z); + m_angularMotor.Zero(); m_angularMotor.SetTarget(m_angularMotorDirection); break; case Vehicle.LINEAR_FRICTION_TIMESCALE: m_linearFrictionTimescale = new Vector3(pValue.X, pValue.Y, pValue.Z); - m_linearMotor.FrictionTimescale = m_linearFrictionTimescale; break; case Vehicle.LINEAR_MOTOR_DIRECTION: m_linearMotorDirection = new Vector3(pValue.X, pValue.Y, pValue.Z); @@ -263,7 +292,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin internal void ProcessRotationVehicleParam(Vehicle pParam, Quaternion pValue) { - VDetailLog("{0},ProcessRotationalVehicleParam,param={1},val={2}", Prim.LocalID, pParam, pValue); + VDetailLog("{0},ProcessRotationalVehicleParam,param={1},val={2}", ControllingPrim.LocalID, pParam, pValue); switch (pParam) { case Vehicle.REFERENCE_FRAME: @@ -277,7 +306,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin internal void ProcessVehicleFlags(int pParam, bool remove) { - VDetailLog("{0},ProcessVehicleFlags,param={1},remove={2}", Prim.LocalID, pParam, remove); + VDetailLog("{0},ProcessVehicleFlags,param={1},remove={2}", ControllingPrim.LocalID, pParam, remove); VehicleFlag parm = (VehicleFlag)pParam; if (pParam == -1) m_flags = (VehicleFlag)0; @@ -290,9 +319,9 @@ namespace OpenSim.Region.Physics.BulletSPlugin } } - internal void ProcessTypeChange(Vehicle pType) + public void ProcessTypeChange(Vehicle pType) { - VDetailLog("{0},ProcessTypeChange,type={1}", Prim.LocalID, pType); + VDetailLog("{0},ProcessTypeChange,type={1}", ControllingPrim.LocalID, pType); // Set Defaults For Type Type = pType; switch (pType) @@ -526,83 +555,138 @@ namespace OpenSim.Region.Physics.BulletSPlugin break; } - // Update any physical parameters based on this type. - Refresh(); + m_linearMotor = new BSVMotor("LinearMotor", m_linearMotorTimescale, m_linearMotorDecayTimescale, 1f); + m_linearMotor.PhysicsScene = m_physicsScene; // DEBUG DEBUG DEBUG (enables detail logging) - m_linearMotor = new BSVMotor("LinearMotor", m_linearMotorTimescale, - m_linearMotorDecayTimescale, m_linearFrictionTimescale, - 1f); - m_linearMotor.PhysicsScene = PhysicsScene; // DEBUG DEBUG DEBUG (enables detail logging) - - m_angularMotor = new BSVMotor("AngularMotor", m_angularMotorTimescale, - m_angularMotorDecayTimescale, m_angularFrictionTimescale, - 1f); - m_angularMotor.PhysicsScene = PhysicsScene; // DEBUG DEBUG DEBUG (enables detail logging) + m_angularMotor = new BSVMotor("AngularMotor", m_angularMotorTimescale, m_angularMotorDecayTimescale, 1f); + m_angularMotor.PhysicsScene = m_physicsScene; // DEBUG DEBUG DEBUG (enables detail logging) + /* Not implemented m_verticalAttractionMotor = new BSVMotor("VerticalAttraction", m_verticalAttractionTimescale, BSMotor.Infinite, BSMotor.InfiniteVector, m_verticalAttractionEfficiency); // Z goes away and we keep X and Y - m_verticalAttractionMotor.FrictionTimescale = new Vector3(BSMotor.Infinite, BSMotor.Infinite, 0.1f); m_verticalAttractionMotor.PhysicsScene = PhysicsScene; // DEBUG DEBUG DEBUG (enables detail logging) + */ + + if (this.Type == Vehicle.TYPE_NONE) + { + UnregisterForSceneEvents(); + } + else + { + RegisterForSceneEvents(); + } + + // Update any physical parameters based on this type. + Refresh(); + } + #endregion // Vehicle parameter setting + + // BSActor.Refresh() + public override void Refresh() + { + // If asking for a refresh, reset the physical parameters before the next simulation step. + // Called whether active or not since the active state may be updated before the next step. + m_physicsScene.PostTaintObject("BSDynamics.Refresh", ControllingPrim.LocalID, delegate() + { + SetPhysicalParameters(); + }); } // Some of the properties of this prim may have changed. // Do any updating needed for a vehicle - public void Refresh() + private void SetPhysicalParameters() { if (IsActive) { // Remember the mass so we don't have to fetch it every step - m_vehicleMass = Prim.Linkset.LinksetMass; + m_vehicleMass = ControllingPrim.TotalMass; // Friction affects are handled by this vehicle code - float friction = 0f; - PhysicsScene.PE.SetFriction(Prim.PhysBody, friction); + m_physicsScene.PE.SetFriction(ControllingPrim.PhysBody, BSParam.VehicleFriction); + m_physicsScene.PE.SetRestitution(ControllingPrim.PhysBody, BSParam.VehicleRestitution); // Moderate angular movement introduced by Bullet. // TODO: possibly set AngularFactor and LinearFactor for the type of vehicle. // Maybe compute linear and angular factor and damping from params. - float angularDamping = BSParam.VehicleAngularDamping; - PhysicsScene.PE.SetAngularDamping(Prim.PhysBody, angularDamping); + m_physicsScene.PE.SetAngularDamping(ControllingPrim.PhysBody, BSParam.VehicleAngularDamping); + m_physicsScene.PE.SetLinearFactor(ControllingPrim.PhysBody, BSParam.VehicleLinearFactor); + m_physicsScene.PE.SetAngularFactorV(ControllingPrim.PhysBody, BSParam.VehicleAngularFactor); // Vehicles report collision events so we know when it's on the ground - PhysicsScene.PE.AddToCollisionFlags(Prim.PhysBody, CollisionFlags.BS_VEHICLE_COLLISIONS); + m_physicsScene.PE.AddToCollisionFlags(ControllingPrim.PhysBody, CollisionFlags.BS_VEHICLE_COLLISIONS); - Vector3 localInertia = PhysicsScene.PE.CalculateLocalInertia(Prim.PhysShape, m_vehicleMass); - PhysicsScene.PE.SetMassProps(Prim.PhysBody, m_vehicleMass, localInertia); - PhysicsScene.PE.UpdateInertiaTensor(Prim.PhysBody); + ControllingPrim.Inertia = m_physicsScene.PE.CalculateLocalInertia(ControllingPrim.PhysShape.physShapeInfo, m_vehicleMass); + m_physicsScene.PE.SetMassProps(ControllingPrim.PhysBody, m_vehicleMass, ControllingPrim.Inertia); + m_physicsScene.PE.UpdateInertiaTensor(ControllingPrim.PhysBody); - Vector3 grav = PhysicsScene.DefaultGravity * (1f - Prim.Buoyancy); - PhysicsScene.PE.SetGravity(Prim.PhysBody, grav); + // Set the gravity for the vehicle depending on the buoyancy + // TODO: what should be done if prim and vehicle buoyancy differ? + m_VehicleGravity = ControllingPrim.ComputeGravity(m_VehicleBuoyancy); + // The actual vehicle gravity is set to zero in Bullet so we can do all the application of same. + m_physicsScene.PE.SetGravity(ControllingPrim.PhysBody, Vector3.Zero); - VDetailLog("{0},BSDynamics.Refresh,mass={1},frict={2},inert={3},aDamp={4}", - Prim.LocalID, m_vehicleMass, friction, localInertia, angularDamping); + VDetailLog("{0},BSDynamics.SetPhysicalParameters,mass={1},inert={2},vehGrav={3},aDamp={4},frict={5},rest={6},lFact={7},aFact={8}", + ControllingPrim.LocalID, m_vehicleMass, ControllingPrim.Inertia, m_VehicleGravity, + BSParam.VehicleAngularDamping, BSParam.VehicleFriction, BSParam.VehicleRestitution, + BSParam.VehicleLinearFactor, BSParam.VehicleAngularFactor + ); } else { - PhysicsScene.PE.RemoveFromCollisionFlags(Prim.PhysBody, CollisionFlags.BS_VEHICLE_COLLISIONS); + if (ControllingPrim.PhysBody.HasPhysicalBody) + m_physicsScene.PE.RemoveFromCollisionFlags(ControllingPrim.PhysBody, CollisionFlags.BS_VEHICLE_COLLISIONS); } } - public bool RemoveBodyDependencies(BSPhysObject prim) + // BSActor.RemoveBodyDependencies + public override void RemoveDependencies() { - // If active, we need to add our properties back when the body is rebuilt. - return IsActive; - } - - public void RestoreBodyDependencies(BSPhysObject prim) - { - if (Prim.LocalID != prim.LocalID) - { - // The call should be on us by our prim. Error if not. - PhysicsScene.Logger.ErrorFormat("{0} RestoreBodyDependencies: called by not my prim. passedLocalID={1}, vehiclePrimLocalID={2}", - LogHeader, prim.LocalID, Prim.LocalID); - return; - } Refresh(); } + // BSActor.Release() + public override void Dispose() + { + UnregisterForSceneEvents(); + Type = Vehicle.TYPE_NONE; + Enabled = false; + return; + } + + private void RegisterForSceneEvents() + { + if (!m_haveRegisteredForSceneEvents) + { + m_physicsScene.BeforeStep += this.Step; + m_physicsScene.AfterStep += this.PostStep; + ControllingPrim.OnPreUpdateProperty += this.PreUpdateProperty; + m_haveRegisteredForSceneEvents = true; + } + } + + private void UnregisterForSceneEvents() + { + if (m_haveRegisteredForSceneEvents) + { + m_physicsScene.BeforeStep -= this.Step; + m_physicsScene.AfterStep -= this.PostStep; + ControllingPrim.OnPreUpdateProperty -= this.PreUpdateProperty; + m_haveRegisteredForSceneEvents = false; + } + } + + private void PreUpdateProperty(ref EntityProperties entprop) + { + // A temporary kludge to suppress the rotational effects introduced on vehicles by Bullet + // TODO: handle physics introduced by Bullet with computed vehicle physics. + if (IsActive) + { + entprop.RotationalVelocity = Vector3.Zero; + } + } + #region Known vehicle value functions // Vehicle physical parameters that we buffer from constant getting and setting. // The "m_known*" values are unknown until they are fetched and the m_knownHas flag is set. @@ -617,70 +701,87 @@ namespace OpenSim.Region.Physics.BulletSPlugin private Vector3 m_knownPosition; private Vector3 m_knownVelocity; private Vector3 m_knownForce; + private Vector3 m_knownForceImpulse; private Quaternion m_knownOrientation; private Vector3 m_knownRotationalVelocity; private Vector3 m_knownRotationalForce; + private Vector3 m_knownRotationalImpulse; private Vector3 m_knownForwardVelocity; // vehicle relative forward speed private const int m_knownChangedPosition = 1 << 0; private const int m_knownChangedVelocity = 1 << 1; private const int m_knownChangedForce = 1 << 2; - private const int m_knownChangedOrientation = 1 << 3; - private const int m_knownChangedRotationalVelocity = 1 << 4; - private const int m_knownChangedRotationalForce = 1 << 5; - private const int m_knownChangedTerrainHeight = 1 << 6; - private const int m_knownChangedWaterLevel = 1 << 7; - private const int m_knownChangedForwardVelocity = 1 << 8; + private const int m_knownChangedForceImpulse = 1 << 3; + private const int m_knownChangedOrientation = 1 << 4; + private const int m_knownChangedRotationalVelocity = 1 << 5; + private const int m_knownChangedRotationalForce = 1 << 6; + private const int m_knownChangedRotationalImpulse = 1 << 7; + private const int m_knownChangedTerrainHeight = 1 << 8; + private const int m_knownChangedWaterLevel = 1 << 9; + private const int m_knownChangedForwardVelocity = 1 <<10; - private void ForgetKnownVehicleProperties() + public void ForgetKnownVehicleProperties() { m_knownHas = 0; m_knownChanged = 0; } // Push all the changed values back into the physics engine - private void PushKnownChanged() + public void PushKnownChanged() { if (m_knownChanged != 0) { if ((m_knownChanged & m_knownChangedPosition) != 0) - Prim.ForcePosition = m_knownPosition; + ControllingPrim.ForcePosition = m_knownPosition; if ((m_knownChanged & m_knownChangedOrientation) != 0) - Prim.ForceOrientation = m_knownOrientation; + ControllingPrim.ForceOrientation = m_knownOrientation; if ((m_knownChanged & m_knownChangedVelocity) != 0) { - Prim.ForceVelocity = m_knownVelocity; - PhysicsScene.PE.SetInterpolationLinearVelocity(Prim.PhysBody, VehicleVelocity); + ControllingPrim.ForceVelocity = m_knownVelocity; + // Fake out Bullet by making it think the velocity is the same as last time. + // Bullet does a bunch of smoothing for changing parameters. + // Since the vehicle is demanding this setting, we override Bullet's smoothing + // by telling Bullet the value was the same last time. + // PhysicsScene.PE.SetInterpolationLinearVelocity(Prim.PhysBody, m_knownVelocity); } if ((m_knownChanged & m_knownChangedForce) != 0) - Prim.AddForce((Vector3)m_knownForce, false, true); + ControllingPrim.AddForce((Vector3)m_knownForce, false /*pushForce*/, true /*inTaintTime*/); + + if ((m_knownChanged & m_knownChangedForceImpulse) != 0) + ControllingPrim.AddForceImpulse((Vector3)m_knownForceImpulse, false /*pushforce*/, true /*inTaintTime*/); if ((m_knownChanged & m_knownChangedRotationalVelocity) != 0) { - Prim.ForceRotationalVelocity = m_knownRotationalVelocity; - // Fake out Bullet by making it think the velocity is the same as last time. - PhysicsScene.PE.SetInterpolationAngularVelocity(Prim.PhysBody, m_knownRotationalVelocity); + ControllingPrim.ForceRotationalVelocity = m_knownRotationalVelocity; + // PhysicsScene.PE.SetInterpolationAngularVelocity(Prim.PhysBody, m_knownRotationalVelocity); } + if ((m_knownChanged & m_knownChangedRotationalImpulse) != 0) + ControllingPrim.ApplyTorqueImpulse((Vector3)m_knownRotationalImpulse, true /*inTaintTime*/); + if ((m_knownChanged & m_knownChangedRotationalForce) != 0) - Prim.AddAngularForce((Vector3)m_knownRotationalForce, false, true); + { + ControllingPrim.AddAngularForce((Vector3)m_knownRotationalForce, false /*pushForce*/, true /*inTaintTime*/); + } // If we set one of the values (ie, the physics engine didn't do it) we must force // an UpdateProperties event to send the changes up to the simulator. - PhysicsScene.PE.PushUpdate(Prim.PhysBody); + m_physicsScene.PE.PushUpdate(ControllingPrim.PhysBody); } m_knownChanged = 0; } // Since the computation of terrain height can be a little involved, this routine // is used to fetch the height only once for each vehicle simulation step. + Vector3 lastRememberedHeightPos; private float GetTerrainHeight(Vector3 pos) { - if ((m_knownHas & m_knownChangedTerrainHeight) == 0) + if ((m_knownHas & m_knownChangedTerrainHeight) == 0 || pos != lastRememberedHeightPos) { - m_knownTerrainHeight = Prim.PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(pos); + lastRememberedHeightPos = pos; + m_knownTerrainHeight = ControllingPrim.PhysScene.TerrainManager.GetTerrainHeightAtXYZ(pos); m_knownHas |= m_knownChangedTerrainHeight; } return m_knownTerrainHeight; @@ -692,7 +793,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin { if ((m_knownHas & m_knownChangedWaterLevel) == 0) { - m_knownWaterLevel = Prim.PhysicsScene.TerrainManager.GetWaterLevelAtXYZ(pos); + m_knownWaterLevel = ControllingPrim.PhysScene.TerrainManager.GetWaterLevelAtXYZ(pos); m_knownHas |= m_knownChangedWaterLevel; } return (float)m_knownWaterLevel; @@ -704,7 +805,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin { if ((m_knownHas & m_knownChangedPosition) == 0) { - m_knownPosition = Prim.ForcePosition; + m_knownPosition = ControllingPrim.ForcePosition; m_knownHas |= m_knownChangedPosition; } return m_knownPosition; @@ -723,7 +824,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin { if ((m_knownHas & m_knownChangedOrientation) == 0) { - m_knownOrientation = Prim.ForceOrientation; + m_knownOrientation = ControllingPrim.ForceOrientation; m_knownHas |= m_knownChangedOrientation; } return m_knownOrientation; @@ -742,10 +843,10 @@ namespace OpenSim.Region.Physics.BulletSPlugin { if ((m_knownHas & m_knownChangedVelocity) == 0) { - m_knownVelocity = Prim.ForceVelocity; + m_knownVelocity = ControllingPrim.ForceVelocity; m_knownHas |= m_knownChangedVelocity; } - return (Vector3)m_knownVelocity; + return m_knownVelocity; } set { @@ -755,15 +856,26 @@ namespace OpenSim.Region.Physics.BulletSPlugin } } - private void VehicleAddForce(Vector3 aForce) + private void VehicleAddForce(Vector3 pForce) { if ((m_knownHas & m_knownChangedForce) == 0) { m_knownForce = Vector3.Zero; + m_knownHas |= m_knownChangedForce; } - m_knownForce += aForce; + m_knownForce += pForce; m_knownChanged |= m_knownChangedForce; - m_knownHas |= m_knownChangedForce; + } + + private void VehicleAddForceImpulse(Vector3 pImpulse) + { + if ((m_knownHas & m_knownChangedForceImpulse) == 0) + { + m_knownForceImpulse = Vector3.Zero; + m_knownHas |= m_knownChangedForceImpulse; + } + m_knownForceImpulse += pImpulse; + m_knownChanged |= m_knownChangedForceImpulse; } private Vector3 VehicleRotationalVelocity @@ -772,7 +884,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin { if ((m_knownHas & m_knownChangedRotationalVelocity) == 0) { - m_knownRotationalVelocity = Prim.ForceRotationalVelocity; + m_knownRotationalVelocity = ControllingPrim.ForceRotationalVelocity; m_knownHas |= m_knownChangedRotationalVelocity; } return (Vector3)m_knownRotationalVelocity; @@ -794,6 +906,17 @@ namespace OpenSim.Region.Physics.BulletSPlugin m_knownChanged |= m_knownChangedRotationalForce; m_knownHas |= m_knownChangedRotationalForce; } + private void VehicleAddRotationalImpulse(Vector3 pImpulse) + { + if ((m_knownHas & m_knownChangedRotationalImpulse) == 0) + { + m_knownRotationalImpulse = Vector3.Zero; + m_knownHas |= m_knownChangedRotationalImpulse; + } + m_knownRotationalImpulse += pImpulse; + m_knownChanged |= m_knownChangedRotationalImpulse; + } + // Vehicle relative forward velocity private Vector3 VehicleForwardVelocity { @@ -822,9 +945,6 @@ namespace OpenSim.Region.Physics.BulletSPlugin { if (!IsActive) return; - if (PhysicsScene.VehiclePhysicalLoggingEnabled) - PhysicsScene.PE.DumpRigidBody(PhysicsScene.World, Prim.PhysBody); - ForgetKnownVehicleProperties(); MoveLinear(pTimestep); @@ -839,106 +959,116 @@ namespace OpenSim.Region.Physics.BulletSPlugin // for the physics engine to note the changes so an UpdateProperties event will happen. PushKnownChanged(); - if (PhysicsScene.VehiclePhysicalLoggingEnabled) - PhysicsScene.PE.DumpRigidBody(PhysicsScene.World, Prim.PhysBody); + if (m_physicsScene.VehiclePhysicalLoggingEnabled) + m_physicsScene.PE.DumpRigidBody(m_physicsScene.World, ControllingPrim.PhysBody); - VDetailLog("{0},BSDynamics.Step,done,pos={1},force={2},velocity={3},angvel={4}", - Prim.LocalID, VehiclePosition, Prim.Force, VehicleVelocity, VehicleRotationalVelocity); + VDetailLog("{0},BSDynamics.Step,done,pos={1}, force={2},velocity={3},angvel={4}", + ControllingPrim.LocalID, VehiclePosition, m_knownForce, VehicleVelocity, VehicleRotationalVelocity); + } + + // Called after the simulation step + internal void PostStep(float pTimestep) + { + if (!IsActive) return; + + if (m_physicsScene.VehiclePhysicalLoggingEnabled) + m_physicsScene.PE.DumpRigidBody(m_physicsScene.World, ControllingPrim.PhysBody); } // Apply the effect of the linear motor and other linear motions (like hover and float). private void MoveLinear(float pTimestep) { - Vector3 linearMotorContribution = m_linearMotor.Step(pTimestep); + ComputeLinearVelocity(pTimestep); - // The movement computed in the linear motor is relative to the vehicle - // coordinates. Rotate the movement to world coordinates. - linearMotorContribution *= VehicleOrientation; + ComputeLinearTerrainHeightCorrection(pTimestep); - // ================================================================== - // Buoyancy: force to overcome gravity. - // m_VehicleBuoyancy: -1=2g; 0=1g; 1=0g; - // So, if zero, don't change anything (let gravity happen). If one, negate the effect of gravity. - Vector3 buoyancyContribution = Prim.PhysicsScene.DefaultGravity * m_VehicleBuoyancy; - - Vector3 terrainHeightContribution = ComputeLinearTerrainHeightCorrection(pTimestep); - - Vector3 hoverContribution = ComputeLinearHover(pTimestep); + ComputeLinearHover(pTimestep); ComputeLinearBlockingEndPoint(pTimestep); - Vector3 limitMotorUpContribution = ComputeLinearMotorUp(pTimestep); + ComputeLinearMotorUp(pTimestep); - // ================================================================== - Vector3 newVelocity = linearMotorContribution - + terrainHeightContribution - + hoverContribution - + limitMotorUpContribution; - - Vector3 newForce = buoyancyContribution; + ApplyGravity(pTimestep); // If not changing some axis, reduce out velocity - if ((m_flags & (VehicleFlag.NO_X)) != 0) - newVelocity.X = 0; - if ((m_flags & (VehicleFlag.NO_Y)) != 0) - newVelocity.Y = 0; - if ((m_flags & (VehicleFlag.NO_Z)) != 0) - newVelocity.Z = 0; + if ((m_flags & (VehicleFlag.NO_X | VehicleFlag.NO_Y | VehicleFlag.NO_Z)) != 0) + { + Vector3 vel = VehicleVelocity; + if ((m_flags & (VehicleFlag.NO_X)) != 0) + vel.X = 0; + if ((m_flags & (VehicleFlag.NO_Y)) != 0) + vel.Y = 0; + if ((m_flags & (VehicleFlag.NO_Z)) != 0) + vel.Z = 0; + VehicleVelocity = vel; + } // ================================================================== // Clamp high or low velocities - float newVelocityLengthSq = newVelocity.LengthSquared(); - if (newVelocityLengthSq > 1000f) + float newVelocityLengthSq = VehicleVelocity.LengthSquared(); + if (newVelocityLengthSq > BSParam.VehicleMaxLinearVelocitySquared) { - newVelocity /= newVelocity.Length(); - newVelocity *= 1000f; + Vector3 origVelW = VehicleVelocity; // DEBUG DEBUG + VehicleVelocity /= VehicleVelocity.Length(); + VehicleVelocity *= BSParam.VehicleMaxLinearVelocity; + VDetailLog("{0}, MoveLinear,clampMax,origVelW={1},lenSq={2},maxVelSq={3},,newVelW={4}", + ControllingPrim.LocalID, origVelW, newVelocityLengthSq, BSParam.VehicleMaxLinearVelocitySquared, VehicleVelocity); } else if (newVelocityLengthSq < 0.001f) - newVelocity = Vector3.Zero; + VehicleVelocity = Vector3.Zero; - // ================================================================== - // Stuff new linear velocity into the vehicle. - // Since the velocity is just being set, it is not scaled by pTimeStep. Bullet will do that for us. - VehicleVelocity = newVelocity; - - // Other linear forces are applied as forces. - Vector3 totalDownForce = newForce * m_vehicleMass; - if (!totalDownForce.ApproxEquals(Vector3.Zero, 0.01f)) - { - VehicleAddForce(totalDownForce); - } - - VDetailLog("{0}, MoveLinear,done,newVel={1},totDown={2},IsColliding={3}", - Prim.LocalID, newVelocity, totalDownForce, Prim.IsColliding); - VDetailLog("{0}, MoveLinear,done,linContrib={1},terrContrib={2},hoverContrib={3},limitContrib={4},buoyContrib={5}", - Prim.LocalID, - linearMotorContribution, terrainHeightContribution, hoverContribution, - limitMotorUpContribution, buoyancyContribution - ); + VDetailLog("{0}, MoveLinear,done,isColl={1},newVel={2}", ControllingPrim.LocalID, ControllingPrim.IsColliding, VehicleVelocity ); } // end MoveLinear() - public Vector3 ComputeLinearTerrainHeightCorrection(float pTimestep) + public void ComputeLinearVelocity(float pTimestep) + { + // Step the motor from the current value. Get the correction needed this step. + Vector3 origVelW = VehicleVelocity; // DEBUG + Vector3 currentVelV = VehicleVelocity * Quaternion.Inverse(VehicleOrientation); + Vector3 linearMotorCorrectionV = m_linearMotor.Step(pTimestep, currentVelV); + + // Friction reduces vehicle motion + Vector3 frictionFactorW = ComputeFrictionFactor(m_linearFrictionTimescale, pTimestep); + linearMotorCorrectionV -= (currentVelV * frictionFactorW); + + // Motor is vehicle coordinates. Rotate it to world coordinates + Vector3 linearMotorVelocityW = linearMotorCorrectionV * VehicleOrientation; + + // If we're a ground vehicle, don't add any upward Z movement + if ((m_flags & VehicleFlag.LIMIT_MOTOR_UP) != 0) + { + if (linearMotorVelocityW.Z > 0f) + linearMotorVelocityW.Z = 0f; + } + + // Add this correction to the velocity to make it faster/slower. + VehicleVelocity += linearMotorVelocityW; + + + + VDetailLog("{0}, MoveLinear,velocity,origVelW={1},velV={2},correctV={3},correctW={4},newVelW={5},fricFact={6}", + ControllingPrim.LocalID, origVelW, currentVelV, linearMotorCorrectionV, + linearMotorVelocityW, VehicleVelocity, frictionFactorW); + } + + public void ComputeLinearTerrainHeightCorrection(float pTimestep) { - Vector3 ret = Vector3.Zero; // If below the terrain, move us above the ground a little. // TODO: Consider taking the rotated size of the object or possibly casting a ray. if (VehiclePosition.Z < GetTerrainHeight(VehiclePosition)) { - // TODO: correct position by applying force rather than forcing position. + // Force position because applying force won't get the vehicle through the terrain Vector3 newPosition = VehiclePosition; newPosition.Z = GetTerrainHeight(VehiclePosition) + 1f; VehiclePosition = newPosition; VDetailLog("{0}, MoveLinear,terrainHeight,terrainHeight={1},pos={2}", - Prim.LocalID, GetTerrainHeight(VehiclePosition), VehiclePosition); + ControllingPrim.LocalID, GetTerrainHeight(VehiclePosition), VehiclePosition); } - return ret; } - public Vector3 ComputeLinearHover(float pTimestep) + public void ComputeLinearHover(float pTimestep) { - Vector3 ret = Vector3.Zero; - // m_VhoverEfficiency: 0=bouncy, 1=totally damped // m_VhoverTimescale: time to achieve height if ((m_flags & (VehicleFlag.HOVER_WATER_ONLY | VehicleFlag.HOVER_TERRAIN_ONLY | VehicleFlag.HOVER_GLOBAL_HEIGHT)) != 0) @@ -963,7 +1093,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin if (VehiclePosition.Z > m_VhoverTargetHeight) m_VhoverTargetHeight = VehiclePosition.Z; } - + if ((m_flags & VehicleFlag.LOCK_HOVER_HEIGHT) != 0) { if (Math.Abs(VehiclePosition.Z - m_VhoverTargetHeight) > 0.2f) @@ -971,26 +1101,42 @@ namespace OpenSim.Region.Physics.BulletSPlugin Vector3 pos = VehiclePosition; pos.Z = m_VhoverTargetHeight; VehiclePosition = pos; + + VDetailLog("{0}, MoveLinear,hover,pos={1},lockHoverHeight", ControllingPrim.LocalID, pos); } } else { // Error is positive if below the target and negative if above. - float verticalError = m_VhoverTargetHeight - VehiclePosition.Z; + Vector3 hpos = VehiclePosition; + float verticalError = m_VhoverTargetHeight - hpos.Z; + float verticalCorrection = verticalError / m_VhoverTimescale; + verticalCorrection *= m_VhoverEfficiency; + + hpos.Z += verticalCorrection; + VehiclePosition = hpos; + + // Since we are hovering, we need to do the opposite of falling -- get rid of world Z + Vector3 vel = VehicleVelocity; + vel.Z = 0f; + VehicleVelocity = vel; + + /* float verticalCorrectionVelocity = verticalError / m_VhoverTimescale; + Vector3 verticalCorrection = new Vector3(0f, 0f, verticalCorrectionVelocity); + verticalCorrection *= m_vehicleMass; // TODO: implement m_VhoverEfficiency correctly - if (Math.Abs(verticalError) > m_VhoverEfficiency) - { - ret = new Vector3(0f, 0f, verticalCorrectionVelocity); - } + VehicleAddForceImpulse(verticalCorrection); + */ + + VDetailLog("{0}, MoveLinear,hover,pos={1},eff={2},hoverTS={3},height={4},target={5},err={6},corr={7}", + ControllingPrim.LocalID, VehiclePosition, m_VhoverEfficiency, + m_VhoverTimescale, m_VhoverHeight, m_VhoverTargetHeight, + verticalError, verticalCorrection); } - VDetailLog("{0}, MoveLinear,hover,pos={1},ret={2},hoverTS={3},height={4},target={5}", - Prim.LocalID, VehiclePosition, ret, m_VhoverTimescale, m_VhoverHeight, m_VhoverTargetHeight); } - - return ret; } public bool ComputeLinearBlockingEndPoint(float pTimestep) @@ -1030,7 +1176,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin { VehiclePosition = pos; VDetailLog("{0}, MoveLinear,blockingEndPoint,block={1},origPos={2},pos={3}", - Prim.LocalID, m_BlockingEndPoint, posChange, pos); + ControllingPrim.LocalID, m_BlockingEndPoint, posChange, pos); } } return changed; @@ -1041,34 +1187,75 @@ namespace OpenSim.Region.Physics.BulletSPlugin // used with conjunction with banking: the strength of the banking will decay when the // vehicle no longer experiences collisions. The decay timescale is the same as // VEHICLE_BANKING_TIMESCALE. This is to help prevent ground vehicles from steering - // when they are in mid jump. + // when they are in mid jump. // TODO: this code is wrong. Also, what should it do for boats (height from water)? // This is just using the ground and a general collision check. Should really be using // a downward raycast to find what is below. - public Vector3 ComputeLinearMotorUp(float pTimestep) + public void ComputeLinearMotorUp(float pTimestep) { - Vector3 ret = Vector3.Zero; - float distanceAboveGround = 0f; - if ((m_flags & (VehicleFlag.LIMIT_MOTOR_UP)) != 0) { + // This code tries to decide if the object is not on the ground and then pushing down + /* float targetHeight = Type == Vehicle.TYPE_BOAT ? GetWaterLevel(VehiclePosition) : GetTerrainHeight(VehiclePosition); distanceAboveGround = VehiclePosition.Z - targetHeight; // Not colliding if the vehicle is off the ground if (!Prim.IsColliding) { // downForce = new Vector3(0, 0, -distanceAboveGround / m_bankingTimescale); - ret = new Vector3(0, 0, -distanceAboveGround); + VehicleVelocity += new Vector3(0, 0, -distanceAboveGround); } // TODO: this calculation is wrong. From the description at // (http://wiki.secondlife.com/wiki/Category:LSL_Vehicle), the downForce // has a decay factor. This says this force should // be computed with a motor. // TODO: add interaction with banking. - } - VDetailLog("{0}, MoveLinear,limitMotorUp,distAbove={1},colliding={2},ret={3}", + VDetailLog("{0}, MoveLinear,limitMotorUp,distAbove={1},colliding={2},ret={3}", Prim.LocalID, distanceAboveGround, Prim.IsColliding, ret); - return ret; + */ + + // Another approach is to measure if we're going up. If going up and not colliding, + // the vehicle is in the air. Fix that by pushing down. + if (!ControllingPrim.IsColliding && VehicleVelocity.Z > 0.1) + { + // Get rid of any of the velocity vector that is pushing us up. + float upVelocity = VehicleVelocity.Z; + VehicleVelocity += new Vector3(0, 0, -upVelocity); + + /* + // If we're pointed up into the air, we should nose down + Vector3 pointingDirection = Vector3.UnitX * VehicleOrientation; + // The rotation around the Y axis is pitch up or down + if (pointingDirection.Y > 0.01f) + { + float angularCorrectionForce = -(float)Math.Asin(pointingDirection.Y); + Vector3 angularCorrectionVector = new Vector3(0f, angularCorrectionForce, 0f); + // Rotate into world coordinates and apply to vehicle + angularCorrectionVector *= VehicleOrientation; + VehicleAddAngularForce(angularCorrectionVector); + VDetailLog("{0}, MoveLinear,limitMotorUp,newVel={1},pntDir={2},corrFrc={3},aCorr={4}", + Prim.LocalID, VehicleVelocity, pointingDirection, angularCorrectionForce, angularCorrectionVector); + } + */ + VDetailLog("{0}, MoveLinear,limitMotorUp,collide={1},upVel={2},newVel={3}", + ControllingPrim.LocalID, ControllingPrim.IsColliding, upVelocity, VehicleVelocity); + } + } + } + + private void ApplyGravity(float pTimeStep) + { + Vector3 appliedGravity = m_VehicleGravity * m_vehicleMass; + + // Hack to reduce downward force if the vehicle is probably sitting on the ground + if (ControllingPrim.IsColliding && IsGroundVehicle) + appliedGravity *= BSParam.VehicleGroundGravityFudge; + + VehicleAddForce(appliedGravity); + + VDetailLog("{0}, MoveLinear,applyGravity,vehGrav={1},collid={2},fudge={3},mass={4},appliedForce={3}", + ControllingPrim.LocalID, m_VehicleGravity, + ControllingPrim.IsColliding, BSParam.VehicleGroundGravityFudge, m_vehicleMass, appliedGravity); } // ======================================================================= @@ -1079,55 +1266,24 @@ namespace OpenSim.Region.Physics.BulletSPlugin // set directly on the vehicle. private void MoveAngular(float pTimestep) { - // The user wants this many radians per second angular change? - Vector3 angularMotorContribution = m_angularMotor.Step(pTimestep); + ComputeAngularTurning(pTimestep); + + ComputeAngularVerticalAttraction(); + + ComputeAngularDeflection(); + + ComputeAngularBanking(); // ================================================================== - // From http://wiki.secondlife.com/wiki/LlSetVehicleFlags : - // This flag prevents linear deflection parallel to world z-axis. This is useful - // for preventing ground vehicles with large linear deflection, like bumper cars, - // from climbing their linear deflection into the sky. - // That is, NO_DEFLECTION_UP says angular motion should not add any pitch or roll movement - if ((m_flags & (VehicleFlag.NO_DEFLECTION_UP)) != 0) - { - angularMotorContribution.X = 0f; - angularMotorContribution.Y = 0f; - VDetailLog("{0}, MoveAngular,noDeflectionUp,angularMotorContrib={1}", Prim.LocalID, angularMotorContribution); - } - - Vector3 verticalAttractionContribution = ComputeAngularVerticalAttraction(); - - Vector3 deflectionContribution = ComputeAngularDeflection(); - - Vector3 bankingContribution = ComputeAngularBanking(); - - // ================================================================== - m_lastVertAttractor = verticalAttractionContribution; - - m_lastAngularVelocity = angularMotorContribution - + verticalAttractionContribution - + deflectionContribution - + bankingContribution; - - // ================================================================== - // Apply the correction velocity. - // TODO: Should this be applied as an angular force (torque)? - if (!m_lastAngularVelocity.ApproxEquals(Vector3.Zero, 0.01f)) - { - VehicleRotationalVelocity = m_lastAngularVelocity; - - VDetailLog("{0}, MoveAngular,done,nonZero,angMotorContrib={1},vertAttrContrib={2},bankContrib={3},deflectContrib={4},totalContrib={5}", - Prim.LocalID, - angularMotorContribution, verticalAttractionContribution, - bankingContribution, deflectionContribution, - m_lastAngularVelocity - ); - } - else + if (VehicleRotationalVelocity.ApproxEquals(Vector3.Zero, 0.0001f)) { // The vehicle is not adding anything angular wise. VehicleRotationalVelocity = Vector3.Zero; - VDetailLog("{0}, MoveAngular,done,zero", Prim.LocalID); + VDetailLog("{0}, MoveAngular,done,zero", ControllingPrim.LocalID); + } + else + { + VDetailLog("{0}, MoveAngular,done,nonZero,angVel={1}", ControllingPrim.LocalID, VehicleRotationalVelocity); } // ================================================================== @@ -1158,10 +1314,42 @@ namespace OpenSim.Region.Physics.BulletSPlugin torqueFromOffset.Z = 0; VehicleAddAngularForce(torqueFromOffset * m_vehicleMass); - VDetailLog("{0}, BSDynamic.MoveAngular,motorOffset,applyTorqueImpulse={1}", Prim.LocalID, torqueFromOffset); + VDetailLog("{0}, BSDynamic.MoveAngular,motorOffset,applyTorqueImpulse={1}", ControllingPrim.LocalID, torqueFromOffset); } } + + private void ComputeAngularTurning(float pTimestep) + { + // The user wants this many radians per second angular change? + Vector3 currentAngularV = VehicleRotationalVelocity * Quaternion.Inverse(VehicleOrientation); + Vector3 angularMotorContributionV = m_angularMotor.Step(pTimestep, currentAngularV); + + // ================================================================== + // From http://wiki.secondlife.com/wiki/LlSetVehicleFlags : + // This flag prevents linear deflection parallel to world z-axis. This is useful + // for preventing ground vehicles with large linear deflection, like bumper cars, + // from climbing their linear deflection into the sky. + // That is, NO_DEFLECTION_UP says angular motion should not add any pitch or roll movement + // TODO: This is here because this is where ODE put it but documentation says it + // is a linear effect. Where should this check go? + //if ((m_flags & (VehicleFlag.NO_DEFLECTION_UP)) != 0) + // { + // angularMotorContributionV.X = 0f; + // angularMotorContributionV.Y = 0f; + // } + + // Reduce any velocity by friction. + Vector3 frictionFactorW = ComputeFrictionFactor(m_angularFrictionTimescale, pTimestep); + angularMotorContributionV -= (currentAngularV * frictionFactorW); + + VehicleRotationalVelocity += angularMotorContributionV * VehicleOrientation; + + + + VDetailLog("{0}, MoveAngular,angularTurning,angContribV={1}", ControllingPrim.LocalID, angularMotorContributionV); + } + // From http://wiki.secondlife.com/wiki/Linden_Vehicle_Tutorial: // Some vehicles, like boats, should always keep their up-side up. This can be done by // enabling the "vertical attractor" behavior that springs the vehicle's local z-axis to @@ -1170,15 +1358,84 @@ namespace OpenSim.Region.Physics.BulletSPlugin // and then set the VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY to control the damping. An // efficiency of 0.0 will cause the spring to wobble around its equilibrium, while an // efficiency of 1.0 will cause the spring to reach its equilibrium with exponential decay. - public Vector3 ComputeAngularVerticalAttraction() + public void ComputeAngularVerticalAttraction() { - Vector3 ret = Vector3.Zero; // If vertical attaction timescale is reasonable - if (m_verticalAttractionTimescale < m_verticalAttractionCutoff) + if (enableAngularVerticalAttraction && m_verticalAttractionTimescale < m_verticalAttractionCutoff) { + //Another formula to try got from : + //http://answers.unity3d.com/questions/10425/how-to-stabilize-angular-motion-alignment-of-hover.html + + Vector3 VehicleUpAxis = Vector3.UnitZ * VehicleOrientation; + + // Flipping what was originally a timescale into a speed variable and then multiplying it by 2 + // since only computing half the distance between the angles. + float VerticalAttractionSpeed = (1 / m_verticalAttractionTimescale) * 2.0f; + + // Make a prediction of where the up axis will be when this is applied rather then where it is now as + // this makes for a smoother adjustment and less fighting between the various forces. + Vector3 predictedUp = VehicleUpAxis * Quaternion.CreateFromAxisAngle(VehicleRotationalVelocity, 0f); + + // This is only half the distance to the target so it will take 2 seconds to complete the turn. + Vector3 torqueVector = Vector3.Cross(predictedUp, Vector3.UnitZ); + + // Scale vector by our timescale since it is an acceleration it is r/s^2 or radians a timescale squared + Vector3 vertContributionV = torqueVector * VerticalAttractionSpeed * VerticalAttractionSpeed; + + VehicleRotationalVelocity += vertContributionV; + + VDetailLog("{0}, MoveAngular,verticalAttraction,UpAxis={1},PredictedUp={2},torqueVector={3},contrib={4}", + ControllingPrim.LocalID, + VehicleUpAxis, + predictedUp, + torqueVector, + vertContributionV); + //===================================================================== + /* + // Possible solution derived from a discussion at: + // http://stackoverflow.com/questions/14939657/computing-vector-from-quaternion-works-computing-quaternion-from-vector-does-no + + // Create a rotation that is only the vehicle's rotation around Z + Vector3 currentEuler = Vector3.Zero; + VehicleOrientation.GetEulerAngles(out currentEuler.X, out currentEuler.Y, out currentEuler.Z); + Quaternion justZOrientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, currentEuler.Z); + + // Create the axis that is perpendicular to the up vector and the rotated up vector. + Vector3 differenceAxis = Vector3.Cross(Vector3.UnitZ * justZOrientation, Vector3.UnitZ * VehicleOrientation); + // Compute the angle between those to vectors. + double differenceAngle = Math.Acos((double)Vector3.Dot(Vector3.UnitZ, Vector3.Normalize(Vector3.UnitZ * VehicleOrientation))); + // 'differenceAngle' is the angle to rotate and 'differenceAxis' is the plane to rotate in to get the vehicle vertical + + // Reduce the change by the time period it is to change in. Timestep is handled when velocity is applied. + // TODO: add 'efficiency'. + differenceAngle /= m_verticalAttractionTimescale; + + // Create the quaterian representing the correction angle + Quaternion correctionRotation = Quaternion.CreateFromAxisAngle(differenceAxis, (float)differenceAngle); + + // Turn that quaternion into Euler values to make it into velocities to apply. + Vector3 vertContributionV = Vector3.Zero; + correctionRotation.GetEulerAngles(out vertContributionV.X, out vertContributionV.Y, out vertContributionV.Z); + vertContributionV *= -1f; + + VehicleRotationalVelocity += vertContributionV; + + VDetailLog("{0}, MoveAngular,verticalAttraction,diffAxis={1},diffAng={2},corrRot={3},contrib={4}", + ControllingPrim.LocalID, + differenceAxis, + differenceAngle, + correctionRotation, + vertContributionV); + */ + + // =================================================================== + /* + Vector3 vertContributionV = Vector3.Zero; + Vector3 origRotVelW = VehicleRotationalVelocity; // DEBUG DEBUG + // Take a vector pointing up and convert it from world to vehicle relative coords. - Vector3 verticalError = Vector3.UnitZ * VehicleOrientation; + Vector3 verticalError = Vector3.Normalize(Vector3.UnitZ * VehicleOrientation); // If vertical attraction correction is needed, the vector that was pointing up (UnitZ) // is now: @@ -1190,49 +1447,57 @@ namespace OpenSim.Region.Physics.BulletSPlugin // Y error means needed rotation around X axis and visa versa. // Since the error goes from zero to one, the asin is the corresponding angle. - ret.X = (float)Math.Asin(verticalError.Y); + vertContributionV.X = (float)Math.Asin(verticalError.Y); // (Tilt forward (positive X) needs to tilt back (rotate negative) around Y axis.) - ret.Y = -(float)Math.Asin(verticalError.X); + vertContributionV.Y = -(float)Math.Asin(verticalError.X); // If verticalError.Z is negative, the vehicle is upside down. Add additional push. if (verticalError.Z < 0f) { - ret.X += PIOverFour; - ret.Y += PIOverFour; + vertContributionV.X += Math.Sign(vertContributionV.X) * PIOverFour; + // vertContribution.Y -= PIOverFour; } - // 'ret' is now the necessary velocity to correct tilt in one second. + // 'vertContrbution' is now the necessary angular correction to correct tilt in one second. // Correction happens over a number of seconds. - Vector3 unscaledContrib = ret; - ret /= m_verticalAttractionTimescale; + Vector3 unscaledContribVerticalErrorV = vertContributionV; // DEBUG DEBUG - VDetailLog("{0}, MoveAngular,verticalAttraction,,verticalError={1},unscaled={2},eff={3},ts={4},vertAttr={5}", - Prim.LocalID, verticalError, unscaledContrib, m_verticalAttractionEfficiency, m_verticalAttractionTimescale, ret); + // The correction happens over the user's time period + vertContributionV /= m_verticalAttractionTimescale; + + // Rotate the vehicle rotation to the world coordinates. + VehicleRotationalVelocity += (vertContributionV * VehicleOrientation); + + VDetailLog("{0}, MoveAngular,verticalAttraction,,origRotVW={1},vertError={2},unscaledV={3},eff={4},ts={5},vertContribV={6}", + Prim.LocalID, origRotVelW, verticalError, unscaledContribVerticalErrorV, + m_verticalAttractionEfficiency, m_verticalAttractionTimescale, vertContributionV); + */ } - return ret; } - // Return the angular correction to correct the direction the vehicle is pointing to be + // Angular correction to correct the direction the vehicle is pointing to be // the direction is should want to be pointing. // The vehicle is moving in some direction and correct its orientation to it is pointing // in that direction. // TODO: implement reference frame. - public Vector3 ComputeAngularDeflection() + public void ComputeAngularDeflection() { - Vector3 ret = Vector3.Zero; - return ret; // DEBUG DEBUG DEBUG - // Disable angular deflection for the moment. // Since angularMotorUp and angularDeflection are computed independently, they will calculate // approximately the same X or Y correction. When added together (when contributions are combined) // this creates an over-correction and then wabbling as the target is overshot. // TODO: rethink how the different correction computations inter-relate. - if (m_angularDeflectionEfficiency != 0) + if (enableAngularDeflection && m_angularDeflectionEfficiency != 0 && VehicleForwardSpeed > 0.2) { + Vector3 deflectContributionV = Vector3.Zero; + // The direction the vehicle is moving Vector3 movingDirection = VehicleVelocity; movingDirection.Normalize(); + // If the vehicle is going backward, it is still pointing forward + movingDirection *= Math.Sign(VehicleForwardSpeed); + // The direction the vehicle is pointing Vector3 pointingDirection = Vector3.UnitX * VehicleOrientation; pointingDirection.Normalize(); @@ -1241,6 +1506,9 @@ namespace OpenSim.Region.Physics.BulletSPlugin Vector3 deflectionError = movingDirection - pointingDirection; // Don't try to correct very large errors (not our job) + // if (Math.Abs(deflectionError.X) > PIOverFour) deflectionError.X = PIOverTwo * Math.Sign(deflectionError.X); + // if (Math.Abs(deflectionError.Y) > PIOverFour) deflectionError.Y = PIOverTwo * Math.Sign(deflectionError.Y); + // if (Math.Abs(deflectionError.Z) > PIOverFour) deflectionError.Z = PIOverTwo * Math.Sign(deflectionError.Z); if (Math.Abs(deflectionError.X) > PIOverFour) deflectionError.X = 0f; if (Math.Abs(deflectionError.Y) > PIOverFour) deflectionError.Y = 0f; if (Math.Abs(deflectionError.Z) > PIOverFour) deflectionError.Z = 0f; @@ -1248,18 +1516,19 @@ namespace OpenSim.Region.Physics.BulletSPlugin // ret = m_angularDeflectionCorrectionMotor(1f, deflectionError); // Scale the correction by recovery timescale and efficiency - ret = (-deflectionError) * m_angularDeflectionEfficiency; - ret /= m_angularDeflectionTimescale; + deflectContributionV = (-deflectionError) * m_angularDeflectionEfficiency; + deflectContributionV /= m_angularDeflectionTimescale; + + VehicleRotationalVelocity += deflectContributionV * VehicleOrientation; VDetailLog("{0}, MoveAngular,Deflection,movingDir={1},pointingDir={2},deflectError={3},ret={4}", - Prim.LocalID, movingDirection, pointingDirection, deflectionError, ret); + ControllingPrim.LocalID, movingDirection, pointingDirection, deflectionError, deflectContributionV); VDetailLog("{0}, MoveAngular,Deflection,fwdSpd={1},defEff={2},defTS={3}", - Prim.LocalID, VehicleForwardSpeed, m_angularDeflectionEfficiency, m_angularDeflectionTimescale); + ControllingPrim.LocalID, VehicleForwardSpeed, m_angularDeflectionEfficiency, m_angularDeflectionTimescale); } - return ret; } - // Return an angular change to rotate the vehicle around the Z axis when the vehicle + // Angular change to rotate the vehicle around the Z axis when the vehicle // is tipped around the X axis. // From http://wiki.secondlife.com/wiki/Linden_Vehicle_Tutorial: // The vertical attractor feature must be enabled in order for the banking behavior to @@ -1267,13 +1536,13 @@ namespace OpenSim.Region.Physics.BulletSPlugin // produce a angular velocity around the yaw-axis, causing the vehicle to turn. The magnitude // of the yaw effect will be proportional to the // VEHICLE_BANKING_EFFICIENCY, the angle of the roll rotation, and sometimes the vehicle's - // velocity along its preferred axis of motion. + // velocity along its preferred axis of motion. // The VEHICLE_BANKING_EFFICIENCY can vary between -1 and +1. When it is positive then any // positive rotation (by the right-hand rule) about the roll-axis will effect a // (negative) torque around the yaw-axis, making it turn to the right--that is the // vehicle will lean into the turn, which is how real airplanes and motorcycle's work. // Negating the banking coefficient will make it so that the vehicle leans to the - // outside of the turn (not very "physical" but might allow interesting vehicles so why not?). + // outside of the turn (not very "physical" but might allow interesting vehicles so why not?). // The VEHICLE_BANKING_MIX is a fake (i.e. non-physical) parameter that is useful for making // banking vehicles do what you want rather than what the laws of physics allow. // For example, consider a real motorcycle...it must be moving forward in order for @@ -1285,46 +1554,44 @@ namespace OpenSim.Region.Physics.BulletSPlugin // totally static (0.0) and totally dynamic (1.0). By "static" we mean that the // banking effect depends only on the vehicle's rotation about its roll-axis compared // to "dynamic" where the banking is also proportional to its velocity along its - // roll-axis. Finding the best value of the "mixture" will probably require trial and error. + // roll-axis. Finding the best value of the "mixture" will probably require trial and error. // The time it takes for the banking behavior to defeat a preexisting angular velocity about the // world z-axis is determined by the VEHICLE_BANKING_TIMESCALE. So if you want the vehicle to // bank quickly then give it a banking timescale of about a second or less, otherwise you can - // make a sluggish vehicle by giving it a timescale of several seconds. - public Vector3 ComputeAngularBanking() + // make a sluggish vehicle by giving it a timescale of several seconds. + public void ComputeAngularBanking() { - Vector3 ret = Vector3.Zero; - - if (m_bankingEfficiency != 0 && m_verticalAttractionTimescale < m_verticalAttractionCutoff) + if (enableAngularBanking && m_bankingEfficiency != 0 && m_verticalAttractionTimescale < m_verticalAttractionCutoff) { - // This works by rotating a unit vector to the orientation of the vehicle. The - // roll (tilt) will be Y component of a tilting Z vector (zero for no tilt - // up to one for full over). + Vector3 bankingContributionV = Vector3.Zero; + + // Rotate a UnitZ vector (pointing up) to how the vehicle is oriented. + // As the vehicle rolls to the right or left, the Y value will increase from + // zero (straight up) to 1 or -1 (full tilt right or left) Vector3 rollComponents = Vector3.UnitZ * VehicleOrientation; // Figure out the yaw value for this much roll. - float turnComponent = rollComponents.Y * rollComponents.Y * m_bankingEfficiency; - // Keep the sign - if (rollComponents.Y < 0f) - turnComponent = -turnComponent; - - // TODO: there must be a better computation of the banking force. - float bankingTurnForce = turnComponent; - + float yawAngle = m_angularMotorDirection.X * m_bankingEfficiency; // actual error = static turn error + dynamic turn error - float mixedBankingError = bankingTurnForce * (1f - m_bankingMix) + bankingTurnForce * m_bankingMix * VehicleForwardSpeed; + float mixedYawAngle =(yawAngle * (1f - m_bankingMix)) + ((yawAngle * m_bankingMix) * VehicleForwardSpeed); + // TODO: the banking effect should not go to infinity but what to limit it to? - mixedBankingError = ClampInRange(-20f, mixedBankingError, 20f); + // And what should happen when this is being added to a user defined yaw that is already PI*4? + mixedYawAngle = ClampInRange(-12, mixedYawAngle, 12); // Build the force vector to change rotation from what it is to what it should be - ret.Z = -mixedBankingError; + bankingContributionV.Z = -mixedYawAngle; - // Don't do it all at once. - ret /= m_bankingTimescale; + // Don't do it all at once. Fudge because 1 second is too fast with most user defined roll as PI*4. + bankingContributionV /= m_bankingTimescale * BSParam.VehicleAngularBankingTimescaleFudge; - VDetailLog("{0}, MoveAngular,Banking,rollComp={1},speed={2},turnComp={3},bankErr={4},mixedBankErr={5},ret={6}", - Prim.LocalID, rollComponents, VehicleForwardSpeed, turnComponent, bankingTurnForce, mixedBankingError, ret); + //VehicleRotationalVelocity += bankingContributionV * VehicleOrientation; + VehicleRotationalVelocity += bankingContributionV; + + + VDetailLog("{0}, MoveAngular,Banking,rollComp={1},speed={2},rollComp={3},yAng={4},mYAng={5},ret={6}", + ControllingPrim.LocalID, rollComponents, VehicleForwardSpeed, rollComponents, yawAngle, mixedYawAngle, bankingContributionV); } - return ret; } // This is from previous instantiations of XXXDynamics.cs. @@ -1362,11 +1629,28 @@ namespace OpenSim.Region.Physics.BulletSPlugin if (rotq != m_rot) { VehicleOrientation = m_rot; - VDetailLog("{0}, LimitRotation,done,orig={1},new={2}", Prim.LocalID, rotq, m_rot); + VDetailLog("{0}, LimitRotation,done,orig={1},new={2}", ControllingPrim.LocalID, rotq, m_rot); } } + // Given a friction vector (reduction in seconds) and a timestep, return the factor to reduce + // some value by to apply this friction. + private Vector3 ComputeFrictionFactor(Vector3 friction, float pTimestep) + { + Vector3 frictionFactor = Vector3.Zero; + if (friction != BSMotor.InfiniteVector) + { + // frictionFactor = (Vector3.One / FrictionTimescale) * timeStep; + // Individual friction components can be 'infinite' so compute each separately. + frictionFactor.X = (friction.X == BSMotor.Infinite) ? 0f : (1f / friction.X); + frictionFactor.Y = (friction.Y == BSMotor.Infinite) ? 0f : (1f / friction.Y); + frictionFactor.Z = (friction.Z == BSMotor.Infinite) ? 0f : (1f / friction.Z); + frictionFactor *= pTimestep; + } + return frictionFactor; + } + private float ClampInRange(float low, float val, float high) { return Math.Max(low, Math.Min(val, high)); @@ -1376,8 +1660,8 @@ namespace OpenSim.Region.Physics.BulletSPlugin // Invoke the detailed logger and output something if it's enabled. private void VDetailLog(string msg, params Object[] args) { - if (Prim.PhysicsScene.VehicleLoggingEnabled) - Prim.PhysicsScene.DetailLog(msg, args); + if (ControllingPrim.PhysScene.VehicleLoggingEnabled) + ControllingPrim.PhysScene.DetailLog(msg, args); } } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs index 756faedb21..76c2187eed 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinkset.cs @@ -52,7 +52,7 @@ public abstract class BSLinkset Manual = 2 // linkset tied together manually (code moves all the pieces) } // Create the correct type of linkset for this child - public static BSLinkset Factory(BSScene physScene, BSPhysObject parent) + public static BSLinkset Factory(BSScene physScene, BSPrimLinkable parent) { BSLinkset ret = null; @@ -71,31 +71,28 @@ public abstract class BSLinkset ret = new BSLinksetCompound(physScene, parent); break; } + if (ret == null) + { + physScene.Logger.ErrorFormat("[BULLETSIM LINKSET] Factory could not create linkset. Parent name={1}, ID={2}", parent.Name, parent.LocalID); + } return ret; } - public BSPhysObject LinksetRoot { get; protected set; } + public BSPrimLinkable LinksetRoot { get; protected set; } - public BSScene PhysicsScene { get; private set; } + protected BSScene m_physicsScene { get; private set; } static int m_nextLinksetID = 1; public int LinksetID { get; private set; } // The children under the root in this linkset. - protected HashSet m_children; + protected HashSet m_children; // We lock the diddling of linkset classes to prevent any badness. // This locks the modification of the instances of this class. Changes // to the physical representation is done via the tainting mechenism. protected object m_linksetActivityLock = new Object(); - // Some linksets have a preferred physical shape. - // Returns SHAPE_UNKNOWN if there is no preference. Causes the correct shape to be selected. - public virtual BSPhysicsShapeType PreferredPhysicalShape(BSPhysObject requestor) - { - return BSPhysicsShapeType.SHAPE_UNKNOWN; - } - // We keep the prim's mass in the linkset structure since it could be dependent on other prims public float LinksetMass { get; protected set; } @@ -111,25 +108,27 @@ public abstract class BSLinkset get { return ComputeLinksetGeometricCenter(); } } - protected BSLinkset(BSScene scene, BSPhysObject parent) + protected BSLinkset(BSScene scene, BSPrimLinkable parent) { // A simple linkset of one (no children) LinksetID = m_nextLinksetID++; // We create LOTS of linksets. if (m_nextLinksetID <= 0) m_nextLinksetID = 1; - PhysicsScene = scene; + m_physicsScene = scene; LinksetRoot = parent; - m_children = new HashSet(); + m_children = new HashSet(); LinksetMass = parent.RawMass; Rebuilding = false; + + parent.ClearDisplacement(); } // Link to a linkset where the child knows the parent. // Parent changing should not happen so do some sanity checking. // We return the parent's linkset so the child can track its membership. // Called at runtime. - public BSLinkset AddMeToLinkset(BSPhysObject child) + public BSLinkset AddMeToLinkset(BSPrimLinkable child) { lock (m_linksetActivityLock) { @@ -145,7 +144,7 @@ public abstract class BSLinkset // Returns a new linkset for the child which is a linkset of one (just the // orphened child). // Called at runtime. - public BSLinkset RemoveMeFromLinkset(BSPhysObject child) + public BSLinkset RemoveMeFromLinkset(BSPrimLinkable child) { lock (m_linksetActivityLock) { @@ -159,11 +158,11 @@ public abstract class BSLinkset } // The child is down to a linkset of just itself - return BSLinkset.Factory(PhysicsScene, child); + return BSLinkset.Factory(m_physicsScene, child); } // Return 'true' if the passed object is the root object of this linkset - public bool IsRoot(BSPhysObject requestor) + public bool IsRoot(BSPrimLinkable requestor) { return (requestor.LocalID == LinksetRoot.LocalID); } @@ -174,14 +173,14 @@ public abstract class BSLinkset public bool HasAnyChildren { get { return (m_children.Count > 0); } } // Return 'true' if this child is in this linkset - public bool HasChild(BSPhysObject child) + public bool HasChild(BSPrimLinkable child) { bool ret = false; lock (m_linksetActivityLock) { ret = m_children.Contains(child); /* Safer version but the above should work - foreach (BSPhysObject bp in m_children) + foreach (BSPrimLinkable bp in m_children) { if (child.LocalID == bp.LocalID) { @@ -196,14 +195,14 @@ public abstract class BSLinkset // Perform an action on each member of the linkset including root prim. // Depends on the action on whether this should be done at taint time. - public delegate bool ForEachMemberAction(BSPhysObject obj); + public delegate bool ForEachMemberAction(BSPrimLinkable obj); public virtual bool ForEachMember(ForEachMemberAction action) { bool ret = false; lock (m_linksetActivityLock) { action(LinksetRoot); - foreach (BSPhysObject po in m_children) + foreach (BSPrimLinkable po in m_children) { if (action(po)) break; @@ -214,16 +213,16 @@ public abstract class BSLinkset // I am the root of a linkset and a new child is being added // Called while LinkActivity is locked. - protected abstract void AddChildToLinkset(BSPhysObject child); - + protected abstract void AddChildToLinkset(BSPrimLinkable child); + // I am the root of a linkset and one of my children is being removed. // Safe to call even if the child is not really in my linkset. - protected abstract void RemoveChildFromLinkset(BSPhysObject child); + protected abstract void RemoveChildFromLinkset(BSPrimLinkable child); // When physical properties are changed the linkset needs to recalculate // its internal properties. // May be called at runtime or taint-time. - public virtual void Refresh(BSPhysObject requestor) + public virtual void Refresh(BSPrimLinkable requestor) { LinksetMass = ComputeLinksetMass(); } @@ -238,31 +237,26 @@ public abstract class BSLinkset // has not yet been fully constructed. // Return 'true' if any properties updated on the passed object. // Called at taint-time! - public abstract bool MakeDynamic(BSPhysObject child); + public abstract bool MakeDynamic(BSPrimLinkable child); // The object is going static (non-physical). Do any setup necessary // for a static linkset. // Return 'true' if any properties updated on the passed object. // Called at taint-time! - public abstract bool MakeStatic(BSPhysObject child); + public abstract bool MakeStatic(BSPrimLinkable child); // Called when a parameter update comes from the physics engine for any object // of the linkset is received. // Passed flag is update came from physics engine (true) or the user (false). // Called at taint-time!! - public abstract void UpdateProperties(BSPhysObject physObject, bool physicalUpdate); + public abstract void UpdateProperties(UpdatedProperties whichUpdated, BSPrimLinkable physObject); // Routine used when rebuilding the body of the root of the linkset // Destroy all the constraints have have been made to root. // This is called when the root body is changing. // Returns 'true' of something was actually removed and would need restoring // Called at taint-time!! - public abstract bool RemoveBodyDependencies(BSPrim child); - - // Companion to RemoveBodyDependencies(). If RemoveBodyDependencies() returns 'true', - // this routine will restore the removed constraints. - // Called at taint-time!! - public abstract void RestoreBodyDependencies(BSPrim child); + public abstract bool RemoveDependencies(BSPrimLinkable child); // ================================================================ protected virtual float ComputeLinksetMass() @@ -272,7 +266,7 @@ public abstract class BSLinkset { lock (m_linksetActivityLock) { - foreach (BSPhysObject bp in m_children) + foreach (BSPrimLinkable bp in m_children) { mass += bp.RawMass; } @@ -281,6 +275,7 @@ public abstract class BSLinkset return mass; } + // Computes linkset's center of mass in world coordinates. protected virtual OMV.Vector3 ComputeLinksetCenterOfMass() { OMV.Vector3 com; @@ -289,7 +284,7 @@ public abstract class BSLinkset com = LinksetRoot.Position * LinksetRoot.RawMass; float totalMass = LinksetRoot.RawMass; - foreach (BSPhysObject bp in m_children) + foreach (BSPrimLinkable bp in m_children) { com += bp.Position * bp.RawMass; totalMass += bp.RawMass; @@ -308,9 +303,9 @@ public abstract class BSLinkset { com = LinksetRoot.Position; - foreach (BSPhysObject bp in m_children) + foreach (BSPrimLinkable bp in m_children) { - com += bp.Position * bp.RawMass; + com += bp.Position; } com /= (m_children.Count + 1); } @@ -321,8 +316,8 @@ public abstract class BSLinkset // Invoke the detailed logger and output something if it's enabled. protected void DetailLog(string msg, params Object[] args) { - if (PhysicsScene.PhysicsLogging.Enabled) - PhysicsScene.DetailLog(msg, args); + if (m_physicsScene.PhysicsLogging.Enabled) + m_physicsScene.DetailLog(msg, args); } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs index bd03d31ce9..350a5d1761 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetCompound.cs @@ -35,59 +35,74 @@ using OMV = OpenMetaverse; namespace OpenSim.Region.Physics.BulletSPlugin { + /* // When a child is linked, the relationship position of the child to the parent // is remembered so the child's world position can be recomputed when it is // removed from the linkset. sealed class BSLinksetCompoundInfo : BSLinksetInfo { - public OMV.Vector3 OffsetPos; + public int Index; + public OMV.Vector3 OffsetFromRoot; + public OMV.Vector3 OffsetFromCenterOfMass; public OMV.Quaternion OffsetRot; - public BSLinksetCompoundInfo(OMV.Vector3 p, OMV.Quaternion r) + public BSLinksetCompoundInfo(int indx, OMV.Vector3 p, OMV.Quaternion r) { - OffsetPos = p; + Index = indx; + OffsetFromRoot = p; + OffsetFromCenterOfMass = p; OffsetRot = r; } + // 'centerDisplacement' is the distance from the root the the center-of-mass (Bullet 'zero' of the shape) + public BSLinksetCompoundInfo(int indx, BSPrimLinkable root, BSPrimLinkable child, OMV.Vector3 centerDisplacement) + { + // Each child position and rotation is given relative to the center-of-mass. + OMV.Quaternion invRootOrientation = OMV.Quaternion.Inverse(root.RawOrientation); + OMV.Vector3 displacementFromRoot = (child.RawPosition - root.RawPosition) * invRootOrientation; + OMV.Vector3 displacementFromCOM = displacementFromRoot - centerDisplacement; + OMV.Quaternion displacementRot = child.RawOrientation * invRootOrientation; + + // Save relative position for recomputing child's world position after moving linkset. + Index = indx; + OffsetFromRoot = displacementFromRoot; + OffsetFromCenterOfMass = displacementFromCOM; + OffsetRot = displacementRot; + } public override void Clear() { - OffsetPos = OMV.Vector3.Zero; + Index = 0; + OffsetFromRoot = OMV.Vector3.Zero; + OffsetFromCenterOfMass = OMV.Vector3.Zero; OffsetRot = OMV.Quaternion.Identity; } public override string ToString() { StringBuilder buff = new StringBuilder(); - buff.Append(""); return buff.ToString(); } }; + */ public sealed class BSLinksetCompound : BSLinkset { private static string LogHeader = "[BULLETSIM LINKSET COMPOUND]"; - public BSLinksetCompound(BSScene scene, BSPhysObject parent) : base(scene, parent) + public BSLinksetCompound(BSScene scene, BSPrimLinkable parent) + : base(scene, parent) { } - // For compound implimented linksets, if there are children, use compound shape for the root. - public override BSPhysicsShapeType PreferredPhysicalShape(BSPhysObject requestor) - { - // Returning 'unknown' means we don't have a preference. - BSPhysicsShapeType ret = BSPhysicsShapeType.SHAPE_UNKNOWN; - if (IsRoot(requestor) && HasAnyChildren) - { - ret = BSPhysicsShapeType.SHAPE_COMPOUND; - } - // DetailLog("{0},BSLinksetCompound.PreferredPhysicalShape,call,shape={1}", LinksetRoot.LocalID, ret); - return ret; - } - // When physical properties are changed the linkset needs to recalculate // its internal properties. - public override void Refresh(BSPhysObject requestor) + public override void Refresh(BSPrimLinkable requestor) { base.Refresh(requestor); @@ -96,16 +111,16 @@ public sealed class BSLinksetCompound : BSLinkset } // Schedule a refresh to happen after all the other taint processing. - private void ScheduleRebuild(BSPhysObject requestor) + private void ScheduleRebuild(BSPrimLinkable requestor) { - DetailLog("{0},BSLinksetCompound.ScheduleRebuild,,rebuilding={1},hasChildren={2}", - requestor.LocalID, Rebuilding, HasAnyChildren); + DetailLog("{0},BSLinksetCompound.ScheduleRebuild,,rebuilding={1},hasChildren={2},actuallyScheduling={3}", + requestor.LocalID, Rebuilding, HasAnyChildren, (!Rebuilding && HasAnyChildren)); // When rebuilding, it is possible to set properties that would normally require a rebuild. // If already rebuilding, don't request another rebuild. // If a linkset with just a root prim (simple non-linked prim) don't bother rebuilding. if (!Rebuilding && HasAnyChildren) { - PhysicsScene.PostTaintObject("BSLinksetCompound.ScheduleRebuild", LinksetRoot.LocalID, delegate() + m_physicsScene.PostTaintObject("BSLinksetCompound.ScheduleRebuild", LinksetRoot.LocalID, delegate() { if (HasAnyChildren) RecomputeLinksetCompound(); @@ -118,7 +133,7 @@ public sealed class BSLinksetCompound : BSLinkset // has not yet been fully constructed. // Return 'true' if any properties updated on the passed object. // Called at taint-time! - public override bool MakeDynamic(BSPhysObject child) + public override bool MakeDynamic(BSPrimLinkable child) { bool ret = false; DetailLog("{0},BSLinksetCompound.MakeDynamic,call,IsRoot={1}", child.LocalID, IsRoot(child)); @@ -127,138 +142,131 @@ public sealed class BSLinksetCompound : BSLinkset // The root is going dynamic. Rebuild the linkset so parts and mass get computed properly. ScheduleRebuild(LinksetRoot); } - else - { - // The origional prims are removed from the world as the shape of the root compound - // shape takes over. - PhysicsScene.PE.AddToCollisionFlags(child.PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE); - PhysicsScene.PE.ForceActivationState(child.PhysBody, ActivationState.DISABLE_SIMULATION); - // We don't want collisions from the old linkset children. - PhysicsScene.PE.RemoveFromCollisionFlags(child.PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); - - child.PhysBody.collisionType = CollisionType.LinksetChild; - - ret = true; - } return ret; } - // The object is going static (non-physical). Do any setup necessary for a static linkset. + // The object is going static (non-physical). We do not do anything for static linksets. // Return 'true' if any properties updated on the passed object. - // This doesn't normally happen -- OpenSim removes the objects from the physical - // world if it is a static linkset. // Called at taint-time! - public override bool MakeStatic(BSPhysObject child) + public override bool MakeStatic(BSPrimLinkable child) { bool ret = false; DetailLog("{0},BSLinksetCompound.MakeStatic,call,IsRoot={1}", child.LocalID, IsRoot(child)); if (IsRoot(child)) { + // Schedule a rebuild to verify that the root shape is set to the real shape. ScheduleRebuild(LinksetRoot); } - else - { - // The non-physical children can come back to life. - PhysicsScene.PE.RemoveFromCollisionFlags(child.PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE); - - child.PhysBody.collisionType = CollisionType.LinksetChild; - - // Don't force activation so setting of DISABLE_SIMULATION can stay if used. - PhysicsScene.PE.Activate(child.PhysBody, false); - ret = true; - } return ret; } - public override void UpdateProperties(BSPhysObject updated, bool physicalUpdate) + // 'physicalUpdate' is true if these changes came directly from the physics engine. Don't need to rebuild then. + // Called at taint-time. + public override void UpdateProperties(UpdatedProperties whichUpdated, BSPrimLinkable updated) { + if (!LinksetRoot.IsPhysicallyActive) + { + // No reason to do this physical stuff for static linksets. + DetailLog("{0},BSLinksetCompound.UpdateProperties,notPhysical", LinksetRoot.LocalID); + return; + } + // The user moving a child around requires the rebuilding of the linkset compound shape // One problem is this happens when a border is crossed -- the simulator implementation - // is to store the position into the group which causes the move of the object + // stores the position into the group which causes the move of the object // but it also means all the child positions get updated. // What would cause an unnecessary rebuild so we make sure the linkset is in a // region before bothering to do a rebuild. - if (!IsRoot(updated) - && !physicalUpdate - && PhysicsScene.TerrainManager.IsWithinKnownTerrain(LinksetRoot.RawPosition)) + if (!IsRoot(updated) && m_physicsScene.TerrainManager.IsWithinKnownTerrain(LinksetRoot.RawPosition)) { - updated.LinksetInfo = null; - ScheduleRebuild(updated); + // If a child of the linkset is updating only the position or rotation, that can be done + // without rebuilding the linkset. + // If a handle for the child can be fetch, we update the child here. If a rebuild was + // scheduled by someone else, the rebuild will just replace this setting. + + bool updatedChild = false; + // Anything other than updating position or orientation usually means a physical update + // and that is caused by us updating the object. + if ((whichUpdated & ~(UpdatedProperties.Position | UpdatedProperties.Orientation)) == 0) + { + // Find the physical instance of the child + if (LinksetRoot.PhysShape.HasPhysicalShape && m_physicsScene.PE.IsCompound(LinksetRoot.PhysShape.physShapeInfo)) + { + // It is possible that the linkset is still under construction and the child is not yet + // inserted into the compound shape. A rebuild of the linkset in a pre-step action will + // build the whole thing with the new position or rotation. + // The index must be checked because Bullet references the child array but does no validity + // checking of the child index passed. + int numLinksetChildren = m_physicsScene.PE.GetNumberOfCompoundChildren(LinksetRoot.PhysShape.physShapeInfo); + if (updated.LinksetChildIndex < numLinksetChildren) + { + BulletShape linksetChildShape = m_physicsScene.PE.GetChildShapeFromCompoundShapeIndex(LinksetRoot.PhysShape.physShapeInfo, updated.LinksetChildIndex); + if (linksetChildShape.HasPhysicalShape) + { + // Found the child shape within the compound shape + m_physicsScene.PE.UpdateChildTransform(LinksetRoot.PhysShape.physShapeInfo, updated.LinksetChildIndex, + updated.RawPosition - LinksetRoot.RawPosition, + updated.RawOrientation * OMV.Quaternion.Inverse(LinksetRoot.RawOrientation), + true /* shouldRecalculateLocalAabb */); + updatedChild = true; + DetailLog("{0},BSLinksetCompound.UpdateProperties,changeChildPosRot,whichUpdated={1},pos={2},rot={3}", + updated.LocalID, whichUpdated, updated.RawPosition, updated.RawOrientation); + } + else // DEBUG DEBUG + { // DEBUG DEBUG + DetailLog("{0},BSLinksetCompound.UpdateProperties,couldNotUpdateChild,noChildShape,shape={1}", + updated.LocalID, linksetChildShape); + } // DEBUG DEBUG + } + else // DEBUG DEBUG + { // DEBUG DEBUG + // the child is not yet in the compound shape. This is non-fatal. + DetailLog("{0},BSLinksetCompound.UpdateProperties,couldNotUpdateChild,childNotInCompoundShape,numChildren={1},index={2}", + updated.LocalID, numLinksetChildren, updated.LinksetChildIndex); + } // DEBUG DEBUG + } + else // DEBUG DEBUG + { // DEBUG DEBUG + DetailLog("{0},BSLinksetCompound.UpdateProperties,couldNotUpdateChild,noBodyOrNotCompound", updated.LocalID); + } // DEBUG DEBUG + + if (!updatedChild) + { + // If couldn't do the individual child, the linkset needs a rebuild to incorporate the new child info. + // Note: there are several ways through this code that will not update the child if + // the linkset is being rebuilt. In this case, scheduling a rebuild is a NOOP since + // there will already be a rebuild scheduled. + DetailLog("{0},BSLinksetCompound.UpdateProperties,couldNotUpdateChild.schedulingRebuild,whichUpdated={1}", + updated.LocalID, whichUpdated); + updated.LinksetInfo = null; // setting to 'null' causes relative position to be recomputed. + ScheduleRebuild(updated); + } + } } } // Routine called when rebuilding the body of some member of the linkset. - // Since we don't keep in world relationships, do nothing unless it's a child changing. + // If one of the bodies is being changed, the linkset needs rebuilding. + // For instance, a linkset is built and then a mesh asset is read in and the mesh is recreated. // Returns 'true' of something was actually removed and would need restoring // Called at taint-time!! - public override bool RemoveBodyDependencies(BSPrim child) + public override bool RemoveDependencies(BSPrimLinkable child) { bool ret = false; DetailLog("{0},BSLinksetCompound.RemoveBodyDependencies,refreshIfChild,rID={1},rBody={2},isRoot={3}", - child.LocalID, LinksetRoot.LocalID, LinksetRoot.PhysBody.AddrString, IsRoot(child)); + child.LocalID, LinksetRoot.LocalID, LinksetRoot.PhysBody, IsRoot(child)); - if (!IsRoot(child)) - { - // Because it is a convenient time, recompute child world position and rotation based on - // its position in the linkset. - RecomputeChildWorldPosition(child, true); - } - - // Cannot schedule a refresh/rebuild here because this routine is called when - // the linkset is being rebuilt. - // InternalRefresh(LinksetRoot); + ScheduleRebuild(child); return ret; } - // Companion to RemoveBodyDependencies(). If RemoveBodyDependencies() returns 'true', - // this routine will restore the removed constraints. - // Called at taint-time!! - public override void RestoreBodyDependencies(BSPrim child) - { - } - - // When the linkset is built, the child shape is added to the compound shape relative to the - // root shape. The linkset then moves around but this does not move the actual child - // prim. The child prim's location must be recomputed based on the location of the root shape. - private void RecomputeChildWorldPosition(BSPhysObject child, bool inTaintTime) - { - BSLinksetCompoundInfo lci = child.LinksetInfo as BSLinksetCompoundInfo; - if (lci != null) - { - if (inTaintTime) - { - OMV.Vector3 oldPos = child.RawPosition; - child.ForcePosition = LinksetRoot.RawPosition + lci.OffsetPos; - child.ForceOrientation = LinksetRoot.RawOrientation * lci.OffsetRot; - DetailLog("{0},BSLinksetCompound.RecomputeChildWorldPosition,oldPos={1},lci={2},newPos={3}", - child.LocalID, oldPos, lci, child.RawPosition); - } - else - { - // TaintedObject is not used here so the raw position is set now and not at taint-time. - child.Position = LinksetRoot.RawPosition + lci.OffsetPos; - child.Orientation = LinksetRoot.RawOrientation * lci.OffsetRot; - } - } - else - { - // This happens when children have been added to the linkset but the linkset - // has not been constructed yet. So like, at taint time, adding children to a linkset - // and then changing properties of the children (makePhysical, for instance) - // but the post-print action of actually rebuilding the linkset has not yet happened. - // PhysicsScene.Logger.WarnFormat("{0} Restoring linkset child position failed because of no relative position computed. ID={1}", - // LogHeader, child.LocalID); - DetailLog("{0},BSLinksetCompound.recomputeChildWorldPosition,noRelativePositonInfo", child.LocalID); - } - } - // ================================================================ // Add a new child to the linkset. // Called while LinkActivity is locked. - protected override void AddChildToLinkset(BSPhysObject child) + protected override void AddChildToLinkset(BSPrimLinkable child) { if (!HasChild(child)) { @@ -274,8 +282,10 @@ public sealed class BSLinksetCompound : BSLinkset // Remove the specified child from the linkset. // Safe to call even if the child is not really in the linkset. - protected override void RemoveChildFromLinkset(BSPhysObject child) + protected override void RemoveChildFromLinkset(BSPrimLinkable child) { + child.ClearDisplacement(); + if (m_children.Remove(child)) { DetailLog("{0},BSLinksetCompound.RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}", @@ -284,7 +294,7 @@ public sealed class BSLinksetCompound : BSLinkset child.LocalID, child.PhysBody.AddrString); // Cause the child's body to be rebuilt and thus restored to normal operation - RecomputeChildWorldPosition(child, false); + child.LinksetInfo = null; child.ForceBodyShapeRebuild(false); if (!HasAnyChildren) @@ -295,7 +305,7 @@ public sealed class BSLinksetCompound : BSLinkset else { // Rebuild the compound shape with the child removed - ScheduleRebuild(child); + ScheduleRebuild(LinksetRoot); } } return; @@ -306,87 +316,113 @@ public sealed class BSLinksetCompound : BSLinkset // Constraint linksets are rebuilt every time. // Note that this works for rebuilding just the root after a linkset is taken apart. // Called at taint time!! + private bool UseBulletSimRootOffsetHack = false; // Attempt to have Bullet track the coords of root compound shape + private bool disableCOM = true; // For basic linkset debugging, turn off the center-of-mass setting private void RecomputeLinksetCompound() { try { - // Suppress rebuilding while rebuilding Rebuilding = true; - // Cause the root shape to be rebuilt as a compound object with just the root in it + // No matter what is being done, force the root prim's PhysBody and PhysShape to get set + // to what they should be as if the root was not in a linkset. + // Not that bad since we only get into this routine if there are children in the linkset and + // something has been updated/changed. LinksetRoot.ForceBodyShapeRebuild(true); - DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,start,rBody={1},rShape={2},numChildren={3}", - LinksetRoot.LocalID, LinksetRoot.PhysBody, LinksetRoot.PhysShape, NumberOfChildren); - - // Add a shape for each of the other children in the linkset - ForEachMember(delegate(BSPhysObject cPrim) + // There is no reason to build all this physical stuff for a non-physical linkset. + if (!LinksetRoot.IsPhysicallyActive) { + // Clean up any old linkset shape and make sure the root shape is set to the root object. + DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,notPhysical", LinksetRoot.LocalID); + + return; // Note the 'finally' clause at the botton which will get executed. + } + + // Get a new compound shape to build the linkset shape in. + BSShape linksetShape = BSShapeCompound.GetReference(m_physicsScene); + + // The center of mass for the linkset is the geometric center of the group. + // Compute a displacement for each component so it is relative to the center-of-mass. + // Bullet presumes an object's origin (relative <0,0,0>) is its center-of-mass + OMV.Vector3 centerOfMassW = ComputeLinksetCenterOfMass(); + + OMV.Quaternion invRootOrientation = OMV.Quaternion.Normalize(OMV.Quaternion.Inverse(LinksetRoot.RawOrientation)); + + // 'centerDisplacement' is the value to subtract from children to give physical offset position + OMV.Vector3 centerDisplacementV = (centerOfMassW - LinksetRoot.RawPosition) * invRootOrientation; + if (UseBulletSimRootOffsetHack || disableCOM) + { + centerDisplacementV = OMV.Vector3.Zero; + LinksetRoot.ClearDisplacement(); + } + else + { + LinksetRoot.SetEffectiveCenterOfMassDisplacement(centerDisplacementV); + } + DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,COM,rootPos={1},com={2},comDisp={3}", + LinksetRoot.LocalID, LinksetRoot.RawPosition, centerOfMassW, centerDisplacementV); + + // Add the shapes of all the components of the linkset + int memberIndex = 1; + ForEachMember(delegate(BSPrimLinkable cPrim) + { + // Root shape is always index zero. + cPrim.LinksetChildIndex = IsRoot(cPrim) ? 0 : memberIndex; + + // Get a reference to the shape of the child and add that shape to the linkset compound shape + BSShape childShape = cPrim.PhysShape.GetReference(m_physicsScene, cPrim); + OMV.Vector3 offsetPos = (cPrim.RawPosition - LinksetRoot.RawPosition) * invRootOrientation - centerDisplacementV; + OMV.Quaternion offsetRot = OMV.Quaternion.Normalize(cPrim.RawOrientation) * invRootOrientation; + m_physicsScene.PE.AddChildShapeToCompoundShape(linksetShape.physShapeInfo, childShape.physShapeInfo, offsetPos, offsetRot); + DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,addChild,indx={1},cShape={2},offPos={3},offRot={4}", + LinksetRoot.LocalID, memberIndex, childShape, offsetPos, offsetRot); + + // Since we are borrowing the shape of the child, disable the origional child body if (!IsRoot(cPrim)) { - // Compute the displacement of the child from the root of the linkset. - // This info is saved in the child prim so the relationship does not - // change over time and the new child position can be computed - // when the linkset is being disassembled (the linkset may have moved). - BSLinksetCompoundInfo lci = cPrim.LinksetInfo as BSLinksetCompoundInfo; - if (lci == null) - { - // Each child position and rotation is given relative to the root. - OMV.Quaternion invRootOrientation = OMV.Quaternion.Inverse(LinksetRoot.RawOrientation); - OMV.Vector3 displacementPos = (cPrim.RawPosition - LinksetRoot.RawPosition) * invRootOrientation; - OMV.Quaternion displacementRot = cPrim.RawOrientation * invRootOrientation; - - // Save relative position for recomputing child's world position after moving linkset. - lci = new BSLinksetCompoundInfo(displacementPos, displacementRot); - cPrim.LinksetInfo = lci; - DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,creatingRelPos,lci={1}", cPrim.LocalID, lci); - } - - DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,addMemberToShape,mID={1},mShape={2},dispPos={3},dispRot={4}", - LinksetRoot.LocalID, cPrim.LocalID, cPrim.PhysShape, lci.OffsetPos, lci.OffsetRot); - - if (cPrim.PhysShape.isNativeShape) - { - // A native shape is turning into a hull collision shape because native - // shapes are not shared so we have to hullify it so it will be tracked - // and freed at the correct time. This also solves the scaling problem - // (native shapes scaled but hull/meshes are assumed to not be). - // TODO: decide of the native shape can just be used in the compound shape. - // Use call to CreateGeomNonSpecial(). - BulletShape saveShape = cPrim.PhysShape; - cPrim.PhysShape.Clear(); // Don't let the create free the child's shape - // PhysicsScene.Shapes.CreateGeomNonSpecial(true, cPrim, null); - PhysicsScene.Shapes.CreateGeomMeshOrHull(cPrim, null); - BulletShape newShape = cPrim.PhysShape; - cPrim.PhysShape = saveShape; - PhysicsScene.PE.AddChildShapeToCompoundShape(LinksetRoot.PhysShape, newShape, lci.OffsetPos, lci.OffsetRot); - } - else - { - // For the shared shapes (meshes and hulls), just use the shape in the child. - // The reference count added here will be decremented when the compound shape - // is destroyed in BSShapeCollection (the child shapes are looped over and dereferenced). - if (PhysicsScene.Shapes.ReferenceShape(cPrim.PhysShape)) - { - PhysicsScene.Logger.ErrorFormat("{0} Rebuilt sharable shape when building linkset! Region={1}, primID={2}, shape={3}", - LogHeader, PhysicsScene.RegionName, cPrim.LocalID, cPrim.PhysShape); - } - PhysicsScene.PE.AddChildShapeToCompoundShape(LinksetRoot.PhysShape, cPrim.PhysShape, lci.OffsetPos, lci.OffsetRot); - } + m_physicsScene.PE.AddToCollisionFlags(cPrim.PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE); + m_physicsScene.PE.ForceActivationState(cPrim.PhysBody, ActivationState.DISABLE_SIMULATION); + // We don't want collisions from the old linkset children. + m_physicsScene.PE.RemoveFromCollisionFlags(cPrim.PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + cPrim.PhysBody.collisionType = CollisionType.LinksetChild; } + + memberIndex++; + return false; // 'false' says to move onto the next child in the list }); + // Replace the root shape with the built compound shape. + // Object removed and added to world to get collision cache rebuilt for new shape. + LinksetRoot.PhysShape.Dereference(m_physicsScene); + LinksetRoot.PhysShape = linksetShape; + m_physicsScene.PE.RemoveObjectFromWorld(m_physicsScene.World, LinksetRoot.PhysBody); + m_physicsScene.PE.SetCollisionShape(m_physicsScene.World, LinksetRoot.PhysBody, linksetShape.physShapeInfo); + m_physicsScene.PE.AddObjectToWorld(m_physicsScene.World, LinksetRoot.PhysBody); + DetailLog("{0},BSLinksetCompound.RecomputeLinksetCompound,addBody,body={1},shape={2}", + LinksetRoot.LocalID, LinksetRoot.PhysBody, linksetShape); + // With all of the linkset packed into the root prim, it has the mass of everyone. LinksetMass = ComputeLinksetMass(); LinksetRoot.UpdatePhysicalMassProperties(LinksetMass, true); + + if (UseBulletSimRootOffsetHack) + { + // Enable the physical position updator to return the position and rotation of the root shape. + // This enables a feature in the C++ code to return the world coordinates of the first shape in the + // compound shape. This eleviates the need to offset the returned physical position by the + // center-of-mass offset. + m_physicsScene.PE.AddToCollisionFlags(LinksetRoot.PhysBody, CollisionFlags.BS_RETURN_ROOT_COMPOUND_SHAPE); + } } finally { Rebuilding = false; } - PhysicsScene.PE.RecalculateCompoundShapeLocalAabb(LinksetRoot.PhysShape); + // See that the Aabb surrounds the new shape + m_physicsScene.PE.RecalculateCompoundShapeLocalAabb(LinksetRoot.PhysShape.physShapeInfo); } } } \ No newline at end of file diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs index d0b2a567a4..a06a44d33f 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSLinksetConstraints.cs @@ -36,7 +36,7 @@ public sealed class BSLinksetConstraints : BSLinkset { // private static string LogHeader = "[BULLETSIM LINKSET CONSTRAINTS]"; - public BSLinksetConstraints(BSScene scene, BSPhysObject parent) : base(scene, parent) + public BSLinksetConstraints(BSScene scene, BSPrimLinkable parent) : base(scene, parent) { } @@ -44,14 +44,14 @@ public sealed class BSLinksetConstraints : BSLinkset // its internal properties. // This is queued in the 'post taint' queue so the // refresh will happen once after all the other taints are applied. - public override void Refresh(BSPhysObject requestor) + public override void Refresh(BSPrimLinkable requestor) { base.Refresh(requestor); if (HasAnyChildren && IsRoot(requestor)) { // Queue to happen after all the other taint processing - PhysicsScene.PostTaintObject("BSLinksetContraints.Refresh", requestor.LocalID, delegate() + m_physicsScene.PostTaintObject("BSLinksetContraints.Refresh", requestor.LocalID, delegate() { if (HasAnyChildren && IsRoot(requestor)) RecomputeLinksetConstraints(); @@ -65,7 +65,7 @@ public sealed class BSLinksetConstraints : BSLinkset // has not yet been fully constructed. // Return 'true' if any properties updated on the passed object. // Called at taint-time! - public override bool MakeDynamic(BSPhysObject child) + public override bool MakeDynamic(BSPrimLinkable child) { // What is done for each object in BSPrim is what we want. return false; @@ -76,14 +76,14 @@ public sealed class BSLinksetConstraints : BSLinkset // This doesn't normally happen -- OpenSim removes the objects from the physical // world if it is a static linkset. // Called at taint-time! - public override bool MakeStatic(BSPhysObject child) + public override bool MakeStatic(BSPrimLinkable child) { // What is done for each object in BSPrim is what we want. return false; } // Called at taint-time!! - public override void UpdateProperties(BSPhysObject updated, bool inTaintTime) + public override void UpdateProperties(UpdatedProperties whichUpdated, BSPrimLinkable pObj) { // Nothing to do for constraints on property updates } @@ -93,11 +93,11 @@ public sealed class BSLinksetConstraints : BSLinkset // up to rebuild the constraints before the next simulation step. // Returns 'true' of something was actually removed and would need restoring // Called at taint-time!! - public override bool RemoveBodyDependencies(BSPrim child) + public override bool RemoveDependencies(BSPrimLinkable child) { bool ret = false; - DetailLog("{0},BSLinksetConstraint.RemoveBodyDependencies,removeChildrenForRoot,rID={1},rBody={2}", + DetailLog("{0},BSLinksetConstraint.RemoveDependencies,removeChildrenForRoot,rID={1},rBody={2}", child.LocalID, LinksetRoot.LocalID, LinksetRoot.PhysBody.AddrString); lock (m_linksetActivityLock) @@ -110,19 +110,11 @@ public sealed class BSLinksetConstraints : BSLinkset return ret; } - // Companion to RemoveBodyDependencies(). If RemoveBodyDependencies() returns 'true', - // this routine will restore the removed constraints. - // Called at taint-time!! - public override void RestoreBodyDependencies(BSPrim child) - { - // The Refresh operation queued by RemoveBodyDependencies() will build any missing constraints. - } - // ================================================================ // Add a new child to the linkset. // Called while LinkActivity is locked. - protected override void AddChildToLinkset(BSPhysObject child) + protected override void AddChildToLinkset(BSPrimLinkable child) { if (!HasChild(child)) { @@ -138,19 +130,19 @@ public sealed class BSLinksetConstraints : BSLinkset // Remove the specified child from the linkset. // Safe to call even if the child is not really in my linkset. - protected override void RemoveChildFromLinkset(BSPhysObject child) + protected override void RemoveChildFromLinkset(BSPrimLinkable child) { if (m_children.Remove(child)) { - BSPhysObject rootx = LinksetRoot; // capture the root and body as of now - BSPhysObject childx = child; + BSPrimLinkable rootx = LinksetRoot; // capture the root and body as of now + BSPrimLinkable childx = child; DetailLog("{0},BSLinksetConstraints.RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}", childx.LocalID, rootx.LocalID, rootx.PhysBody.AddrString, childx.LocalID, childx.PhysBody.AddrString); - PhysicsScene.TaintedObject("BSLinksetConstraints.RemoveChildFromLinkset", delegate() + m_physicsScene.TaintedObject("BSLinksetConstraints.RemoveChildFromLinkset", delegate() { PhysicallyUnlinkAChildFromRoot(rootx, childx); }); @@ -167,13 +159,13 @@ public sealed class BSLinksetConstraints : BSLinkset // Create a constraint between me (root of linkset) and the passed prim (the child). // Called at taint time! - private void PhysicallyLinkAChildToRoot(BSPhysObject rootPrim, BSPhysObject childPrim) + private void PhysicallyLinkAChildToRoot(BSPrimLinkable rootPrim, BSPrimLinkable childPrim) { // Don't build the constraint when asked. Put it off until just before the simulation step. Refresh(rootPrim); } - private BSConstraint BuildConstraint(BSPhysObject rootPrim, BSPhysObject childPrim) + private BSConstraint BuildConstraint(BSPrimLinkable rootPrim, BSPrimLinkable childPrim) { // Zero motion for children so they don't interpolate childPrim.ZeroMotion(true); @@ -195,7 +187,7 @@ public sealed class BSLinksetConstraints : BSLinkset // http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=4818 BSConstraint6Dof constrain = new BSConstraint6Dof( - PhysicsScene.World, rootPrim.PhysBody, childPrim.PhysBody, midPoint, true, true ); + m_physicsScene.World, rootPrim.PhysBody, childPrim.PhysBody, midPoint, true, true ); // PhysicsScene.World, childPrim.BSBody, rootPrim.BSBody, midPoint, true, true ); /* NOTE: below is an attempt to build constraint with full frame computation, etc. @@ -224,15 +216,15 @@ public sealed class BSLinksetConstraints : BSLinkset // ================================================================================== */ - PhysicsScene.Constraints.AddConstraint(constrain); + m_physicsScene.Constraints.AddConstraint(constrain); // zero linear and angular limits makes the objects unable to move in relation to each other constrain.SetLinearLimits(OMV.Vector3.Zero, OMV.Vector3.Zero); constrain.SetAngularLimits(OMV.Vector3.Zero, OMV.Vector3.Zero); // tweek the constraint to increase stability - constrain.UseFrameOffset(BSParam.BoolNumeric(BSParam.LinkConstraintUseFrameOffset)); - constrain.TranslationalLimitMotor(BSParam.BoolNumeric(BSParam.LinkConstraintEnableTransMotor), + constrain.UseFrameOffset(BSParam.LinkConstraintUseFrameOffset); + constrain.TranslationalLimitMotor(BSParam.LinkConstraintEnableTransMotor, BSParam.LinkConstraintTransMotorMaxVel, BSParam.LinkConstraintTransMotorMaxForce); constrain.SetCFMAndERP(BSParam.LinkConstraintCFM, BSParam.LinkConstraintERP); @@ -247,7 +239,7 @@ public sealed class BSLinksetConstraints : BSLinkset // The root and child bodies are passed in because we need to remove the constraint between // the bodies that were present at unlink time. // Called at taint time! - private bool PhysicallyUnlinkAChildFromRoot(BSPhysObject rootPrim, BSPhysObject childPrim) + private bool PhysicallyUnlinkAChildFromRoot(BSPrimLinkable rootPrim, BSPrimLinkable childPrim) { bool ret = false; DetailLog("{0},BSLinksetConstraint.PhysicallyUnlinkAChildFromRoot,taint,root={1},rBody={2},child={3},cBody={4}", @@ -256,10 +248,10 @@ public sealed class BSLinksetConstraints : BSLinkset childPrim.LocalID, childPrim.PhysBody.AddrString); // Find the constraint for this link and get rid of it from the overall collection and from my list - if (PhysicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.PhysBody, childPrim.PhysBody)) + if (m_physicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.PhysBody, childPrim.PhysBody)) { // Make the child refresh its location - PhysicsScene.PE.PushUpdate(childPrim.PhysBody); + m_physicsScene.PE.PushUpdate(childPrim.PhysBody); ret = true; } @@ -269,11 +261,11 @@ public sealed class BSLinksetConstraints : BSLinkset // Remove linkage between myself and any possible children I might have. // Returns 'true' of any constraints were destroyed. // Called at taint time! - private bool PhysicallyUnlinkAllChildrenFromRoot(BSPhysObject rootPrim) + private bool PhysicallyUnlinkAllChildrenFromRoot(BSPrimLinkable rootPrim) { DetailLog("{0},BSLinksetConstraint.PhysicallyUnlinkAllChildren,taint", rootPrim.LocalID); - return PhysicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.PhysBody); + return m_physicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.PhysBody); } // Call each of the constraints that make up this linkset and recompute the @@ -289,7 +281,7 @@ public sealed class BSLinksetConstraints : BSLinkset DetailLog("{0},BSLinksetConstraint.RecomputeLinksetConstraints,set,rBody={1},linksetMass={2}", LinksetRoot.LocalID, LinksetRoot.PhysBody.AddrString, linksetMass); - foreach (BSPhysObject child in m_children) + foreach (BSPrimLinkable child in m_children) { // A child in the linkset physically shows the mass of the whole linkset. // This allows Bullet to apply enough force on the child to move the whole linkset. @@ -297,7 +289,7 @@ public sealed class BSLinksetConstraints : BSLinkset child.UpdatePhysicalMassProperties(linksetMass, true); BSConstraint constrain; - if (!PhysicsScene.Constraints.TryGetConstraint(LinksetRoot.PhysBody, child.PhysBody, out constrain)) + if (!m_physicsScene.Constraints.TryGetConstraint(LinksetRoot.PhysBody, child.PhysBody, out constrain)) { // If constraint doesn't exist yet, create it. constrain = BuildConstraint(LinksetRoot, child); diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSMaterials.cs b/OpenSim/Region/Physics/BulletSPlugin/BSMaterials.cs index 92d62ff9ea..ee77d6e3f8 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSMaterials.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSMaterials.cs @@ -180,11 +180,14 @@ public static class BSMaterials // Use reflection to set the value in the attribute structure. private static void SetAttributeValue(int matType, string attribName, float val) { + // Get the current attribute values for this material MaterialAttributes thisAttrib = Attributes[matType]; + // Find the field for the passed attribute name (eg, find field named 'friction') FieldInfo fieldInfo = thisAttrib.GetType().GetField(attribName.ToLower()); if (fieldInfo != null) { fieldInfo.SetValue(thisAttrib, val); + // Copy new attributes back to array -- since MaterialAttributes is 'struct', passed by value, not reference. Attributes[matType] = thisAttrib; } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSMotors.cs b/OpenSim/Region/Physics/BulletSPlugin/BSMotors.cs index 817a5f7e7e..ef662b5919 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSMotors.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSMotors.cs @@ -59,22 +59,17 @@ public abstract class BSMotor { if (PhysicsScene != null) { - if (PhysicsScene.VehicleLoggingEnabled) - { - PhysicsScene.DetailLog(msg, parms); - } + PhysicsScene.DetailLog(msg, parms); } } } // Motor which moves CurrentValue to TargetValue over TimeScale seconds. -// The TargetValue decays in TargetValueDecayTimeScale and -// the CurrentValue will be held back by FrictionTimeScale. +// The TargetValue decays in TargetValueDecayTimeScale. // This motor will "zero itself" over time in that the targetValue will // decay to zero and the currentValue will follow it to that zero. // The overall effect is for the returned correction value to go from large -// values (the total difference between current and target minus friction) -// to small and eventually zero values. +// values to small and eventually zero values. // TimeScale and TargetDelayTimeScale may be 'infinite' which means no decay. // For instance, if something is moving at speed X and the desired speed is Y, @@ -91,7 +86,6 @@ public class BSVMotor : BSMotor public virtual float TimeScale { get; set; } public virtual float TargetValueDecayTimeScale { get; set; } - public virtual Vector3 FrictionTimescale { get; set; } public virtual float Efficiency { get; set; } public virtual float ErrorZeroThreshold { get; set; } @@ -100,10 +94,13 @@ public class BSVMotor : BSMotor public virtual Vector3 CurrentValue { get; protected set; } public virtual Vector3 LastError { get; protected set; } - public virtual bool ErrorIsZero - { get { - return (LastError == Vector3.Zero || LastError.LengthSquared() <= ErrorZeroThreshold); - } + public virtual bool ErrorIsZero() + { + return ErrorIsZero(LastError); + } + public virtual bool ErrorIsZero(Vector3 err) + { + return (err == Vector3.Zero || err.ApproxEquals(Vector3.Zero, ErrorZeroThreshold)); } public BSVMotor(string useName) @@ -111,16 +108,14 @@ public class BSVMotor : BSMotor { TimeScale = TargetValueDecayTimeScale = BSMotor.Infinite; Efficiency = 1f; - FrictionTimescale = BSMotor.InfiniteVector; CurrentValue = TargetValue = Vector3.Zero; ErrorZeroThreshold = 0.001f; } - public BSVMotor(string useName, float timeScale, float decayTimeScale, Vector3 frictionTimeScale, float efficiency) + public BSVMotor(string useName, float timeScale, float decayTimeScale, float efficiency) : this(useName) { TimeScale = timeScale; TargetValueDecayTimeScale = decayTimeScale; - FrictionTimescale = frictionTimeScale; Efficiency = efficiency; CurrentValue = TargetValue = Vector3.Zero; } @@ -138,7 +133,8 @@ public class BSVMotor : BSMotor CurrentValue = TargetValue = Vector3.Zero; } - // Compute the next step and return the new current value + // Compute the next step and return the new current value. + // Returns the correction needed to move 'current' to 'target'. public virtual Vector3 Step(float timeStep) { if (!Enabled) return TargetValue; @@ -148,9 +144,10 @@ public class BSVMotor : BSMotor Vector3 correction = Vector3.Zero; Vector3 error = TargetValue - CurrentValue; - if (!error.ApproxEquals(Vector3.Zero, ErrorZeroThreshold)) + LastError = error; + if (!ErrorIsZero(error)) { - correction = Step(timeStep, error); + correction = StepError(timeStep, error); CurrentValue += correction; @@ -163,44 +160,40 @@ public class BSVMotor : BSMotor TargetValue *= (1f - decayFactor); } - // The amount we can correct the error is reduced by the friction - Vector3 frictionFactor = Vector3.Zero; - if (FrictionTimescale != BSMotor.InfiniteVector) - { - // frictionFactor = (Vector3.One / FrictionTimescale) * timeStep; - // Individual friction components can be 'infinite' so compute each separately. - frictionFactor.X = (FrictionTimescale.X == BSMotor.Infinite) ? 0f : (1f / FrictionTimescale.X); - frictionFactor.Y = (FrictionTimescale.Y == BSMotor.Infinite) ? 0f : (1f / FrictionTimescale.Y); - frictionFactor.Z = (FrictionTimescale.Z == BSMotor.Infinite) ? 0f : (1f / FrictionTimescale.Z); - frictionFactor *= timeStep; - CurrentValue *= (Vector3.One - frictionFactor); - } - MDetailLog("{0}, BSVMotor.Step,nonZero,{1},origCurr={2},origTarget={3},timeStep={4},err={5},corr={6}", BSScene.DetailLogZero, UseName, origCurrVal, origTarget, timeStep, error, correction); - MDetailLog("{0}, BSVMotor.Step,nonZero,{1},tgtDecayTS={2},decayFact={3},frictTS={4},frictFact={5},tgt={6},curr={7}", - BSScene.DetailLogZero, UseName, - TargetValueDecayTimeScale, decayFactor, FrictionTimescale, frictionFactor, - TargetValue, CurrentValue); + MDetailLog("{0}, BSVMotor.Step,nonZero,{1},tgtDecayTS={2},decayFact={3},tgt={4},curr={5}", + BSScene.DetailLogZero, UseName, TargetValueDecayTimeScale, decayFactor, TargetValue, CurrentValue); } else { // Difference between what we have and target is small. Motor is done. + if (TargetValue.ApproxEquals(Vector3.Zero, ErrorZeroThreshold)) + { + // The target can step down to nearly zero but not get there. If close to zero + // it is really zero. + TargetValue = Vector3.Zero; + } CurrentValue = TargetValue; - MDetailLog("{0}, BSVMotor.Step,zero,{1},origTgt={2},origCurr={3},ret={4}", - BSScene.DetailLogZero, UseName, origCurrVal, origTarget, CurrentValue); + MDetailLog("{0}, BSVMotor.Step,zero,{1},origTgt={2},origCurr={3},currTgt={4},currCurr={5}", + BSScene.DetailLogZero, UseName, origCurrVal, origTarget, TargetValue, CurrentValue); } - return CurrentValue; + return correction; } - public virtual Vector3 Step(float timeStep, Vector3 error) + // version of step that sets the current value before doing the step + public virtual Vector3 Step(float timeStep, Vector3 current) + { + CurrentValue = current; + return Step(timeStep); + } + public virtual Vector3 StepError(float timeStep, Vector3 error) { if (!Enabled) return Vector3.Zero; - LastError = error; Vector3 returnCorrection = Vector3.Zero; - if (!error.ApproxEquals(Vector3.Zero, ErrorZeroThreshold)) + if (!ErrorIsZero(error)) { // correction = error / secondsItShouldTakeToCorrect Vector3 correctionAmount; @@ -222,9 +215,9 @@ public class BSVMotor : BSMotor // maximum number of outputs to generate. int maxOutput = 50; MDetailLog("{0},BSVMotor.Test,{1},===================================== BEGIN Test Output", BSScene.DetailLogZero, UseName); - MDetailLog("{0},BSVMotor.Test,{1},timeScale={2},targDlyTS={3},frictTS={4},eff={5},curr={6},tgt={7}", + MDetailLog("{0},BSVMotor.Test,{1},timeScale={2},targDlyTS={3},eff={4},curr={5},tgt={6}", BSScene.DetailLogZero, UseName, - TimeScale, TargetValueDecayTimeScale, FrictionTimescale, Efficiency, + TimeScale, TargetValueDecayTimeScale, Efficiency, CurrentValue, TargetValue); LastError = BSMotor.InfiniteVector; @@ -235,43 +228,141 @@ public class BSVMotor : BSMotor BSScene.DetailLogZero, UseName, CurrentValue, TargetValue, LastError, lastStep); } MDetailLog("{0},BSVMotor.Test,{1},===================================== END Test Output", BSScene.DetailLogZero, UseName); - + } public override string ToString() { - return String.Format("<{0},curr={1},targ={2},lastErr={3},decayTS={4},frictTS={5}>", - UseName, CurrentValue, TargetValue, LastError, TargetValueDecayTimeScale, FrictionTimescale); + return String.Format("<{0},curr={1},targ={2},lastErr={3},decayTS={4}>", + UseName, CurrentValue, TargetValue, LastError, TargetValueDecayTimeScale); } } +// ============================================================================ +// ============================================================================ public class BSFMotor : BSMotor { - public float TimeScale { get; set; } - public float DecayTimeScale { get; set; } - public float Friction { get; set; } - public float Efficiency { get; set; } + public virtual float TimeScale { get; set; } + public virtual float TargetValueDecayTimeScale { get; set; } + public virtual float Efficiency { get; set; } - public float Target { get; private set; } - public float CurrentValue { get; private set; } + public virtual float ErrorZeroThreshold { get; set; } - public BSFMotor(string useName, float timeScale, float decayTimescale, float friction, float efficiency) + public virtual float TargetValue { get; protected set; } + public virtual float CurrentValue { get; protected set; } + public virtual float LastError { get; protected set; } + + public virtual bool ErrorIsZero() + { + return ErrorIsZero(LastError); + } + public virtual bool ErrorIsZero(float err) + { + return (err >= -ErrorZeroThreshold && err <= ErrorZeroThreshold); + } + + public BSFMotor(string useName, float timeScale, float decayTimescale, float efficiency) : base(useName) { + TimeScale = TargetValueDecayTimeScale = BSMotor.Infinite; + Efficiency = 1f; + CurrentValue = TargetValue = 0f; + ErrorZeroThreshold = 0.01f; } - public void SetCurrent(float target) + public void SetCurrent(float current) { + CurrentValue = current; } public void SetTarget(float target) { + TargetValue = target; } + public override void Zero() + { + base.Zero(); + CurrentValue = TargetValue = 0f; + } + public virtual float Step(float timeStep) { - return 0f; + if (!Enabled) return TargetValue; + + float origTarget = TargetValue; // DEBUG + float origCurrVal = CurrentValue; // DEBUG + + float correction = 0f; + float error = TargetValue - CurrentValue; + LastError = error; + if (!ErrorIsZero(error)) + { + correction = StepError(timeStep, error); + + CurrentValue += correction; + + // The desired value reduces to zero which also reduces the difference with current. + // If the decay time is infinite, don't decay at all. + float decayFactor = 0f; + if (TargetValueDecayTimeScale != BSMotor.Infinite) + { + decayFactor = (1.0f / TargetValueDecayTimeScale) * timeStep; + TargetValue *= (1f - decayFactor); + } + + MDetailLog("{0}, BSFMotor.Step,nonZero,{1},origCurr={2},origTarget={3},timeStep={4},err={5},corr={6}", + BSScene.DetailLogZero, UseName, origCurrVal, origTarget, + timeStep, error, correction); + MDetailLog("{0}, BSFMotor.Step,nonZero,{1},tgtDecayTS={2},decayFact={3},tgt={4},curr={5}", + BSScene.DetailLogZero, UseName, TargetValueDecayTimeScale, decayFactor, TargetValue, CurrentValue); + } + else + { + // Difference between what we have and target is small. Motor is done. + if (Util.InRange(TargetValue, -ErrorZeroThreshold, ErrorZeroThreshold)) + { + // The target can step down to nearly zero but not get there. If close to zero + // it is really zero. + TargetValue = 0f; + } + CurrentValue = TargetValue; + MDetailLog("{0}, BSFMotor.Step,zero,{1},origTgt={2},origCurr={3},ret={4}", + BSScene.DetailLogZero, UseName, origCurrVal, origTarget, CurrentValue); + } + + return CurrentValue; } + + public virtual float StepError(float timeStep, float error) + { + if (!Enabled) return 0f; + + float returnCorrection = 0f; + if (!ErrorIsZero(error)) + { + // correction = error / secondsItShouldTakeToCorrect + float correctionAmount; + if (TimeScale == 0f || TimeScale == BSMotor.Infinite) + correctionAmount = error * timeStep; + else + correctionAmount = error / TimeScale * timeStep; + + returnCorrection = correctionAmount; + MDetailLog("{0}, BSFMotor.Step,nonZero,{1},timeStep={2},timeScale={3},err={4},corr={5}", + BSScene.DetailLogZero, UseName, timeStep, TimeScale, error, correctionAmount); + } + return returnCorrection; + } + + public override string ToString() + { + return String.Format("<{0},curr={1},targ={2},lastErr={3},decayTS={4}>", + UseName, CurrentValue, TargetValue, LastError, TargetValueDecayTimeScale); + } + } +// ============================================================================ +// ============================================================================ // Proportional, Integral, Derivitive Motor // Good description at http://www.answers.com/topic/pid-controller . Includes processes for choosing p, i and d factors. public class BSPIDVMotor : BSVMotor @@ -281,6 +372,12 @@ public class BSPIDVMotor : BSVMotor public Vector3 integralFactor { get; set; } public Vector3 derivFactor { get; set; } + // The factors are vectors for the three dimensions. This is the proportional of each + // that is applied. This could be multiplied through the actual factors but it + // is sometimes easier to manipulate the factors and their mix separately. + // to + public Vector3 FactorMix; + // Arbritrary factor range. // EfficiencyHigh means move quickly to the correct number. EfficiencyLow means might over correct. public float EfficiencyHigh = 0.4f; @@ -295,6 +392,7 @@ public class BSPIDVMotor : BSVMotor proportionFactor = new Vector3(1.00f, 1.00f, 1.00f); integralFactor = new Vector3(1.00f, 1.00f, 1.00f); derivFactor = new Vector3(1.00f, 1.00f, 1.00f); + FactorMix = new Vector3(0.5f, 0.25f, 0.25f); RunningIntegration = Vector3.Zero; LastError = Vector3.Zero; } @@ -310,20 +408,24 @@ public class BSPIDVMotor : BSVMotor set { base.Efficiency = Util.Clamp(value, 0f, 1f); + // Compute factors based on efficiency. // If efficiency is high (1f), use a factor value that moves the error value to zero with little overshoot. // If efficiency is low (0f), use a factor value that overcorrects. // TODO: might want to vary contribution of different factor depending on efficiency. float factor = ((1f - this.Efficiency) * EfficiencyHigh + EfficiencyLow) / 3f; // float factor = (1f - this.Efficiency) * EfficiencyHigh + EfficiencyLow; + proportionFactor = new Vector3(factor, factor, factor); integralFactor = new Vector3(factor, factor, factor); derivFactor = new Vector3(factor, factor, factor); + + MDetailLog("{0},BSPIDVMotor.setEfficiency,eff={1},factor={2}", BSScene.DetailLogZero, Efficiency, factor); } } - // Ignore Current and Target Values and just advance the PID computation on this error. - public override Vector3 Step(float timeStep, Vector3 error) + // Advance the PID computation on this error. + public override Vector3 StepError(float timeStep, Vector3 error) { if (!Enabled) return Vector3.Zero; @@ -331,15 +433,17 @@ public class BSPIDVMotor : BSVMotor RunningIntegration += error * timeStep; // A simple derivitive is the rate of change from the last error. - Vector3 derivFactor = (error - LastError) * timeStep; + Vector3 derivitive = (error - LastError) * timeStep; LastError = error; - // Correction = -(proportionOfPresentError + accumulationOfPastError + rateOfChangeOfError) - Vector3 ret = -( - error * proportionFactor - + RunningIntegration * integralFactor - + derivFactor * derivFactor - ); + // Correction = (proportionOfPresentError + accumulationOfPastError + rateOfChangeOfError) + Vector3 ret = error * timeStep * proportionFactor * FactorMix.X + + RunningIntegration * integralFactor * FactorMix.Y + + derivitive * derivFactor * FactorMix.Z + ; + + MDetailLog("{0},BSPIDVMotor.step,ts={1},err={2},runnInt={3},deriv={4},ret={5}", + BSScene.DetailLogZero, timeStep, error, RunningIntegration, derivitive, ret); return ret; } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs index 69ac8cdc1a..3ca7e160ce 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs @@ -26,6 +26,7 @@ */ using System; using System.Collections.Generic; +using System.Reflection; using System.Text; using OpenSim.Region.Physics.Manager; @@ -37,14 +38,41 @@ namespace OpenSim.Region.Physics.BulletSPlugin { public static class BSParam { + private static string LogHeader = "[BULLETSIM PARAMETERS]"; + + // Tuning notes: + // From: http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=6575 + // Contact points can be added even if the distance is positive. The constraint solver can deal with + // contacts with positive distances as well as negative (penetration). Contact points are discarded + // if the distance exceeds a certain threshold. + // Bullet has a contact processing threshold and a contact breaking threshold. + // If the distance is larger than the contact breaking threshold, it will be removed after one frame. + // If the distance is larger than the contact processing threshold, the constraint solver will ignore it. + + // This is separate/independent from the collision margin. The collision margin increases the object a bit + // to improve collision detection performance and accuracy. + // =================== + // From: + // Level of Detail values kept as float because that's what the Meshmerizer wants public static float MeshLOD { get; private set; } + public static float MeshCircularLOD { get; private set; } public static float MeshMegaPrimLOD { get; private set; } public static float MeshMegaPrimThreshold { get; private set; } public static float SculptLOD { get; private set; } + public static int CrossingFailuresBeforeOutOfBounds { get; private set; } + public static float UpdateVelocityChangeThreshold { get; private set; } + public static float MinimumObjectMass { get; private set; } public static float MaximumObjectMass { get; private set; } + public static float MaxLinearVelocity { get; private set; } + public static float MaxLinearVelocitySquared { get; private set; } + public static float MaxAngularVelocity { get; private set; } + public static float MaxAngularVelocitySquared { get; private set; } + public static float MaxAddForceMagnitude { get; private set; } + public static float MaxAddForceMagnitudeSquared { get; private set; } + public static float DensityScaleFactor { get; private set; } public static float LinearDamping { get; private set; } public static float AngularDamping { get; private set; } @@ -58,13 +86,36 @@ public static class BSParam public static bool ShouldMeshSculptedPrim { get; private set; } // cause scuplted prims to get meshed public static bool ShouldForceSimplePrimMeshing { get; private set; } // if a cube or sphere, let Bullet do internal shapes public static bool ShouldUseHullsForPhysicalObjects { get; private set; } // 'true' if should create hulls for physical objects + public static bool ShouldRemoveZeroWidthTriangles { get; private set; } + public static bool ShouldUseBulletHACD { get; set; } + public static bool ShouldUseSingleConvexHullForPrims { get; set; } public static float TerrainImplementation { get; private set; } + public static int TerrainMeshMagnification { get; private set; } public static float TerrainFriction { get; private set; } public static float TerrainHitFraction { get; private set; } public static float TerrainRestitution { get; private set; } + public static float TerrainContactProcessingThreshold { get; private set; } public static float TerrainCollisionMargin { get; private set; } + public static float DefaultFriction { get; private set; } + public static float DefaultDensity { get; private set; } + public static float DefaultRestitution { get; private set; } + public static float CollisionMargin { get; private set; } + public static float Gravity { get; private set; } + + // Physics Engine operation + public static float MaxPersistantManifoldPoolSize { get; private set; } + public static float MaxCollisionAlgorithmPoolSize { get; private set; } + public static bool ShouldDisableContactPoolDynamicAllocation { get; private set; } + public static bool ShouldForceUpdateAllAabbs { get; private set; } + public static bool ShouldRandomizeSolverOrder { get; private set; } + public static bool ShouldSplitSimulationIslands { get; private set; } + public static bool ShouldEnableFrictionCaching { get; private set; } + public static float NumberOfSolverIterations { get; private set; } + public static bool UseSingleSidedMeshes { get; private set; } + public static float GlobalContactBreakingThreshold { get; private set; } + // Avatar parameters public static float AvatarFriction { get; private set; } public static float AvatarStandingFriction { get; private set; } @@ -75,12 +126,48 @@ public static class BSParam public static float AvatarCapsuleDepth { get; private set; } public static float AvatarCapsuleHeight { get; private set; } public static float AvatarContactProcessingThreshold { get; private set; } + public static float AvatarBelowGroundUpCorrectionMeters { get; private set; } + public static float AvatarStepHeight { get; private set; } + public static float AvatarStepApproachFactor { get; private set; } + public static float AvatarStepForceFactor { get; private set; } + public static float AvatarStepUpCorrectionFactor { get; private set; } + public static int AvatarStepSmoothingSteps { get; private set; } + // Vehicle parameters + public static float VehicleMaxLinearVelocity { get; private set; } + public static float VehicleMaxLinearVelocitySquared { get; private set; } + public static float VehicleMaxAngularVelocity { get; private set; } + public static float VehicleMaxAngularVelocitySq { get; private set; } public static float VehicleAngularDamping { get; private set; } + public static float VehicleFriction { get; private set; } + public static float VehicleRestitution { get; private set; } + public static Vector3 VehicleLinearFactor { get; private set; } + public static Vector3 VehicleAngularFactor { get; private set; } + public static float VehicleGroundGravityFudge { get; private set; } + public static float VehicleAngularBankingTimescaleFudge { get; private set; } + public static bool VehicleDebuggingEnable { get; private set; } + // Convex Hulls + public static int CSHullMaxDepthSplit { get; private set; } + public static int CSHullMaxDepthSplitForSimpleShapes { get; private set; } + public static float CSHullConcavityThresholdPercent { get; private set; } + public static float CSHullVolumeConservationThresholdPercent { get; private set; } + public static int CSHullMaxVertices { get; private set; } + public static float CSHullMaxSkinWidth { get; private set; } + public static float BHullMaxVerticesPerHull { get; private set; } // 100 + public static float BHullMinClusters { get; private set; } // 2 + public static float BHullCompacityWeight { get; private set; } // 0.1 + public static float BHullVolumeWeight { get; private set; } // 0.0 + public static float BHullConcavity { get; private set; } // 100 + public static bool BHullAddExtraDistPoints { get; private set; } // false + public static bool BHullAddNeighboursDistPoints { get; private set; } // false + public static bool BHullAddFacesPoints { get; private set; } // false + public static bool BHullShouldAdjustCollisionMargin { get; private set; } // false + + // Linkset implementation parameters public static float LinksetImplementation { get; private set; } - public static float LinkConstraintUseFrameOffset { get; private set; } - public static float LinkConstraintEnableTransMotor { get; private set; } + public static bool LinkConstraintUseFrameOffset { get; private set; } + public static bool LinkConstraintEnableTransMotor { get; private set; } public static float LinkConstraintTransMotorMaxVel { get; private set; } public static float LinkConstraintTransMotorMaxForce { get; private set; } public static float LinkConstraintERP { get; private set; } @@ -90,51 +177,152 @@ public static class BSParam public static float PID_D { get; private set; } // derivative public static float PID_P { get; private set; } // proportional - // Various constants that come from that other virtual world that shall not be named + // Various constants that come from that other virtual world that shall not be named. public const float MinGravityZ = -1f; public const float MaxGravityZ = 28f; public const float MinFriction = 0f; public const float MaxFriction = 255f; - public const float MinDensity = 0f; + public const float MinDensity = 0.01f; public const float MaxDensity = 22587f; public const float MinRestitution = 0f; public const float MaxRestitution = 1f; - public const float MaxAddForceMagnitude = 20000f; - // =========================================================================== - public delegate void ParamUser(BSScene scene, IConfig conf, string paramName, float val); - public delegate float ParamGet(BSScene scene); - public delegate void ParamSet(BSScene scene, string paramName, uint localID, float val); - public delegate void SetOnObject(BSScene scene, BSPhysObject obj, float val); + // ===================================================================================== + // ===================================================================================== - public struct ParameterDefn + // Base parameter definition that gets and sets parameter values via a string + public abstract class ParameterDefnBase { public string name; // string name of the parameter public string desc; // a short description of what the parameter means - public float defaultValue; // default value if not specified anywhere else - public ParamUser userParam; // get the value from the configuration file - public ParamGet getter; // return the current value stored for this parameter - public ParamSet setter; // set the current value for this parameter - public SetOnObject onObject; // set the value on an object in the physical domain - public ParameterDefn(string n, string d, float v, ParamUser u, ParamGet g, ParamSet s) + public ParameterDefnBase(string pName, string pDesc) { - name = n; - desc = d; - defaultValue = v; - userParam = u; - getter = g; - setter = s; - onObject = null; + name = pName; + desc = pDesc; } - public ParameterDefn(string n, string d, float v, ParamUser u, ParamGet g, ParamSet s, SetOnObject o) + // Set the parameter value to the default + public abstract void AssignDefault(BSScene s); + // Get the value as a string + public abstract string GetValue(BSScene s); + // Set the value to this string value + public abstract void SetValue(BSScene s, string valAsString); + // set the value on a particular object (usually sets in physics engine) + public abstract void SetOnObject(BSScene s, BSPhysObject obj); + public abstract bool HasSetOnObject { get; } + } + + // Specific parameter definition for a parameter of a specific type. + public delegate T PGetValue(BSScene s); + public delegate void PSetValue(BSScene s, T val); + public delegate void PSetOnObject(BSScene scene, BSPhysObject obj); + public sealed class ParameterDefn : ParameterDefnBase + { + private T defaultValue; + private PSetValue setter; + private PGetValue getter; + private PSetOnObject objectSet; + public ParameterDefn(string pName, string pDesc, T pDefault, PGetValue pGetter, PSetValue pSetter) + : base(pName, pDesc) { - name = n; - desc = d; - defaultValue = v; - userParam = u; - getter = g; - setter = s; - onObject = o; + defaultValue = pDefault; + setter = pSetter; + getter = pGetter; + objectSet = null; + } + public ParameterDefn(string pName, string pDesc, T pDefault, PGetValue pGetter, PSetValue pSetter, PSetOnObject pObjSetter) + : base(pName, pDesc) + { + defaultValue = pDefault; + setter = pSetter; + getter = pGetter; + objectSet = pObjSetter; + } + // Simple parameter variable where property name is the same as the INI file name + // and the value is only a simple get and set. + public ParameterDefn(string pName, string pDesc, T pDefault) + : base(pName, pDesc) + { + defaultValue = pDefault; + setter = (s, v) => { SetValueByName(s, name, v); }; + getter = (s) => { return GetValueByName(s, name); }; + objectSet = null; + } + // Use reflection to find the property named 'pName' in BSParam and assign 'val' to same. + private void SetValueByName(BSScene s, string pName, T val) + { + PropertyInfo prop = typeof(BSParam).GetProperty(pName, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (prop == null) + { + // This should only be output when someone adds a new INI parameter and misspells the name. + s.Logger.ErrorFormat("{0} SetValueByName: did not find '{1}'. Verify specified property name is the same as the given INI parameters name.", LogHeader, pName); + } + else + { + prop.SetValue(null, val, null); + } + } + // Use reflection to find the property named 'pName' in BSParam and return the value in same. + private T GetValueByName(BSScene s, string pName) + { + PropertyInfo prop = typeof(BSParam).GetProperty(pName, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (prop == null) + { + // This should only be output when someone adds a new INI parameter and misspells the name. + s.Logger.ErrorFormat("{0} GetValueByName: did not find '{1}'. Verify specified property name is the same as the given INI parameter name.", LogHeader, pName); + } + return (T)prop.GetValue(null, null); + } + public override void AssignDefault(BSScene s) + { + setter(s, defaultValue); + } + public override string GetValue(BSScene s) + { + return getter(s).ToString(); + } + public override void SetValue(BSScene s, string valAsString) + { + // Get the generic type of the setter + Type genericType = setter.GetType().GetGenericArguments()[0]; + // Find the 'Parse' method on that type + System.Reflection.MethodInfo parser = null; + try + { + parser = genericType.GetMethod("Parse", new Type[] { typeof(String) } ); + } + catch (Exception e) + { + s.Logger.ErrorFormat("{0} Exception getting parser for type '{1}': {2}", LogHeader, genericType, e); + parser = null; + } + if (parser != null) + { + // Parse the input string + try + { + T setValue = (T)parser.Invoke(genericType, new Object[] { valAsString }); + // Store the parsed value + setter(s, setValue); + // s.Logger.DebugFormat("{0} Parameter {1} = {2}", LogHeader, name, setValue); + } + catch + { + s.Logger.ErrorFormat("{0} Failed parsing parameter value '{1}' as type '{2}'", LogHeader, valAsString, genericType); + } + } + else + { + s.Logger.ErrorFormat("{0} Could not find parameter parser for type '{1}'", LogHeader, genericType); + } + } + public override bool HasSetOnObject + { + get { return objectSet != null; } + } + public override void SetOnObject(BSScene s, BSPhysObject obj) + { + if (objectSet != null) + objectSet(s, obj); } } @@ -144,359 +332,356 @@ public static class BSParam // location somewhere in the program and make an entry in this table with the // getters and setters. // It is easiest to find an existing definition and copy it. - // Parameter values are floats. Booleans are converted to a floating value. // - // A ParameterDefn() takes the following parameters: + // A ParameterDefn() takes the following parameters: // -- the text name of the parameter. This is used for console input and ini file. // -- a short text description of the parameter. This shows up in the console listing. - // -- a default value (float) - // -- a delegate for fetching the parameter from the ini file. - // Should handle fetching the right type from the ini file and converting it. - // -- a delegate for getting the value as a float - // -- a delegate for setting the value from a float + // -- a default value + // -- a delegate for getting the value + // -- a delegate for setting the value // -- an optional delegate to update the value in the world. Most often used to // push the new value to an in-world object. // // The single letter parameters for the delegates are: // s = BSScene // o = BSPhysObject - // p = string parameter name - // l = localID of referenced object - // v = value (float) - // cf = parameter configuration class (for fetching values from ini file) - private static ParameterDefn[] ParameterDefinitions = + // v = value (appropriate type) + private static ParameterDefnBase[] ParameterDefinitions = { - new ParameterDefn("MeshSculptedPrim", "Whether to create meshes for sculpties", - ConfigurationParameters.numericTrue, - (s,cf,p,v) => { ShouldMeshSculptedPrim = cf.GetBoolean(p, BSParam.BoolNumeric(v)); }, - (s) => { return BSParam.NumericBool(ShouldMeshSculptedPrim); }, - (s,p,l,v) => { ShouldMeshSculptedPrim = BSParam.BoolNumeric(v); } ), - new ParameterDefn("ForceSimplePrimMeshing", "If true, only use primitive meshes for objects", - ConfigurationParameters.numericFalse, - (s,cf,p,v) => { ShouldForceSimplePrimMeshing = cf.GetBoolean(p, BSParam.BoolNumeric(v)); }, - (s) => { return BSParam.NumericBool(ShouldForceSimplePrimMeshing); }, - (s,p,l,v) => { ShouldForceSimplePrimMeshing = BSParam.BoolNumeric(v); } ), - new ParameterDefn("UseHullsForPhysicalObjects", "If true, create hulls for physical objects", - ConfigurationParameters.numericTrue, - (s,cf,p,v) => { ShouldUseHullsForPhysicalObjects = cf.GetBoolean(p, BSParam.BoolNumeric(v)); }, - (s) => { return BSParam.NumericBool(ShouldUseHullsForPhysicalObjects); }, - (s,p,l,v) => { ShouldUseHullsForPhysicalObjects = BSParam.BoolNumeric(v); } ), + new ParameterDefn("MeshSculptedPrim", "Whether to create meshes for sculpties", + true, + (s) => { return ShouldMeshSculptedPrim; }, + (s,v) => { ShouldMeshSculptedPrim = v; } ), + new ParameterDefn("ForceSimplePrimMeshing", "If true, only use primitive meshes for objects", + false, + (s) => { return ShouldForceSimplePrimMeshing; }, + (s,v) => { ShouldForceSimplePrimMeshing = v; } ), + new ParameterDefn("UseHullsForPhysicalObjects", "If true, create hulls for physical objects", + true, + (s) => { return ShouldUseHullsForPhysicalObjects; }, + (s,v) => { ShouldUseHullsForPhysicalObjects = v; } ), + new ParameterDefn("ShouldRemoveZeroWidthTriangles", "If true, remove degenerate triangles from meshes", + true ), + new ParameterDefn("ShouldUseBulletHACD", "If true, use the Bullet version of HACD", + false ), + new ParameterDefn("ShouldUseSingleConvexHullForPrims", "If true, use a single convex hull shape for physical prims", + true ), - new ParameterDefn("MeshLevelOfDetail", "Level of detail to render meshes (32, 16, 8 or 4. 32=most detailed)", - 8f, - (s,cf,p,v) => { MeshLOD = (float)cf.GetInt(p, (int)v); }, - (s) => { return MeshLOD; }, - (s,p,l,v) => { MeshLOD = v; } ), - new ParameterDefn("MeshLevelOfDetailMegaPrim", "Level of detail to render meshes larger than threshold meters", - 16f, - (s,cf,p,v) => { MeshMegaPrimLOD = (float)cf.GetInt(p, (int)v); }, - (s) => { return MeshMegaPrimLOD; }, - (s,p,l,v) => { MeshMegaPrimLOD = v; } ), - new ParameterDefn("MeshLevelOfDetailMegaPrimThreshold", "Size (in meters) of a mesh before using MeshMegaPrimLOD", - 10f, - (s,cf,p,v) => { MeshMegaPrimThreshold = (float)cf.GetInt(p, (int)v); }, - (s) => { return MeshMegaPrimThreshold; }, - (s,p,l,v) => { MeshMegaPrimThreshold = v; } ), - new ParameterDefn("SculptLevelOfDetail", "Level of detail to render sculpties (32, 16, 8 or 4. 32=most detailed)", + new ParameterDefn("CrossingFailuresBeforeOutOfBounds", "How forgiving we are about getting into adjactent regions", + 5 ), + new ParameterDefn("UpdateVelocityChangeThreshold", "Change in updated velocity required before reporting change to simulator", + 0.1f ), + + new ParameterDefn("MeshLevelOfDetail", "Level of detail to render meshes (32, 16, 8 or 4. 32=most detailed)", 32f, - (s,cf,p,v) => { SculptLOD = (float)cf.GetInt(p, (int)v); }, - (s) => { return SculptLOD; }, - (s,p,l,v) => { SculptLOD = v; } ), - - new ParameterDefn("MaxSubStep", "In simulation step, maximum number of substeps", + (s) => { return MeshLOD; }, + (s,v) => { MeshLOD = v; } ), + new ParameterDefn("MeshLevelOfDetailCircular", "Level of detail for prims with circular cuts or shapes", + 32f, + (s) => { return MeshCircularLOD; }, + (s,v) => { MeshCircularLOD = v; } ), + new ParameterDefn("MeshLevelOfDetailMegaPrimThreshold", "Size (in meters) of a mesh before using MeshMegaPrimLOD", 10f, - (s,cf,p,v) => { s.m_maxSubSteps = cf.GetInt(p, (int)v); }, - (s) => { return (float)s.m_maxSubSteps; }, - (s,p,l,v) => { s.m_maxSubSteps = (int)v; } ), - new ParameterDefn("FixedTimeStep", "In simulation step, seconds of one substep (1/60)", + (s) => { return MeshMegaPrimThreshold; }, + (s,v) => { MeshMegaPrimThreshold = v; } ), + new ParameterDefn("MeshLevelOfDetailMegaPrim", "Level of detail to render meshes larger than threshold meters", + 32f, + (s) => { return MeshMegaPrimLOD; }, + (s,v) => { MeshMegaPrimLOD = v; } ), + new ParameterDefn("SculptLevelOfDetail", "Level of detail to render sculpties (32, 16, 8 or 4. 32=most detailed)", + 32f, + (s) => { return SculptLOD; }, + (s,v) => { SculptLOD = v; } ), + + new ParameterDefn("MaxSubStep", "In simulation step, maximum number of substeps", + 10, + (s) => { return s.m_maxSubSteps; }, + (s,v) => { s.m_maxSubSteps = (int)v; } ), + new ParameterDefn("FixedTimeStep", "In simulation step, seconds of one substep (1/60)", 1f / 60f, - (s,cf,p,v) => { s.m_fixedTimeStep = cf.GetFloat(p, v); }, - (s) => { return (float)s.m_fixedTimeStep; }, - (s,p,l,v) => { s.m_fixedTimeStep = v; } ), - new ParameterDefn("NominalFrameRate", "The base frame rate we claim", + (s) => { return s.m_fixedTimeStep; }, + (s,v) => { s.m_fixedTimeStep = v; } ), + new ParameterDefn("NominalFrameRate", "The base frame rate we claim", 55f, - (s,cf,p,v) => { s.NominalFrameRate = cf.GetInt(p, (int)v); }, - (s) => { return (float)s.NominalFrameRate; }, - (s,p,l,v) => { s.NominalFrameRate = (int)v; } ), - new ParameterDefn("MaxCollisionsPerFrame", "Max collisions returned at end of each frame", - 2048f, - (s,cf,p,v) => { s.m_maxCollisionsPerFrame = cf.GetInt(p, (int)v); }, - (s) => { return (float)s.m_maxCollisionsPerFrame; }, - (s,p,l,v) => { s.m_maxCollisionsPerFrame = (int)v; } ), - new ParameterDefn("MaxUpdatesPerFrame", "Max updates returned at end of each frame", - 8000f, - (s,cf,p,v) => { s.m_maxUpdatesPerFrame = cf.GetInt(p, (int)v); }, - (s) => { return (float)s.m_maxUpdatesPerFrame; }, - (s,p,l,v) => { s.m_maxUpdatesPerFrame = (int)v; } ), - new ParameterDefn("MaxTaintsToProcessPerStep", "Number of update taints to process before each simulation step", - 500f, - (s,cf,p,v) => { s.m_taintsToProcessPerStep = cf.GetInt(p, (int)v); }, - (s) => { return (float)s.m_taintsToProcessPerStep; }, - (s,p,l,v) => { s.m_taintsToProcessPerStep = (int)v; } ), - new ParameterDefn("MinObjectMass", "Minimum object mass (0.0001)", + (s) => { return s.NominalFrameRate; }, + (s,v) => { s.NominalFrameRate = (int)v; } ), + new ParameterDefn("MaxCollisionsPerFrame", "Max collisions returned at end of each frame", + 2048, + (s) => { return s.m_maxCollisionsPerFrame; }, + (s,v) => { s.m_maxCollisionsPerFrame = (int)v; } ), + new ParameterDefn("MaxUpdatesPerFrame", "Max updates returned at end of each frame", + 8000, + (s) => { return s.m_maxUpdatesPerFrame; }, + (s,v) => { s.m_maxUpdatesPerFrame = (int)v; } ), + + new ParameterDefn("MinObjectMass", "Minimum object mass (0.0001)", 0.0001f, - (s,cf,p,v) => { MinimumObjectMass = cf.GetFloat(p, v); }, - (s) => { return (float)MinimumObjectMass; }, - (s,p,l,v) => { MinimumObjectMass = v; } ), - new ParameterDefn("MaxObjectMass", "Maximum object mass (10000.01)", + (s) => { return MinimumObjectMass; }, + (s,v) => { MinimumObjectMass = v; } ), + new ParameterDefn("MaxObjectMass", "Maximum object mass (10000.01)", 10000.01f, - (s,cf,p,v) => { MaximumObjectMass = cf.GetFloat(p, v); }, - (s) => { return (float)MaximumObjectMass; }, - (s,p,l,v) => { MaximumObjectMass = v; } ), + (s) => { return MaximumObjectMass; }, + (s,v) => { MaximumObjectMass = v; } ), + new ParameterDefn("MaxLinearVelocity", "Maximum velocity magnitude that can be assigned to an object", + 1000.0f, + (s) => { return MaxLinearVelocity; }, + (s,v) => { MaxLinearVelocity = v; MaxLinearVelocitySquared = v * v; } ), + new ParameterDefn("MaxAngularVelocity", "Maximum rotational velocity magnitude that can be assigned to an object", + 1000.0f, + (s) => { return MaxAngularVelocity; }, + (s,v) => { MaxAngularVelocity = v; MaxAngularVelocitySquared = v * v; } ), + // LL documentation says thie number should be 20f for llApplyImpulse and 200f for llRezObject + new ParameterDefn("MaxAddForceMagnitude", "Maximum force that can be applied by llApplyImpulse (SL says 20f)", + 20000.0f, + (s) => { return MaxAddForceMagnitude; }, + (s,v) => { MaxAddForceMagnitude = v; MaxAddForceMagnitudeSquared = v * v; } ), + // Density is passed around as 100kg/m3. This scales that to 1kg/m3. + new ParameterDefn("DensityScaleFactor", "Conversion for simulator/viewer density (100kg/m3) to physical density (1kg/m3)", + 0.01f ), - new ParameterDefn("PID_D", "Derivitive factor for motion smoothing", - 2200f, - (s,cf,p,v) => { PID_D = cf.GetFloat(p, v); }, - (s) => { return (float)PID_D; }, - (s,p,l,v) => { PID_D = v; } ), - new ParameterDefn("PID_P", "Parameteric factor for motion smoothing", - 900f, - (s,cf,p,v) => { PID_P = cf.GetFloat(p, v); }, - (s) => { return (float)PID_P; }, - (s,p,l,v) => { PID_P = v; } ), + new ParameterDefn("PID_D", "Derivitive factor for motion smoothing", + 2200f ), + new ParameterDefn("PID_P", "Parameteric factor for motion smoothing", + 900f ), - new ParameterDefn("DefaultFriction", "Friction factor used on new objects", + new ParameterDefn("DefaultFriction", "Friction factor used on new objects", 0.2f, - (s,cf,p,v) => { s.UnmanagedParams[0].defaultFriction = cf.GetFloat(p, v); }, - (s) => { return s.UnmanagedParams[0].defaultFriction; }, - (s,p,l,v) => { s.UnmanagedParams[0].defaultFriction = v; } ), - new ParameterDefn("DefaultDensity", "Density for new objects" , + (s) => { return DefaultFriction; }, + (s,v) => { DefaultFriction = v; s.UnmanagedParams[0].defaultFriction = v; } ), + new ParameterDefn("DefaultDensity", "Density for new objects" , 10.000006836f, // Aluminum g/cm3 - (s,cf,p,v) => { s.UnmanagedParams[0].defaultDensity = cf.GetFloat(p, v); }, - (s) => { return s.UnmanagedParams[0].defaultDensity; }, - (s,p,l,v) => { s.UnmanagedParams[0].defaultDensity = v; } ), - new ParameterDefn("DefaultRestitution", "Bouncyness of an object" , + (s) => { return DefaultDensity; }, + (s,v) => { DefaultDensity = v; s.UnmanagedParams[0].defaultDensity = v; } ), + new ParameterDefn("DefaultRestitution", "Bouncyness of an object" , 0f, - (s,cf,p,v) => { s.UnmanagedParams[0].defaultRestitution = cf.GetFloat(p, v); }, - (s) => { return s.UnmanagedParams[0].defaultRestitution; }, - (s,p,l,v) => { s.UnmanagedParams[0].defaultRestitution = v; } ), - new ParameterDefn("CollisionMargin", "Margin around objects before collisions are calculated (must be zero!)", + (s) => { return DefaultRestitution; }, + (s,v) => { DefaultRestitution = v; s.UnmanagedParams[0].defaultRestitution = v; } ), + new ParameterDefn("CollisionMargin", "Margin around objects before collisions are calculated (must be zero!)", 0.04f, - (s,cf,p,v) => { s.UnmanagedParams[0].collisionMargin = cf.GetFloat(p, v); }, - (s) => { return s.UnmanagedParams[0].collisionMargin; }, - (s,p,l,v) => { s.UnmanagedParams[0].collisionMargin = v; } ), - new ParameterDefn("Gravity", "Vertical force of gravity (negative means down)", + (s) => { return CollisionMargin; }, + (s,v) => { CollisionMargin = v; s.UnmanagedParams[0].collisionMargin = v; } ), + new ParameterDefn("Gravity", "Vertical force of gravity (negative means down)", -9.80665f, - (s,cf,p,v) => { s.UnmanagedParams[0].gravity = cf.GetFloat(p, v); }, - (s) => { return s.UnmanagedParams[0].gravity; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{s.UnmanagedParams[0].gravity=x;}, p, PhysParameterEntry.APPLY_TO_NONE, v); }, - (s,o,v) => { s.PE.SetGravity(o.PhysBody, new Vector3(0f,0f,v)); } ), + (s) => { return Gravity; }, + (s,v) => { Gravity = v; s.UnmanagedParams[0].gravity = v; }, + (s,o) => { s.PE.SetGravity(o.PhysBody, new Vector3(0f,0f,Gravity)); } ), - new ParameterDefn("LinearDamping", "Factor to damp linear movement per second (0.0 - 1.0)", + new ParameterDefn("LinearDamping", "Factor to damp linear movement per second (0.0 - 1.0)", 0f, - (s,cf,p,v) => { LinearDamping = cf.GetFloat(p, v); }, (s) => { return LinearDamping; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{LinearDamping=x;}, p, l, v); }, - (s,o,v) => { s.PE.SetDamping(o.PhysBody, v, AngularDamping); } ), - new ParameterDefn("AngularDamping", "Factor to damp angular movement per second (0.0 - 1.0)", + (s,v) => { LinearDamping = v; }, + (s,o) => { s.PE.SetDamping(o.PhysBody, LinearDamping, AngularDamping); } ), + new ParameterDefn("AngularDamping", "Factor to damp angular movement per second (0.0 - 1.0)", 0f, - (s,cf,p,v) => { AngularDamping = cf.GetFloat(p, v); }, (s) => { return AngularDamping; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AngularDamping=x;}, p, l, v); }, - (s,o,v) => { s.PE.SetDamping(o.PhysBody, LinearDamping, v); } ), - new ParameterDefn("DeactivationTime", "Seconds before considering an object potentially static", + (s,v) => { AngularDamping = v; }, + (s,o) => { s.PE.SetDamping(o.PhysBody, LinearDamping, AngularDamping); } ), + new ParameterDefn("DeactivationTime", "Seconds before considering an object potentially static", 0.2f, - (s,cf,p,v) => { DeactivationTime = cf.GetFloat(p, v); }, (s) => { return DeactivationTime; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{DeactivationTime=x;}, p, l, v); }, - (s,o,v) => { s.PE.SetDeactivationTime(o.PhysBody, v); } ), - new ParameterDefn("LinearSleepingThreshold", "Seconds to measure linear movement before considering static", + (s,v) => { DeactivationTime = v; }, + (s,o) => { s.PE.SetDeactivationTime(o.PhysBody, DeactivationTime); } ), + new ParameterDefn("LinearSleepingThreshold", "Seconds to measure linear movement before considering static", 0.8f, - (s,cf,p,v) => { LinearSleepingThreshold = cf.GetFloat(p, v); }, (s) => { return LinearSleepingThreshold; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{LinearSleepingThreshold=x;}, p, l, v); }, - (s,o,v) => { s.PE.SetSleepingThresholds(o.PhysBody, v, v); } ), - new ParameterDefn("AngularSleepingThreshold", "Seconds to measure angular movement before considering static", + (s,v) => { LinearSleepingThreshold = v;}, + (s,o) => { s.PE.SetSleepingThresholds(o.PhysBody, LinearSleepingThreshold, AngularSleepingThreshold); } ), + new ParameterDefn("AngularSleepingThreshold", "Seconds to measure angular movement before considering static", 1.0f, - (s,cf,p,v) => { AngularSleepingThreshold = cf.GetFloat(p, v); }, (s) => { return AngularSleepingThreshold; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AngularSleepingThreshold=x;}, p, l, v); }, - (s,o,v) => { s.PE.SetSleepingThresholds(o.PhysBody, v, v); } ), - new ParameterDefn("CcdMotionThreshold", "Continuious collision detection threshold (0 means no CCD)" , - 0f, // set to zero to disable - (s,cf,p,v) => { CcdMotionThreshold = cf.GetFloat(p, v); }, + (s,v) => { AngularSleepingThreshold = v;}, + (s,o) => { s.PE.SetSleepingThresholds(o.PhysBody, LinearSleepingThreshold, AngularSleepingThreshold); } ), + new ParameterDefn("CcdMotionThreshold", "Continuious collision detection threshold (0 means no CCD)" , + 0.0f, // set to zero to disable (s) => { return CcdMotionThreshold; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{CcdMotionThreshold=x;}, p, l, v); }, - (s,o,v) => { s.PE.SetCcdMotionThreshold(o.PhysBody, v); } ), - new ParameterDefn("CcdSweptSphereRadius", "Continuious collision detection test radius" , - 0f, - (s,cf,p,v) => { CcdSweptSphereRadius = cf.GetFloat(p, v); }, - (s) => { return CcdSweptSphereRadius; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{CcdSweptSphereRadius=x;}, p, l, v); }, - (s,o,v) => { s.PE.SetCcdSweptSphereRadius(o.PhysBody, v); } ), - new ParameterDefn("ContactProcessingThreshold", "Distance between contacts before doing collision check" , - 0.1f, - (s,cf,p,v) => { ContactProcessingThreshold = cf.GetFloat(p, v); }, - (s) => { return ContactProcessingThreshold; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{ContactProcessingThreshold=x;}, p, l, v); }, - (s,o,v) => { s.PE.SetContactProcessingThreshold(o.PhysBody, v); } ), - - new ParameterDefn("TerrainImplementation", "Type of shape to use for terrain (0=heightmap, 1=mesh)", - (float)BSTerrainPhys.TerrainImplementation.Mesh, - (s,cf,p,v) => { TerrainImplementation = cf.GetFloat(p,v); }, - (s) => { return TerrainImplementation; }, - (s,p,l,v) => { TerrainImplementation = v; } ), - new ParameterDefn("TerrainFriction", "Factor to reduce movement against terrain surface" , - 0.3f, - (s,cf,p,v) => { TerrainFriction = cf.GetFloat(p, v); }, - (s) => { return TerrainFriction; }, - (s,p,l,v) => { TerrainFriction = v; /* TODO: set on real terrain */} ), - new ParameterDefn("TerrainHitFraction", "Distance to measure hit collisions" , - 0.8f, - (s,cf,p,v) => { TerrainHitFraction = cf.GetFloat(p, v); }, - (s) => { return TerrainHitFraction; }, - (s,p,l,v) => { TerrainHitFraction = v; /* TODO: set on real terrain */ } ), - new ParameterDefn("TerrainRestitution", "Bouncyness" , - 0f, - (s,cf,p,v) => { TerrainRestitution = cf.GetFloat(p, v); }, - (s) => { return TerrainRestitution; }, - (s,p,l,v) => { TerrainRestitution = v; /* TODO: set on real terrain */ } ), - new ParameterDefn("TerrainCollisionMargin", "Margin where collision checking starts" , - 0.04f, - (s,cf,p,v) => { TerrainCollisionMargin = cf.GetFloat(p, v); }, - (s) => { return TerrainCollisionMargin; }, - (s,p,l,v) => { TerrainCollisionMargin = v; /* TODO: set on real terrain */ } ), - - new ParameterDefn("AvatarFriction", "Factor to reduce movement against an avatar. Changed on avatar recreation.", + (s,v) => { CcdMotionThreshold = v;}, + (s,o) => { s.PE.SetCcdMotionThreshold(o.PhysBody, CcdMotionThreshold); } ), + new ParameterDefn("CcdSweptSphereRadius", "Continuious collision detection test radius" , 0.2f, - (s,cf,p,v) => { AvatarFriction = cf.GetFloat(p, v); }, - (s) => { return AvatarFriction; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AvatarFriction=x;}, p, l, v); } ), - new ParameterDefn("AvatarStandingFriction", "Avatar friction when standing. Changed on avatar recreation.", - 10.0f, - (s,cf,p,v) => { AvatarStandingFriction = cf.GetFloat(p, v); }, - (s) => { return AvatarStandingFriction; }, - (s,p,l,v) => { AvatarStandingFriction = v; } ), - new ParameterDefn("AvatarAlwaysRunFactor", "Speed multiplier if avatar is set to always run", - 1.3f, - (s,cf,p,v) => { AvatarAlwaysRunFactor = cf.GetFloat(p, v); }, - (s) => { return AvatarAlwaysRunFactor; }, - (s,p,l,v) => { AvatarAlwaysRunFactor = v; } ), - new ParameterDefn("AvatarDensity", "Density of an avatar. Changed on avatar recreation.", - 3.5f, - (s,cf,p,v) => { AvatarDensity = cf.GetFloat(p, v); }, - (s) => { return AvatarDensity; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AvatarDensity=x;}, p, l, v); } ), - new ParameterDefn("AvatarRestitution", "Bouncyness. Changed on avatar recreation.", - 0f, - (s,cf,p,v) => { AvatarRestitution = cf.GetFloat(p, v); }, - (s) => { return AvatarRestitution; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AvatarRestitution=x;}, p, l, v); } ), - new ParameterDefn("AvatarCapsuleWidth", "The distance between the sides of the avatar capsule", - 0.6f, - (s,cf,p,v) => { AvatarCapsuleWidth = cf.GetFloat(p, v); }, - (s) => { return AvatarCapsuleWidth; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AvatarCapsuleWidth=x;}, p, l, v); } ), - new ParameterDefn("AvatarCapsuleDepth", "The distance between the front and back of the avatar capsule", - 0.45f, - (s,cf,p,v) => { AvatarCapsuleDepth = cf.GetFloat(p, v); }, - (s) => { return AvatarCapsuleDepth; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AvatarCapsuleDepth=x;}, p, l, v); } ), - new ParameterDefn("AvatarCapsuleHeight", "Default height of space around avatar", - 1.5f, - (s,cf,p,v) => { AvatarCapsuleHeight = cf.GetFloat(p, v); }, - (s) => { return AvatarCapsuleHeight; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AvatarCapsuleHeight=x;}, p, l, v); } ), - new ParameterDefn("AvatarContactProcessingThreshold", "Distance from capsule to check for collisions", - 0.1f, - (s,cf,p,v) => { AvatarContactProcessingThreshold = cf.GetFloat(p, v); }, - (s) => { return AvatarContactProcessingThreshold; }, - (s,p,l,v) => { s.UpdateParameterObject((x)=>{AvatarContactProcessingThreshold=x;}, p, l, v); } ), + (s) => { return CcdSweptSphereRadius; }, + (s,v) => { CcdSweptSphereRadius = v;}, + (s,o) => { s.PE.SetCcdSweptSphereRadius(o.PhysBody, CcdSweptSphereRadius); } ), + new ParameterDefn("ContactProcessingThreshold", "Distance above which contacts can be discarded (0 means no discard)" , + 0.0f, + (s) => { return ContactProcessingThreshold; }, + (s,v) => { ContactProcessingThreshold = v;}, + (s,o) => { s.PE.SetContactProcessingThreshold(o.PhysBody, ContactProcessingThreshold); } ), - new ParameterDefn("VehicleAngularDamping", "Factor to damp vehicle angular movement per second (0.0 - 1.0)", - 0.95f, - (s,cf,p,v) => { VehicleAngularDamping = cf.GetFloat(p, v); }, - (s) => { return VehicleAngularDamping; }, - (s,p,l,v) => { VehicleAngularDamping = v; } ), + new ParameterDefn("TerrainImplementation", "Type of shape to use for terrain (0=heightmap, 1=mesh)", + (float)BSTerrainPhys.TerrainImplementation.Mesh ), + new ParameterDefn("TerrainMeshMagnification", "Number of times the 256x256 heightmap is multiplied to create the terrain mesh" , + 2 ), + new ParameterDefn("TerrainFriction", "Factor to reduce movement against terrain surface" , + 0.3f ), + new ParameterDefn("TerrainHitFraction", "Distance to measure hit collisions" , + 0.8f ), + new ParameterDefn("TerrainRestitution", "Bouncyness" , + 0f ), + new ParameterDefn("TerrainContactProcessingThreshold", "Distance from terrain to stop processing collisions" , + 0.0f ), + new ParameterDefn("TerrainCollisionMargin", "Margin where collision checking starts" , + 0.08f ), - new ParameterDefn("MaxPersistantManifoldPoolSize", "Number of manifolds pooled (0 means default of 4096)", + new ParameterDefn("AvatarFriction", "Factor to reduce movement against an avatar. Changed on avatar recreation.", + 0.2f ), + new ParameterDefn("AvatarStandingFriction", "Avatar friction when standing. Changed on avatar recreation.", + 0.95f ), + new ParameterDefn("AvatarAlwaysRunFactor", "Speed multiplier if avatar is set to always run", + 1.3f ), + new ParameterDefn("AvatarDensity", "Density of an avatar. Changed on avatar recreation.", + 3.5f) , + new ParameterDefn("AvatarRestitution", "Bouncyness. Changed on avatar recreation.", + 0f ), + new ParameterDefn("AvatarCapsuleWidth", "The distance between the sides of the avatar capsule", + 0.6f ) , + new ParameterDefn("AvatarCapsuleDepth", "The distance between the front and back of the avatar capsule", + 0.45f ), + new ParameterDefn("AvatarCapsuleHeight", "Default height of space around avatar", + 1.5f ), + new ParameterDefn("AvatarContactProcessingThreshold", "Distance from capsule to check for collisions", + 0.1f ), + new ParameterDefn("AvatarBelowGroundUpCorrectionMeters", "Meters to move avatar up if it seems to be below ground", + 1.0f ), + new ParameterDefn("AvatarStepHeight", "Height of a step obstacle to consider step correction", + 0.6f ) , + new ParameterDefn("AvatarStepApproachFactor", "Factor to control angle of approach to step (0=straight on)", + 0.6f ), + new ParameterDefn("AvatarStepForceFactor", "Controls the amount of force up applied to step up onto a step", + 1.0f ), + new ParameterDefn("AvatarStepUpCorrectionFactor", "Multiplied by height of step collision to create up movement at step", + 1.0f ), + new ParameterDefn("AvatarStepSmoothingSteps", "Number of frames after a step collision that we continue walking up stairs", + 2 ), + + new ParameterDefn("VehicleMaxLinearVelocity", "Maximum velocity magnitude that can be assigned to a vehicle", + 1000.0f, + (s) => { return (float)VehicleMaxLinearVelocity; }, + (s,v) => { VehicleMaxLinearVelocity = v; VehicleMaxLinearVelocitySquared = v * v; } ), + new ParameterDefn("VehicleMaxAngularVelocity", "Maximum rotational velocity magnitude that can be assigned to a vehicle", + 12.0f, + (s) => { return (float)VehicleMaxAngularVelocity; }, + (s,v) => { VehicleMaxAngularVelocity = v; VehicleMaxAngularVelocitySq = v * v; } ), + new ParameterDefn("VehicleAngularDamping", "Factor to damp vehicle angular movement per second (0.0 - 1.0)", + 0.0f ), + new ParameterDefn("VehicleLinearFactor", "Fraction of physical linear changes applied to vehicle (<0,0,0> to <1,1,1>)", + new Vector3(1f, 1f, 1f) ), + new ParameterDefn("VehicleAngularFactor", "Fraction of physical angular changes applied to vehicle (<0,0,0> to <1,1,1>)", + new Vector3(1f, 1f, 1f) ), + new ParameterDefn("VehicleFriction", "Friction of vehicle on the ground (0.0 - 1.0)", + 0.0f ), + new ParameterDefn("VehicleRestitution", "Bouncyness factor for vehicles (0.0 - 1.0)", + 0.0f ), + new ParameterDefn("VehicleGroundGravityFudge", "Factor to multiply gravity if a ground vehicle is probably on the ground (0.0 - 1.0)", + 0.2f ), + new ParameterDefn("VehicleAngularBankingTimescaleFudge", "Factor to multiple angular banking timescale. Tune to increase realism.", + 60.0f ), + new ParameterDefn("VehicleDebuggingEnable", "Turn on/off vehicle debugging", + false ), + + new ParameterDefn("MaxPersistantManifoldPoolSize", "Number of manifolds pooled (0 means default of 4096)", 0f, - (s,cf,p,v) => { s.UnmanagedParams[0].maxPersistantManifoldPoolSize = cf.GetFloat(p, v); }, - (s) => { return s.UnmanagedParams[0].maxPersistantManifoldPoolSize; }, - (s,p,l,v) => { s.UnmanagedParams[0].maxPersistantManifoldPoolSize = v; } ), - new ParameterDefn("MaxCollisionAlgorithmPoolSize", "Number of collisions pooled (0 means default of 4096)", + (s) => { return MaxPersistantManifoldPoolSize; }, + (s,v) => { MaxPersistantManifoldPoolSize = v; s.UnmanagedParams[0].maxPersistantManifoldPoolSize = v; } ), + new ParameterDefn("MaxCollisionAlgorithmPoolSize", "Number of collisions pooled (0 means default of 4096)", 0f, - (s,cf,p,v) => { s.UnmanagedParams[0].maxCollisionAlgorithmPoolSize = cf.GetFloat(p, v); }, - (s) => { return s.UnmanagedParams[0].maxCollisionAlgorithmPoolSize; }, - (s,p,l,v) => { s.UnmanagedParams[0].maxCollisionAlgorithmPoolSize = v; } ), - new ParameterDefn("ShouldDisableContactPoolDynamicAllocation", "Enable to allow large changes in object count", - ConfigurationParameters.numericFalse, - (s,cf,p,v) => { s.UnmanagedParams[0].shouldDisableContactPoolDynamicAllocation = BSParam.NumericBool(cf.GetBoolean(p, BSParam.BoolNumeric(v))); }, - (s) => { return s.UnmanagedParams[0].shouldDisableContactPoolDynamicAllocation; }, - (s,p,l,v) => { s.UnmanagedParams[0].shouldDisableContactPoolDynamicAllocation = v; } ), - new ParameterDefn("ShouldForceUpdateAllAabbs", "Enable to recomputer AABBs every simulator step", - ConfigurationParameters.numericFalse, - (s,cf,p,v) => { s.UnmanagedParams[0].shouldForceUpdateAllAabbs = BSParam.NumericBool(cf.GetBoolean(p, BSParam.BoolNumeric(v))); }, - (s) => { return s.UnmanagedParams[0].shouldForceUpdateAllAabbs; }, - (s,p,l,v) => { s.UnmanagedParams[0].shouldForceUpdateAllAabbs = v; } ), - new ParameterDefn("ShouldRandomizeSolverOrder", "Enable for slightly better stacking interaction", - ConfigurationParameters.numericTrue, - (s,cf,p,v) => { s.UnmanagedParams[0].shouldRandomizeSolverOrder = BSParam.NumericBool(cf.GetBoolean(p, BSParam.BoolNumeric(v))); }, - (s) => { return s.UnmanagedParams[0].shouldRandomizeSolverOrder; }, - (s,p,l,v) => { s.UnmanagedParams[0].shouldRandomizeSolverOrder = v; } ), - new ParameterDefn("ShouldSplitSimulationIslands", "Enable splitting active object scanning islands", - ConfigurationParameters.numericTrue, - (s,cf,p,v) => { s.UnmanagedParams[0].shouldSplitSimulationIslands = BSParam.NumericBool(cf.GetBoolean(p, BSParam.BoolNumeric(v))); }, - (s) => { return s.UnmanagedParams[0].shouldSplitSimulationIslands; }, - (s,p,l,v) => { s.UnmanagedParams[0].shouldSplitSimulationIslands = v; } ), - new ParameterDefn("ShouldEnableFrictionCaching", "Enable friction computation caching", - ConfigurationParameters.numericFalse, - (s,cf,p,v) => { s.UnmanagedParams[0].shouldEnableFrictionCaching = BSParam.NumericBool(cf.GetBoolean(p, BSParam.BoolNumeric(v))); }, - (s) => { return s.UnmanagedParams[0].shouldEnableFrictionCaching; }, - (s,p,l,v) => { s.UnmanagedParams[0].shouldEnableFrictionCaching = v; } ), - new ParameterDefn("NumberOfSolverIterations", "Number of internal iterations (0 means default)", + (s) => { return MaxCollisionAlgorithmPoolSize; }, + (s,v) => { MaxCollisionAlgorithmPoolSize = v; s.UnmanagedParams[0].maxCollisionAlgorithmPoolSize = v; } ), + new ParameterDefn("ShouldDisableContactPoolDynamicAllocation", "Enable to allow large changes in object count", + false, + (s) => { return ShouldDisableContactPoolDynamicAllocation; }, + (s,v) => { ShouldDisableContactPoolDynamicAllocation = v; + s.UnmanagedParams[0].shouldDisableContactPoolDynamicAllocation = NumericBool(v); } ), + new ParameterDefn("ShouldForceUpdateAllAabbs", "Enable to recomputer AABBs every simulator step", + false, + (s) => { return ShouldForceUpdateAllAabbs; }, + (s,v) => { ShouldForceUpdateAllAabbs = v; s.UnmanagedParams[0].shouldForceUpdateAllAabbs = NumericBool(v); } ), + new ParameterDefn("ShouldRandomizeSolverOrder", "Enable for slightly better stacking interaction", + true, + (s) => { return ShouldRandomizeSolverOrder; }, + (s,v) => { ShouldRandomizeSolverOrder = v; s.UnmanagedParams[0].shouldRandomizeSolverOrder = NumericBool(v); } ), + new ParameterDefn("ShouldSplitSimulationIslands", "Enable splitting active object scanning islands", + true, + (s) => { return ShouldSplitSimulationIslands; }, + (s,v) => { ShouldSplitSimulationIslands = v; s.UnmanagedParams[0].shouldSplitSimulationIslands = NumericBool(v); } ), + new ParameterDefn("ShouldEnableFrictionCaching", "Enable friction computation caching", + true, + (s) => { return ShouldEnableFrictionCaching; }, + (s,v) => { ShouldEnableFrictionCaching = v; s.UnmanagedParams[0].shouldEnableFrictionCaching = NumericBool(v); } ), + new ParameterDefn("NumberOfSolverIterations", "Number of internal iterations (0 means default)", 0f, // zero says use Bullet default - (s,cf,p,v) => { s.UnmanagedParams[0].numberOfSolverIterations = cf.GetFloat(p, v); }, - (s) => { return s.UnmanagedParams[0].numberOfSolverIterations; }, - (s,p,l,v) => { s.UnmanagedParams[0].numberOfSolverIterations = v; } ), - - new ParameterDefn("LinksetImplementation", "Type of linkset implementation (0=Constraint, 1=Compound, 2=Manual)", - (float)BSLinkset.LinksetImplementation.Compound, - (s,cf,p,v) => { LinksetImplementation = cf.GetFloat(p,v); }, - (s) => { return LinksetImplementation; }, - (s,p,l,v) => { LinksetImplementation = v; } ), - new ParameterDefn("LinkConstraintUseFrameOffset", "For linksets built with constraints, enable frame offsetFor linksets built with constraints, enable frame offset.", - ConfigurationParameters.numericFalse, - (s,cf,p,v) => { LinkConstraintUseFrameOffset = BSParam.NumericBool(cf.GetBoolean(p, BSParam.BoolNumeric(v))); }, - (s) => { return LinkConstraintUseFrameOffset; }, - (s,p,l,v) => { LinkConstraintUseFrameOffset = v; } ), - new ParameterDefn("LinkConstraintEnableTransMotor", "Whether to enable translational motor on linkset constraints", - ConfigurationParameters.numericTrue, - (s,cf,p,v) => { LinkConstraintEnableTransMotor = BSParam.NumericBool(cf.GetBoolean(p, BSParam.BoolNumeric(v))); }, - (s) => { return LinkConstraintEnableTransMotor; }, - (s,p,l,v) => { LinkConstraintEnableTransMotor = v; } ), - new ParameterDefn("LinkConstraintTransMotorMaxVel", "Maximum velocity to be applied by translational motor in linkset constraints", - 5.0f, - (s,cf,p,v) => { LinkConstraintTransMotorMaxVel = cf.GetFloat(p, v); }, - (s) => { return LinkConstraintTransMotorMaxVel; }, - (s,p,l,v) => { LinkConstraintTransMotorMaxVel = v; } ), - new ParameterDefn("LinkConstraintTransMotorMaxForce", "Maximum force to be applied by translational motor in linkset constraints", - 0.1f, - (s,cf,p,v) => { LinkConstraintTransMotorMaxForce = cf.GetFloat(p, v); }, - (s) => { return LinkConstraintTransMotorMaxForce; }, - (s,p,l,v) => { LinkConstraintTransMotorMaxForce = v; } ), - new ParameterDefn("LinkConstraintCFM", "Amount constraint can be violated. 0=no violation, 1=infinite. Default=0.1", - 0.1f, - (s,cf,p,v) => { LinkConstraintCFM = cf.GetFloat(p, v); }, - (s) => { return LinkConstraintCFM; }, - (s,p,l,v) => { LinkConstraintCFM = v; } ), - new ParameterDefn("LinkConstraintERP", "Amount constraint is corrected each tick. 0=none, 1=all. Default = 0.2", - 0.1f, - (s,cf,p,v) => { LinkConstraintERP = cf.GetFloat(p, v); }, - (s) => { return LinkConstraintERP; }, - (s,p,l,v) => { LinkConstraintERP = v; } ), - new ParameterDefn("LinkConstraintSolverIterations", "Number of solver iterations when computing constraint. (0 = Bullet default)", - 40, - (s,cf,p,v) => { LinkConstraintSolverIterations = cf.GetFloat(p, v); }, - (s) => { return LinkConstraintSolverIterations; }, - (s,p,l,v) => { LinkConstraintSolverIterations = v; } ), - - new ParameterDefn("PhysicsMetricFrames", "Frames between outputting detailed phys metrics. (0 is off)", + (s) => { return NumberOfSolverIterations; }, + (s,v) => { NumberOfSolverIterations = v; s.UnmanagedParams[0].numberOfSolverIterations = v; } ), + new ParameterDefn("UseSingleSidedMeshes", "Whether to compute collisions based on single sided meshes.", + true, + (s) => { return UseSingleSidedMeshes; }, + (s,v) => { UseSingleSidedMeshes = v; s.UnmanagedParams[0].useSingleSidedMeshes = NumericBool(v); } ), + new ParameterDefn("GlobalContactBreakingThreshold", "Amount of shape radius before breaking a collision contact (0 says Bullet default (0.2))", 0f, - (s,cf,p,v) => { s.PhysicsMetricDumpFrames = cf.GetFloat(p, (int)v); }, - (s) => { return (float)s.PhysicsMetricDumpFrames; }, - (s,p,l,v) => { s.PhysicsMetricDumpFrames = (int)v; } ), + (s) => { return GlobalContactBreakingThreshold; }, + (s,v) => { GlobalContactBreakingThreshold = v; s.UnmanagedParams[0].globalContactBreakingThreshold = v; } ), + + new ParameterDefn("CSHullMaxDepthSplit", "CS impl: max depth to split for hull. 1-10 but > 7 is iffy", + 7 ), + new ParameterDefn("CSHullMaxDepthSplitForSimpleShapes", "CS impl: max depth setting for simple prim shapes", + 2 ), + new ParameterDefn("CSHullConcavityThresholdPercent", "CS impl: concavity threshold percent (0-20)", + 5f ), + new ParameterDefn("CSHullVolumeConservationThresholdPercent", "percent volume conservation to collapse hulls (0-30)", + 5f ), + new ParameterDefn("CSHullMaxVertices", "CS impl: maximum number of vertices in output hulls. Keep < 50.", + 32 ), + new ParameterDefn("CSHullMaxSkinWidth", "CS impl: skin width to apply to output hulls.", + 0f ), + + new ParameterDefn("BHullMaxVerticesPerHull", "Bullet impl: max number of vertices per created hull", + 100f ), + new ParameterDefn("BHullMinClusters", "Bullet impl: minimum number of hulls to create per mesh", + 2f ), + new ParameterDefn("BHullCompacityWeight", "Bullet impl: weight factor for how compact to make hulls", + 0.1f ), + new ParameterDefn("BHullVolumeWeight", "Bullet impl: weight factor for volume in created hull", + 0f ), + new ParameterDefn("BHullConcavity", "Bullet impl: weight factor for how convex a created hull can be", + 100f ), + new ParameterDefn("BHullAddExtraDistPoints", "Bullet impl: whether to add extra vertices for long distance vectors", + false ), + new ParameterDefn("BHullAddNeighboursDistPoints", "Bullet impl: whether to add extra vertices between neighbor hulls", + false ), + new ParameterDefn("BHullAddFacesPoints", "Bullet impl: whether to add extra vertices to break up hull faces", + false ), + new ParameterDefn("BHullShouldAdjustCollisionMargin", "Bullet impl: whether to shrink resulting hulls to account for collision margin", + false ), + + new ParameterDefn("LinksetImplementation", "Type of linkset implementation (0=Constraint, 1=Compound, 2=Manual)", + (float)BSLinkset.LinksetImplementation.Compound ), + new ParameterDefn("LinkConstraintUseFrameOffset", "For linksets built with constraints, enable frame offsetFor linksets built with constraints, enable frame offset.", + false ), + new ParameterDefn("LinkConstraintEnableTransMotor", "Whether to enable translational motor on linkset constraints", + true ), + new ParameterDefn("LinkConstraintTransMotorMaxVel", "Maximum velocity to be applied by translational motor in linkset constraints", + 5.0f ), + new ParameterDefn("LinkConstraintTransMotorMaxForce", "Maximum force to be applied by translational motor in linkset constraints", + 0.1f ), + new ParameterDefn("LinkConstraintCFM", "Amount constraint can be violated. 0=no violation, 1=infinite. Default=0.1", + 0.1f ), + new ParameterDefn("LinkConstraintERP", "Amount constraint is corrected each tick. 0=none, 1=all. Default = 0.2", + 0.1f ), + new ParameterDefn("LinkConstraintSolverIterations", "Number of solver iterations when computing constraint. (0 = Bullet default)", + 40 ), + + new ParameterDefn("PhysicsMetricFrames", "Frames between outputting detailed phys metrics. (0 is off)", + 0, + (s) => { return s.PhysicsMetricDumpFrames; }, + (s,v) => { s.PhysicsMetricDumpFrames = v; } ), + new ParameterDefn("ResetBroadphasePool", "Setting this is any value resets the broadphase collision pool", + 0f, + (s) => { return 0f; }, + (s,v) => { BSParam.ResetBroadphasePoolTainted(s, v); } ), + new ParameterDefn("ResetConstraintSolver", "Setting this is any value resets the constraint solver", + 0f, + (s) => { return 0f; }, + (s,v) => { BSParam.ResetConstraintSolverTainted(s, v); } ), }; // Convert a boolean to our numeric true and false values @@ -515,13 +700,13 @@ public static class BSParam // ParameterDefn structure. // Case does not matter as names are compared after converting to lower case. // Returns 'false' if the parameter is not found. - internal static bool TryGetParameter(string paramName, out ParameterDefn defn) + internal static bool TryGetParameter(string paramName, out ParameterDefnBase defn) { bool ret = false; - ParameterDefn foundDefn = new ParameterDefn(); + ParameterDefnBase foundDefn = null; string pName = paramName.ToLower(); - foreach (ParameterDefn parm in ParameterDefinitions) + foreach (ParameterDefnBase parm in ParameterDefinitions) { if (pName == parm.name.ToLower()) { @@ -537,18 +722,18 @@ public static class BSParam // Pass through the settable parameters and set the default values internal static void SetParameterDefaultValues(BSScene physicsScene) { - foreach (ParameterDefn parm in ParameterDefinitions) + foreach (ParameterDefnBase parm in ParameterDefinitions) { - parm.setter(physicsScene, parm.name, PhysParameterEntry.APPLY_TO_NONE, parm.defaultValue); + parm.AssignDefault(physicsScene); } } // Get user set values out of the ini file. internal static void SetParameterConfigurationValues(BSScene physicsScene, IConfig cfg) { - foreach (ParameterDefn parm in ParameterDefinitions) + foreach (ParameterDefnBase parm in ParameterDefinitions) { - parm.userParam(physicsScene, cfg, parm.name, parm.defaultValue); + parm.SetValue(physicsScene, cfg.GetString(parm.name, parm.GetValue(physicsScene))); } } @@ -563,20 +748,38 @@ public static class BSParam List entries = new List(); for (int ii = 0; ii < ParameterDefinitions.Length; ii++) { - ParameterDefn pd = ParameterDefinitions[ii]; + ParameterDefnBase pd = ParameterDefinitions[ii]; entries.Add(new PhysParameterEntry(pd.name, pd.desc)); } - // make the list in alphabetical order for estetic reasons - entries.Sort(delegate(PhysParameterEntry ppe1, PhysParameterEntry ppe2) - { - return ppe1.name.CompareTo(ppe2.name); - }); + // make the list alphabetical for ease of finding anything + entries.Sort((ppe1, ppe2) => { return ppe1.name.CompareTo(ppe2.name); }); SettableParameters = entries.ToArray(); } } + // ===================================================================== + // ===================================================================== + // There are parameters that, when set, cause things to happen in the physics engine. + // This causes the broadphase collision cache to be cleared. + private static void ResetBroadphasePoolTainted(BSScene pPhysScene, float v) + { + BSScene physScene = pPhysScene; + physScene.TaintedObject("BSParam.ResetBroadphasePoolTainted", delegate() + { + physScene.PE.ResetBroadphasePool(physScene.World); + }); + } + // This causes the constraint solver cache to be cleared and reset. + private static void ResetConstraintSolverTainted(BSScene pPhysScene, float v) + { + BSScene physScene = pPhysScene; + physScene.TaintedObject("BSParam.ResetConstraintSolver", delegate() + { + physScene.PE.ResetConstraintSolver(physScene.World); + }); + } } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs index e7cb3e0527..cca887afa0 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSPhysObject.cs @@ -38,12 +38,12 @@ namespace OpenSim.Region.Physics.BulletSPlugin * Class to wrap all objects. * The rest of BulletSim doesn't need to keep checking for avatars or prims * unless the difference is significant. - * + * * Variables in the physicsl objects are in three forms: * VariableName: used by the simulator and performs taint operations, etc * RawVariableName: direct reference to the BulletSim storage for the variable value * ForceVariableName: direct reference (store and fetch) to the value in the physics engine. - * The last two (and certainly the last one) should be referenced only in taint-time. + * The last one should only be referenced in taint-time. */ /* @@ -52,9 +52,19 @@ namespace OpenSim.Region.Physics.BulletSPlugin * SOP.ApplyImpulse SOP.ApplyAngularImpulse SOP.SetAngularImpulse SOP.SetForce * SOG.ApplyImpulse SOG.ApplyAngularImpulse SOG.SetAngularImpulse * PA.AddForce PA.AddAngularForce PA.Torque = v PA.Force = v - * BS.ApplyCentralForce BS.ApplyTorque + * BS.ApplyCentralForce BS.ApplyTorque */ +// Flags used to denote which properties updates when making UpdateProperties calls to linksets, etc. +public enum UpdatedProperties : uint +{ + Position = 1 << 0, + Orientation = 1 << 1, + Velocity = 1 << 2, + Acceleration = 1 << 3, + RotationalVelocity = 1 << 4, + EntPropUpdates = Position | Orientation | Velocity | Acceleration | RotationalVelocity, +} public abstract class BSPhysObject : PhysicsActor { protected BSPhysObject() @@ -62,41 +72,61 @@ public abstract class BSPhysObject : PhysicsActor } protected BSPhysObject(BSScene parentScene, uint localID, string name, string typeName) { - PhysicsScene = parentScene; + PhysScene = parentScene; LocalID = localID; PhysObjectName = name; + Name = name; // PhysicsActor also has the name of the object. Someday consolidate. TypeName = typeName; + // The collection of things that push me around + PhysicalActors = new BSActorCollection(PhysScene); + + // Initialize variables kept in base. + GravModifier = 1.0f; + Gravity = new OMV.Vector3(0f, 0f, BSParam.Gravity); + HoverActive = false; + // We don't have any physical representation yet. PhysBody = new BulletBody(localID); - PhysShape = new BulletShape(); + PhysShape = new BSShapeNull(); - // A linkset of just me - Linkset = BSLinkset.Factory(PhysicsScene, this); - LastAssetBuildFailed = false; + PrimAssetState = PrimAssetCondition.Unknown; - // Default material type - Material = MaterialAttributes.Material.Wood; + // Default material type. Also sets Friction, Restitution and Density. + SetMaterial((int)MaterialAttributes.Material.Wood); CollisionCollection = new CollisionEventUpdate(); + CollisionsLastReported = CollisionCollection; + CollisionsLastTick = new CollisionEventUpdate(); + CollisionsLastTickStep = -1; + SubscribedEventsMs = 0; CollidingStep = 0; CollidingGroundStep = 0; + CollisionAccumulation = 0; + ColliderIsMoving = false; + CollisionScore = 0; + + // All axis free. + LockedLinearAxis = LockedAxisFree; + LockedAngularAxis = LockedAxisFree; } // Tell the object to clean up. public virtual void Destroy() { - UnRegisterAllPreStepActions(); + PhysicalActors.Enable(false); + PhysScene.TaintedObject("BSPhysObject.Destroy", delegate() + { + PhysicalActors.Dispose(); + }); } - public BSScene PhysicsScene { get; protected set; } + public BSScene PhysScene { get; protected set; } // public override uint LocalID { get; set; } // Use the LocalID definition in PhysicsActor public string PhysObjectName { get; protected set; } public string TypeName { get; protected set; } - public BSLinkset Linkset { get; set; } - public BSLinksetInfo LinksetInfo { get; set; } // Return the object mass without calculating it or having side effects public abstract float RawMass { get; } @@ -104,26 +134,26 @@ public abstract class BSPhysObject : PhysicsActor // 'inWorld' true if the object has already been added to the dynamic world. public abstract void UpdatePhysicalMassProperties(float mass, bool inWorld); + // The gravity being applied to the object. A function of default grav, GravityModifier and Buoyancy. + public virtual OMV.Vector3 Gravity { get; set; } // The last value calculated for the prim's inertia public OMV.Vector3 Inertia { get; set; } // Reference to the physical body (btCollisionObject) of this object public BulletBody PhysBody; // Reference to the physical shape (btCollisionShape) of this object - public BulletShape PhysShape; + public BSShape PhysShape; - // 'true' if the mesh's underlying asset failed to build. - // This will keep us from looping after the first time the build failed. - public bool LastAssetBuildFailed { get; set; } + // The physical representation of the prim might require an asset fetch. + // The asset state is first 'Unknown' then 'Waiting' then either 'Failed' or 'Fetched'. + public enum PrimAssetCondition + { + Unknown, Waiting, Failed, Fetched + } + public PrimAssetCondition PrimAssetState { get; set; } // The objects base shape information. Null if not a prim type shape. public PrimitiveBaseShape BaseShape { get; protected set; } - // Some types of objects have preferred physical representations. - // Returns SHAPE_UNKNOWN if there is no preference. - public virtual BSPhysicsShapeType PreferredPhysicalShape - { - get { return BSPhysicsShapeType.SHAPE_UNKNOWN; } - } // When the physical properties are updated, an EntityProperty holds the update values. // Keep the current and last EntityProperties to enable computation of differences @@ -132,23 +162,35 @@ public abstract class BSPhysObject : PhysicsActor public EntityProperties LastEntityProperties { get; set; } public virtual OMV.Vector3 Scale { get; set; } + + // It can be confusing for an actor to know if it should move or update an object + // depeneding on the setting of 'selected', 'physical, ... + // This flag is the true test -- if true, the object is being acted on in the physical world + public abstract bool IsPhysicallyActive { get; } + + // Detailed state of the object. public abstract bool IsSolid { get; } public abstract bool IsStatic { get; } + public abstract bool IsSelected { get; } // Materialness public MaterialAttributes.Material Material { get; private set; } public override void SetMaterial(int material) { Material = (MaterialAttributes.Material)material; + + // Setting the material sets the material attributes also. + MaterialAttributes matAttrib = BSMaterials.GetAttributes(Material, false); + Friction = matAttrib.friction; + Restitution = matAttrib.restitution; + Density = matAttrib.density / BSParam.DensityScaleFactor; + // DetailLog("{0},{1}.SetMaterial,Mat={2},frict={3},rest={4},den={5}", LocalID, TypeName, Material, Friction, Restitution, Density); } // Stop all physical motion. public abstract void ZeroMotion(bool inTaintTime); public abstract void ZeroAngularMotion(bool inTaintTime); - // Step the vehicle simulation for this object. A NOOP if the vehicle was not configured. - public virtual void StepVehicle(float timeStep) { } - // Update the physical location and motion of the object. Called with data from Bullet. public abstract void UpdateProperties(EntityProperties entprop); @@ -158,25 +200,122 @@ public abstract class BSPhysObject : PhysicsActor public abstract OMV.Quaternion RawOrientation { get; set; } public abstract OMV.Quaternion ForceOrientation { get; set; } - // The system is telling us the velocity it wants to move at. - // protected OMV.Vector3 m_targetVelocity; // use the definition in PhysicsActor - public override OMV.Vector3 TargetVelocity - { - get { return m_targetVelocity; } - set - { - m_targetVelocity = value; - Velocity = value; - } - } + public OMV.Vector3 RawVelocity { get; set; } public abstract OMV.Vector3 ForceVelocity { get; set; } + public OMV.Vector3 RawForce { get; set; } + public OMV.Vector3 RawTorque { get; set; } + public override void AddAngularForce(OMV.Vector3 force, bool pushforce) + { + AddAngularForce(force, pushforce, false); + } + public abstract void AddAngularForce(OMV.Vector3 force, bool pushforce, bool inTaintTime); + public abstract OMV.Vector3 ForceRotationalVelocity { get; set; } public abstract float ForceBuoyancy { get; set; } public virtual bool ForceBodyShapeRebuild(bool inTaintTime) { return false; } + public override bool PIDActive { set { MoveToTargetActive = value; } } + public override OMV.Vector3 PIDTarget { set { MoveToTargetTarget = value; } } + public override float PIDTau { set { MoveToTargetTau = value; } } + + public bool MoveToTargetActive { get; set; } + public OMV.Vector3 MoveToTargetTarget { get; set; } + public float MoveToTargetTau { get; set; } + + // Used for llSetHoverHeight and maybe vehicle height. Hover Height will override MoveTo target's Z + public override bool PIDHoverActive { set { HoverActive = value; } } + public override float PIDHoverHeight { set { HoverHeight = value; } } + public override PIDHoverType PIDHoverType { set { HoverType = value; } } + public override float PIDHoverTau { set { HoverTau = value; } } + + public bool HoverActive { get; set; } + public float HoverHeight { get; set; } + public PIDHoverType HoverType { get; set; } + public float HoverTau { get; set; } + + // For RotLookAt + public override OMV.Quaternion APIDTarget { set { return; } } + public override bool APIDActive { set { return; } } + public override float APIDStrength { set { return; } } + public override float APIDDamping { set { return; } } + + // The current velocity forward + public virtual float ForwardSpeed + { + get + { + OMV.Vector3 characterOrientedVelocity = RawVelocity * OMV.Quaternion.Inverse(OMV.Quaternion.Normalize(RawOrientation)); + return characterOrientedVelocity.X; + } + } + // The forward speed we are trying to achieve (TargetVelocity) + public virtual float TargetVelocitySpeed + { + get + { + OMV.Vector3 characterOrientedVelocity = TargetVelocity * OMV.Quaternion.Inverse(OMV.Quaternion.Normalize(RawOrientation)); + return characterOrientedVelocity.X; + } + } + + // The user can optionally set the center of mass. The user's setting will override any + // computed center-of-mass (like in linksets). + // Note this is a displacement from the root's coordinates. Zero means use the root prim as center-of-mass. + public OMV.Vector3? UserSetCenterOfMassDisplacement { get; set; } + + public OMV.Vector3 LockedLinearAxis { get; set; } // zero means locked. one means free. + public OMV.Vector3 LockedAngularAxis { get; set; } // zero means locked. one means free. + public const float FreeAxis = 1f; + public readonly OMV.Vector3 LockedAxisFree = new OMV.Vector3(FreeAxis, FreeAxis, FreeAxis); // All axis are free + + // Enable physical actions. Bullet will keep sleeping non-moving physical objects so + // they need waking up when parameters are changed. + // Called in taint-time!! + public void ActivateIfPhysical(bool forceIt) + { + if (IsPhysical && PhysBody.HasPhysicalBody) + PhysScene.PE.Activate(PhysBody, forceIt); + } + + // 'actors' act on the physical object to change or constrain its motion. These can range from + // hovering to complex vehicle motion. + // May be called at non-taint time as this just adds the actor to the action list and the real + // work is done during the simulation step. + // Note that, if the actor is already in the list and we are disabling same, the actor is just left + // in the list disabled. + public delegate BSActor CreateActor(); + public void EnableActor(bool enableActor, string actorName, CreateActor creator) + { + lock (PhysicalActors) + { + BSActor theActor; + if (PhysicalActors.TryGetActor(actorName, out theActor)) + { + // The actor already exists so just turn it on or off + DetailLog("{0},BSPhysObject.EnableActor,enablingExistingActor,name={1},enable={2}", LocalID, actorName, enableActor); + theActor.Enabled = enableActor; + } + else + { + // The actor does not exist. If it should, create it. + if (enableActor) + { + DetailLog("{0},BSPhysObject.EnableActor,creatingActor,name={1}", LocalID, actorName); + theActor = creator(); + PhysicalActors.Add(actorName, theActor); + theActor.Enabled = true; + } + else + { + DetailLog("{0},BSPhysObject.EnableActor,notCreatingActorSinceNotEnabled,name={1}", LocalID, actorName); + } + } + } + } + #region Collisions // Requested number of milliseconds between collision events. Zero means disabled. @@ -191,41 +330,56 @@ public abstract class BSPhysObject : PhysicsActor protected long CollidingObjectStep { get; set; } // The collision flags we think are set in Bullet protected CollisionFlags CurrentCollisionFlags { get; set; } + // On a collision, check the collider and remember if the last collider was moving + // Used to modify the standing of avatars (avatars on stationary things stand still) + public bool ColliderIsMoving; + // Used by BSCharacter to manage standing (and not slipping) + public bool IsStationary; + + // Count of collisions for this object + protected long CollisionAccumulation { get; set; } public override bool IsColliding { - get { return (CollidingStep == PhysicsScene.SimulationStep); } + get { return (CollidingStep == PhysScene.SimulationStep); } set { if (value) - CollidingStep = PhysicsScene.SimulationStep; + CollidingStep = PhysScene.SimulationStep; else CollidingStep = 0; } } public override bool CollidingGround { - get { return (CollidingGroundStep == PhysicsScene.SimulationStep); } + get { return (CollidingGroundStep == PhysScene.SimulationStep); } set { if (value) - CollidingGroundStep = PhysicsScene.SimulationStep; + CollidingGroundStep = PhysScene.SimulationStep; else CollidingGroundStep = 0; } } public override bool CollidingObj { - get { return (CollidingObjectStep == PhysicsScene.SimulationStep); } - set { + get { return (CollidingObjectStep == PhysScene.SimulationStep); } + set { if (value) - CollidingObjectStep = PhysicsScene.SimulationStep; + CollidingObjectStep = PhysScene.SimulationStep; else CollidingObjectStep = 0; } } - // The collisions that have been collected this tick + // The collisions that have been collected for the next collision reporting (throttled by subscription) protected CollisionEventUpdate CollisionCollection; + // This is the collision collection last reported to the Simulator. + public CollisionEventUpdate CollisionsLastReported; + // Remember the collisions recorded in the last tick for fancy collision checking + // (like a BSCharacter walking up stairs). + public CollisionEventUpdate CollisionsLastTick; + private long CollisionsLastTickStep = -1; // The simulation step is telling this object about a collision. // Return 'true' if a collision was processed and should be sent up. + // Return 'false' if this object is not enabled/subscribed/appropriate for or has already seen this collision. // Called at taint time from within the Step() function public virtual bool Collide(uint collidingWith, BSPhysObject collidee, OMV.Vector3 contactPoint, OMV.Vector3 contactNormal, float pentrationDepth) @@ -233,27 +387,35 @@ public abstract class BSPhysObject : PhysicsActor bool ret = false; // The following lines make IsColliding(), CollidingGround() and CollidingObj work - CollidingStep = PhysicsScene.SimulationStep; - if (collidingWith <= PhysicsScene.TerrainManager.HighestTerrainID) + CollidingStep = PhysScene.SimulationStep; + if (collidingWith <= PhysScene.TerrainManager.HighestTerrainID) { - CollidingGroundStep = PhysicsScene.SimulationStep; + CollidingGroundStep = PhysScene.SimulationStep; } else { - CollidingObjectStep = PhysicsScene.SimulationStep; + CollidingObjectStep = PhysScene.SimulationStep; } - // prims in the same linkset cannot collide with each other - if (collidee != null && (this.Linkset.LinksetID == collidee.Linkset.LinksetID)) + CollisionAccumulation++; + + // For movement tests, remember if we are colliding with an object that is moving. + ColliderIsMoving = collidee != null ? (collidee.RawVelocity != OMV.Vector3.Zero) : false; + + // Make a collection of the collisions that happened the last simulation tick. + // This is different than the collection created for sending up to the simulator as it is cleared every tick. + if (CollisionsLastTickStep != PhysScene.SimulationStep) { - return ret; + CollisionsLastTick = new CollisionEventUpdate(); + CollisionsLastTickStep = PhysScene.SimulationStep; } + CollisionsLastTick.AddCollider(collidingWith, new ContactPoint(contactPoint, contactNormal, pentrationDepth)); - // if someone has subscribed for collision events.... + // If someone has subscribed for collision events log the collision so it will be reported up if (SubscribedEvents()) { CollisionCollection.AddCollider(collidingWith, new ContactPoint(contactPoint, contactNormal, pentrationDepth)); - DetailLog("{0},{1}.Collison.AddCollider,call,with={2},point={3},normal={4},depth={5}", - LocalID, TypeName, collidingWith, contactPoint, contactNormal, pentrationDepth); + DetailLog("{0},{1}.Collison.AddCollider,call,with={2},point={3},normal={4},depth={5},colliderMoving={6}", + LocalID, TypeName, collidingWith, contactPoint, contactNormal, pentrationDepth, ColliderIsMoving); ret = true; } @@ -267,13 +429,14 @@ public abstract class BSPhysObject : PhysicsActor public virtual bool SendCollisions() { bool ret = true; + // If the 'no collision' call, force it to happen right now so quick collision_end - bool force = (CollisionCollection.Count == 0); + bool force = (CollisionCollection.Count == 0 && CollisionsLastReported.Count != 0); // throttle the collisions to the number of milliseconds specified in the subscription - if (force || (PhysicsScene.SimulationNowTime >= NextCollisionOkTime)) + if (force || (PhysScene.SimulationNowTime >= NextCollisionOkTime)) { - NextCollisionOkTime = PhysicsScene.SimulationNowTime + SubscribedEventsMs; + NextCollisionOkTime = PhysScene.SimulationNowTime + SubscribedEventsMs; // We are called if we previously had collisions. If there are no collisions // this time, send up one last empty event so OpenSim can sense collision end. @@ -283,12 +446,15 @@ public abstract class BSPhysObject : PhysicsActor ret = false; } - // DetailLog("{0},{1}.SendCollisionUpdate,call,numCollisions={2}", LocalID, TypeName, CollisionCollection.Count); + DetailLog("{0},{1}.SendCollisionUpdate,call,numCollisions={2}", LocalID, TypeName, CollisionCollection.Count); base.SendCollisionUpdate(CollisionCollection); + // Remember the collisions from this tick for some collision specific processing. + CollisionsLastReported = CollisionCollection; + // The CollisionCollection instance is passed around in the simulator. // Make sure we don't have a handle to that one and that a new one is used for next time. - // This fixes an interesting 'gotcha'. If we call CollisionCollection.Clear() here, + // This fixes an interesting 'gotcha'. If we call CollisionCollection.Clear() here, // a race condition is created for the other users of this instance. CollisionCollection = new CollisionEventUpdate(); } @@ -305,10 +471,10 @@ public abstract class BSPhysObject : PhysicsActor // make sure first collision happens NextCollisionOkTime = Util.EnvironmentTickCountSubtract(SubscribedEventsMs); - PhysicsScene.TaintedObject(TypeName+".SubscribeEvents", delegate() + PhysScene.TaintedObject(TypeName+".SubscribeEvents", delegate() { if (PhysBody.HasPhysicalBody) - CurrentCollisionFlags = PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + CurrentCollisionFlags = PhysScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); }); } else @@ -320,66 +486,53 @@ public abstract class BSPhysObject : PhysicsActor public override void UnSubscribeEvents() { // DetailLog("{0},{1}.UnSubscribeEvents,unsubscribing", LocalID, TypeName); SubscribedEventsMs = 0; - PhysicsScene.TaintedObject(TypeName+".UnSubscribeEvents", delegate() + PhysScene.TaintedObject(TypeName+".UnSubscribeEvents", delegate() { // Make sure there is a body there because sometimes destruction happens in an un-ideal order. if (PhysBody.HasPhysicalBody) - CurrentCollisionFlags = PhysicsScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + CurrentCollisionFlags = PhysScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); }); } // Return 'true' if the simulator wants collision events public override bool SubscribedEvents() { return (SubscribedEventsMs > 0); } + // Because 'CollisionScore' is called many times while sorting, it should not be recomputed + // each time called. So this is built to be light weight for each collision and to do + // all the processing when the user asks for the info. + public void ComputeCollisionScore() + { + // Scale the collision count by the time since the last collision. + // The "+1" prevents dividing by zero. + long timeAgo = PhysScene.SimulationStep - CollidingStep + 1; + CollisionScore = CollisionAccumulation / timeAgo; + } + public override float CollisionScore { get; set; } #endregion // Collisions #region Per Simulation Step actions - // There are some actions that must be performed for a physical object before each simulation step. - // These actions are optional so, rather than scanning all the physical objects and asking them - // if they have anything to do, a physical object registers for an event call before the step is performed. - // This bookkeeping makes it easy to add, remove and clean up after all these registrations. - private Dictionary RegisteredActions = new Dictionary(); - protected void RegisterPreStepAction(string op, uint id, BSScene.PreStepAction actn) + + public BSActorCollection PhysicalActors; + + // When an update to the physical properties happens, this event is fired to let + // different actors to modify the update before it is passed around + public delegate void PreUpdatePropertyAction(ref EntityProperties entprop); + public event PreUpdatePropertyAction OnPreUpdateProperty; + protected void TriggerPreUpdatePropertyAction(ref EntityProperties entprop) { - string identifier = op + "-" + id.ToString(); - RegisteredActions[identifier] = actn; - PhysicsScene.BeforeStep += actn; - DetailLog("{0},BSPhysObject.RegisterPreStepAction,id={1}", LocalID, identifier); + PreUpdatePropertyAction actions = OnPreUpdateProperty; + if (actions != null) + actions(ref entprop); } - // Unregister a pre step action. Safe to call if the action has not been registered. - protected void UnRegisterPreStepAction(string op, uint id) - { - string identifier = op + "-" + id.ToString(); - bool removed = false; - if (RegisteredActions.ContainsKey(identifier)) - { - PhysicsScene.BeforeStep -= RegisteredActions[identifier]; - RegisteredActions.Remove(identifier); - removed = true; - } - DetailLog("{0},BSPhysObject.UnRegisterPreStepAction,id={1},removed={2}", LocalID, identifier, removed); - } - - protected void UnRegisterAllPreStepActions() - { - foreach (KeyValuePair kvp in RegisteredActions) - { - PhysicsScene.BeforeStep -= kvp.Value; - } - RegisteredActions.Clear(); - DetailLog("{0},BSPhysObject.UnRegisterAllPreStepActions,", LocalID); - } - - #endregion // Per Simulation Step actions // High performance detailed logging routine used by the physical objects. protected void DetailLog(string msg, params Object[] args) { - if (PhysicsScene.PhysicsLogging.Enabled) - PhysicsScene.DetailLog(msg, args); + if (PhysScene.PhysicsLogging.Enabled) + PhysScene.DetailLog(msg, args); } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPlugin.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPlugin.cs index 65be52ad04..944285414d 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSPlugin.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSPlugin.cs @@ -59,7 +59,7 @@ public class BSPlugin : IPhysicsPlugin { if (_mScene == null) { - _mScene = new BSScene(sceneIdentifier); + _mScene = new BSScene(GetName(), sceneIdentifier); } return (_mScene); } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs index 826261c223..f5b03611dd 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSPrim.cs @@ -39,7 +39,7 @@ namespace OpenSim.Region.Physics.BulletSPlugin { [Serializable] -public sealed class BSPrim : BSPhysObject +public class BSPrim : BSPhysObject { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private static readonly string LogHeader = "[BULLETS PRIM]"; @@ -50,39 +50,38 @@ public sealed class BSPrim : BSPhysObject private bool _grabbed; private bool _isSelected; private bool _isVolumeDetect; + + // _position is what the simulator thinks the positions of the prim is. private OMV.Vector3 _position; + private float _mass; // the mass of this object - private float _density; - private OMV.Vector3 _force; - private OMV.Vector3 _velocity; - private OMV.Vector3 _torque; - private float _collisionScore; private OMV.Vector3 _acceleration; private OMV.Quaternion _orientation; private int _physicsActorType; private bool _isPhysical; private bool _flying; - private float _friction; - private float _restitution; private bool _setAlwaysRun; private bool _throttleUpdates; - private bool _isColliding; - private bool _collidingGround; - private bool _collidingObj; private bool _floatOnWater; private OMV.Vector3 _rotationalVelocity; private bool _kinematic; private float _buoyancy; - private BSDynamics _vehicle; + private int CrossingFailures { get; set; } - private OMV.Vector3 _PIDTarget; - private bool _usePID; - private float _PIDTau; - private bool _useHoverPID; - private float _PIDHoverHeight; - private PIDHoverType _PIDHoverType; - private float _PIDHoverTao; + // Keep a handle to the vehicle actor so it is easy to set parameters on same. + public BSDynamics VehicleActor; + public const string VehicleActorName = "BasicVehicle"; + + // Parameters for the hover actor + public const string HoverActorName = "HoverActor"; + // Parameters for the axis lock actor + public const String LockedAxisActorName = "BSPrim.LockedAxis"; + // Parameters for the move to target actor + public const string MoveToTargetActorName = "MoveToTargetActor"; + // Parameters for the setForce and setTorque actors + public const string SetForceActorName = "SetForceActor"; + public const string SetTorqueActorName = "SetTorqueActor"; public BSPrim(uint localID, String primName, BSScene parent_scene, OMV.Vector3 pos, OMV.Vector3 size, OMV.Quaternion rotation, PrimitiveBaseShape pbs, bool pisPhysical) @@ -95,32 +94,28 @@ public sealed class BSPrim : BSPhysObject Scale = size; // prims are the size the user wants them to be (different for BSCharactes). _orientation = rotation; _buoyancy = 0f; - _velocity = OMV.Vector3.Zero; + RawVelocity = OMV.Vector3.Zero; _rotationalVelocity = OMV.Vector3.Zero; BaseShape = pbs; _isPhysical = pisPhysical; _isVolumeDetect = false; - // Someday set default attributes based on the material but, for now, we don't know the prim material yet. - // MaterialAttributes primMat = BSMaterials.GetAttributes(Material, pisPhysical); - _density = PhysicsScene.Params.defaultDensity; - _friction = PhysicsScene.Params.defaultFriction; - _restitution = PhysicsScene.Params.defaultRestitution; - - _vehicle = new BSDynamics(PhysicsScene, this); // add vehicleness + // We keep a handle to the vehicle actor so we can set vehicle parameters later. + VehicleActor = new BSDynamics(PhysScene, this, VehicleActorName); + PhysicalActors.Add(VehicleActorName, VehicleActor); _mass = CalculateMass(); - // Cause linkset variables to be initialized (like mass) - Linkset.Refresh(this); - - DetailLog("{0},BSPrim.constructor,call", LocalID); + // DetailLog("{0},BSPrim.constructor,call", LocalID); // do the actual object creation at taint time - PhysicsScene.TaintedObject("BSPrim.create", delegate() + PhysScene.TaintedObject("BSPrim.create", delegate() { + // Make sure the object is being created with some sanity. + ExtremeSanityCheck(true /* inTaintTime */); + CreateGeomAndObject(true); - CurrentCollisionFlags = PhysicsScene.PE.GetCollisionFlags(PhysBody); + CurrentCollisionFlags = PhysScene.PE.GetCollisionFlags(PhysBody); }); } @@ -130,26 +125,17 @@ public sealed class BSPrim : BSPhysObject // m_log.DebugFormat("{0}: Destroy, id={1}", LogHeader, LocalID); base.Destroy(); - // Undo any links between me and any other object - BSPhysObject parentBefore = Linkset.LinksetRoot; - int childrenBefore = Linkset.NumberOfChildren; - - Linkset = Linkset.RemoveMeFromLinkset(this); - - DetailLog("{0},BSPrim.Destroy,call,parentBefore={1},childrenBefore={2},parentAfter={3},childrenAfter={4}", - LocalID, parentBefore.LocalID, childrenBefore, Linkset.LinksetRoot.LocalID, Linkset.NumberOfChildren); - // Undo any vehicle properties this.VehicleType = (int)Vehicle.TYPE_NONE; - PhysicsScene.TaintedObject("BSPrim.destroy", delegate() + PhysScene.TaintedObject("BSPrim.Destroy", delegate() { DetailLog("{0},BSPrim.Destroy,taint,", LocalID); // If there are physical body and shape, release my use of same. - PhysicsScene.Shapes.DereferenceBody(PhysBody, true, null); + PhysScene.Shapes.DereferenceBody(PhysBody, null); PhysBody.Clear(); - PhysicsScene.Shapes.DereferenceShape(PhysShape, true, null); - PhysShape.Clear(); + PhysShape.Dereference(PhysScene); + PhysShape = new BSShapeNull(); }); } @@ -171,17 +157,13 @@ public sealed class BSPrim : BSPhysObject public override PrimitiveBaseShape Shape { set { BaseShape = value; + PrimAssetState = PrimAssetCondition.Unknown; ForceBodyShapeRebuild(false); } } - // Whatever the linkset wants is what I want. - public override BSPhysicsShapeType PreferredPhysicalShape - { get { return Linkset.PreferredPhysicalShape(this); } } - public override bool ForceBodyShapeRebuild(bool inTaintTime) { - LastAssetBuildFailed = false; - PhysicsScene.TaintedObject(inTaintTime, "BSPrim.ForceBodyShapeRebuild", delegate() + PhysScene.TaintedObject(inTaintTime, "BSPrim.ForceBodyShapeRebuild", delegate() { _mass = CalculateMass(); // changing the shape changes the mass CreateGeomAndObject(true); @@ -198,7 +180,7 @@ public sealed class BSPrim : BSPhysObject if (value != _isSelected) { _isSelected = value; - PhysicsScene.TaintedObject("BSPrim.setSelected", delegate() + PhysScene.TaintedObject("BSPrim.setSelected", delegate() { DetailLog("{0},BSPrim.selected,taint,selected={1}", LocalID, _isSelected); SetObjectDynamic(false); @@ -206,37 +188,31 @@ public sealed class BSPrim : BSPhysObject } } } - public override void CrossingFailure() { return; } + public override bool IsSelected + { + get { return _isSelected; } + } - // link me to the specified parent - public override void link(PhysicsActor obj) { - BSPrim parent = obj as BSPrim; - if (parent != null) + public override void CrossingFailure() + { + CrossingFailures++; + if (CrossingFailures > BSParam.CrossingFailuresBeforeOutOfBounds) { - BSPhysObject parentBefore = Linkset.LinksetRoot; - int childrenBefore = Linkset.NumberOfChildren; - - Linkset = parent.Linkset.AddMeToLinkset(this); - - DetailLog("{0},BSPrim.link,call,parentBefore={1}, childrenBefore=={2}, parentAfter={3}, childrenAfter={4}", - LocalID, parentBefore.LocalID, childrenBefore, Linkset.LinksetRoot.LocalID, Linkset.NumberOfChildren); + base.RaiseOutOfBounds(RawPosition); + } + else if (CrossingFailures == BSParam.CrossingFailuresBeforeOutOfBounds) + { + m_log.WarnFormat("{0} Too many crossing failures for {1}", LogHeader, Name); } return; } + // link me to the specified parent + public override void link(PhysicsActor obj) { + } + // delink me from my linkset public override void delink() { - // TODO: decide if this parent checking needs to happen at taint time - // Race condition here: if link() and delink() in same simulation tick, the delink will not happen - - BSPhysObject parentBefore = Linkset.LinksetRoot; - int childrenBefore = Linkset.NumberOfChildren; - - Linkset = Linkset.RemoveMeFromLinkset(this); - - DetailLog("{0},BSPrim.delink,parentBefore={1},childrenBefore={2},parentAfter={3},childrenAfter={4}, ", - LocalID, parentBefore.LocalID, childrenBefore, Linkset.LinksetRoot.LocalID, Linkset.NumberOfChildren); - return; } // Set motion values to zero. @@ -245,28 +221,28 @@ public sealed class BSPrim : BSPhysObject // Called at taint time! public override void ZeroMotion(bool inTaintTime) { - _velocity = OMV.Vector3.Zero; + RawVelocity = OMV.Vector3.Zero; _acceleration = OMV.Vector3.Zero; _rotationalVelocity = OMV.Vector3.Zero; // Zero some other properties in the physics engine - PhysicsScene.TaintedObject(inTaintTime, "BSPrim.ZeroMotion", delegate() + PhysScene.TaintedObject(inTaintTime, "BSPrim.ZeroMotion", delegate() { if (PhysBody.HasPhysicalBody) - PhysicsScene.PE.ClearAllForces(PhysBody); + PhysScene.PE.ClearAllForces(PhysBody); }); } public override void ZeroAngularMotion(bool inTaintTime) { _rotationalVelocity = OMV.Vector3.Zero; // Zero some other properties in the physics engine - PhysicsScene.TaintedObject(inTaintTime, "BSPrim.ZeroMotion", delegate() + PhysScene.TaintedObject(inTaintTime, "BSPrim.ZeroMotion", delegate() { // DetailLog("{0},BSPrim.ZeroAngularMotion,call,rotVel={1}", LocalID, _rotationalVelocity); if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.SetInterpolationAngularVelocity(PhysBody, _rotationalVelocity); - PhysicsScene.PE.SetAngularVelocity(PhysBody, _rotationalVelocity); + PhysScene.PE.SetInterpolationAngularVelocity(PhysBody, _rotationalVelocity); + PhysScene.PE.SetAngularVelocity(PhysBody, _rotationalVelocity); } }); } @@ -274,6 +250,25 @@ public sealed class BSPrim : BSPhysObject public override void LockAngularMotion(OMV.Vector3 axis) { DetailLog("{0},BSPrim.LockAngularMotion,call,axis={1}", LocalID, axis); + + // "1" means free, "0" means locked + OMV.Vector3 locking = LockedAxisFree; + if (axis.X != 1) locking.X = 0f; + if (axis.Y != 1) locking.Y = 0f; + if (axis.Z != 1) locking.Z = 0f; + LockedAngularAxis = locking; + + EnableActor(LockedAngularAxis != LockedAxisFree, LockedAxisActorName, delegate() + { + return new BSActorLockAxis(PhysScene, this, LockedAxisActorName); + }); + + // Update parameters so the new actor's Refresh() action is called at the right time. + PhysScene.TaintedObject("BSPrim.LockAngularMotion", delegate() + { + UpdatePhysicalParameters(); + }); + return; } @@ -284,15 +279,8 @@ public sealed class BSPrim : BSPhysObject } public override OMV.Vector3 Position { get { - /* NOTE: this refetch is not necessary. The simulator knows about linkset children - * and does not fetch this position info for children. Thus this is commented out. - // child prims move around based on their parent. Need to get the latest location - if (!Linkset.IsRoot(this)) - _position = Linkset.PositionGet(this); - */ - // don't do the GetObjectPosition for root elements because this function is called a zillion times. - // _position = PhysicsScene.PE.GetObjectPosition2(PhysicsScene.World, BSBody); + // _position = ForcePosition; return _position; } set { @@ -306,26 +294,24 @@ public sealed class BSPrim : BSPhysObject _position = value; PositionSanityCheck(false); - // A linkset might need to know if a component information changed. - Linkset.UpdateProperties(this, false); - - PhysicsScene.TaintedObject("BSPrim.setPosition", delegate() + PhysScene.TaintedObject("BSPrim.setPosition", delegate() { DetailLog("{0},BSPrim.SetPosition,taint,pos={1},orient={2}", LocalID, _position, _orientation); ForcePosition = _position; }); } } + public override OMV.Vector3 ForcePosition { get { - _position = PhysicsScene.PE.GetPosition(PhysBody); + _position = PhysScene.PE.GetPosition(PhysBody); return _position; } set { _position = value; if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation); + PhysScene.PE.SetTranslation(PhysBody, _position, _orientation); ActivateIfPhysical(false); } } @@ -338,7 +324,11 @@ public sealed class BSPrim : BSPhysObject { bool ret = false; - if (!PhysicsScene.TerrainManager.IsWithinKnownTerrain(_position)) + // We don't care where non-physical items are placed + if (!IsPhysicallyActive) + return ret; + + if (!PhysScene.TerrainManager.IsWithinKnownTerrain(RawPosition)) { // The physical object is out of the known/simulated area. // Upper levels of code will handle the transition to other areas so, for @@ -346,39 +336,74 @@ public sealed class BSPrim : BSPhysObject return ret; } - float terrainHeight = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(_position); + float terrainHeight = PhysScene.TerrainManager.GetTerrainHeightAtXYZ(RawPosition); OMV.Vector3 upForce = OMV.Vector3.Zero; - if (RawPosition.Z < terrainHeight) + float approxSize = Math.Max(Size.X, Math.Max(Size.Y, Size.Z)); + if ((RawPosition.Z + approxSize / 2f) < terrainHeight) { - DetailLog("{0},BSPrim.PositionAdjustUnderGround,call,pos={1},terrain={2}", LocalID, _position, terrainHeight); + DetailLog("{0},BSPrim.PositionAdjustUnderGround,call,pos={1},terrain={2}", LocalID, RawPosition, terrainHeight); float targetHeight = terrainHeight + (Size.Z / 2f); - // Upforce proportional to the distance away from the terrain. Correct the error in 1 sec. - upForce.Z = (terrainHeight - RawPosition.Z) * 1f; + // If the object is below ground it just has to be moved up because pushing will + // not get it through the terrain + _position.Z = targetHeight; + if (inTaintTime) + { + ForcePosition = _position; + } + // If we are throwing the object around, zero its other forces + ZeroMotion(inTaintTime); ret = true; } if ((CurrentCollisionFlags & CollisionFlags.BS_FLOATS_ON_WATER) != 0) { - float waterHeight = PhysicsScene.TerrainManager.GetWaterLevelAtXYZ(_position); + float waterHeight = PhysScene.TerrainManager.GetWaterLevelAtXYZ(_position); // TODO: a floating motor so object will bob in the water if (Math.Abs(RawPosition.Z - waterHeight) > 0.1f) { // Upforce proportional to the distance away from the water. Correct the error in 1 sec. upForce.Z = (waterHeight - RawPosition.Z) * 1f; + + // Apply upforce and overcome gravity. + OMV.Vector3 correctionForce = upForce - PhysScene.DefaultGravity; + DetailLog("{0},BSPrim.PositionSanityCheck,applyForce,pos={1},upForce={2},correctionForce={3}", LocalID, _position, upForce, correctionForce); + AddForce(correctionForce, false, inTaintTime); ret = true; } } - // The above code computes a force to apply to correct any out-of-bounds problems. Apply same. - // TODO: This should be intergrated with a geneal physics action mechanism. - // TODO: This should be moderated with PID'ness. - if (ret) + return ret; + } + + // Occasionally things will fly off and really get lost. + // Find the wanderers and bring them back. + // Return 'true' if some parameter need some sanity. + private bool ExtremeSanityCheck(bool inTaintTime) + { + bool ret = false; + + uint wayOutThere = Constants.RegionSize * Constants.RegionSize; + // There have been instances of objects getting thrown way out of bounds and crashing + // the border crossing code. + if ( _position.X < -Constants.RegionSize || _position.X > wayOutThere + || _position.Y < -Constants.RegionSize || _position.Y > wayOutThere + || _position.Z < -Constants.RegionSize || _position.Z > wayOutThere) { - // Apply upforce and overcome gravity. - OMV.Vector3 correctionForce = upForce - PhysicsScene.DefaultGravity; - DetailLog("{0},BSPrim.PositionSanityCheck,applyForce,pos={1},upForce={2},correctionForce={3}", LocalID, _position, upForce, correctionForce); - AddForce(correctionForce, false, inTaintTime); + _position = new OMV.Vector3(10, 10, 50); + ZeroMotion(inTaintTime); + ret = true; } + if (RawVelocity.LengthSquared() > BSParam.MaxLinearVelocity) + { + RawVelocity = Util.ClampV(RawVelocity, BSParam.MaxLinearVelocity); + ret = true; + } + if (_rotationalVelocity.LengthSquared() > BSParam.MaxAngularVelocitySquared) + { + _rotationalVelocity = Util.ClampV(_rotationalVelocity, BSParam.MaxAngularVelocity); + ret = true; + } + return ret; } @@ -387,72 +412,69 @@ public sealed class BSPrim : BSPhysObject // If the simulator cares about the mass of the linkset, it will sum it itself. public override float Mass { - get - { - return _mass; - } + get { return _mass; } + } + // TotalMass returns the mass of the large object the prim may be in (overridden by linkset code) + public virtual float TotalMass + { + get { return _mass; } } - // used when we only want this prim's mass and not the linkset thing - public override float RawMass { + public override float RawMass { get { return _mass; } } // Set the physical mass to the passed mass. // Note that this does not change _mass! public override void UpdatePhysicalMassProperties(float physMass, bool inWorld) { - if (PhysBody.HasPhysicalBody) + if (PhysBody.HasPhysicalBody && PhysShape.HasPhysicalShape) { if (IsStatic) { - PhysicsScene.PE.SetGravity(PhysBody, PhysicsScene.DefaultGravity); + PhysScene.PE.SetGravity(PhysBody, PhysScene.DefaultGravity); Inertia = OMV.Vector3.Zero; - PhysicsScene.PE.SetMassProps(PhysBody, 0f, Inertia); - PhysicsScene.PE.UpdateInertiaTensor(PhysBody); + PhysScene.PE.SetMassProps(PhysBody, 0f, Inertia); + PhysScene.PE.UpdateInertiaTensor(PhysBody); } else { - OMV.Vector3 grav = ComputeGravity(); - if (inWorld) { // Changing interesting properties doesn't change proxy and collision cache // information. The Bullet solution is to re-add the object to the world // after parameters are changed. - PhysicsScene.PE.RemoveObjectFromWorld(PhysicsScene.World, PhysBody); + PhysScene.PE.RemoveObjectFromWorld(PhysScene.World, PhysBody); } // The computation of mass props requires gravity to be set on the object. - PhysicsScene.PE.SetGravity(PhysBody, grav); + Gravity = ComputeGravity(Buoyancy); + PhysScene.PE.SetGravity(PhysBody, Gravity); - Inertia = PhysicsScene.PE.CalculateLocalInertia(PhysShape, physMass); - PhysicsScene.PE.SetMassProps(PhysBody, physMass, Inertia); - PhysicsScene.PE.UpdateInertiaTensor(PhysBody); + Inertia = PhysScene.PE.CalculateLocalInertia(PhysShape.physShapeInfo, physMass); + PhysScene.PE.SetMassProps(PhysBody, physMass, Inertia); + PhysScene.PE.UpdateInertiaTensor(PhysBody); - // center of mass is at the zero of the object - // DEBUG DEBUG PhysicsScene.PE.SetCenterOfMassByPosRot(PhysBody, ForcePosition, ForceOrientation); - DetailLog("{0},BSPrim.UpdateMassProperties,mass={1},localInertia={2},grav={3},inWorld={4}", LocalID, physMass, Inertia, grav, inWorld); + DetailLog("{0},BSPrim.UpdateMassProperties,mass={1},localInertia={2},grav={3},inWorld={4}", + LocalID, physMass, Inertia, Gravity, inWorld); if (inWorld) { AddObjectToPhysicalWorld(); } - - // Must set gravity after it has been added to the world because, for unknown reasons, - // adding the object resets the object's gravity to world gravity - PhysicsScene.PE.SetGravity(PhysBody, grav); - } } } // Return what gravity should be set to this very moment - private OMV.Vector3 ComputeGravity() + public OMV.Vector3 ComputeGravity(float buoyancy) { - OMV.Vector3 ret = PhysicsScene.DefaultGravity; + OMV.Vector3 ret = PhysScene.DefaultGravity; if (!IsStatic) - ret *= (1f - Buoyancy); + { + ret *= (1f - buoyancy); + ret *= GravModifier; + } return ret; } @@ -460,93 +482,70 @@ public sealed class BSPrim : BSPhysObject // Is this used? public override OMV.Vector3 CenterOfMass { - get { return Linkset.CenterOfMass; } + get { return RawPosition; } } // Is this used? public override OMV.Vector3 GeometricCenter { - get { return Linkset.GeometricCenter; } + get { return RawPosition; } } public override OMV.Vector3 Force { - get { return _force; } + get { return RawForce; } set { - _force = value; - if (_force != OMV.Vector3.Zero) + RawForce = value; + EnableActor(RawForce != OMV.Vector3.Zero, SetForceActorName, delegate() { - // If the force is non-zero, it must be reapplied each tick because - // Bullet clears the forces applied last frame. - RegisterPreStepAction("BSPrim.setForce", LocalID, - delegate(float timeStep) - { - DetailLog("{0},BSPrim.setForce,preStep,force={1}", LocalID, _force); - if (PhysBody.HasPhysicalBody) - { - PhysicsScene.PE.ApplyCentralForce(PhysBody, _force); - ActivateIfPhysical(false); - } - } - ); - } - else - { - UnRegisterPreStepAction("BSPrim.setForce", LocalID); - } + return new BSActorSetForce(PhysScene, this, SetForceActorName); + }); } } public override int VehicleType { get { - return (int)_vehicle.Type; // if we are a vehicle, return that type + return (int)VehicleActor.Type; } set { Vehicle type = (Vehicle)value; - PhysicsScene.TaintedObject("setVehicleType", delegate() + PhysScene.TaintedObject("setVehicleType", delegate() { - // Done at taint time so we're sure the physics engine is not using the variables - // Vehicle code changes the parameters for this vehicle type. - _vehicle.ProcessTypeChange(type); + ZeroMotion(true /* inTaintTime */); + VehicleActor.ProcessTypeChange(type); ActivateIfPhysical(false); - - // If an active vehicle, register the vehicle code to be called before each step - if (_vehicle.Type == Vehicle.TYPE_NONE) - UnRegisterPreStepAction("BSPrim.Vehicle", LocalID); - else - RegisterPreStepAction("BSPrim.Vehicle", LocalID, _vehicle.Step); }); } } public override void VehicleFloatParam(int param, float value) { - PhysicsScene.TaintedObject("BSPrim.VehicleFloatParam", delegate() + PhysScene.TaintedObject("BSPrim.VehicleFloatParam", delegate() { - _vehicle.ProcessFloatVehicleParam((Vehicle)param, value); + VehicleActor.ProcessFloatVehicleParam((Vehicle)param, value); ActivateIfPhysical(false); }); } public override void VehicleVectorParam(int param, OMV.Vector3 value) { - PhysicsScene.TaintedObject("BSPrim.VehicleVectorParam", delegate() + PhysScene.TaintedObject("BSPrim.VehicleVectorParam", delegate() { - _vehicle.ProcessVectorVehicleParam((Vehicle)param, value); + VehicleActor.ProcessVectorVehicleParam((Vehicle)param, value); ActivateIfPhysical(false); }); } public override void VehicleRotationParam(int param, OMV.Quaternion rotation) { - PhysicsScene.TaintedObject("BSPrim.VehicleRotationParam", delegate() + PhysScene.TaintedObject("BSPrim.VehicleRotationParam", delegate() { - _vehicle.ProcessRotationVehicleParam((Vehicle)param, rotation); + VehicleActor.ProcessRotationVehicleParam((Vehicle)param, rotation); ActivateIfPhysical(false); }); } public override void VehicleFlags(int param, bool remove) { - PhysicsScene.TaintedObject("BSPrim.VehicleFlags", delegate() + PhysScene.TaintedObject("BSPrim.VehicleFlags", delegate() { - _vehicle.ProcessVehicleFlags(param, remove); + VehicleActor.ProcessVehicleFlags(param, remove); }); } @@ -556,7 +555,7 @@ public sealed class BSPrim : BSPhysObject if (_isVolumeDetect != newValue) { _isVolumeDetect = newValue; - PhysicsScene.TaintedObject("BSPrim.SetVolumeDetect", delegate() + PhysScene.TaintedObject("BSPrim.SetVolumeDetect", delegate() { // DetailLog("{0},setVolumeDetect,taint,volDetect={1}", LocalID, _isVolumeDetect); SetObjectDynamic(true); @@ -564,56 +563,110 @@ public sealed class BSPrim : BSPhysObject } return; } - public override OMV.Vector3 Velocity { - get { return _velocity; } - set { - _velocity = value; - PhysicsScene.TaintedObject("BSPrim.setVelocity", delegate() + public override void SetMaterial(int material) + { + base.SetMaterial(material); + PhysScene.TaintedObject("BSPrim.SetMaterial", delegate() + { + UpdatePhysicalParameters(); + }); + } + public override float Friction + { + get { return base.Friction; } + set + { + if (base.Friction != value) { - // DetailLog("{0},BSPrim.SetVelocity,taint,vel={1}", LocalID, _velocity); - ForceVelocity = _velocity; + base.Friction = value; + PhysScene.TaintedObject("BSPrim.setFriction", delegate() + { + UpdatePhysicalParameters(); + }); + } + } + } + public override float Restitution + { + get { return base.Restitution; } + set + { + if (base.Restitution != value) + { + base.Restitution = value; + PhysScene.TaintedObject("BSPrim.setRestitution", delegate() + { + UpdatePhysicalParameters(); + }); + } + } + } + // The simulator/viewer keep density as 100kg/m3. + // Remember to use BSParam.DensityScaleFactor to create the physical density. + public override float Density + { + get { return base.Density; } + set + { + if (base.Density != value) + { + base.Density = value; + PhysScene.TaintedObject("BSPrim.setDensity", delegate() + { + UpdatePhysicalParameters(); + }); + } + } + } + public override float GravModifier + { + get { return base.GravModifier; } + set + { + if (base.GravModifier != value) + { + base.GravModifier = value; + PhysScene.TaintedObject("BSPrim.setGravityModifier", delegate() + { + UpdatePhysicalParameters(); + }); + } + } + } + public override OMV.Vector3 Velocity { + get { return RawVelocity; } + set { + RawVelocity = value; + PhysScene.TaintedObject("BSPrim.setVelocity", delegate() + { + // DetailLog("{0},BSPrim.SetVelocity,taint,vel={1}", LocalID, RawVelocity); + ForceVelocity = RawVelocity; }); } } public override OMV.Vector3 ForceVelocity { - get { return _velocity; } + get { return RawVelocity; } set { - PhysicsScene.AssertInTaintTime("BSPrim.ForceVelocity"); + PhysScene.AssertInTaintTime("BSPrim.ForceVelocity"); - _velocity = value; + RawVelocity = Util.ClampV(value, BSParam.MaxLinearVelocity); if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.SetLinearVelocity(PhysBody, _velocity); + DetailLog("{0},BSPrim.ForceVelocity,taint,vel={1}", LocalID, RawVelocity); + PhysScene.PE.SetLinearVelocity(PhysBody, RawVelocity); ActivateIfPhysical(false); } } } public override OMV.Vector3 Torque { - get { return _torque; } + get { return RawTorque; } set { - _torque = value; - if (_torque != OMV.Vector3.Zero) + RawTorque = value; + EnableActor(RawTorque != OMV.Vector3.Zero, SetTorqueActorName, delegate() { - // If the torque is non-zero, it must be reapplied each tick because - // Bullet clears the forces applied last frame. - RegisterPreStepAction("BSPrim.setTorque", LocalID, - delegate(float timeStep) - { - if (PhysBody.HasPhysicalBody) - AddAngularForce(_torque, false, true); - } - ); - } - else - { - UnRegisterPreStepAction("BSPrim.setTorque", LocalID); - } - // DetailLog("{0},BSPrim.SetTorque,call,torque={1}", LocalID, _torque); - } - } - public override float CollisionScore { - get { return _collisionScore; } - set { _collisionScore = value; + return new BSActorSetTorque(PhysScene, this, SetTorqueActorName); + }); + DetailLog("{0},BSPrim.SetTorque,call,torque={1}", LocalID, RawTorque); } } public override OMV.Vector3 Acceleration { @@ -627,14 +680,6 @@ public sealed class BSPrim : BSPhysObject } public override OMV.Quaternion Orientation { get { - /* NOTE: this refetch is not necessary. The simulator knows about linkset children - * and does not fetch this position info for children. Thus this is commented out. - // Children move around because tied to parent. Get a fresh value. - if (!Linkset.IsRoot(this)) - { - _orientation = Linkset.OrientationGet(this); - } - */ return _orientation; } set { @@ -642,17 +687,9 @@ public sealed class BSPrim : BSPhysObject return; _orientation = value; - // A linkset might need to know if a component information changed. - Linkset.UpdateProperties(this, false); - - PhysicsScene.TaintedObject("BSPrim.setOrientation", delegate() + PhysScene.TaintedObject("BSPrim.setOrientation", delegate() { - if (PhysBody.HasPhysicalBody) - { - // _position = PhysicsScene.PE.GetObjectPosition(PhysicsScene.World, BSBody); - // DetailLog("{0},BSPrim.setOrientation,taint,pos={1},orient={2}", LocalID, _position, _orientation); - PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation); - } + ForceOrientation = _orientation; }); } } @@ -661,13 +698,14 @@ public sealed class BSPrim : BSPhysObject { get { - _orientation = PhysicsScene.PE.GetOrientation(PhysBody); + _orientation = PhysScene.PE.GetOrientation(PhysBody); return _orientation; } set { _orientation = value; - PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation); + if (PhysBody.HasPhysicalBody) + PhysScene.PE.SetTranslation(PhysBody, _position, _orientation); } } public override int PhysicsActorType { @@ -680,12 +718,13 @@ public sealed class BSPrim : BSPhysObject if (_isPhysical != value) { _isPhysical = value; - PhysicsScene.TaintedObject("BSPrim.setIsPhysical", delegate() + PhysScene.TaintedObject("BSPrim.setIsPhysical", delegate() { DetailLog("{0},setIsPhysical,taint,isPhys={1}", LocalID, _isPhysical); SetObjectDynamic(true); // whether phys-to-static or static-to-phys, the object is not moving. ZeroMotion(true); + }); } } @@ -703,6 +742,12 @@ public sealed class BSPrim : BSPhysObject get { return !IsPhantom && !_isVolumeDetect; } } + // The object is moving and is actively being dynamic in the physical world + public override bool IsPhysicallyActive + { + get { return !_isSelected && IsPhysical; } + } + // Make gravity work if the object is physical and not selected // Called at taint-time!! private void SetObjectDynamic(bool forceRebuild) @@ -717,19 +762,24 @@ public sealed class BSPrim : BSPhysObject // isSolid: other objects bounce off of this object // isVolumeDetect: other objects pass through but can generate collisions // collisionEvents: whether this object returns collision events - private void UpdatePhysicalParameters() + public virtual void UpdatePhysicalParameters() { - // DetailLog("{0},BSPrim.UpdatePhysicalParameters,entry,body={1},shape={2}", LocalID, BSBody, BSShape); + if (!PhysBody.HasPhysicalBody) + { + // This would only happen if updates are called for during initialization when the body is not set up yet. + // DetailLog("{0},BSPrim.UpdatePhysicalParameters,taint,calledWithNoPhysBody", LocalID); + return; + } // Mangling all the physical properties requires the object not be in the physical world. // This is a NOOP if the object is not in the world (BulletSim and Bullet ignore objects not found). - PhysicsScene.PE.RemoveObjectFromWorld(PhysicsScene.World, PhysBody); + PhysScene.PE.RemoveObjectFromWorld(PhysScene.World, PhysBody); // Set up the object physicalness (does gravity and collisions move this object) MakeDynamic(IsStatic); // Update vehicle specific parameters (after MakeDynamic() so can change physical parameters) - _vehicle.Refresh(); + PhysicalActors.Refresh(); // Arrange for collision events if the simulator wants them EnableCollisions(SubscribedEvents()); @@ -740,16 +790,11 @@ public sealed class BSPrim : BSPhysObject AddObjectToPhysicalWorld(); // Rebuild its shape - PhysicsScene.PE.UpdateSingleAabb(PhysicsScene.World, PhysBody); - - // Recompute any linkset parameters. - // When going from non-physical to physical, this re-enables the constraints that - // had been automatically disabled when the mass was set to zero. - // For compound based linksets, this enables and disables interactions of the children. - Linkset.Refresh(this); + PhysScene.PE.UpdateSingleAabb(PhysScene.World, PhysBody); DetailLog("{0},BSPrim.UpdatePhysicalParameters,taintExit,static={1},solid={2},mass={3},collide={4},cf={5:X},cType={6},body={7},shape={8}", - LocalID, IsStatic, IsSolid, Mass, SubscribedEvents(), CurrentCollisionFlags, PhysBody.collisionType, PhysBody, PhysShape); + LocalID, IsStatic, IsSolid, Mass, SubscribedEvents(), + CurrentCollisionFlags, PhysBody.collisionType, PhysBody, PhysShape); } // "Making dynamic" means changing to and from static. @@ -757,59 +802,55 @@ public sealed class BSPrim : BSPhysObject // When dynamic, the object can fall and be pushed by others. // This is independent of its 'solidness' which controls what passes through // this object and what interacts with it. - private void MakeDynamic(bool makeStatic) + protected virtual void MakeDynamic(bool makeStatic) { if (makeStatic) { // Become a Bullet 'static' object type - CurrentCollisionFlags = PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.CF_STATIC_OBJECT); + CurrentCollisionFlags = PhysScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.CF_STATIC_OBJECT); // Stop all movement ZeroMotion(true); // Set various physical properties so other object interact properly - MaterialAttributes matAttrib = BSMaterials.GetAttributes(Material, false); - PhysicsScene.PE.SetFriction(PhysBody, matAttrib.friction); - PhysicsScene.PE.SetRestitution(PhysBody, matAttrib.restitution); + PhysScene.PE.SetFriction(PhysBody, Friction); + PhysScene.PE.SetRestitution(PhysBody, Restitution); + PhysScene.PE.SetContactProcessingThreshold(PhysBody, BSParam.ContactProcessingThreshold); // Mass is zero which disables a bunch of physics stuff in Bullet UpdatePhysicalMassProperties(0f, false); // Set collision detection parameters if (BSParam.CcdMotionThreshold > 0f) { - PhysicsScene.PE.SetCcdMotionThreshold(PhysBody, BSParam.CcdMotionThreshold); - PhysicsScene.PE.SetCcdSweptSphereRadius(PhysBody, BSParam.CcdSweptSphereRadius); + PhysScene.PE.SetCcdMotionThreshold(PhysBody, BSParam.CcdMotionThreshold); + PhysScene.PE.SetCcdSweptSphereRadius(PhysBody, BSParam.CcdSweptSphereRadius); } // The activation state is 'disabled' so Bullet will not try to act on it. // PhysicsScene.PE.ForceActivationState(PhysBody, ActivationState.DISABLE_SIMULATION); // Start it out sleeping and physical actions could wake it up. - PhysicsScene.PE.ForceActivationState(PhysBody, ActivationState.ISLAND_SLEEPING); + PhysScene.PE.ForceActivationState(PhysBody, ActivationState.ISLAND_SLEEPING); // This collides like a static object PhysBody.collisionType = CollisionType.Static; - - // There can be special things needed for implementing linksets - Linkset.MakeStatic(this); } else { // Not a Bullet static object - CurrentCollisionFlags = PhysicsScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.CF_STATIC_OBJECT); + CurrentCollisionFlags = PhysScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.CF_STATIC_OBJECT); // Set various physical properties so other object interact properly - MaterialAttributes matAttrib = BSMaterials.GetAttributes(Material, true); - PhysicsScene.PE.SetFriction(PhysBody, matAttrib.friction); - PhysicsScene.PE.SetRestitution(PhysBody, matAttrib.restitution); + PhysScene.PE.SetFriction(PhysBody, Friction); + PhysScene.PE.SetRestitution(PhysBody, Restitution); + // DetailLog("{0},BSPrim.MakeDynamic,frict={1},rest={2}", LocalID, Friction, Restitution); // per http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=3382 // Since this can be called multiple times, only zero forces when becoming physical // PhysicsScene.PE.ClearAllForces(BSBody); // For good measure, make sure the transform is set through to the motion state - PhysicsScene.PE.SetTranslation(PhysBody, _position, _orientation); - - // Center of mass is at the center of the object - // DEBUG DEBUG PhysicsScene.PE.SetCenterOfMassByPosRot(Linkset.LinksetRoot.PhysBody, _position, _orientation); + ForcePosition = _position; + ForceVelocity = RawVelocity; + ForceRotationalVelocity = _rotationalVelocity; // A dynamic object has mass UpdatePhysicalMassProperties(RawMass, false); @@ -817,25 +858,22 @@ public sealed class BSPrim : BSPhysObject // Set collision detection parameters if (BSParam.CcdMotionThreshold > 0f) { - PhysicsScene.PE.SetCcdMotionThreshold(PhysBody, BSParam.CcdMotionThreshold); - PhysicsScene.PE.SetCcdSweptSphereRadius(PhysBody, BSParam.CcdSweptSphereRadius); + PhysScene.PE.SetCcdMotionThreshold(PhysBody, BSParam.CcdMotionThreshold); + PhysScene.PE.SetCcdSweptSphereRadius(PhysBody, BSParam.CcdSweptSphereRadius); } // Various values for simulation limits - PhysicsScene.PE.SetDamping(PhysBody, BSParam.LinearDamping, BSParam.AngularDamping); - PhysicsScene.PE.SetDeactivationTime(PhysBody, BSParam.DeactivationTime); - PhysicsScene.PE.SetSleepingThresholds(PhysBody, BSParam.LinearSleepingThreshold, BSParam.AngularSleepingThreshold); - PhysicsScene.PE.SetContactProcessingThreshold(PhysBody, BSParam.ContactProcessingThreshold); + PhysScene.PE.SetDamping(PhysBody, BSParam.LinearDamping, BSParam.AngularDamping); + PhysScene.PE.SetDeactivationTime(PhysBody, BSParam.DeactivationTime); + PhysScene.PE.SetSleepingThresholds(PhysBody, BSParam.LinearSleepingThreshold, BSParam.AngularSleepingThreshold); + PhysScene.PE.SetContactProcessingThreshold(PhysBody, BSParam.ContactProcessingThreshold); // This collides like an object. PhysBody.collisionType = CollisionType.Dynamic; // Force activation of the object so Bullet will act on it. // Must do the ForceActivationState2() to overcome the DISABLE_SIMULATION from static objects. - PhysicsScene.PE.ForceActivationState(PhysBody, ActivationState.ACTIVE_TAG); - - // There might be special things needed for implementing linksets. - Linkset.MakeDynamic(this); + PhysScene.PE.ForceActivationState(PhysBody, ActivationState.ACTIVE_TAG); } } @@ -845,7 +883,7 @@ public sealed class BSPrim : BSPhysObject // the functions after this one set up the state of a possibly newly created collision body. private void MakeSolid(bool makeSolid) { - CollisionObjectTypes bodyType = (CollisionObjectTypes)PhysicsScene.PE.GetBodyType(PhysBody); + CollisionObjectTypes bodyType = (CollisionObjectTypes)PhysScene.PE.GetBodyType(PhysBody); if (makeSolid) { // Verify the previous code created the correct shape for this type of thing. @@ -853,7 +891,7 @@ public sealed class BSPrim : BSPhysObject { m_log.ErrorFormat("{0} MakeSolid: physical body of wrong type for solidity. id={1}, type={2}", LogHeader, LocalID, bodyType); } - CurrentCollisionFlags = PhysicsScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE); + CurrentCollisionFlags = PhysScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE); } else { @@ -861,32 +899,23 @@ public sealed class BSPrim : BSPhysObject { m_log.ErrorFormat("{0} MakeSolid: physical body of wrong type for non-solidness. id={1}, type={2}", LogHeader, LocalID, bodyType); } - CurrentCollisionFlags = PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE); + CurrentCollisionFlags = PhysScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.CF_NO_CONTACT_RESPONSE); // Change collision info from a static object to a ghosty collision object PhysBody.collisionType = CollisionType.VolumeDetect; } } - // Enable physical actions. Bullet will keep sleeping non-moving physical objects so - // they need waking up when parameters are changed. - // Called in taint-time!! - private void ActivateIfPhysical(bool forceIt) - { - if (IsPhysical && PhysBody.HasPhysicalBody) - PhysicsScene.PE.Activate(PhysBody, forceIt); - } - // Turn on or off the flag controlling whether collision events are returned to the simulator. private void EnableCollisions(bool wantsCollisionEvents) { if (wantsCollisionEvents) { - CurrentCollisionFlags = PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + CurrentCollisionFlags = PhysScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); } else { - CurrentCollisionFlags = PhysicsScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + CurrentCollisionFlags = PhysScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); } } @@ -897,12 +926,12 @@ public sealed class BSPrim : BSPhysObject { if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.AddObjectToWorld(PhysicsScene.World, PhysBody); + PhysScene.PE.AddObjectToWorld(PhysScene.World, PhysBody); } else { m_log.ErrorFormat("{0} Attempt to add physical object without body. id={1}", LogHeader, LocalID); - DetailLog("{0},BSPrim.UpdatePhysicalParameters,addObjectWithoutBody,cType={1}", LocalID, PhysBody.collisionType); + DetailLog("{0},BSPrim.AddObjectToPhysicalWorld,addObjectWithoutBody,cType={1}", LocalID, PhysBody.collisionType); } } @@ -932,12 +961,12 @@ public sealed class BSPrim : BSPhysObject public override bool FloatOnWater { set { _floatOnWater = value; - PhysicsScene.TaintedObject("BSPrim.setFloatOnWater", delegate() + PhysScene.TaintedObject("BSPrim.setFloatOnWater", delegate() { if (_floatOnWater) - CurrentCollisionFlags = PhysicsScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER); + CurrentCollisionFlags = PhysScene.PE.AddToCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER); else - CurrentCollisionFlags = PhysicsScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER); + CurrentCollisionFlags = PhysScene.PE.RemoveFromCollisionFlags(PhysBody, CollisionFlags.BS_FLOATS_ON_WATER); }); } } @@ -947,10 +976,10 @@ public sealed class BSPrim : BSPhysObject } set { _rotationalVelocity = value; + Util.ClampV(_rotationalVelocity, BSParam.MaxAngularVelocity); // m_log.DebugFormat("{0}: RotationalVelocity={1}", LogHeader, _rotationalVelocity); - PhysicsScene.TaintedObject("BSPrim.setRotationalVelocity", delegate() + PhysScene.TaintedObject("BSPrim.setRotationalVelocity", delegate() { - DetailLog("{0},BSPrim.SetRotationalVel,taint,rotvel={1}", LocalID, _rotationalVelocity); ForceRotationalVelocity = _rotationalVelocity; }); } @@ -960,10 +989,12 @@ public sealed class BSPrim : BSPhysObject return _rotationalVelocity; } set { - _rotationalVelocity = value; + _rotationalVelocity = Util.ClampV(value, BSParam.MaxAngularVelocity); if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.SetAngularVelocity(PhysBody, _rotationalVelocity); + DetailLog("{0},BSPrim.ForceRotationalVel,taint,rotvel={1}", LocalID, _rotationalVelocity); + PhysScene.PE.SetAngularVelocity(PhysBody, _rotationalVelocity); + // PhysicsScene.PE.SetInterpolationAngularVelocity(PhysBody, _rotationalVelocity); ActivateIfPhysical(false); } } @@ -978,7 +1009,7 @@ public sealed class BSPrim : BSPhysObject get { return _buoyancy; } set { _buoyancy = value; - PhysicsScene.TaintedObject("BSPrim.setBuoyancy", delegate() + PhysScene.TaintedObject("BSPrim.setBuoyancy", delegate() { ForceBuoyancy = _buoyancy; }); @@ -990,96 +1021,113 @@ public sealed class BSPrim : BSPhysObject _buoyancy = value; // DetailLog("{0},BSPrim.setForceBuoyancy,taint,buoy={1}", LocalID, _buoyancy); // Force the recalculation of the various inertia,etc variables in the object - DetailLog("{0},BSPrim.ForceBuoyancy,buoy={1},mass={2}", LocalID, _buoyancy, _mass); - UpdatePhysicalMassProperties(_mass, true); + UpdatePhysicalMassProperties(RawMass, true); + DetailLog("{0},BSPrim.ForceBuoyancy,buoy={1},mass={2},grav={3}", LocalID, _buoyancy, RawMass, Gravity); ActivateIfPhysical(false); } } - // Used for MoveTo - public override OMV.Vector3 PIDTarget { - set { _PIDTarget = value; } - } - public override float PIDTau { - set { _PIDTau = value; } - } public override bool PIDActive { - set { _usePID = value; } + set { + base.MoveToTargetActive = value; + EnableActor(MoveToTargetActive, MoveToTargetActorName, delegate() + { + return new BSActorMoveToTarget(PhysScene, this, MoveToTargetActorName); + }); + } } // Used for llSetHoverHeight and maybe vehicle height // Hover Height will override MoveTo target's Z public override bool PIDHoverActive { - set { _useHoverPID = value; } + set { + base.HoverActive = value; + EnableActor(HoverActive, HoverActorName, delegate() + { + return new BSActorHover(PhysScene, this, HoverActorName); + }); + } } - public override float PIDHoverHeight { - set { _PIDHoverHeight = value; } - } - public override PIDHoverType PIDHoverType { - set { _PIDHoverType = value; } - } - public override float PIDHoverTau { - set { _PIDHoverTao = value; } - } - - // For RotLookAt - public override OMV.Quaternion APIDTarget { set { return; } } - public override bool APIDActive { set { return; } } - public override float APIDStrength { set { return; } } - public override float APIDDamping { set { return; } } public override void AddForce(OMV.Vector3 force, bool pushforce) { + // Per documentation, max force is limited. + OMV.Vector3 addForce = Util.ClampV(force, BSParam.MaxAddForceMagnitude); + // Since this force is being applied in only one step, make this a force per second. - OMV.Vector3 addForce = force / PhysicsScene.LastTimeStep; - AddForce(addForce, pushforce, false); + addForce /= PhysScene.LastTimeStep; + AddForce(addForce, pushforce, false /* inTaintTime */); } + // Applying a force just adds this to the total force on the object. // This added force will only last the next simulation tick. public void AddForce(OMV.Vector3 force, bool pushforce, bool inTaintTime) { // for an object, doesn't matter if force is a pushforce or not - if (force.IsFinite()) + if (IsPhysicallyActive) { - float magnitude = force.Length(); - if (magnitude > BSParam.MaxAddForceMagnitude) + if (force.IsFinite()) { - // Force has a limit - force = force / magnitude * BSParam.MaxAddForceMagnitude; - } + // DetailLog("{0},BSPrim.addForce,call,force={1}", LocalID, addForce); - OMV.Vector3 addForce = force; - DetailLog("{0},BSPrim.addForce,call,force={1}", LocalID, addForce); - - PhysicsScene.TaintedObject(inTaintTime, "BSPrim.AddForce", delegate() - { - // Bullet adds this central force to the total force for this tick - DetailLog("{0},BSPrim.addForce,taint,force={1}", LocalID, addForce); - if (PhysBody.HasPhysicalBody) + OMV.Vector3 addForce = force; + PhysScene.TaintedObject(inTaintTime, "BSPrim.AddForce", delegate() { - PhysicsScene.PE.ApplyCentralForce(PhysBody, addForce); - ActivateIfPhysical(false); - } - }); - } - else - { - m_log.WarnFormat("{0}: Got a NaN force applied to a prim. LocalID={1}", LogHeader, LocalID); - return; + // Bullet adds this central force to the total force for this tick + DetailLog("{0},BSPrim.addForce,taint,force={1}", LocalID, addForce); + if (PhysBody.HasPhysicalBody) + { + PhysScene.PE.ApplyCentralForce(PhysBody, addForce); + ActivateIfPhysical(false); + } + }); + } + else + { + m_log.WarnFormat("{0}: AddForce: Got a NaN force applied to a prim. LocalID={1}", LogHeader, LocalID); + return; + } } } - public override void AddAngularForce(OMV.Vector3 force, bool pushforce) { - AddAngularForce(force, pushforce, false); + public void AddForceImpulse(OMV.Vector3 impulse, bool pushforce, bool inTaintTime) { + // for an object, doesn't matter if force is a pushforce or not + if (!IsPhysicallyActive) + { + if (impulse.IsFinite()) + { + OMV.Vector3 addImpulse = Util.ClampV(impulse, BSParam.MaxAddForceMagnitude); + // DetailLog("{0},BSPrim.addForceImpulse,call,impulse={1}", LocalID, impulse); + + PhysScene.TaintedObject(inTaintTime, "BSPrim.AddImpulse", delegate() + { + // Bullet adds this impulse immediately to the velocity + DetailLog("{0},BSPrim.addForceImpulse,taint,impulseforce={1}", LocalID, addImpulse); + if (PhysBody.HasPhysicalBody) + { + PhysScene.PE.ApplyCentralImpulse(PhysBody, addImpulse); + ActivateIfPhysical(false); + } + }); + } + else + { + m_log.WarnFormat("{0}: AddForceImpulse: Got a NaN impulse applied to a prim. LocalID={1}", LogHeader, LocalID); + return; + } + } } - public void AddAngularForce(OMV.Vector3 force, bool pushforce, bool inTaintTime) + + // BSPhysObject.AddAngularForce() + public override void AddAngularForce(OMV.Vector3 force, bool pushforce, bool inTaintTime) { if (force.IsFinite()) { OMV.Vector3 angForce = force; - PhysicsScene.TaintedObject(inTaintTime, "BSPrim.AddAngularForce", delegate() + PhysScene.TaintedObject(inTaintTime, "BSPrim.AddAngularForce", delegate() { if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.ApplyTorque(PhysBody, angForce); + DetailLog("{0},BSPrim.AddAngularForce,taint,angForce={1}", LocalID, angForce); + PhysScene.PE.ApplyTorque(PhysBody, angForce); ActivateIfPhysical(false); } }); @@ -1098,11 +1146,11 @@ public sealed class BSPrim : BSPhysObject public void ApplyTorqueImpulse(OMV.Vector3 impulse, bool inTaintTime) { OMV.Vector3 applyImpulse = impulse; - PhysicsScene.TaintedObject(inTaintTime, "BSPrim.ApplyTorqueImpulse", delegate() + PhysScene.TaintedObject(inTaintTime, "BSPrim.ApplyTorqueImpulse", delegate() { if (PhysBody.HasPhysicalBody) { - PhysicsScene.PE.ApplyTorqueImpulse(PhysBody, applyImpulse); + PhysScene.PE.ApplyTorqueImpulse(PhysBody, applyImpulse); ActivateIfPhysical(false); } }); @@ -1387,19 +1435,10 @@ public sealed class BSPrim : BSPhysObject profileEnd = 1.0f - (float)BaseShape.ProfileEnd * 2.0e-5f; volume *= (profileEnd - profileBegin); - returnMass = _density * volume; - - /* Comment out code that computes the mass of the linkset. That is done in the Linkset class. - if (IsRootOfLinkset) - { - foreach (BSPrim prim in _childrenPrims) - { - returnMass += prim.CalculateMass(); - } - } - */ + returnMass = Density * BSParam.DensityScaleFactor * volume; returnMass = Util.Clamp(returnMass, BSParam.MinimumObjectMass, BSParam.MaximumObjectMass); + // DetailLog("{0},BSPrim.CalculateMass,den={1},vol={2},mass={3}", LocalID, Density, volume, returnMass); return returnMass; }// end CalculateMass @@ -1410,104 +1449,68 @@ public sealed class BSPrim : BSPhysObject // Called at taint-time!!! public void CreateGeomAndObject(bool forceRebuild) { - // If this prim is part of a linkset, we must remove and restore the physical - // links if the body is rebuilt. - bool needToRestoreLinkset = false; - bool needToRestoreVehicle = false; - // Create the correct physical representation for this type of object. - // Updates PhysBody and PhysShape with the new information. - // Ignore 'forceRebuild'. This routine makes the right choices and changes of necessary. - PhysicsScene.Shapes.GetBodyAndShape(false, PhysicsScene.World, this, null, delegate(BulletBody dBody) + // Updates base.PhysBody and base.PhysShape with the new information. + // Ignore 'forceRebuild'. 'GetBodyAndShape' makes the right choices and changes of necessary. + PhysScene.Shapes.GetBodyAndShape(false /*forceRebuild */, PhysScene.World, this, delegate(BulletBody pBody, BulletShape pShape) { // Called if the current prim body is about to be destroyed. // Remove all the physical dependencies on the old body. // (Maybe someday make the changing of BSShape an event to be subscribed to by BSLinkset, ...) - needToRestoreLinkset = Linkset.RemoveBodyDependencies(this); - needToRestoreVehicle = _vehicle.RemoveBodyDependencies(this); + // Note: this virtual function is overloaded by BSPrimLinkable to remove linkset constraints. + RemoveDependencies(); }); - if (needToRestoreLinkset) - { - // If physical body dependencies were removed, restore them - Linkset.RestoreBodyDependencies(this); - } - if (needToRestoreVehicle) - { - // If physical body dependencies were removed, restore them - _vehicle.RestoreBodyDependencies(this); - } - // Make sure the properties are set on the new object UpdatePhysicalParameters(); return; } + // Called at taint-time + protected virtual void RemoveDependencies() + { + PhysicalActors.RemoveDependencies(); + } + // The physics engine says that properties have updated. Update same and inform // the world that things have changed. - // TODO: do we really need to check for changed? Maybe just copy values and call RequestPhysicsterseUpdate() - enum UpdatedProperties { - Position = 1 << 0, - Rotation = 1 << 1, - Velocity = 1 << 2, - Acceleration = 1 << 3, - RotationalVel = 1 << 4 - } - - const float ROTATION_TOLERANCE = 0.01f; - const float VELOCITY_TOLERANCE = 0.001f; - const float POSITION_TOLERANCE = 0.05f; - const float ACCELERATION_TOLERANCE = 0.01f; - const float ROTATIONAL_VELOCITY_TOLERANCE = 0.01f; - public override void UpdateProperties(EntityProperties entprop) { - // Updates only for individual prims and for the root object of a linkset. - if (Linkset.IsRoot(this)) + // Let anyone (like the actors) modify the updated properties before they are pushed into the object and the simulator. + TriggerPreUpdatePropertyAction(ref entprop); + + // DetailLog("{0},BSPrim.UpdateProperties,entry,entprop={1}", LocalID, entprop); // DEBUG DEBUG + + // Assign directly to the local variables so the normal set actions do not happen + _position = entprop.Position; + _orientation = entprop.Rotation; + // DEBUG DEBUG DEBUG -- smooth velocity changes a bit. The simulator seems to be + // very sensitive to velocity changes. + if (entprop.Velocity == OMV.Vector3.Zero || !entprop.Velocity.ApproxEquals(RawVelocity, BSParam.UpdateVelocityChangeThreshold)) + RawVelocity = entprop.Velocity; + _acceleration = entprop.Acceleration; + _rotationalVelocity = entprop.RotationalVelocity; + + // DetailLog("{0},BSPrim.UpdateProperties,afterAssign,entprop={1}", LocalID, entprop); // DEBUG DEBUG + + // The sanity check can change the velocity and/or position. + if (PositionSanityCheck(true /* inTaintTime */ )) { - // A temporary kludge to suppress the rotational effects introduced on vehicles by Bullet - // TODO: handle physics introduced by Bullet with computed vehicle physics. - if (_vehicle.IsActive) - { - entprop.RotationalVelocity = OMV.Vector3.Zero; - } - - // Assign directly to the local variables so the normal set action does not happen - _position = entprop.Position; - _orientation = entprop.Rotation; - _velocity = entprop.Velocity; - _acceleration = entprop.Acceleration; - _rotationalVelocity = entprop.RotationalVelocity; - - // The sanity check can change the velocity and/or position. - if (IsPhysical && PositionSanityCheck(true)) - { - entprop.Position = _position; - entprop.Velocity = _velocity; - } - - OMV.Vector3 direction = OMV.Vector3.UnitX * _orientation; // DEBUG DEBUG DEBUG - DetailLog("{0},BSPrim.UpdateProperties,call,pos={1},orient={2},dir={3},vel={4},rotVel={5}", - LocalID, _position, _orientation, direction, _velocity, _rotationalVelocity); - - // remember the current and last set values - LastEntityProperties = CurrentEntityProperties; - CurrentEntityProperties = entprop; - - base.RequestPhysicsterseUpdate(); + entprop.Position = _position; + entprop.Velocity = RawVelocity; + entprop.RotationalVelocity = _rotationalVelocity; + entprop.Acceleration = _acceleration; } - /* - else - { - // For debugging, report the movement of children - DetailLog("{0},BSPrim.UpdateProperties,child,pos={1},orient={2},vel={3},accel={4},rotVel={5}", - LocalID, entprop.Position, entprop.Rotation, entprop.Velocity, - entprop.Acceleration, entprop.RotationalVelocity); - } - */ - // The linkset implimentation might want to know about this. - Linkset.UpdateProperties(this, true); + OMV.Vector3 direction = OMV.Vector3.UnitX * _orientation; // DEBUG DEBUG DEBUG + DetailLog("{0},BSPrim.UpdateProperties,call,entProp={1},dir={2}", LocalID, entprop, direction); + + // remember the current and last set values + LastEntityProperties = CurrentEntityProperties; + CurrentEntityProperties = entprop; + + // Note that BSPrim can be overloaded by BSPrimLinkable which controls updates from root and children prims. + base.RequestPhysicsterseUpdate(); } } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPrimDisplaced.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPrimDisplaced.cs new file mode 100755 index 0000000000..f5ee671862 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSPrimDisplaced.cs @@ -0,0 +1,165 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The quotations from http://wiki.secondlife.com/wiki/Linden_Vehicle_Tutorial + * are Copyright (c) 2009 Linden Research, Inc and are used under their license + * of Creative Commons Attribution-Share Alike 3.0 + * (http://creativecommons.org/licenses/by-sa/3.0/). + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Physics.Manager; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSPrimDisplaced : BSPrim +{ + // The purpose of this module is to do any mapping between what the simulator thinks + // the prim position and orientation is and what the physical position/orientation. + // This difference happens because Bullet assumes the center-of-mass is the <0,0,0> + // of the prim/linkset. The simulator tracks the location of the prim/linkset by + // the location of the root prim. So, if center-of-mass is anywhere but the origin + // of the root prim, the physical origin is displaced from the simulator origin. + // + // This routine works by capturing the Force* setting of position/orientation/... and + // adjusting the simulator values (being set) into the physical values. + // The conversion is also done in the opposite direction (physical origin -> simulator origin). + // + // The updateParameter call is also captured and the values from the physics engine + // are converted into simulator origin values before being passed to the base + // class. + + public virtual OMV.Vector3 PositionDisplacement { get; set; } + public virtual OMV.Quaternion OrientationDisplacement { get; set; } + + public BSPrimDisplaced(uint localID, String primName, BSScene parent_scene, OMV.Vector3 pos, OMV.Vector3 size, + OMV.Quaternion rotation, PrimitiveBaseShape pbs, bool pisPhysical) + : base(localID, primName, parent_scene, pos, size, rotation, pbs, pisPhysical) + { + ClearDisplacement(); + } + + public void ClearDisplacement() + { + PositionDisplacement = OMV.Vector3.Zero; + OrientationDisplacement = OMV.Quaternion.Identity; + } + + // Set this sets and computes the displacement from the passed prim to the center-of-mass. + // A user set value for center-of-mass overrides whatever might be passed in here. + // The displacement is in local coordinates (relative to root prim in linkset oriented coordinates). + public virtual void SetEffectiveCenterOfMassDisplacement(Vector3 centerOfMassDisplacement) + { + Vector3 comDisp; + if (UserSetCenterOfMassDisplacement.HasValue) + comDisp = (OMV.Vector3)UserSetCenterOfMassDisplacement; + else + comDisp = centerOfMassDisplacement; + + DetailLog("{0},BSPrimDisplaced.SetEffectiveCenterOfMassDisplacement,userSet={1},comDisp={2}", + LocalID, UserSetCenterOfMassDisplacement.HasValue, comDisp); + if (comDisp == Vector3.Zero) + { + // If there is no diplacement. Things get reset. + PositionDisplacement = OMV.Vector3.Zero; + OrientationDisplacement = OMV.Quaternion.Identity; + } + else + { + // Remember the displacement from root as well as the origional rotation of the + // new center-of-mass. + PositionDisplacement = comDisp; + OrientationDisplacement = OMV.Quaternion.Identity; + } + } + + public override Vector3 ForcePosition + { + get { return base.ForcePosition; } + set + { + if (PositionDisplacement != OMV.Vector3.Zero) + { + OMV.Vector3 displacedPos = value - (PositionDisplacement * RawOrientation); + DetailLog("{0},BSPrimDisplaced.ForcePosition,val={1},disp={2},newPos={3}", LocalID, value, PositionDisplacement, displacedPos); + base.ForcePosition = displacedPos; + } + else + { + base.ForcePosition = value; + } + } + } + + public override Quaternion ForceOrientation + { + get { return base.ForceOrientation; } + set + { + // TODO: + base.ForceOrientation = value; + } + } + + // TODO: decide if this is the right place for these variables. + // Somehow incorporate the optional settability by the user. + // Is this used? + public override OMV.Vector3 CenterOfMass + { + get { return RawPosition; } + } + + // Is this used? + public override OMV.Vector3 GeometricCenter + { + get { return RawPosition; } + } + + public override void UpdateProperties(EntityProperties entprop) + { + // Undo any center-of-mass displacement that might have been done. + if (PositionDisplacement != OMV.Vector3.Zero || OrientationDisplacement != OMV.Quaternion.Identity) + { + // Correct for any rotation around the center-of-mass + // TODO!!! + + OMV.Vector3 displacedPos = entprop.Position + (PositionDisplacement * entprop.Rotation); + DetailLog("{0},BSPrimDisplaced.ForcePosition,physPos={1},disp={2},newPos={3}", LocalID, entprop.Position, PositionDisplacement, displacedPos); + entprop.Position = displacedPos; + // entprop.Rotation = something; + } + + base.UpdateProperties(entprop); + } +} +} diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSPrimLinkable.cs b/OpenSim/Region/Physics/BulletSPlugin/BSPrimLinkable.cs new file mode 100755 index 0000000000..235da782e1 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSPlugin/BSPrimLinkable.cs @@ -0,0 +1,192 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyrightD + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OpenSim.Framework; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSPlugin +{ +public class BSPrimLinkable : BSPrimDisplaced +{ + public BSLinkset Linkset { get; set; } + // The index of this child prim. + public int LinksetChildIndex { get; set; } + + public BSLinksetInfo LinksetInfo { get; set; } + + public BSPrimLinkable(uint localID, String primName, BSScene parent_scene, OMV.Vector3 pos, OMV.Vector3 size, + OMV.Quaternion rotation, PrimitiveBaseShape pbs, bool pisPhysical) + : base(localID, primName, parent_scene, pos, size, rotation, pbs, pisPhysical) + { + Linkset = BSLinkset.Factory(PhysScene, this); + + PhysScene.TaintedObject("BSPrimLinksetCompound.Refresh", delegate() + { + Linkset.Refresh(this); + }); + } + + public override void Destroy() + { + Linkset = Linkset.RemoveMeFromLinkset(this); + base.Destroy(); + } + + public override void link(Manager.PhysicsActor obj) + { + BSPrimLinkable parent = obj as BSPrimLinkable; + if (parent != null) + { + BSPhysObject parentBefore = Linkset.LinksetRoot; + int childrenBefore = Linkset.NumberOfChildren; + + Linkset = parent.Linkset.AddMeToLinkset(this); + + DetailLog("{0},BSPrimLinkset.link,call,parentBefore={1}, childrenBefore=={2}, parentAfter={3}, childrenAfter={4}", + LocalID, parentBefore.LocalID, childrenBefore, Linkset.LinksetRoot.LocalID, Linkset.NumberOfChildren); + } + return; + } + + public override void delink() + { + // TODO: decide if this parent checking needs to happen at taint time + // Race condition here: if link() and delink() in same simulation tick, the delink will not happen + + BSPhysObject parentBefore = Linkset.LinksetRoot; + int childrenBefore = Linkset.NumberOfChildren; + + Linkset = Linkset.RemoveMeFromLinkset(this); + + DetailLog("{0},BSPrimLinkset.delink,parentBefore={1},childrenBefore={2},parentAfter={3},childrenAfter={4}, ", + LocalID, parentBefore.LocalID, childrenBefore, Linkset.LinksetRoot.LocalID, Linkset.NumberOfChildren); + return; + } + + // When simulator changes position, this might be moving a child of the linkset. + public override OMV.Vector3 Position + { + get { return base.Position; } + set + { + base.Position = value; + PhysScene.TaintedObject("BSPrimLinkset.setPosition", delegate() + { + Linkset.UpdateProperties(UpdatedProperties.Position, this); + }); + } + } + + // When simulator changes orientation, this might be moving a child of the linkset. + public override OMV.Quaternion Orientation + { + get { return base.Orientation; } + set + { + base.Orientation = value; + PhysScene.TaintedObject("BSPrimLinkset.setOrientation", delegate() + { + Linkset.UpdateProperties(UpdatedProperties.Orientation, this); + }); + } + } + + public override float TotalMass + { + get { return Linkset.LinksetMass; } + } + + public override void UpdatePhysicalParameters() + { + base.UpdatePhysicalParameters(); + // Recompute any linkset parameters. + // When going from non-physical to physical, this re-enables the constraints that + // had been automatically disabled when the mass was set to zero. + // For compound based linksets, this enables and disables interactions of the children. + if (Linkset != null) // null can happen during initialization + Linkset.Refresh(this); + } + + protected override void MakeDynamic(bool makeStatic) + { + base.MakeDynamic(makeStatic); + if (makeStatic) + Linkset.MakeStatic(this); + else + Linkset.MakeDynamic(this); + } + + // Body is being taken apart. Remove physical dependencies and schedule a rebuild. + protected override void RemoveDependencies() + { + Linkset.RemoveDependencies(this); + base.RemoveDependencies(); + } + + public override void UpdateProperties(EntityProperties entprop) + { + if (Linkset.IsRoot(this)) + { + // Properties are only updated for the roots of a linkset. + // TODO: this will have to change when linksets are articulated. + base.UpdateProperties(entprop); + } + /* + else + { + // For debugging, report the movement of children + DetailLog("{0},BSPrim.UpdateProperties,child,pos={1},orient={2},vel={3},accel={4},rotVel={5}", + LocalID, entprop.Position, entprop.Rotation, entprop.Velocity, + entprop.Acceleration, entprop.RotationalVelocity); + } + */ + // The linkset might like to know about changing locations + Linkset.UpdateProperties(UpdatedProperties.EntPropUpdates, this); + } + + public override bool Collide(uint collidingWith, BSPhysObject collidee, + OMV.Vector3 contactPoint, OMV.Vector3 contactNormal, float pentrationDepth) + { + // prims in the same linkset cannot collide with each other + BSPrimLinkable convCollidee = collidee as BSPrimLinkable; + if (convCollidee != null && (this.Linkset.LinksetID == convCollidee.Linkset.LinksetID)) + { + return false; + } + + // TODO: handle collisions of other objects with with children of linkset. + // This is a problem for LinksetCompound since the children are packed into the root. + + return base.Collide(collidingWith, collidee, contactPoint, contactNormal, pentrationDepth); + } +} +} diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs b/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs index 7017194c23..a4a87943a3 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSScene.cs @@ -26,6 +26,7 @@ */ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; @@ -81,14 +82,13 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters internal long m_simulationStep = 0; internal float NominalFrameRate { get; set; } public long SimulationStep { get { return m_simulationStep; } } - internal int m_taintsToProcessPerStep; internal float LastTimeStep { get; private set; } // Physical objects can register for prestep or poststep events public delegate void PreStepAction(float timeStep); public delegate void PostStepAction(float timeStep); public event PreStepAction BeforeStep; - public event PreStepAction AfterStep; + public event PostStepAction AfterStep; // A value of the time now so all the collision and update routines do not have to get their own // Set to 'now' just before all the prims and actors are called for collisions and updates @@ -161,17 +161,22 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters private int m_physicsLoggingFileMinutes; private bool m_physicsLoggingDoFlush; private bool m_physicsPhysicalDumpEnabled; - public float PhysicsMetricDumpFrames { get; set; } + public int PhysicsMetricDumpFrames { get; set; } // 'true' of the vehicle code is to log lots of details public bool VehicleLoggingEnabled { get; private set; } public bool VehiclePhysicalLoggingEnabled { get; private set; } #region Construction and Initialization - public BSScene(string identifier) + public BSScene(string engineType, string identifier) { m_initialized = false; - // we are passed the name of the region we're working for. + + // The name of the region we're working for is passed to us. Keep for identification. RegionName = identifier; + + // Set identifying variables in the PhysicsScene interface. + EngineType = engineType; + Name = EngineType + "/" + RegionName; } public override void Initialise(IMesher meshmerizer, IConfigSource config) @@ -311,6 +316,10 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters break; case "bulletxna": ret = new BSAPIXNA(engineName, this); + // Disable some features that are not implemented in BulletXNA + m_log.InfoFormat("{0} Disabling some physics features not implemented by BulletXNA", LogHeader); + BSParam.ShouldUseBulletHACD = false; + BSParam.ShouldUseSingleConvexHullForPrims = false; break; } @@ -382,12 +391,14 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters if (!m_initialized) return null; BSCharacter actor = new BSCharacter(localID, avName, this, position, size, isFlying); - lock (PhysObjects) PhysObjects.Add(localID, actor); + lock (PhysObjects) + PhysObjects.Add(localID, actor); // TODO: Remove kludge someday. // We must generate a collision for avatars whether they collide or not. // This is required by OpenSim to update avatar animations, etc. - lock (m_avatars) m_avatars.Add(actor); + lock (m_avatars) + m_avatars.Add(actor); return actor; } @@ -403,9 +414,11 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters { try { - lock (PhysObjects) PhysObjects.Remove(actor.LocalID); + lock (PhysObjects) + PhysObjects.Remove(bsactor.LocalID); // Remove kludge someday - lock (m_avatars) m_avatars.Remove(bsactor); + lock (m_avatars) + m_avatars.Remove(bsactor); } catch (Exception e) { @@ -414,13 +427,18 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters bsactor.Destroy(); // bsactor.dispose(); } + else + { + m_log.ErrorFormat("{0}: Requested to remove avatar that is not a BSCharacter. ID={1}, type={2}", + LogHeader, actor.LocalID, actor.GetType().Name); + } } public override void RemovePrim(PhysicsActor prim) { if (!m_initialized) return; - BSPrim bsprim = prim as BSPrim; + BSPhysObject bsprim = prim as BSPhysObject; if (bsprim != null) { DetailLog("{0},RemovePrim,call", bsprim.LocalID); @@ -449,9 +467,9 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters if (!m_initialized) return null; - DetailLog("{0},AddPrimShape,call", localID); + // DetailLog("{0},BSScene.AddPrimShape,call", localID); - BSPrim prim = new BSPrim(localID, primName, this, position, size, rotation, pbs, isPhysical); + BSPhysObject prim = new BSPrimLinkable(localID, primName, this, position, size, rotation, pbs, isPhysical); lock (PhysObjects) PhysObjects.Add(localID, prim); return prim; } @@ -486,6 +504,7 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters ProcessTaints(); // Some of the physical objects requre individual, pre-step calls + // (vehicles and avatar movement, in particular) TriggerPreStepEvent(timeStep); // the prestep actions might have added taints @@ -527,7 +546,7 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters collidersCount = 0; } - if ((m_simulationStep % PhysicsMetricDumpFrames) == 0) + if (PhysicsMetricDumpFrames != 0 && ((m_simulationStep % PhysicsMetricDumpFrames) == 0)) PE.DumpPhysicsStatistics(World); // Get a value for 'now' so all the collision and update routines don't have to get their own. @@ -543,8 +562,9 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters uint cB = m_collisionArray[ii].bID; Vector3 point = m_collisionArray[ii].point; Vector3 normal = m_collisionArray[ii].normal; - SendCollision(cA, cB, point, normal, 0.01f); - SendCollision(cB, cA, point, -normal, 0.01f); + float penetration = m_collisionArray[ii].penetration; + SendCollision(cA, cB, point, normal, penetration); + SendCollision(cB, cA, point, -normal, penetration); } } @@ -682,7 +702,21 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters public override Dictionary GetTopColliders() { - return new Dictionary(); + Dictionary topColliders; + + lock (PhysObjects) + { + foreach (KeyValuePair kvp in PhysObjects) + { + kvp.Value.ComputeCollisionScore(); + } + + List orderedPrims = new List(PhysObjects.Values); + orderedPrims.OrderByDescending(p => p.CollisionScore); + topColliders = orderedPrims.Take(25).ToDictionary(p => p.LocalID, p => p.CollisionScore); + } + + return topColliders; } public override bool IsThreaded { get { return false; } } @@ -694,8 +728,8 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters // TriggerPreStepEvent // DoOneTimeTaints // Step() - // ProcessAndForwardCollisions - // ProcessAndForwardPropertyUpdates + // ProcessAndSendToSimulatorCollisions + // ProcessAndSendToSimulatorPropertyUpdates // TriggerPostStepEvent // Calls to the PhysicsActors can't directly call into the physics engine @@ -733,7 +767,7 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters private void TriggerPostStepEvent(float timeStep) { - PreStepAction actions = AfterStep; + PostStepAction actions = AfterStep; if (actions != null) actions(timeStep); @@ -825,15 +859,13 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters { DetailLog("{0},BSScene.AssertInTaintTime,NOT IN TAINT TIME,Region={1},Where={2}", DetailLogZero, RegionName, whereFrom); m_log.ErrorFormat("{0} NOT IN TAINT TIME!! Region={1}, Where={2}", LogHeader, RegionName, whereFrom); - Util.PrintCallStack(DetailLog); + // Util.PrintCallStack(DetailLog); } return InTaintTime; } #endregion // Taints - #region INI and command line parameter processing - #region IPhysicsParameters // Get the list of parameters this physics engine supports public PhysParameterEntry[] GetParameterList() @@ -848,64 +880,65 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters // will use the next time since it's pinned and shared memory. // Some of the values require calling into the physics engine to get the new // value activated ('terrainFriction' for instance). - public bool SetPhysicsParameter(string parm, float val, uint localID) + public bool SetPhysicsParameter(string parm, string val, uint localID) { bool ret = false; - BSParam.ParameterDefn theParam; + + BSParam.ParameterDefnBase theParam; if (BSParam.TryGetParameter(parm, out theParam)) { - theParam.setter(this, parm, localID, val); + // Set the value in the C# code + theParam.SetValue(this, val); + + // Optionally set the parameter in the unmanaged code + if (theParam.HasSetOnObject) + { + // update all the localIDs specified + // If the local ID is APPLY_TO_NONE, just change the default value + // If the localID is APPLY_TO_ALL change the default value and apply the new value to all the lIDs + // If the localID is a specific object, apply the parameter change to only that object + List objectIDs = new List(); + switch (localID) + { + case PhysParameterEntry.APPLY_TO_NONE: + // This will cause a call into the physical world if some operation is specified (SetOnObject). + objectIDs.Add(TERRAIN_ID); + TaintedUpdateParameter(parm, objectIDs, val); + break; + case PhysParameterEntry.APPLY_TO_ALL: + lock (PhysObjects) objectIDs = new List(PhysObjects.Keys); + TaintedUpdateParameter(parm, objectIDs, val); + break; + default: + // setting only one localID + objectIDs.Add(localID); + TaintedUpdateParameter(parm, objectIDs, val); + break; + } + } + ret = true; } return ret; } - // update all the localIDs specified - // If the local ID is APPLY_TO_NONE, just change the default value - // If the localID is APPLY_TO_ALL change the default value and apply the new value to all the lIDs - // If the localID is a specific object, apply the parameter change to only that object - internal delegate void AssignVal(float x); - internal void UpdateParameterObject(AssignVal setDefault, string parm, uint localID, float val) - { - List objectIDs = new List(); - switch (localID) - { - case PhysParameterEntry.APPLY_TO_NONE: - setDefault(val); // setting only the default value - // This will cause a call into the physical world if some operation is specified (SetOnObject). - objectIDs.Add(TERRAIN_ID); - TaintedUpdateParameter(parm, objectIDs, val); - break; - case PhysParameterEntry.APPLY_TO_ALL: - setDefault(val); // setting ALL also sets the default value - lock (PhysObjects) objectIDs = new List(PhysObjects.Keys); - TaintedUpdateParameter(parm, objectIDs, val); - break; - default: - // setting only one localID - objectIDs.Add(localID); - TaintedUpdateParameter(parm, objectIDs, val); - break; - } - } - // schedule the actual updating of the paramter to when the phys engine is not busy - private void TaintedUpdateParameter(string parm, List lIDs, float val) + private void TaintedUpdateParameter(string parm, List lIDs, string val) { - float xval = val; + string xval = val; List xlIDs = lIDs; string xparm = parm; TaintedObject("BSScene.UpdateParameterSet", delegate() { - BSParam.ParameterDefn thisParam; + BSParam.ParameterDefnBase thisParam; if (BSParam.TryGetParameter(xparm, out thisParam)) { - if (thisParam.onObject != null) + if (thisParam.HasSetOnObject) { foreach (uint lID in xlIDs) { BSPhysObject theObject = null; - PhysObjects.TryGetValue(lID, out theObject); - thisParam.onObject(this, theObject, xval); + if (PhysObjects.TryGetValue(lID, out theObject)) + thisParam.SetOnObject(this, theObject); } } } @@ -914,14 +947,14 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters // Get parameter. // Return 'false' if not able to get the parameter. - public bool GetPhysicsParameter(string parm, out float value) + public bool GetPhysicsParameter(string parm, out string value) { - float val = 0f; + string val = String.Empty; bool ret = false; - BSParam.ParameterDefn theParam; + BSParam.ParameterDefnBase theParam; if (BSParam.TryGetParameter(parm, out theParam)) { - val = theParam.getter(this); + val = theParam.GetValue(this); ret = true; } value = val; @@ -930,8 +963,6 @@ public sealed class BSScene : PhysicsScene, IPhysicsParameters #endregion IPhysicsParameters - #endregion Runtime settable parameters - // Invoke the detailed logger and output something if it's enabled. public void DetailLog(string msg, params Object[] args) { diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs index d361f18201..64aaa1503d 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSShapeCollection.cs @@ -38,38 +38,15 @@ public sealed class BSShapeCollection : IDisposable { private static string LogHeader = "[BULLETSIM SHAPE COLLECTION]"; - private BSScene PhysicsScene { get; set; } + private BSScene m_physicsScene { get; set; } private Object m_collectionActivityLock = new Object(); - // Description of a Mesh - private struct MeshDesc - { - public BulletShape shape; - public int referenceCount; - public DateTime lastReferenced; - public UInt64 shapeKey; - } - - // Description of a hull. - // Meshes and hulls have the same shape hash key but we only need hulls for efficient collision calculations. - private struct HullDesc - { - public BulletShape shape; - public int referenceCount; - public DateTime lastReferenced; - public UInt64 shapeKey; - } - - // The sharable set of meshes and hulls. Indexed by their shape hash. - private Dictionary Meshes = new Dictionary(); - private Dictionary Hulls = new Dictionary(); - private bool DDetail = false; public BSShapeCollection(BSScene physScene) { - PhysicsScene = physScene; + m_physicsScene = physScene; // Set the next to 'true' for very detailed shape update detailed logging (detailed details?) // While detailed debugging is still active, this is better than commenting out all the // DetailLog statements. When debugging slows down, this and the protected logging @@ -86,22 +63,18 @@ public sealed class BSShapeCollection : IDisposable // Mostly used for changing bodies out from under Linksets. // Useful for other cases where parameters need saving. // Passing 'null' says no callback. - public delegate void ShapeDestructionCallback(BulletShape shape); - public delegate void BodyDestructionCallback(BulletBody body); + public delegate void PhysicalDestructionCallback(BulletBody pBody, BulletShape pShape); // Called to update/change the body and shape for an object. - // First checks the shape and updates that if necessary then makes - // sure the body is of the right type. + // The object has some shape and body on it. Here we decide if that is the correct shape + // for the current state of the object (static/dynamic/...). + // If bodyCallback is not null, it is called if either the body or the shape are changed + // so dependencies (like constraints) can be removed before the physical object is dereferenced. // Return 'true' if either the body or the shape changed. - // 'shapeCallback' and 'bodyCallback' are, if non-null, functions called just before - // the current shape or body is destroyed. This allows the caller to remove any - // higher level dependencies on the shape or body. Mostly used for LinkSets to - // remove the physical constraints before the body is destroyed. - // Called at taint-time!! - public bool GetBodyAndShape(bool forceRebuild, BulletWorld sim, BSPhysObject prim, - ShapeDestructionCallback shapeCallback, BodyDestructionCallback bodyCallback) + // Called at taint-time. + public bool GetBodyAndShape(bool forceRebuild, BulletWorld sim, BSPhysObject prim, PhysicalDestructionCallback bodyCallback) { - PhysicsScene.AssertInTaintTime("BSShapeCollection.GetBodyAndShape"); + m_physicsScene.AssertInTaintTime("BSShapeCollection.GetBodyAndShape"); bool ret = false; @@ -111,13 +84,12 @@ public sealed class BSShapeCollection : IDisposable // Do we have the correct geometry for this type of object? // Updates prim.BSShape with information/pointers to shape. // Returns 'true' of BSShape is changed to a new shape. - bool newGeom = CreateGeom(forceRebuild, prim, shapeCallback); + bool newGeom = CreateGeom(forceRebuild, prim, bodyCallback); // If we had to select a new shape geometry for the object, // rebuild the body around it. // Updates prim.BSBody with information/pointers to requested body // Returns 'true' if BSBody was changed. - bool newBody = CreateBody((newGeom || forceRebuild), prim, PhysicsScene.World, - prim.PhysShape, bodyCallback); + bool newBody = CreateBody((newGeom || forceRebuild), prim, m_physicsScene.World, bodyCallback); ret = newGeom || newBody; } DetailLog("{0},BSShapeCollection.GetBodyAndShape,taintExit,force={1},ret={2},body={3},shape={4}", @@ -128,279 +100,20 @@ public sealed class BSShapeCollection : IDisposable public bool GetBodyAndShape(bool forceRebuild, BulletWorld sim, BSPhysObject prim) { - return GetBodyAndShape(forceRebuild, sim, prim, null, null); + return GetBodyAndShape(forceRebuild, sim, prim, null); } - // Track another user of a body. - // We presume the caller has allocated the body. - // Bodies only have one user so the body is just put into the world if not already there. - public void ReferenceBody(BulletBody body, bool inTaintTime) + // If the existing prim's shape is to be replaced, remove the tie to the existing shape + // before replacing it. + private void DereferenceExistingShape(BSPhysObject prim, PhysicalDestructionCallback shapeCallback) { - lock (m_collectionActivityLock) + if (prim.PhysShape.HasPhysicalShape) { - if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,newBody,body={1}", body.ID, body); - PhysicsScene.TaintedObject(inTaintTime, "BSShapeCollection.ReferenceBody", delegate() - { - if (!PhysicsScene.PE.IsInWorld(PhysicsScene.World, body)) - { - PhysicsScene.PE.AddObjectToWorld(PhysicsScene.World, body); - if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,addedToWorld,ref={1}", body.ID, body); - } - }); - } - } - - // Release the usage of a body. - // Called when releasing use of a BSBody. BSShape is handled separately. - public void DereferenceBody(BulletBody body, bool inTaintTime, BodyDestructionCallback bodyCallback ) - { - if (!body.HasPhysicalBody) - return; - - lock (m_collectionActivityLock) - { - PhysicsScene.TaintedObject(inTaintTime, "BSShapeCollection.DereferenceBody", delegate() - { - if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceBody,DestroyingBody,body={1},inTaintTime={2}", - body.ID, body, inTaintTime); - // If the caller needs to know the old body is going away, pass the event up. - if (bodyCallback != null) bodyCallback(body); - - if (PhysicsScene.PE.IsInWorld(PhysicsScene.World, body)) - { - PhysicsScene.PE.RemoveObjectFromWorld(PhysicsScene.World, body); - if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceBody,removingFromWorld. Body={1}", body.ID, body); - } - - // Zero any reference to the shape so it is not freed when the body is deleted. - PhysicsScene.PE.SetCollisionShape(PhysicsScene.World, body, null); - PhysicsScene.PE.DestroyObject(PhysicsScene.World, body); - }); - } - } - - // Track the datastructures and use count for a shape. - // When creating a hull, this is called first to reference the mesh - // and then again to reference the hull. - // Meshes and hulls for the same shape have the same hash key. - // NOTE that native shapes are not added to the mesh list or removed. - // Returns 'true' if this is the initial reference to the shape. Otherwise reused. - public bool ReferenceShape(BulletShape shape) - { - bool ret = false; - switch (shape.type) - { - case BSPhysicsShapeType.SHAPE_MESH: - MeshDesc meshDesc; - if (Meshes.TryGetValue(shape.shapeKey, out meshDesc)) - { - // There is an existing instance of this mesh. - meshDesc.referenceCount++; - if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceShape,existingMesh,key={1},cnt={2}", - BSScene.DetailLogZero, shape.shapeKey.ToString("X"), meshDesc.referenceCount); - } - else - { - // This is a new reference to a mesh - meshDesc.shape = shape.Clone(); - meshDesc.shapeKey = shape.shapeKey; - // We keep a reference to the underlying IMesh data so a hull can be built - meshDesc.referenceCount = 1; - if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceShape,newMesh,key={1},cnt={2}", - BSScene.DetailLogZero, shape.shapeKey.ToString("X"), meshDesc.referenceCount); - ret = true; - } - meshDesc.lastReferenced = System.DateTime.Now; - Meshes[shape.shapeKey] = meshDesc; - break; - case BSPhysicsShapeType.SHAPE_HULL: - HullDesc hullDesc; - if (Hulls.TryGetValue(shape.shapeKey, out hullDesc)) - { - // There is an existing instance of this hull. - hullDesc.referenceCount++; - if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceShape,existingHull,key={1},cnt={2}", - BSScene.DetailLogZero, shape.shapeKey.ToString("X"), hullDesc.referenceCount); - } - else - { - // This is a new reference to a hull - hullDesc.shape = shape.Clone(); - hullDesc.shapeKey = shape.shapeKey; - hullDesc.referenceCount = 1; - if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceShape,newHull,key={1},cnt={2}", - BSScene.DetailLogZero, shape.shapeKey.ToString("X"), hullDesc.referenceCount); - ret = true; - - } - hullDesc.lastReferenced = System.DateTime.Now; - Hulls[shape.shapeKey] = hullDesc; - break; - case BSPhysicsShapeType.SHAPE_UNKNOWN: - break; - default: - // Native shapes are not tracked and they don't go into any list - break; - } - return ret; - } - - // Release the usage of a shape. - public void DereferenceShape(BulletShape shape, bool inTaintTime, ShapeDestructionCallback shapeCallback) - { - if (!shape.HasPhysicalShape) - return; - - PhysicsScene.TaintedObject(inTaintTime, "BSShapeCollection.DereferenceShape", delegate() - { - if (shape.HasPhysicalShape) - { - if (shape.isNativeShape) - { - // Native shapes are not tracked and are released immediately - if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceShape,deleteNativeShape,ptr={1},taintTime={2}", - BSScene.DetailLogZero, shape.AddrString, inTaintTime); - if (shapeCallback != null) shapeCallback(shape); - PhysicsScene.PE.DeleteCollisionShape(PhysicsScene.World, shape); - } - else - { - switch (shape.type) - { - case BSPhysicsShapeType.SHAPE_HULL: - DereferenceHull(shape, shapeCallback); - break; - case BSPhysicsShapeType.SHAPE_MESH: - DereferenceMesh(shape, shapeCallback); - break; - case BSPhysicsShapeType.SHAPE_COMPOUND: - DereferenceCompound(shape, shapeCallback); - break; - case BSPhysicsShapeType.SHAPE_UNKNOWN: - break; - default: - break; - } - } - } - }); - } - - // Count down the reference count for a mesh shape - // Called at taint-time. - private void DereferenceMesh(BulletShape shape, ShapeDestructionCallback shapeCallback) - { - MeshDesc meshDesc; - if (Meshes.TryGetValue(shape.shapeKey, out meshDesc)) - { - meshDesc.referenceCount--; - // TODO: release the Bullet storage - if (shapeCallback != null) shapeCallback(shape); - meshDesc.lastReferenced = System.DateTime.Now; - Meshes[shape.shapeKey] = meshDesc; - if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceMesh,shape={1},refCnt={2}", - BSScene.DetailLogZero, shape, meshDesc.referenceCount); - - } - } - - // Count down the reference count for a hull shape - // Called at taint-time. - private void DereferenceHull(BulletShape shape, ShapeDestructionCallback shapeCallback) - { - HullDesc hullDesc; - if (Hulls.TryGetValue(shape.shapeKey, out hullDesc)) - { - hullDesc.referenceCount--; - // TODO: release the Bullet storage (aging old entries?) - - // Tell upper layers that, if they have dependencies on this shape, this link is going away - if (shapeCallback != null) shapeCallback(shape); - - hullDesc.lastReferenced = System.DateTime.Now; - Hulls[shape.shapeKey] = hullDesc; - if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceHull,shape={1},refCnt={2}", - BSScene.DetailLogZero, shape, hullDesc.referenceCount); - } - } - - // Remove a reference to a compound shape. - // Taking a compound shape apart is a little tricky because if you just delete the - // physical shape, it will free all the underlying children. We can't do that because - // they could be shared. So, this removes each of the children from the compound and - // dereferences them separately before destroying the compound collision object itself. - // Called at taint-time. - private void DereferenceCompound(BulletShape shape, ShapeDestructionCallback shapeCallback) - { - if (!PhysicsScene.PE.IsCompound(shape)) - { - // Failed the sanity check!! - PhysicsScene.Logger.ErrorFormat("{0} Attempt to free a compound shape that is not compound!! type={1}, ptr={2}", - LogHeader, shape.type, shape.AddrString); - if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceCompound,notACompoundShape,type={1},ptr={2}", - BSScene.DetailLogZero, shape.type, shape.AddrString); - return; - } - - int numChildren = PhysicsScene.PE.GetNumberOfCompoundChildren(shape); - if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceCompound,shape={1},children={2}", BSScene.DetailLogZero, shape, numChildren); - - for (int ii = numChildren - 1; ii >= 0; ii--) - { - BulletShape childShape = PhysicsScene.PE.RemoveChildShapeFromCompoundShapeIndex(shape, ii); - DereferenceAnonCollisionShape(childShape); - } - PhysicsScene.PE.DeleteCollisionShape(PhysicsScene.World, shape); - } - - // Sometimes we have a pointer to a collision shape but don't know what type it is. - // Figure out type and call the correct dereference routine. - // Called at taint-time. - private void DereferenceAnonCollisionShape(BulletShape shapeInfo) - { - MeshDesc meshDesc; - HullDesc hullDesc; - - if (TryGetMeshByPtr(shapeInfo, out meshDesc)) - { - shapeInfo.type = BSPhysicsShapeType.SHAPE_MESH; - shapeInfo.shapeKey = meshDesc.shapeKey; - } - else - { - if (TryGetHullByPtr(shapeInfo, out hullDesc)) - { - shapeInfo.type = BSPhysicsShapeType.SHAPE_HULL; - shapeInfo.shapeKey = hullDesc.shapeKey; - } - else - { - if (PhysicsScene.PE.IsCompound(shapeInfo)) - { - shapeInfo.type = BSPhysicsShapeType.SHAPE_COMPOUND; - } - else - { - if (PhysicsScene.PE.IsNativeShape(shapeInfo)) - { - shapeInfo.isNativeShape = true; - shapeInfo.type = BSPhysicsShapeType.SHAPE_BOX; // (technically, type doesn't matter) - } - } - } - } - - if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceAnonCollisionShape,shape={1}", BSScene.DetailLogZero, shapeInfo); - - if (shapeInfo.type != BSPhysicsShapeType.SHAPE_UNKNOWN) - { - DereferenceShape(shapeInfo, true, null); - } - else - { - PhysicsScene.Logger.ErrorFormat("{0} Could not decypher shape type. Region={1}, addr={2}", - LogHeader, PhysicsScene.RegionName, shapeInfo.AddrString); + if (shapeCallback != null) + shapeCallback(prim.PhysBody, prim.PhysShape.physShapeInfo); + prim.PhysShape.Dereference(m_physicsScene); } + prim.PhysShape = new BSShapeNull(); } // Create the geometry information in Bullet for later use. @@ -411,95 +124,75 @@ public sealed class BSShapeCollection : IDisposable // Info in prim.BSShape is updated to the new shape. // Returns 'true' if the geometry was rebuilt. // Called at taint-time! - private bool CreateGeom(bool forceRebuild, BSPhysObject prim, ShapeDestructionCallback shapeCallback) - { - bool ret = false; - bool haveShape = false; - - if (!haveShape && prim.PreferredPhysicalShape == BSPhysicsShapeType.SHAPE_CAPSULE) - { - // an avatar capsule is close to a native shape (it is not shared) - GetReferenceToNativeShape(prim, BSPhysicsShapeType.SHAPE_CAPSULE, FixedShapeKey.KEY_CAPSULE, shapeCallback); - if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,avatarCapsule,shape={1}", prim.LocalID, prim.PhysShape); - ret = true; - haveShape = true; - } - - // Compound shapes are handled special as they are rebuilt from scratch. - // This isn't too great a hardship since most of the child shapes will have already been created. - if (!haveShape && prim.PreferredPhysicalShape == BSPhysicsShapeType.SHAPE_COMPOUND) - { - ret = GetReferenceToCompoundShape(prim, shapeCallback); - if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,compoundShape,shape={1}", prim.LocalID, prim.PhysShape); - haveShape = true; - } - - if (!haveShape) - { - ret = CreateGeomNonSpecial(forceRebuild, prim, shapeCallback); - } - - return ret; - } - - // Create a mesh/hull shape or a native shape if 'nativeShapePossible' is 'true'. - public bool CreateGeomNonSpecial(bool forceRebuild, BSPhysObject prim, ShapeDestructionCallback shapeCallback) + private bool CreateGeom(bool forceRebuild, BSPhysObject prim, PhysicalDestructionCallback shapeCallback) { bool ret = false; bool haveShape = false; bool nativeShapePossible = true; PrimitiveBaseShape pbs = prim.BaseShape; + // Kludge to create the capsule for the avatar. + // TDOD: Remove/redo this when BSShapeAvatar is working!! + BSCharacter theChar = prim as BSCharacter; + if (theChar != null) + { + DereferenceExistingShape(prim, shapeCallback); + prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim, + BSPhysicsShapeType.SHAPE_CAPSULE, FixedShapeKey.KEY_CAPSULE); + ret = true; + haveShape = true; + } + // If the prim attributes are simple, this could be a simple Bullet native shape + // Native shapes work whether to object is static or physical. if (!haveShape - && pbs != null && nativeShapePossible - && ((pbs.SculptEntry && !BSParam.ShouldMeshSculptedPrim) - || (pbs.ProfileBegin == 0 && pbs.ProfileEnd == 0 - && pbs.ProfileHollow == 0 - && pbs.PathTwist == 0 && pbs.PathTwistBegin == 0 - && pbs.PathBegin == 0 && pbs.PathEnd == 0 - && pbs.PathTaperX == 0 && pbs.PathTaperY == 0 - && pbs.PathScaleX == 100 && pbs.PathScaleY == 100 - && pbs.PathShearX == 0 && pbs.PathShearY == 0) ) ) + && pbs != null + && PrimHasNoCuts(pbs) + && ( !pbs.SculptEntry || (pbs.SculptEntry && !BSParam.ShouldMeshSculptedPrim) ) + ) { // Get the scale of any existing shape so we can see if the new shape is same native type and same size. OMV.Vector3 scaleOfExistingShape = OMV.Vector3.Zero; if (prim.PhysShape.HasPhysicalShape) - scaleOfExistingShape = PhysicsScene.PE.GetLocalScaling(prim.PhysShape); + scaleOfExistingShape = m_physicsScene.PE.GetLocalScaling(prim.PhysShape.physShapeInfo); if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,maybeNative,force={1},primScale={2},primSize={3},primShape={4}", - prim.LocalID, forceRebuild, prim.Scale, prim.Size, prim.PhysShape.type); + prim.LocalID, forceRebuild, prim.Scale, prim.Size, prim.PhysShape.physShapeInfo.shapeType); - // It doesn't look like Bullet scales spheres so make sure the scales are all equal + // It doesn't look like Bullet scales native spheres so make sure the scales are all equal if ((pbs.ProfileShape == ProfileShape.HalfCircle && pbs.PathCurve == (byte)Extrusion.Curve1) && pbs.Scale.X == pbs.Scale.Y && pbs.Scale.Y == pbs.Scale.Z) { haveShape = true; if (forceRebuild - || prim.Scale != scaleOfExistingShape - || prim.PhysShape.type != BSPhysicsShapeType.SHAPE_SPHERE - ) + || prim.PhysShape.ShapeType != BSPhysicsShapeType.SHAPE_SPHERE + ) { - ret = GetReferenceToNativeShape(prim, BSPhysicsShapeType.SHAPE_SPHERE, - FixedShapeKey.KEY_SPHERE, shapeCallback); - if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,sphere,force={1},shape={2}", - prim.LocalID, forceRebuild, prim.PhysShape); + DereferenceExistingShape(prim, shapeCallback); + prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim, + BSPhysicsShapeType.SHAPE_SPHERE, FixedShapeKey.KEY_SPHERE); + ret = true; } + if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,sphere,force={1},rebuilt={2},shape={3}", + prim.LocalID, forceRebuild, ret, prim.PhysShape); } + // If we didn't make a sphere, maybe a box will work. if (!haveShape && pbs.ProfileShape == ProfileShape.Square && pbs.PathCurve == (byte)Extrusion.Straight) { haveShape = true; if (forceRebuild || prim.Scale != scaleOfExistingShape - || prim.PhysShape.type != BSPhysicsShapeType.SHAPE_BOX + || prim.PhysShape.ShapeType != BSPhysicsShapeType.SHAPE_BOX ) { - ret = GetReferenceToNativeShape( prim, BSPhysicsShapeType.SHAPE_BOX, - FixedShapeKey.KEY_BOX, shapeCallback); - if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,box,force={1},shape={2}", - prim.LocalID, forceRebuild, prim.PhysShape); + DereferenceExistingShape(prim, shapeCallback); + prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim, + BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX); + ret = true; } + if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,box,force={1},rebuilt={2},shape={3}", + prim.LocalID, forceRebuild, ret, prim.PhysShape); } } @@ -512,7 +205,20 @@ public sealed class BSShapeCollection : IDisposable return ret; } - public bool CreateGeomMeshOrHull(BSPhysObject prim, ShapeDestructionCallback shapeCallback) + // return 'true' if this shape description does not include any cutting or twisting. + public static bool PrimHasNoCuts(PrimitiveBaseShape pbs) + { + return pbs.ProfileBegin == 0 && pbs.ProfileEnd == 0 + && pbs.ProfileHollow == 0 + && pbs.PathTwist == 0 && pbs.PathTwistBegin == 0 + && pbs.PathBegin == 0 && pbs.PathEnd == 0 + && pbs.PathTaperX == 0 && pbs.PathTaperY == 0 + && pbs.PathScaleX == 100 && pbs.PathScaleY == 100 + && pbs.PathShearX == 0 && pbs.PathShearY == 0; + } + + // return 'true' if the prim's shape was changed. + private bool CreateGeomMeshOrHull(BSPhysObject prim, PhysicalDestructionCallback shapeCallback) { bool ret = false; @@ -520,404 +226,110 @@ public sealed class BSShapeCollection : IDisposable // made. Native shapes work in either case. if (prim.IsPhysical && BSParam.ShouldUseHullsForPhysicalObjects) { - // Update prim.BSShape to reference a hull of this shape. - ret = GetReferenceToHull(prim,shapeCallback); - if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,hull,shape={1},key={2}", - prim.LocalID, prim.PhysShape, prim.PhysShape.shapeKey.ToString("X")); + // Use a simple, single mesh convex hull shape if the object is simple enough + BSShape potentialHull = null; + + PrimitiveBaseShape pbs = prim.BaseShape; + if (BSParam.ShouldUseSingleConvexHullForPrims + && pbs != null + && !pbs.SculptEntry + && PrimHasNoCuts(pbs) + ) + { + potentialHull = BSShapeConvexHull.GetReference(m_physicsScene, false /* forceRebuild */, prim); + } + else + { + potentialHull = BSShapeHull.GetReference(m_physicsScene, false /*forceRebuild*/, prim); + } + + // If the current shape is not what is on the prim at the moment, time to change. + if (!prim.PhysShape.HasPhysicalShape + || potentialHull.ShapeType != prim.PhysShape.ShapeType + || potentialHull.physShapeInfo.shapeKey != prim.PhysShape.physShapeInfo.shapeKey) + { + DereferenceExistingShape(prim, shapeCallback); + prim.PhysShape = potentialHull; + ret = true; + } + else + { + // The current shape on the prim is the correct one. We don't need the potential reference. + potentialHull.Dereference(m_physicsScene); + } + if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,hull,shape={1}", prim.LocalID, prim.PhysShape); } else { - ret = GetReferenceToMesh(prim, shapeCallback); - if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,mesh,shape={1},key={2}", - prim.LocalID, prim.PhysShape, prim.PhysShape.shapeKey.ToString("X")); + // Update prim.BSShape to reference a mesh of this shape. + BSShape potentialMesh = BSShapeMesh.GetReference(m_physicsScene, false /*forceRebuild*/, prim); + // If the current shape is not what is on the prim at the moment, time to change. + if (!prim.PhysShape.HasPhysicalShape + || potentialMesh.ShapeType != prim.PhysShape.ShapeType + || potentialMesh.physShapeInfo.shapeKey != prim.PhysShape.physShapeInfo.shapeKey) + { + DereferenceExistingShape(prim, shapeCallback); + prim.PhysShape = potentialMesh; + ret = true; + } + else + { + // We don't need this reference to the mesh that is already being using. + potentialMesh.Dereference(m_physicsScene); + } + if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,mesh,shape={1}", prim.LocalID, prim.PhysShape); } return ret; } - // Creates a native shape and assignes it to prim.BSShape. - // "Native" shapes are never shared. they are created here and destroyed in DereferenceShape(). - private bool GetReferenceToNativeShape(BSPhysObject prim, - BSPhysicsShapeType shapeType, FixedShapeKey shapeKey, - ShapeDestructionCallback shapeCallback) + // Track another user of a body. + // We presume the caller has allocated the body. + // Bodies only have one user so the body is just put into the world if not already there. + private void ReferenceBody(BulletBody body) { - // release any previous shape - DereferenceShape(prim.PhysShape, true, shapeCallback); - - BulletShape newShape = BuildPhysicalNativeShape(prim, shapeType, shapeKey); - - // Don't need to do a 'ReferenceShape()' here because native shapes are not shared. - if (DDetail) DetailLog("{0},BSShapeCollection.AddNativeShapeToPrim,create,newshape={1},scale={2}", - prim.LocalID, newShape, prim.Scale); - - // native shapes are scaled by Bullet - prim.PhysShape = newShape; - return true; - } - - private BulletShape BuildPhysicalNativeShape(BSPhysObject prim, BSPhysicsShapeType shapeType, - FixedShapeKey shapeKey) - { - BulletShape newShape; - // Need to make sure the passed shape information is for the native type. - ShapeData nativeShapeData = new ShapeData(); - nativeShapeData.Type = shapeType; - nativeShapeData.ID = prim.LocalID; - nativeShapeData.Scale = prim.Scale; - nativeShapeData.Size = prim.Scale; // unneeded, I think. - nativeShapeData.MeshKey = (ulong)shapeKey; - nativeShapeData.HullKey = (ulong)shapeKey; - - if (shapeType == BSPhysicsShapeType.SHAPE_CAPSULE) + lock (m_collectionActivityLock) { - - newShape = PhysicsScene.PE.BuildCapsuleShape(PhysicsScene.World, 1f, 1f, prim.Scale); - if (DDetail) DetailLog("{0},BSShapeCollection.BuiletPhysicalNativeShape,capsule,scale={1}", prim.LocalID, prim.Scale); - } - else - { - // Native shapes are scaled in Bullet so set the scaling to the size - newShape = PhysicsScene.PE.BuildNativeShape(PhysicsScene.World, nativeShapeData); - - } - if (!newShape.HasPhysicalShape) - { - PhysicsScene.Logger.ErrorFormat("{0} BuildPhysicalNativeShape failed. ID={1}, shape={2}", - LogHeader, prim.LocalID, shapeType); - } - newShape.shapeKey = (System.UInt64)shapeKey; - newShape.isNativeShape = true; - - return newShape; - } - - // Builds a mesh shape in the physical world and updates prim.BSShape. - // Dereferences previous shape in BSShape and adds a reference for this new shape. - // Returns 'true' of a mesh was actually built. Otherwise . - // Called at taint-time! - private bool GetReferenceToMesh(BSPhysObject prim, ShapeDestructionCallback shapeCallback) - { - BulletShape newShape = new BulletShape(); - - float lod; - System.UInt64 newMeshKey = ComputeShapeKey(prim.Size, prim.BaseShape, out lod); - - // if this new shape is the same as last time, don't recreate the mesh - if (newMeshKey == prim.PhysShape.shapeKey && prim.PhysShape.type == BSPhysicsShapeType.SHAPE_MESH) - return false; - - if (DDetail) DetailLog("{0},BSShapeCollection.GetReferenceToMesh,create,oldKey={1},newKey={2}", - prim.LocalID, prim.PhysShape.shapeKey.ToString("X"), newMeshKey.ToString("X")); - - // Since we're recreating new, get rid of the reference to the previous shape - DereferenceShape(prim.PhysShape, true, shapeCallback); - - newShape = CreatePhysicalMesh(prim.PhysObjectName, newMeshKey, prim.BaseShape, prim.Size, lod); - // Take evasive action if the mesh was not constructed. - newShape = VerifyMeshCreated(newShape, prim); - - ReferenceShape(newShape); - - prim.PhysShape = newShape; - - return true; // 'true' means a new shape has been added to this prim - } - - private BulletShape CreatePhysicalMesh(string objName, System.UInt64 newMeshKey, PrimitiveBaseShape pbs, OMV.Vector3 size, float lod) - { - BulletShape newShape = new BulletShape(); - IMesh meshData = null; - - MeshDesc meshDesc; - if (Meshes.TryGetValue(newMeshKey, out meshDesc)) - { - // If the mesh has already been built just use it. - newShape = meshDesc.shape.Clone(); - } - else - { - meshData = PhysicsScene.mesher.CreateMesh(objName, pbs, size, lod, true, false); - - if (meshData != null) + if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,newBody,body={1}", body.ID, body); + if (!m_physicsScene.PE.IsInWorld(m_physicsScene.World, body)) { - int[] indices = meshData.getIndexListAsInt(); - List vertices = meshData.getVertexList(); - - float[] verticesAsFloats = new float[vertices.Count * 3]; - int vi = 0; - foreach (OMV.Vector3 vv in vertices) - { - verticesAsFloats[vi++] = vv.X; - verticesAsFloats[vi++] = vv.Y; - verticesAsFloats[vi++] = vv.Z; - } - - // m_log.DebugFormat("{0}: BSShapeCollection.CreatePhysicalMesh: calling CreateMesh. lid={1}, key={2}, indices={3}, vertices={4}", - // LogHeader, prim.LocalID, newMeshKey, indices.Length, vertices.Count); - - newShape = PhysicsScene.PE.CreateMeshShape(PhysicsScene.World, - indices.GetLength(0), indices, vertices.Count, verticesAsFloats); + m_physicsScene.PE.AddObjectToWorld(m_physicsScene.World, body); + if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,addedToWorld,ref={1}", body.ID, body); } } - newShape.shapeKey = newMeshKey; - - return newShape; } - // See that hull shape exists in the physical world and update prim.BSShape. - // We could be creating the hull because scale changed or whatever. - private bool GetReferenceToHull(BSPhysObject prim, ShapeDestructionCallback shapeCallback) + // Release the usage of a body. + // Called when releasing use of a BSBody. BSShape is handled separately. + // Called in taint time. + public void DereferenceBody(BulletBody body, PhysicalDestructionCallback bodyCallback ) { - BulletShape newShape; + if (!body.HasPhysicalBody) + return; - float lod; - System.UInt64 newHullKey = ComputeShapeKey(prim.Size, prim.BaseShape, out lod); + m_physicsScene.AssertInTaintTime("BSShapeCollection.DereferenceBody"); - // if the hull hasn't changed, don't rebuild it - if (newHullKey == prim.PhysShape.shapeKey && prim.PhysShape.type == BSPhysicsShapeType.SHAPE_HULL) - return false; - - if (DDetail) DetailLog("{0},BSShapeCollection.GetReferenceToHull,create,oldKey={1},newKey={2}", - prim.LocalID, prim.PhysShape.shapeKey.ToString("X"), newHullKey.ToString("X")); - - // Remove usage of the previous shape. - DereferenceShape(prim.PhysShape, true, shapeCallback); - - newShape = CreatePhysicalHull(prim.PhysObjectName, newHullKey, prim.BaseShape, prim.Size, lod); - newShape = VerifyMeshCreated(newShape, prim); - - ReferenceShape(newShape); - - prim.PhysShape = newShape; - return true; // 'true' means a new shape has been added to this prim - } - - List m_hulls; - private BulletShape CreatePhysicalHull(string objName, System.UInt64 newHullKey, PrimitiveBaseShape pbs, OMV.Vector3 size, float lod) - { - - BulletShape newShape = new BulletShape(); - IntPtr hullPtr = IntPtr.Zero; - - HullDesc hullDesc; - if (Hulls.TryGetValue(newHullKey, out hullDesc)) + lock (m_collectionActivityLock) { - // If the hull shape already is created, just use it. - newShape = hullDesc.shape.Clone(); + if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceBody,DestroyingBody,body={1}", body.ID, body); + // If the caller needs to know the old body is going away, pass the event up. + if (bodyCallback != null) + bodyCallback(body, null); + + // Removing an object not in the world is a NOOP + m_physicsScene.PE.RemoveObjectFromWorld(m_physicsScene.World, body); + + // Zero any reference to the shape so it is not freed when the body is deleted. + m_physicsScene.PE.SetCollisionShape(m_physicsScene.World, body, null); + + m_physicsScene.PE.DestroyObject(m_physicsScene.World, body); } - else - { - // Build a new hull in the physical world - // Pass true for physicalness as this creates some sort of bounding box which we don't need - IMesh meshData = PhysicsScene.mesher.CreateMesh(objName, pbs, size, lod, true, false); - if (meshData != null) - { - - int[] indices = meshData.getIndexListAsInt(); - List vertices = meshData.getVertexList(); - - //format conversion from IMesh format to DecompDesc format - List convIndices = new List(); - List convVertices = new List(); - for (int ii = 0; ii < indices.GetLength(0); ii++) - { - convIndices.Add(indices[ii]); - } - foreach (OMV.Vector3 vv in vertices) - { - convVertices.Add(new float3(vv.X, vv.Y, vv.Z)); - } - - // setup and do convex hull conversion - m_hulls = new List(); - DecompDesc dcomp = new DecompDesc(); - dcomp.mIndices = convIndices; - dcomp.mVertices = convVertices; - ConvexBuilder convexBuilder = new ConvexBuilder(HullReturn); - // create the hull into the _hulls variable - convexBuilder.process(dcomp); - - // Convert the vertices and indices for passing to unmanaged. - // The hull information is passed as a large floating point array. - // The format is: - // convHulls[0] = number of hulls - // convHulls[1] = number of vertices in first hull - // convHulls[2] = hull centroid X coordinate - // convHulls[3] = hull centroid Y coordinate - // convHulls[4] = hull centroid Z coordinate - // convHulls[5] = first hull vertex X - // convHulls[6] = first hull vertex Y - // convHulls[7] = first hull vertex Z - // convHulls[8] = second hull vertex X - // ... - // convHulls[n] = number of vertices in second hull - // convHulls[n+1] = second hull centroid X coordinate - // ... - // - // TODO: is is very inefficient. Someday change the convex hull generator to return - // data structures that do not need to be converted in order to pass to Bullet. - // And maybe put the values directly into pinned memory rather than marshaling. - int hullCount = m_hulls.Count; - int totalVertices = 1; // include one for the count of the hulls - foreach (ConvexResult cr in m_hulls) - { - totalVertices += 4; // add four for the vertex count and centroid - totalVertices += cr.HullIndices.Count * 3; // we pass just triangles - } - float[] convHulls = new float[totalVertices]; - - convHulls[0] = (float)hullCount; - int jj = 1; - foreach (ConvexResult cr in m_hulls) - { - // copy vertices for index access - float3[] verts = new float3[cr.HullVertices.Count]; - int kk = 0; - foreach (float3 ff in cr.HullVertices) - { - verts[kk++] = ff; - } - - // add to the array one hull's worth of data - convHulls[jj++] = cr.HullIndices.Count; - convHulls[jj++] = 0f; // centroid x,y,z - convHulls[jj++] = 0f; - convHulls[jj++] = 0f; - foreach (int ind in cr.HullIndices) - { - convHulls[jj++] = verts[ind].x; - convHulls[jj++] = verts[ind].y; - convHulls[jj++] = verts[ind].z; - } - } - // create the hull data structure in Bullet - newShape = PhysicsScene.PE.CreateHullShape(PhysicsScene.World, hullCount, convHulls); - } - } - - newShape.shapeKey = newHullKey; - - return newShape; - } - - // Callback from convex hull creater with a newly created hull. - // Just add it to our collection of hulls for this shape. - private void HullReturn(ConvexResult result) - { - m_hulls.Add(result); - return; - } - - // Compound shapes are always built from scratch. - // This shouldn't be to bad since most of the parts will be meshes that had been built previously. - private bool GetReferenceToCompoundShape(BSPhysObject prim, ShapeDestructionCallback shapeCallback) - { - // Remove reference to the old shape - // Don't need to do this as the shape is freed when the new root shape is created below. - // DereferenceShape(prim.PhysShape, true, shapeCallback); - - - BulletShape cShape = PhysicsScene.PE.CreateCompoundShape(PhysicsScene.World, false); - - // Create the shape for the root prim and add it to the compound shape. Cannot be a native shape. - CreateGeomMeshOrHull(prim, shapeCallback); - PhysicsScene.PE.AddChildShapeToCompoundShape(cShape, prim.PhysShape, OMV.Vector3.Zero, OMV.Quaternion.Identity); - if (DDetail) DetailLog("{0},BSShapeCollection.GetReferenceToCompoundShape,addRootPrim,compShape={1},rootShape={2}", - prim.LocalID, cShape, prim.PhysShape); - - prim.PhysShape = cShape; - - return true; - } - - // Create a hash of all the shape parameters to be used as a key - // for this particular shape. - private System.UInt64 ComputeShapeKey(OMV.Vector3 size, PrimitiveBaseShape pbs, out float retLod) - { - // level of detail based on size and type of the object - float lod = BSParam.MeshLOD; - if (pbs.SculptEntry) - lod = BSParam.SculptLOD; - - // Mega prims usually get more detail because one can interact with shape approximations at this size. - float maxAxis = Math.Max(size.X, Math.Max(size.Y, size.Z)); - if (maxAxis > BSParam.MeshMegaPrimThreshold) - lod = BSParam.MeshMegaPrimLOD; - - retLod = lod; - return pbs.GetMeshKey(size, lod); - } - // For those who don't want the LOD - private System.UInt64 ComputeShapeKey(OMV.Vector3 size, PrimitiveBaseShape pbs) - { - float lod; - return ComputeShapeKey(size, pbs, out lod); - } - - // The creation of a mesh or hull can fail if an underlying asset is not available. - // There are two cases: 1) the asset is not in the cache and it needs to be fetched; - // and 2) the asset cannot be converted (like failed decompression of JPEG2000s). - // The first case causes the asset to be fetched. The second case requires - // us to not loop forever. - // Called after creating a physical mesh or hull. If the physical shape was created, - // just return. - private BulletShape VerifyMeshCreated(BulletShape newShape, BSPhysObject prim) - { - // If the shape was successfully created, nothing more to do - if (newShape.HasPhysicalShape) - return newShape; - - // If this mesh has an underlying asset and we have not failed getting it before, fetch the asset - if (prim.BaseShape.SculptEntry && !prim.LastAssetBuildFailed && prim.BaseShape.SculptTexture != OMV.UUID.Zero) - { - prim.LastAssetBuildFailed = true; - BSPhysObject xprim = prim; - DetailLog("{0},BSShapeCollection.VerifyMeshCreated,fetchAsset,lID={1},lastFailed={2}", - LogHeader, prim.LocalID, prim.LastAssetBuildFailed); - Util.FireAndForget(delegate - { - RequestAssetDelegate assetProvider = PhysicsScene.RequestAssetMethod; - if (assetProvider != null) - { - BSPhysObject yprim = xprim; // probably not necessary, but, just in case. - assetProvider(yprim.BaseShape.SculptTexture, delegate(AssetBase asset) - { - if (!yprim.BaseShape.SculptEntry) - return; - if (yprim.BaseShape.SculptTexture.ToString() != asset.ID) - return; - - yprim.BaseShape.SculptData = asset.Data; - // This will cause the prim to see that the filler shape is not the right - // one and try again to build the object. - // No race condition with the normal shape setting since the rebuild is at taint time. - yprim.ForceBodyShapeRebuild(false); - - }); - } - }); - } - else - { - if (prim.LastAssetBuildFailed) - { - PhysicsScene.Logger.ErrorFormat("{0} Mesh failed to fetch asset. lID={1}, texture={2}", - LogHeader, prim.LocalID, prim.BaseShape.SculptTexture); - } - } - - // While we figure out the real problem, stick a simple native shape on the object. - BulletShape fillinShape = - BuildPhysicalNativeShape(prim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX); - - return fillinShape; } // Create a body object in Bullet. // Updates prim.BSBody with the information about the new body if one is created. // Returns 'true' if an object was actually created. // Called at taint-time. - private bool CreateBody(bool forceRebuild, BSPhysObject prim, BulletWorld sim, BulletShape shape, - BodyDestructionCallback bodyCallback) + private bool CreateBody(bool forceRebuild, BSPhysObject prim, BulletWorld sim, PhysicalDestructionCallback bodyCallback) { bool ret = false; @@ -928,33 +340,34 @@ public sealed class BSShapeCollection : IDisposable // If not a solid object, body is a GhostObject. Otherwise a RigidBody. if (!mustRebuild) { - CollisionObjectTypes bodyType = (CollisionObjectTypes)PhysicsScene.PE.GetBodyType(prim.PhysBody); + CollisionObjectTypes bodyType = (CollisionObjectTypes)m_physicsScene.PE.GetBodyType(prim.PhysBody); if (prim.IsSolid && bodyType != CollisionObjectTypes.CO_RIGID_BODY || !prim.IsSolid && bodyType != CollisionObjectTypes.CO_GHOST_OBJECT) { // If the collisionObject is not the correct type for solidness, rebuild what's there mustRebuild = true; + if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,forceRebuildBecauseChangingBodyType,bodyType={1}", prim.LocalID, bodyType); } } if (mustRebuild || forceRebuild) { // Free any old body - DereferenceBody(prim.PhysBody, true, bodyCallback); + DereferenceBody(prim.PhysBody, bodyCallback); BulletBody aBody; if (prim.IsSolid) { - aBody = PhysicsScene.PE.CreateBodyFromShape(sim, shape, prim.LocalID, prim.RawPosition, prim.RawOrientation); - if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,mesh,body={1}", prim.LocalID, aBody); + aBody = m_physicsScene.PE.CreateBodyFromShape(sim, prim.PhysShape.physShapeInfo, prim.LocalID, prim.RawPosition, prim.RawOrientation); + if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,rigid,body={1}", prim.LocalID, aBody); } else { - aBody = PhysicsScene.PE.CreateGhostFromShape(sim, shape, prim.LocalID, prim.RawPosition, prim.RawOrientation); + aBody = m_physicsScene.PE.CreateGhostFromShape(sim, prim.PhysShape.physShapeInfo, prim.LocalID, prim.RawPosition, prim.RawOrientation); if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,ghost,body={1}", prim.LocalID, aBody); } - ReferenceBody(aBody, true); + ReferenceBody(aBody); prim.PhysBody = aBody; @@ -964,46 +377,10 @@ public sealed class BSShapeCollection : IDisposable return ret; } - private bool TryGetMeshByPtr(BulletShape shape, out MeshDesc outDesc) - { - bool ret = false; - MeshDesc foundDesc = new MeshDesc(); - foreach (MeshDesc md in Meshes.Values) - { - if (md.shape.ReferenceSame(shape)) - { - foundDesc = md; - ret = true; - break; - } - - } - outDesc = foundDesc; - return ret; - } - - private bool TryGetHullByPtr(BulletShape shape, out HullDesc outDesc) - { - bool ret = false; - HullDesc foundDesc = new HullDesc(); - foreach (HullDesc hd in Hulls.Values) - { - if (hd.shape.ReferenceSame(shape)) - { - foundDesc = hd; - ret = true; - break; - } - - } - outDesc = foundDesc; - return ret; - } - private void DetailLog(string msg, params Object[] args) { - if (PhysicsScene.PhysicsLogging.Enabled) - PhysicsScene.DetailLog(msg, args); + if (m_physicsScene.PhysicsLogging.Enabled) + m_physicsScene.DetailLog(msg, args); } } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSShapes.cs b/OpenSim/Region/Physics/BulletSPlugin/BSShapes.cs index c75eb9bcb2..9d47657154 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSShapes.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSShapes.cs @@ -27,118 +27,287 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; +using OpenSim.Framework; +using OpenSim.Region.Physics.Manager; +using OpenSim.Region.Physics.ConvexDecompositionDotNet; + +using OMV = OpenMetaverse; + namespace OpenSim.Region.Physics.BulletSPlugin { public abstract class BSShape { - public IntPtr ptr { get; set; } - public BSPhysicsShapeType type { get; set; } - public System.UInt64 key { get; set; } + private static string LogHeader = "[BULLETSIM SHAPE]"; + public int referenceCount { get; set; } public DateTime lastReferenced { get; set; } + public BulletShape physShapeInfo { get; set; } public BSShape() { - ptr = IntPtr.Zero; - type = BSPhysicsShapeType.SHAPE_UNKNOWN; - key = 0; - referenceCount = 0; + referenceCount = 1; + lastReferenced = DateTime.Now; + physShapeInfo = new BulletShape(); + } + public BSShape(BulletShape pShape) + { + referenceCount = 1; + lastReferenced = DateTime.Now; + physShapeInfo = pShape; + } + + // Get another reference to this shape. + public abstract BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim); + + // Called when this shape is being used again. + // Used internally. External callers should call instance.GetReference() to properly copy/reference + // the shape. + protected virtual void IncrementReference() + { + referenceCount++; lastReferenced = DateTime.Now; } - // Get a reference to a physical shape. Create if it doesn't exist - public static BSShape GetShapeReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) + // Called when this shape is being used again. + protected virtual void DecrementReference() { - BSShape ret = null; - - if (prim.PreferredPhysicalShape == BSPhysicsShapeType.SHAPE_CAPSULE) - { - // an avatar capsule is close to a native shape (it is not shared) - ret = BSShapeNative.GetReference(physicsScene, prim, BSPhysicsShapeType.SHAPE_CAPSULE, - FixedShapeKey.KEY_CAPSULE); - physicsScene.DetailLog("{0},BSShape.GetShapeReference,avatarCapsule,shape={1}", prim.LocalID, ret); - } - - // Compound shapes are handled special as they are rebuilt from scratch. - // This isn't too great a hardship since most of the child shapes will already been created. - if (ret == null && prim.PreferredPhysicalShape == BSPhysicsShapeType.SHAPE_COMPOUND) - { - // Getting a reference to a compound shape gets you the compound shape with the root prim shape added - ret = BSShapeCompound.GetReference(prim); - physicsScene.DetailLog("{0},BSShapeCollection.CreateGeom,compoundShape,shape={1}", prim.LocalID, ret); - } - - if (ret == null) - ret = GetShapeReferenceNonSpecial(physicsScene, forceRebuild, prim); - - return ret; - } - public static BSShape GetShapeReferenceNonSpecial(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) - { - return null; - } - public static BSShape GetShapeReferenceNonNative(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) - { - return null; + referenceCount--; + lastReferenced = DateTime.Now; } // Release the use of a physical shape. public abstract void Dereference(BSScene physicsScene); - // All shapes have a static call to get a reference to the physical shape - // protected abstract static BSShape GetReference(); + // Return 'true' if there is an allocated physics physical shape under this class instance. + public virtual bool HasPhysicalShape + { + get + { + if (physShapeInfo != null) + return physShapeInfo.HasPhysicalShape; + return false; + } + } + public virtual BSPhysicsShapeType ShapeType + { + get + { + BSPhysicsShapeType ret = BSPhysicsShapeType.SHAPE_UNKNOWN; + if (physShapeInfo != null && physShapeInfo.HasPhysicalShape) + ret = physShapeInfo.shapeType; + return ret; + } + } // Returns a string for debugging that uniquily identifies the memory used by this instance - public string AddrString + public virtual string AddrString { - get { return ptr.ToString("X"); } + get + { + if (physShapeInfo != null) + return physShapeInfo.AddrString; + return "unknown"; + } } public override string ToString() { StringBuilder buff = new StringBuilder(); - buff.Append(""); return buff.ToString(); } + + #region Common shape routines + // Create a hash of all the shape parameters to be used as a key for this particular shape. + public static System.UInt64 ComputeShapeKey(OMV.Vector3 size, PrimitiveBaseShape pbs, out float retLod) + { + // level of detail based on size and type of the object + float lod = BSParam.MeshLOD; + if (pbs.SculptEntry) + lod = BSParam.SculptLOD; + + // Mega prims usually get more detail because one can interact with shape approximations at this size. + float maxAxis = Math.Max(size.X, Math.Max(size.Y, size.Z)); + if (maxAxis > BSParam.MeshMegaPrimThreshold) + lod = BSParam.MeshMegaPrimLOD; + + retLod = lod; + return pbs.GetMeshKey(size, lod); + } + + // The creation of a mesh or hull can fail if an underlying asset is not available. + // There are two cases: 1) the asset is not in the cache and it needs to be fetched; + // and 2) the asset cannot be converted (like failed decompression of JPEG2000s). + // The first case causes the asset to be fetched. The second case requires + // us to not loop forever. + // Called after creating a physical mesh or hull. If the physical shape was created, + // just return. + public static BulletShape VerifyMeshCreated(BSScene physicsScene, BulletShape newShape, BSPhysObject prim) + { + // If the shape was successfully created, nothing more to do + if (newShape.HasPhysicalShape) + return newShape; + + // VerifyMeshCreated is called after trying to create the mesh. If we think the asset had been + // fetched but we end up here again, the meshing of the asset must have failed. + // Prevent trying to keep fetching the mesh by declaring failure. + if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched) + { + prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Failed; + physicsScene.Logger.WarnFormat("{0} Fetched asset would not mesh. {1}, texture={2}", + LogHeader, prim.PhysObjectName, prim.BaseShape.SculptTexture); + physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,setFailed,objNam={1},tex={2}", + prim.LocalID, prim.PhysObjectName, prim.BaseShape.SculptTexture); + } + else + { + // If this mesh has an underlying asset and we have not failed getting it before, fetch the asset + if (prim.BaseShape.SculptEntry + && prim.PrimAssetState != BSPhysObject.PrimAssetCondition.Failed + && prim.PrimAssetState != BSPhysObject.PrimAssetCondition.Waiting + && prim.BaseShape.SculptTexture != OMV.UUID.Zero + ) + { + physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,fetchAsset,objNam={1},tex={2}", + prim.LocalID, prim.PhysObjectName, prim.BaseShape.SculptTexture); + // Multiple requestors will know we're waiting for this asset + prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Waiting; + + BSPhysObject xprim = prim; + Util.FireAndForget(delegate + { + // physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,inFireAndForget", xprim.LocalID); + RequestAssetDelegate assetProvider = physicsScene.RequestAssetMethod; + if (assetProvider != null) + { + BSPhysObject yprim = xprim; // probably not necessary, but, just in case. + assetProvider(yprim.BaseShape.SculptTexture, delegate(AssetBase asset) + { + // physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,assetProviderCallback", xprim.LocalID); + bool assetFound = false; + string mismatchIDs = String.Empty; // DEBUG DEBUG + if (asset != null && yprim.BaseShape.SculptEntry) + { + if (yprim.BaseShape.SculptTexture.ToString() == asset.ID) + { + yprim.BaseShape.SculptData = asset.Data; + // This will cause the prim to see that the filler shape is not the right + // one and try again to build the object. + // No race condition with the normal shape setting since the rebuild is at taint time. + yprim.PrimAssetState = BSPhysObject.PrimAssetCondition.Fetched; + yprim.ForceBodyShapeRebuild(false /* inTaintTime */); + assetFound = true; + } + else + { + mismatchIDs = yprim.BaseShape.SculptTexture.ToString() + "/" + asset.ID; + } + } + if (!assetFound) + { + yprim.PrimAssetState = BSPhysObject.PrimAssetCondition.Failed; + } + physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,fetchAssetCallback,found={1},isSculpt={2},ids={3}", + yprim.LocalID, assetFound, yprim.BaseShape.SculptEntry, mismatchIDs ); + }); + } + else + { + xprim.PrimAssetState = BSPhysObject.PrimAssetCondition.Failed; + physicsScene.Logger.ErrorFormat("{0} Physical object requires asset but no asset provider. Name={1}", + LogHeader, physicsScene.Name); + } + }); + } + else + { + if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Failed) + { + physicsScene.Logger.WarnFormat("{0} Mesh failed to fetch asset. obj={1}, texture={2}", + LogHeader, prim.PhysObjectName, prim.BaseShape.SculptTexture); + physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,wasFailed,objNam={1},tex={2}", + prim.LocalID, prim.PhysObjectName, prim.BaseShape.SculptTexture); + } + } + } + + // While we wait for the mesh defining asset to be loaded, stick in a simple box for the object. + BSShape fillShape = BSShapeNative.GetReference(physicsScene, prim, BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX); + physicsScene.DetailLog("{0},BSShape.VerifyMeshCreated,boxTempShape", prim.LocalID); + + return fillShape.physShapeInfo; + } + + #endregion // Common shape routines } +// ============================================================================================================ public class BSShapeNull : BSShape { public BSShapeNull() : base() { } public static BSShape GetReference() { return new BSShapeNull(); } + public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) { return new BSShapeNull(); } public override void Dereference(BSScene physicsScene) { /* The magic of garbage collection will make this go away */ } } +// ============================================================================================================ public class BSShapeNative : BSShape { private static string LogHeader = "[BULLETSIM SHAPE NATIVE]"; - public BSShapeNative() : base() + public BSShapeNative(BulletShape pShape) : base(pShape) { } - public static BSShape GetReference(BSScene physicsScene, BSPhysObject prim, - BSPhysicsShapeType shapeType, FixedShapeKey shapeKey) - { - // Native shapes are not shared and are always built anew. - //return new BSShapeNative(physicsScene, prim, shapeType, shapeKey); - return null; - } - private BSShapeNative(BSScene physicsScene, BSPhysObject prim, - BSPhysicsShapeType shapeType, FixedShapeKey shapeKey) + public static BSShape GetReference(BSScene physicsScene, BSPhysObject prim, + BSPhysicsShapeType shapeType, FixedShapeKey shapeKey) { + // Native shapes are not shared and are always built anew. + return new BSShapeNative(CreatePhysicalNativeShape(physicsScene, prim, shapeType, shapeKey)); + } + + public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) + { + // Native shapes are not shared so we return a new shape. + return new BSShapeNative(CreatePhysicalNativeShape(pPhysicsScene, pPrim, + physShapeInfo.shapeType, (FixedShapeKey)physShapeInfo.shapeKey) ); + } + + // Make this reference to the physical shape go away since native shapes are not shared. + public override void Dereference(BSScene physicsScene) + { + // Native shapes are not tracked and are released immediately + lock (physShapeInfo) + { + if (physShapeInfo.HasPhysicalShape) + { + physicsScene.DetailLog("{0},BSShapeNative.Dereference,deleteNativeShape,shape={1}", BSScene.DetailLogZero, this); + physicsScene.PE.DeleteCollisionShape(physicsScene.World, physShapeInfo); + } + physShapeInfo.Clear(); + // Garbage collection will free up this instance. + } + } + + private static BulletShape CreatePhysicalNativeShape(BSScene physicsScene, BSPhysObject prim, + BSPhysicsShapeType shapeType, FixedShapeKey shapeKey) + { + BulletShape newShape; + ShapeData nativeShapeData = new ShapeData(); nativeShapeData.Type = shapeType; nativeShapeData.ID = prim.LocalID; @@ -147,86 +316,760 @@ public class BSShapeNative : BSShape nativeShapeData.MeshKey = (ulong)shapeKey; nativeShapeData.HullKey = (ulong)shapeKey; - - /* if (shapeType == BSPhysicsShapeType.SHAPE_CAPSULE) { - ptr = PhysicsScene.PE.BuildCapsuleShape(physicsScene.World, 1f, 1f, prim.Scale); - physicsScene.DetailLog("{0},BSShapeCollection.BuiletPhysicalNativeShape,capsule,scale={1}", prim.LocalID, prim.Scale); + newShape = physicsScene.PE.BuildCapsuleShape(physicsScene.World, 1f, 1f, prim.Scale); + physicsScene.DetailLog("{0},BSShapeNative,capsule,scale={1}", prim.LocalID, prim.Scale); } else { - ptr = PhysicsScene.PE.BuildNativeShape(physicsScene.World, nativeShapeData); + newShape = physicsScene.PE.BuildNativeShape(physicsScene.World, nativeShapeData); } - if (ptr == IntPtr.Zero) + if (!newShape.HasPhysicalShape) { physicsScene.Logger.ErrorFormat("{0} BuildPhysicalNativeShape failed. ID={1}, shape={2}", LogHeader, prim.LocalID, shapeType); } - type = shapeType; - key = (UInt64)shapeKey; - */ - } - // Make this reference to the physical shape go away since native shapes are not shared. - public override void Dereference(BSScene physicsScene) - { - /* - // Native shapes are not tracked and are released immediately - physicsScene.DetailLog("{0},BSShapeCollection.DereferenceShape,deleteNativeShape,shape={1}", BSScene.DetailLogZero, this); - PhysicsScene.PE.DeleteCollisionShape(physicsScene.World, this); - ptr = IntPtr.Zero; - // Garbage collection will free up this instance. - */ + newShape.shapeType = shapeType; + newShape.isNativeShape = true; + newShape.shapeKey = (UInt64)shapeKey; + return newShape; } + } +// ============================================================================================================ public class BSShapeMesh : BSShape { private static string LogHeader = "[BULLETSIM SHAPE MESH]"; - private static Dictionary Meshes = new Dictionary(); + public static Dictionary Meshes = new Dictionary(); - public BSShapeMesh() : base() + public BSShapeMesh(BulletShape pShape) : base(pShape) { } - public static BSShape GetReference() { return new BSShapeNull(); } - public override void Dereference(BSScene physicsScene) { } + public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) + { + float lod; + System.UInt64 newMeshKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod); + + BSShapeMesh retMesh = null; + lock (Meshes) + { + if (Meshes.TryGetValue(newMeshKey, out retMesh)) + { + // The mesh has already been created. Return a new reference to same. + retMesh.IncrementReference(); + } + else + { + retMesh = new BSShapeMesh(new BulletShape()); + // An instance of this mesh has not been created. Build and remember same. + BulletShape newShape = retMesh.CreatePhysicalMesh(physicsScene, prim, newMeshKey, prim.BaseShape, prim.Size, lod); + + // Check to see if mesh was created (might require an asset). + newShape = VerifyMeshCreated(physicsScene, newShape, prim); + if (!newShape.isNativeShape) + { + // If a mesh was what was created, remember the built shape for later sharing. + Meshes.Add(newMeshKey, retMesh); + } + + retMesh.physShapeInfo = newShape; + } + } + physicsScene.DetailLog("{0},BSShapeMesh,getReference,mesh={1},size={2},lod={3}", prim.LocalID, retMesh, prim.Size, lod); + return retMesh; + } + public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) + { + // Another reference to this shape is just counted. + IncrementReference(); + return this; + } + public override void Dereference(BSScene physicsScene) + { + lock (Meshes) + { + this.DecrementReference(); + physicsScene.DetailLog("{0},BSShapeMesh.Dereference,shape={1}", BSScene.DetailLogZero, this); + // TODO: schedule aging and destruction of unused meshes. + } + } + // Loop through all the known meshes and return the description based on the physical address. + public static bool TryGetMeshByPtr(BulletShape pShape, out BSShapeMesh outMesh) + { + bool ret = false; + BSShapeMesh foundDesc = null; + lock (Meshes) + { + foreach (BSShapeMesh sm in Meshes.Values) + { + if (sm.physShapeInfo.ReferenceSame(pShape)) + { + foundDesc = sm; + ret = true; + break; + } + + } + } + outMesh = foundDesc; + return ret; + } + private BulletShape CreatePhysicalMesh(BSScene physicsScene, BSPhysObject prim, System.UInt64 newMeshKey, + PrimitiveBaseShape pbs, OMV.Vector3 size, float lod) + { + BulletShape newShape = new BulletShape(); + + IMesh meshData = physicsScene.mesher.CreateMesh(prim.PhysObjectName, pbs, size, lod, + false, // say it is not physical so a bounding box is not built + false // do not cache the mesh and do not use previously built versions + ); + + if (meshData != null) + { + if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched) + { + // Release the fetched asset data once it has been used. + pbs.SculptData = new byte[0]; + prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Unknown; + } + + int[] indices = meshData.getIndexListAsInt(); + int realIndicesIndex = indices.Length; + float[] verticesAsFloats = meshData.getVertexListAsFloat(); + + if (BSParam.ShouldRemoveZeroWidthTriangles) + { + // Remove degenerate triangles. These are triangles with two of the vertices + // are the same. This is complicated by the problem that vertices are not + // made unique in sculpties so we have to compare the values in the vertex. + realIndicesIndex = 0; + for (int tri = 0; tri < indices.Length; tri += 3) + { + // Compute displacements into vertex array for each vertex of the triangle + int v1 = indices[tri + 0] * 3; + int v2 = indices[tri + 1] * 3; + int v3 = indices[tri + 2] * 3; + // Check to see if any two of the vertices are the same + if (!( ( verticesAsFloats[v1 + 0] == verticesAsFloats[v2 + 0] + && verticesAsFloats[v1 + 1] == verticesAsFloats[v2 + 1] + && verticesAsFloats[v1 + 2] == verticesAsFloats[v2 + 2]) + || ( verticesAsFloats[v2 + 0] == verticesAsFloats[v3 + 0] + && verticesAsFloats[v2 + 1] == verticesAsFloats[v3 + 1] + && verticesAsFloats[v2 + 2] == verticesAsFloats[v3 + 2]) + || ( verticesAsFloats[v1 + 0] == verticesAsFloats[v3 + 0] + && verticesAsFloats[v1 + 1] == verticesAsFloats[v3 + 1] + && verticesAsFloats[v1 + 2] == verticesAsFloats[v3 + 2]) ) + ) + { + // None of the vertices of the triangles are the same. This is a good triangle; + indices[realIndicesIndex + 0] = indices[tri + 0]; + indices[realIndicesIndex + 1] = indices[tri + 1]; + indices[realIndicesIndex + 2] = indices[tri + 2]; + realIndicesIndex += 3; + } + } + } + physicsScene.DetailLog("{0},BSShapeMesh.CreatePhysicalMesh,key={1},origTri={2},realTri={3},numVerts={4}", + BSScene.DetailLogZero, newMeshKey.ToString("X"), indices.Length / 3, realIndicesIndex / 3, verticesAsFloats.Length / 3); + + if (realIndicesIndex != 0) + { + newShape = physicsScene.PE.CreateMeshShape(physicsScene.World, + realIndicesIndex, indices, verticesAsFloats.Length / 3, verticesAsFloats); + } + else + { + physicsScene.Logger.DebugFormat("{0} All mesh triangles degenerate. Prim {1} at {2} in {3}", + LogHeader, prim.PhysObjectName, prim.RawPosition, physicsScene.Name); + } + } + newShape.shapeKey = newMeshKey; + + return newShape; + } } +// ============================================================================================================ public class BSShapeHull : BSShape { private static string LogHeader = "[BULLETSIM SHAPE HULL]"; - private static Dictionary Hulls = new Dictionary(); + public static Dictionary Hulls = new Dictionary(); - public BSShapeHull() : base() + public BSShapeHull(BulletShape pShape) : base(pShape) { } - public static BSShape GetReference() { return new BSShapeNull(); } - public override void Dereference(BSScene physicsScene) { } + public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) + { + float lod; + System.UInt64 newHullKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod); + + BSShapeHull retHull = null; + lock (Hulls) + { + if (Hulls.TryGetValue(newHullKey, out retHull)) + { + // The mesh has already been created. Return a new reference to same. + retHull.IncrementReference(); + } + else + { + retHull = new BSShapeHull(new BulletShape()); + // An instance of this mesh has not been created. Build and remember same. + BulletShape newShape = retHull.CreatePhysicalHull(physicsScene, prim, newHullKey, prim.BaseShape, prim.Size, lod); + + // Check to see if hull was created (might require an asset). + newShape = VerifyMeshCreated(physicsScene, newShape, prim); + if (!newShape.isNativeShape) + { + // If a mesh was what was created, remember the built shape for later sharing. + Hulls.Add(newHullKey, retHull); + } + retHull.physShapeInfo = newShape; + } + } + physicsScene.DetailLog("{0},BSShapeHull,getReference,hull={1},size={2},lod={3}", prim.LocalID, retHull, prim.Size, lod); + return retHull; + } + public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) + { + // Another reference to this shape is just counted. + IncrementReference(); + return this; + } + public override void Dereference(BSScene physicsScene) + { + lock (Hulls) + { + this.DecrementReference(); + physicsScene.DetailLog("{0},BSShapeHull.Dereference,shape={1}", BSScene.DetailLogZero, this); + // TODO: schedule aging and destruction of unused meshes. + } + } + List m_hulls; + private BulletShape CreatePhysicalHull(BSScene physicsScene, BSPhysObject prim, System.UInt64 newHullKey, + PrimitiveBaseShape pbs, OMV.Vector3 size, float lod) + { + BulletShape newShape = new BulletShape(); + IntPtr hullPtr = IntPtr.Zero; + + if (BSParam.ShouldUseBulletHACD) + { + // Build the hull shape from an existing mesh shape. + // The mesh should have already been created in Bullet. + physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,shouldUseBulletHACD,entry", prim.LocalID); + BSShape meshShape = BSShapeMesh.GetReference(physicsScene, true, prim); + + if (meshShape.physShapeInfo.HasPhysicalShape) + { + HACDParams parms; + parms.maxVerticesPerHull = BSParam.BHullMaxVerticesPerHull; + parms.minClusters = BSParam.BHullMinClusters; + parms.compacityWeight = BSParam.BHullCompacityWeight; + parms.volumeWeight = BSParam.BHullVolumeWeight; + parms.concavity = BSParam.BHullConcavity; + parms.addExtraDistPoints = BSParam.NumericBool(BSParam.BHullAddExtraDistPoints); + parms.addNeighboursDistPoints = BSParam.NumericBool(BSParam.BHullAddNeighboursDistPoints); + parms.addFacesPoints = BSParam.NumericBool(BSParam.BHullAddFacesPoints); + parms.shouldAdjustCollisionMargin = BSParam.NumericBool(BSParam.BHullShouldAdjustCollisionMargin); + + physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,hullFromMesh,beforeCall", prim.LocalID, newShape.HasPhysicalShape); + newShape = physicsScene.PE.BuildHullShapeFromMesh(physicsScene.World, meshShape.physShapeInfo, parms); + physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,hullFromMesh,hasBody={1}", prim.LocalID, newShape.HasPhysicalShape); + + // Now done with the mesh shape. + meshShape.Dereference(physicsScene); + } + physicsScene.DetailLog("{0},BSShapeHull.CreatePhysicalHull,shouldUseBulletHACD,exit,hasBody={1}", prim.LocalID, newShape.HasPhysicalShape); + } + if (!newShape.HasPhysicalShape) + { + // Build a new hull in the physical world using the C# HACD algorigthm. + // Pass true for physicalness as this prevents the creation of bounding box which is not needed + IMesh meshData = physicsScene.mesher.CreateMesh(prim.PhysObjectName, pbs, size, lod, true /* isPhysical */, false /* shouldCache */); + if (meshData != null) + { + if (prim.PrimAssetState == BSPhysObject.PrimAssetCondition.Fetched) + { + // Release the fetched asset data once it has been used. + pbs.SculptData = new byte[0]; + prim.PrimAssetState = BSPhysObject.PrimAssetCondition.Unknown; + } + + int[] indices = meshData.getIndexListAsInt(); + List vertices = meshData.getVertexList(); + + //format conversion from IMesh format to DecompDesc format + List convIndices = new List(); + List convVertices = new List(); + for (int ii = 0; ii < indices.GetLength(0); ii++) + { + convIndices.Add(indices[ii]); + } + foreach (OMV.Vector3 vv in vertices) + { + convVertices.Add(new float3(vv.X, vv.Y, vv.Z)); + } + + uint maxDepthSplit = (uint)BSParam.CSHullMaxDepthSplit; + if (BSParam.CSHullMaxDepthSplit != BSParam.CSHullMaxDepthSplitForSimpleShapes) + { + // Simple primitive shapes we know are convex so they are better implemented with + // fewer hulls. + // Check for simple shape (prim without cuts) and reduce split parameter if so. + if (BSShapeCollection.PrimHasNoCuts(pbs)) + { + maxDepthSplit = (uint)BSParam.CSHullMaxDepthSplitForSimpleShapes; + } + } + + // setup and do convex hull conversion + m_hulls = new List(); + DecompDesc dcomp = new DecompDesc(); + dcomp.mIndices = convIndices; + dcomp.mVertices = convVertices; + dcomp.mDepth = maxDepthSplit; + dcomp.mCpercent = BSParam.CSHullConcavityThresholdPercent; + dcomp.mPpercent = BSParam.CSHullVolumeConservationThresholdPercent; + dcomp.mMaxVertices = (uint)BSParam.CSHullMaxVertices; + dcomp.mSkinWidth = BSParam.CSHullMaxSkinWidth; + ConvexBuilder convexBuilder = new ConvexBuilder(HullReturn); + // create the hull into the _hulls variable + convexBuilder.process(dcomp); + + physicsScene.DetailLog("{0},BSShapeCollection.CreatePhysicalHull,key={1},inVert={2},inInd={3},split={4},hulls={5}", + BSScene.DetailLogZero, newHullKey, indices.GetLength(0), vertices.Count, maxDepthSplit, m_hulls.Count); + + // Convert the vertices and indices for passing to unmanaged. + // The hull information is passed as a large floating point array. + // The format is: + // convHulls[0] = number of hulls + // convHulls[1] = number of vertices in first hull + // convHulls[2] = hull centroid X coordinate + // convHulls[3] = hull centroid Y coordinate + // convHulls[4] = hull centroid Z coordinate + // convHulls[5] = first hull vertex X + // convHulls[6] = first hull vertex Y + // convHulls[7] = first hull vertex Z + // convHulls[8] = second hull vertex X + // ... + // convHulls[n] = number of vertices in second hull + // convHulls[n+1] = second hull centroid X coordinate + // ... + // + // TODO: is is very inefficient. Someday change the convex hull generator to return + // data structures that do not need to be converted in order to pass to Bullet. + // And maybe put the values directly into pinned memory rather than marshaling. + int hullCount = m_hulls.Count; + int totalVertices = 1; // include one for the count of the hulls + foreach (ConvexResult cr in m_hulls) + { + totalVertices += 4; // add four for the vertex count and centroid + totalVertices += cr.HullIndices.Count * 3; // we pass just triangles + } + float[] convHulls = new float[totalVertices]; + + convHulls[0] = (float)hullCount; + int jj = 1; + foreach (ConvexResult cr in m_hulls) + { + // copy vertices for index access + float3[] verts = new float3[cr.HullVertices.Count]; + int kk = 0; + foreach (float3 ff in cr.HullVertices) + { + verts[kk++] = ff; + } + + // add to the array one hull's worth of data + convHulls[jj++] = cr.HullIndices.Count; + convHulls[jj++] = 0f; // centroid x,y,z + convHulls[jj++] = 0f; + convHulls[jj++] = 0f; + foreach (int ind in cr.HullIndices) + { + convHulls[jj++] = verts[ind].x; + convHulls[jj++] = verts[ind].y; + convHulls[jj++] = verts[ind].z; + } + } + // create the hull data structure in Bullet + newShape = physicsScene.PE.CreateHullShape(physicsScene.World, hullCount, convHulls); + } + newShape.shapeKey = newHullKey; + } + return newShape; + } + // Callback from convex hull creater with a newly created hull. + // Just add it to our collection of hulls for this shape. + private void HullReturn(ConvexResult result) + { + m_hulls.Add(result); + return; + } + // Loop through all the known hulls and return the description based on the physical address. + public static bool TryGetHullByPtr(BulletShape pShape, out BSShapeHull outHull) + { + bool ret = false; + BSShapeHull foundDesc = null; + lock (Hulls) + { + foreach (BSShapeHull sh in Hulls.Values) + { + if (sh.physShapeInfo.ReferenceSame(pShape)) + { + foundDesc = sh; + ret = true; + break; + } + + } + } + outHull = foundDesc; + return ret; + } } +// ============================================================================================================ public class BSShapeCompound : BSShape { private static string LogHeader = "[BULLETSIM SHAPE COMPOUND]"; - public BSShapeCompound() : base() + public BSShapeCompound(BulletShape pShape) : base(pShape) { } - public static BSShape GetReference(BSPhysObject prim) - { - return new BSShapeNull(); + public static BSShape GetReference(BSScene physicsScene) + { + // Base compound shapes are not shared so this returns a raw shape. + // A built compound shape can be reused in linksets. + return new BSShapeCompound(CreatePhysicalCompoundShape(physicsScene)); + } + public override BSShape GetReference(BSScene physicsScene, BSPhysObject prim) + { + // Calling this reference means we want another handle to an existing compound shape + // (usually linksets) so return this copy. + IncrementReference(); + return this; + } + // Dereferencing a compound shape releases the hold on all the child shapes. + public override void Dereference(BSScene physicsScene) + { + lock (physShapeInfo) + { + this.DecrementReference(); + physicsScene.DetailLog("{0},BSShapeCompound.Dereference,shape={1}", BSScene.DetailLogZero, this); + if (referenceCount <= 0) + { + if (!physicsScene.PE.IsCompound(physShapeInfo)) + { + // Failed the sanity check!! + physicsScene.Logger.ErrorFormat("{0} Attempt to free a compound shape that is not compound!! type={1}, ptr={2}", + LogHeader, physShapeInfo.shapeType, physShapeInfo.AddrString); + physicsScene.DetailLog("{0},BSShapeCollection.DereferenceCompound,notACompoundShape,type={1},ptr={2}", + BSScene.DetailLogZero, physShapeInfo.shapeType, physShapeInfo.AddrString); + return; + } + + int numChildren = physicsScene.PE.GetNumberOfCompoundChildren(physShapeInfo); + physicsScene.DetailLog("{0},BSShapeCollection.DereferenceCompound,shape={1},children={2}", + BSScene.DetailLogZero, physShapeInfo, numChildren); + + // Loop through all the children dereferencing each. + for (int ii = numChildren - 1; ii >= 0; ii--) + { + BulletShape childShape = physicsScene.PE.RemoveChildShapeFromCompoundShapeIndex(physShapeInfo, ii); + DereferenceAnonCollisionShape(physicsScene, childShape); + } + physicsScene.PE.DeleteCollisionShape(physicsScene.World, physShapeInfo); + } + } + } + private static BulletShape CreatePhysicalCompoundShape(BSScene physicsScene) + { + BulletShape cShape = physicsScene.PE.CreateCompoundShape(physicsScene.World, false); + return cShape; + } + // Sometimes we have a pointer to a collision shape but don't know what type it is. + // Figure out type and call the correct dereference routine. + // Called at taint-time. + private void DereferenceAnonCollisionShape(BSScene physicsScene, BulletShape pShape) + { + BSShapeMesh meshDesc; + if (BSShapeMesh.TryGetMeshByPtr(pShape, out meshDesc)) + { + meshDesc.Dereference(physicsScene); + } + else + { + BSShapeHull hullDesc; + if (BSShapeHull.TryGetHullByPtr(pShape, out hullDesc)) + { + hullDesc.Dereference(physicsScene); + } + else + { + BSShapeConvexHull chullDesc; + if (BSShapeConvexHull.TryGetHullByPtr(pShape, out chullDesc)) + { + chullDesc.Dereference(physicsScene); + } + else + { + if (physicsScene.PE.IsCompound(pShape)) + { + BSShapeCompound recursiveCompound = new BSShapeCompound(pShape); + recursiveCompound.Dereference(physicsScene); + } + else + { + if (physicsScene.PE.IsNativeShape(pShape)) + { + BSShapeNative nativeShape = new BSShapeNative(pShape); + nativeShape.Dereference(physicsScene); + } + } + } + } + } } - public override void Dereference(BSScene physicsScene) { } } +// ============================================================================================================ +public class BSShapeConvexHull : BSShape +{ + private static string LogHeader = "[BULLETSIM SHAPE CONVEX HULL]"; + public static Dictionary ConvexHulls = new Dictionary(); + + public BSShapeConvexHull(BulletShape pShape) : base(pShape) + { + } + public static BSShape GetReference(BSScene physicsScene, bool forceRebuild, BSPhysObject prim) + { + float lod; + System.UInt64 newMeshKey = BSShape.ComputeShapeKey(prim.Size, prim.BaseShape, out lod); + + physicsScene.DetailLog("{0},BSShapeMesh,getReference,newKey={1},size={2},lod={3}", + prim.LocalID, newMeshKey.ToString("X"), prim.Size, lod); + + BSShapeConvexHull retConvexHull = null; + lock (ConvexHulls) + { + if (ConvexHulls.TryGetValue(newMeshKey, out retConvexHull)) + { + // The mesh has already been created. Return a new reference to same. + retConvexHull.IncrementReference(); + } + else + { + retConvexHull = new BSShapeConvexHull(new BulletShape()); + BulletShape convexShape = null; + + // Get a handle to a mesh to build the hull from + BSShape baseMesh = BSShapeMesh.GetReference(physicsScene, false /* forceRebuild */, prim); + if (baseMesh.physShapeInfo.isNativeShape) + { + // We get here if the mesh was not creatable. Could be waiting for an asset from the disk. + // In the short term, we return the native shape and a later ForceBodyShapeRebuild should + // get back to this code with a buildable mesh. + // TODO: not sure the temp native shape is freed when the mesh is rebuilt. When does this get freed? + convexShape = baseMesh.physShapeInfo; + } + else + { + convexShape = physicsScene.PE.BuildConvexHullShapeFromMesh(physicsScene.World, baseMesh.physShapeInfo); + convexShape.shapeKey = newMeshKey; + ConvexHulls.Add(convexShape.shapeKey, retConvexHull); + } + + // Done with the base mesh + baseMesh.Dereference(physicsScene); + + retConvexHull.physShapeInfo = convexShape; + } + } + return retConvexHull; + } + public override BSShape GetReference(BSScene physicsScene, BSPhysObject prim) + { + // Calling this reference means we want another handle to an existing shape + // (usually linksets) so return this copy. + IncrementReference(); + return this; + } + // Dereferencing a compound shape releases the hold on all the child shapes. + public override void Dereference(BSScene physicsScene) + { + lock (ConvexHulls) + { + this.DecrementReference(); + physicsScene.DetailLog("{0},BSShapeConvexHull.Dereference,shape={1}", BSScene.DetailLogZero, this); + // TODO: schedule aging and destruction of unused meshes. + } + } + // Loop through all the known hulls and return the description based on the physical address. + public static bool TryGetHullByPtr(BulletShape pShape, out BSShapeConvexHull outHull) + { + bool ret = false; + BSShapeConvexHull foundDesc = null; + lock (ConvexHulls) + { + foreach (BSShapeConvexHull sh in ConvexHulls.Values) + { + if (sh.physShapeInfo.ReferenceSame(pShape)) + { + foundDesc = sh; + ret = true; + break; + } + + } + } + outHull = foundDesc; + return ret; + } +} + +// ============================================================================================================ public class BSShapeAvatar : BSShape { private static string LogHeader = "[BULLETSIM SHAPE AVATAR]"; public BSShapeAvatar() : base() { } - public static BSShape GetReference(BSPhysObject prim) - { + public static BSShape GetReference(BSPhysObject prim) + { + return new BSShapeNull(); + } + public override BSShape GetReference(BSScene pPhysicsScene, BSPhysObject pPrim) + { return new BSShapeNull(); } public override void Dereference(BSScene physicsScene) { } + + // From the front: + // A---A + // / \ + // B-------B + // / \ +Z + // C-----------C | + // \ / -Y --+-- +Y + // \ / | + // \ / -Z + // D-----D + // \ / + // E-E + + // From the top A and E are just lines. + // B, C and D are hexagons: + // + // C1--C2 +X + // / \ | + // C0 C3 -Y --+-- +Y + // \ / | + // C5--C4 -X + + // Zero goes directly through the middle so the offsets are from that middle axis + // and up and down from a middle horizon (A and E are the same distance from the zero). + // The height, width and depth is one. All scaling is done by the simulator. + + // Z component -- how far the level is from the middle zero + private const float Aup = 0.5f; + private const float Bup = 0.4f; + private const float Cup = 0.3f; + private const float Dup = -0.4f; + private const float Eup = -0.5f; + + // Y component -- distance from center to x0 and x3 + private const float Awid = 0.25f; + private const float Bwid = 0.3f; + private const float Cwid = 0.5f; + private const float Dwid = 0.3f; + private const float Ewid = 0.2f; + + // Y component -- distance from center to x1, x2, x4 and x5 + private const float Afwid = 0.0f; + private const float Bfwid = 0.2f; + private const float Cfwid = 0.4f; + private const float Dfwid = 0.2f; + private const float Efwid = 0.0f; + + // X component -- distance from zero to the front or back of a level + private const float Adep = 0f; + private const float Bdep = 0.3f; + private const float Cdep = 0.5f; + private const float Ddep = 0.2f; + private const float Edep = 0f; + + private OMV.Vector3[] avatarVertices = { + new OMV.Vector3( 0.0f, -Awid, Aup), // A0 + new OMV.Vector3( 0.0f, +Awid, Aup), // A3 + + new OMV.Vector3( 0.0f, -Bwid, Bup), // B0 + new OMV.Vector3(+Bdep, -Bfwid, Bup), // B1 + new OMV.Vector3(+Bdep, +Bfwid, Bup), // B2 + new OMV.Vector3( 0.0f, +Bwid, Bup), // B3 + new OMV.Vector3(-Bdep, +Bfwid, Bup), // B4 + new OMV.Vector3(-Bdep, -Bfwid, Bup), // B5 + + new OMV.Vector3( 0.0f, -Cwid, Cup), // C0 + new OMV.Vector3(+Cdep, -Cfwid, Cup), // C1 + new OMV.Vector3(+Cdep, +Cfwid, Cup), // C2 + new OMV.Vector3( 0.0f, +Cwid, Cup), // C3 + new OMV.Vector3(-Cdep, +Cfwid, Cup), // C4 + new OMV.Vector3(-Cdep, -Cfwid, Cup), // C5 + + new OMV.Vector3( 0.0f, -Dwid, Dup), // D0 + new OMV.Vector3(+Ddep, -Dfwid, Dup), // D1 + new OMV.Vector3(+Ddep, +Dfwid, Dup), // D2 + new OMV.Vector3( 0.0f, +Dwid, Dup), // D3 + new OMV.Vector3(-Ddep, +Dfwid, Dup), // D4 + new OMV.Vector3(-Ddep, -Dfwid, Dup), // D5 + + new OMV.Vector3( 0.0f, -Ewid, Eup), // E0 + new OMV.Vector3( 0.0f, +Ewid, Eup), // E3 + }; + + // Offsets of the vertices in the vertices array + private enum Ind : int + { + A0, A3, + B0, B1, B2, B3, B4, B5, + C0, C1, C2, C3, C4, C5, + D0, D1, D2, D3, D4, D5, + E0, E3 + } + + // Comments specify trianges and quads in clockwise direction + private Ind[] avatarIndices = { + Ind.A0, Ind.B0, Ind.B1, // A0,B0,B1 + Ind.A0, Ind.B1, Ind.B2, Ind.B2, Ind.A3, Ind.A0, // A0,B1,B2,A3 + Ind.A3, Ind.B2, Ind.B3, // A3,B2,B3 + Ind.A3, Ind.B3, Ind.B4, // A3,B3,B4 + Ind.A3, Ind.B4, Ind.B5, Ind.B5, Ind.A0, Ind.A3, // A3,B4,B5,A0 + Ind.A0, Ind.B5, Ind.B0, // A0,B5,B0 + + Ind.B0, Ind.C0, Ind.C1, Ind.C1, Ind.B1, Ind.B0, // B0,C0,C1,B1 + Ind.B1, Ind.C1, Ind.C2, Ind.C2, Ind.B2, Ind.B1, // B1,C1,C2,B2 + Ind.B2, Ind.C2, Ind.C3, Ind.C3, Ind.B3, Ind.B2, // B2,C2,C3,B3 + Ind.B3, Ind.C3, Ind.C4, Ind.C4, Ind.B4, Ind.B3, // B3,C3,C4,B4 + Ind.B4, Ind.C4, Ind.C5, Ind.C5, Ind.B5, Ind.B4, // B4,C4,C5,B5 + Ind.B5, Ind.C5, Ind.C0, Ind.C0, Ind.B0, Ind.B5, // B5,C5,C0,B0 + + Ind.C0, Ind.D0, Ind.D1, Ind.D1, Ind.C1, Ind.C0, // C0,D0,D1,C1 + Ind.C1, Ind.D1, Ind.D2, Ind.D2, Ind.C2, Ind.C1, // C1,D1,D2,C2 + Ind.C2, Ind.D2, Ind.D3, Ind.D3, Ind.C3, Ind.C2, // C2,D2,D3,C3 + Ind.C3, Ind.D3, Ind.D4, Ind.D4, Ind.C4, Ind.C3, // C3,D3,D4,C4 + Ind.C4, Ind.D4, Ind.D5, Ind.D5, Ind.C5, Ind.C4, // C4,D4,D5,C5 + Ind.C5, Ind.D5, Ind.D0, Ind.D0, Ind.C0, Ind.C5, // C5,D5,D0,C0 + + Ind.E0, Ind.D0, Ind.D1, // E0,D0,D1 + Ind.E0, Ind.D1, Ind.D2, Ind.D2, Ind.E3, Ind.E0, // E0,D1,D2,E3 + Ind.E3, Ind.D2, Ind.D3, // E3,D2,D3 + Ind.E3, Ind.D3, Ind.D4, // E3,D3,D4 + Ind.E3, Ind.D4, Ind.D5, Ind.D5, Ind.E0, Ind.E3, // E3,D4,D5,E0 + Ind.E0, Ind.D5, Ind.D0, // E0,D5,D0 + + }; + } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainHeightmap.cs b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainHeightmap.cs index e4fecc3aaf..c7deb4ec9d 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainHeightmap.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainHeightmap.cs @@ -68,7 +68,7 @@ public sealed class BSTerrainHeightmap : BSTerrainPhys // This minCoords and maxCoords passed in give the size of the terrain (min and max Z // are the high and low points of the heightmap). - public BSTerrainHeightmap(BSScene physicsScene, Vector3 regionBase, uint id, float[] initialMap, + public BSTerrainHeightmap(BSScene physicsScene, Vector3 regionBase, uint id, float[] initialMap, Vector3 minCoords, Vector3 maxCoords) : base(physicsScene, regionBase, id) { @@ -92,7 +92,7 @@ public sealed class BSTerrainHeightmap : BSTerrainPhys private void BuildHeightmapTerrain() { // Create the terrain shape from the mapInfo - m_mapInfo.terrainShape = PhysicsScene.PE.CreateTerrainShape( m_mapInfo.ID, + m_mapInfo.terrainShape = m_physicsScene.PE.CreateTerrainShape( m_mapInfo.ID, new Vector3(m_mapInfo.sizeX, m_mapInfo.sizeY, 0), m_mapInfo.minZ, m_mapInfo.maxZ, m_mapInfo.heightMap, 1f, BSParam.TerrainCollisionMargin); @@ -103,26 +103,26 @@ public sealed class BSTerrainHeightmap : BSTerrainPhys centerPos.Y = m_mapInfo.minCoords.Y + (m_mapInfo.sizeY / 2f); centerPos.Z = m_mapInfo.minZ + ((m_mapInfo.maxZ - m_mapInfo.minZ) / 2f); - m_mapInfo.terrainBody = PhysicsScene.PE.CreateBodyWithDefaultMotionState(m_mapInfo.terrainShape, + m_mapInfo.terrainBody = m_physicsScene.PE.CreateBodyWithDefaultMotionState(m_mapInfo.terrainShape, m_mapInfo.ID, centerPos, Quaternion.Identity); // Set current terrain attributes - PhysicsScene.PE.SetFriction(m_mapInfo.terrainBody, BSParam.TerrainFriction); - PhysicsScene.PE.SetHitFraction(m_mapInfo.terrainBody, BSParam.TerrainHitFraction); - PhysicsScene.PE.SetRestitution(m_mapInfo.terrainBody, BSParam.TerrainRestitution); - PhysicsScene.PE.SetCollisionFlags(m_mapInfo.terrainBody, CollisionFlags.CF_STATIC_OBJECT); + m_physicsScene.PE.SetFriction(m_mapInfo.terrainBody, BSParam.TerrainFriction); + m_physicsScene.PE.SetHitFraction(m_mapInfo.terrainBody, BSParam.TerrainHitFraction); + m_physicsScene.PE.SetRestitution(m_mapInfo.terrainBody, BSParam.TerrainRestitution); + m_physicsScene.PE.SetCollisionFlags(m_mapInfo.terrainBody, CollisionFlags.CF_STATIC_OBJECT); // Return the new terrain to the world of physical objects - PhysicsScene.PE.AddObjectToWorld(PhysicsScene.World, m_mapInfo.terrainBody); + m_physicsScene.PE.AddObjectToWorld(m_physicsScene.World, m_mapInfo.terrainBody); // redo its bounding box now that it is in the world - PhysicsScene.PE.UpdateSingleAabb(PhysicsScene.World, m_mapInfo.terrainBody); + m_physicsScene.PE.UpdateSingleAabb(m_physicsScene.World, m_mapInfo.terrainBody); m_mapInfo.terrainBody.collisionType = CollisionType.Terrain; - m_mapInfo.terrainBody.ApplyCollisionMask(PhysicsScene); + m_mapInfo.terrainBody.ApplyCollisionMask(m_physicsScene); // Make it so the terrain will not move or be considered for movement. - PhysicsScene.PE.ForceActivationState(m_mapInfo.terrainBody, ActivationState.DISABLE_SIMULATION); + m_physicsScene.PE.ForceActivationState(m_mapInfo.terrainBody, ActivationState.DISABLE_SIMULATION); return; } @@ -134,9 +134,9 @@ public sealed class BSTerrainHeightmap : BSTerrainPhys { if (m_mapInfo.terrainBody.HasPhysicalBody) { - PhysicsScene.PE.RemoveObjectFromWorld(PhysicsScene.World, m_mapInfo.terrainBody); + m_physicsScene.PE.RemoveObjectFromWorld(m_physicsScene.World, m_mapInfo.terrainBody); // Frees both the body and the shape. - PhysicsScene.PE.DestroyObject(PhysicsScene.World, m_mapInfo.terrainBody); + m_physicsScene.PE.DestroyObject(m_physicsScene.World, m_mapInfo.terrainBody); } } m_mapInfo = null; @@ -155,7 +155,7 @@ public sealed class BSTerrainHeightmap : BSTerrainPhys catch { // Sometimes they give us wonky values of X and Y. Give a warning and return something. - PhysicsScene.Logger.WarnFormat("{0} Bad request for terrain height. terrainBase={1}, pos={2}", + m_physicsScene.Logger.WarnFormat("{0} Bad request for terrain height. terrainBase={1}, pos={2}", LogHeader, m_mapInfo.terrainRegionBase, pos); ret = BSTerrainManager.HEIGHT_GETHEIGHT_RET; } @@ -165,7 +165,7 @@ public sealed class BSTerrainHeightmap : BSTerrainPhys // The passed position is relative to the base of the region. public override float GetWaterLevelAtXYZ(Vector3 pos) { - return PhysicsScene.SimpleWaterLevel; + return m_physicsScene.SimpleWaterLevel; } } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainManager.cs b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainManager.cs index 2e9db39f73..c4807c41b0 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainManager.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainManager.cs @@ -50,14 +50,14 @@ public abstract class BSTerrainPhys : IDisposable Mesh = 1 } - public BSScene PhysicsScene { get; private set; } + protected BSScene m_physicsScene { get; private set; } // Base of the region in world coordinates. Coordinates inside the region are relative to this. public Vector3 TerrainBase { get; private set; } public uint ID { get; private set; } public BSTerrainPhys(BSScene physicsScene, Vector3 regionBase, uint id) { - PhysicsScene = physicsScene; + m_physicsScene = physicsScene; TerrainBase = regionBase; ID = id; } @@ -86,7 +86,7 @@ public sealed class BSTerrainManager : IDisposable public Vector3 DefaultRegionSize = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight); // The scene that I am part of - private BSScene PhysicsScene { get; set; } + private BSScene m_physicsScene { get; set; } // The ground plane created to keep thing from falling to infinity. private BulletBody m_groundPlane; @@ -113,7 +113,7 @@ public sealed class BSTerrainManager : IDisposable public BSTerrainManager(BSScene physicsScene) { - PhysicsScene = physicsScene; + m_physicsScene = physicsScene; m_terrains = new Dictionary(); // Assume one region of default size @@ -132,32 +132,37 @@ public sealed class BSTerrainManager : IDisposable // safe to call Bullet in real time. We hope no one is moving prims around yet. public void CreateInitialGroundPlaneAndTerrain() { + DetailLog("{0},BSTerrainManager.CreateInitialGroundPlaneAndTerrain,region={1}", BSScene.DetailLogZero, m_physicsScene.RegionName); // The ground plane is here to catch things that are trying to drop to negative infinity - BulletShape groundPlaneShape = PhysicsScene.PE.CreateGroundPlaneShape(BSScene.GROUNDPLANE_ID, 1f, BSParam.TerrainCollisionMargin); - m_groundPlane = PhysicsScene.PE.CreateBodyWithDefaultMotionState(groundPlaneShape, + BulletShape groundPlaneShape = m_physicsScene.PE.CreateGroundPlaneShape(BSScene.GROUNDPLANE_ID, 1f, BSParam.TerrainCollisionMargin); + m_groundPlane = m_physicsScene.PE.CreateBodyWithDefaultMotionState(groundPlaneShape, BSScene.GROUNDPLANE_ID, Vector3.Zero, Quaternion.Identity); - PhysicsScene.PE.AddObjectToWorld(PhysicsScene.World, m_groundPlane); - PhysicsScene.PE.UpdateSingleAabb(PhysicsScene.World, m_groundPlane); + m_physicsScene.PE.AddObjectToWorld(m_physicsScene.World, m_groundPlane); + m_physicsScene.PE.UpdateSingleAabb(m_physicsScene.World, m_groundPlane); // Ground plane does not move - PhysicsScene.PE.ForceActivationState(m_groundPlane, ActivationState.DISABLE_SIMULATION); + m_physicsScene.PE.ForceActivationState(m_groundPlane, ActivationState.DISABLE_SIMULATION); // Everything collides with the ground plane. m_groundPlane.collisionType = CollisionType.Groundplane; - m_groundPlane.ApplyCollisionMask(PhysicsScene); + m_groundPlane.ApplyCollisionMask(m_physicsScene); - // Build an initial terrain and put it in the world. This quickly gets replaced by the real region terrain. - BSTerrainPhys initialTerrain = new BSTerrainHeightmap(PhysicsScene, Vector3.Zero, BSScene.TERRAIN_ID, DefaultRegionSize); - m_terrains.Add(Vector3.Zero, initialTerrain); + BSTerrainPhys initialTerrain = new BSTerrainHeightmap(m_physicsScene, Vector3.Zero, BSScene.TERRAIN_ID, DefaultRegionSize); + lock (m_terrains) + { + // Build an initial terrain and put it in the world. This quickly gets replaced by the real region terrain. + m_terrains.Add(Vector3.Zero, initialTerrain); + } } // Release all the terrain structures we might have allocated public void ReleaseGroundPlaneAndTerrain() { + DetailLog("{0},BSTerrainManager.ReleaseGroundPlaneAndTerrain,region={1}", BSScene.DetailLogZero, m_physicsScene.RegionName); if (m_groundPlane.HasPhysicalBody) { - if (PhysicsScene.PE.RemoveObjectFromWorld(PhysicsScene.World, m_groundPlane)) + if (m_physicsScene.PE.RemoveObjectFromWorld(m_physicsScene.World, m_groundPlane)) { - PhysicsScene.PE.DestroyObject(PhysicsScene.World, m_groundPlane); + m_physicsScene.PE.DestroyObject(m_physicsScene.World, m_groundPlane); } m_groundPlane.Clear(); } @@ -183,7 +188,7 @@ public sealed class BSTerrainManager : IDisposable float[] localHeightMap = heightMap; // If there are multiple requests for changes to the same terrain between ticks, // only do that last one. - PhysicsScene.PostTaintObject("TerrainManager.SetTerrain-"+ m_worldOffset.ToString(), 0, delegate() + m_physicsScene.PostTaintObject("TerrainManager.SetTerrain-"+ m_worldOffset.ToString(), 0, delegate() { if (m_worldOffset != Vector3.Zero && MegaRegionParentPhysicsScene != null) { @@ -193,11 +198,9 @@ public sealed class BSTerrainManager : IDisposable // the terrain is added to our parent if (MegaRegionParentPhysicsScene is BSScene) { - DetailLog("{0},SetTerrain.ToParent,offset={1},worldMax={2}", - BSScene.DetailLogZero, m_worldOffset, m_worldMax); - ((BSScene)MegaRegionParentPhysicsScene).TerrainManager.UpdateTerrain( - BSScene.CHILDTERRAIN_ID, localHeightMap, - m_worldOffset, m_worldOffset + DefaultRegionSize, true); + DetailLog("{0},SetTerrain.ToParent,offset={1},worldMax={2}", BSScene.DetailLogZero, m_worldOffset, m_worldMax); + ((BSScene)MegaRegionParentPhysicsScene).TerrainManager.AddMegaRegionChildTerrain( + BSScene.CHILDTERRAIN_ID, localHeightMap, m_worldOffset, m_worldOffset + DefaultRegionSize); } } else @@ -205,26 +208,36 @@ public sealed class BSTerrainManager : IDisposable // If not doing the mega-prim thing, just change the terrain DetailLog("{0},SetTerrain.Existing", BSScene.DetailLogZero); - UpdateTerrain(BSScene.TERRAIN_ID, localHeightMap, - m_worldOffset, m_worldOffset + DefaultRegionSize, true); + UpdateTerrain(BSScene.TERRAIN_ID, localHeightMap, m_worldOffset, m_worldOffset + DefaultRegionSize); } }); } - // If called with no mapInfo for the terrain, this will create a new mapInfo and terrain + // Another region is calling this region and passing a terrain. + // A region that is not the mega-region root will pass its terrain to the root region so the root region + // physics engine will have all the terrains. + private void AddMegaRegionChildTerrain(uint id, float[] heightMap, Vector3 minCoords, Vector3 maxCoords) + { + // Since we are called by another region's thread, the action must be rescheduled onto our processing thread. + m_physicsScene.PostTaintObject("TerrainManager.AddMegaRegionChild" + minCoords.ToString(), id, delegate() + { + UpdateTerrain(id, heightMap, minCoords, maxCoords); + }); + } + + // If called for terrain has has not been previously allocated, a new terrain will be built // based on the passed information. The 'id' should be either the terrain id or // BSScene.CHILDTERRAIN_ID. If the latter, a new child terrain ID will be allocated and used. // The latter feature is for creating child terrains for mega-regions. - // If called with a mapInfo in m_heightMaps and there is an existing terrain body, a new + // If there is an existing terrain body, a new // terrain shape is created and added to the body. // This call is most often used to update the heightMap and parameters of the terrain. // (The above does suggest that some simplification/refactoring is in order.) // Called during taint-time. - private void UpdateTerrain(uint id, float[] heightMap, - Vector3 minCoords, Vector3 maxCoords, bool inTaintTime) + private void UpdateTerrain(uint id, float[] heightMap, Vector3 minCoords, Vector3 maxCoords) { - DetailLog("{0},BSTerrainManager.UpdateTerrain,call,minC={1},maxC={2},inTaintTime={3}", - BSScene.DetailLogZero, minCoords, maxCoords, inTaintTime); + DetailLog("{0},BSTerrainManager.UpdateTerrain,call,id={1},minC={2},maxC={3}", + BSScene.DetailLogZero, id, minCoords, maxCoords); // Find high and low points of passed heightmap. // The min and max passed in is usually the area objects can be in (maximum @@ -253,7 +266,7 @@ public sealed class BSTerrainManager : IDisposable if (m_terrains.TryGetValue(terrainRegionBase, out terrainPhys)) { // There is already a terrain in this spot. Free the old and build the new. - DetailLog("{0},UpdateTerrain:UpdateExisting,call,id={1},base={2},minC={3},maxC={4}", + DetailLog("{0},BSTErrainManager.UpdateTerrain:UpdateExisting,call,id={1},base={2},minC={3},maxC={4}", BSScene.DetailLogZero, id, terrainRegionBase, minCoords, minCoords); // Remove old terrain from the collection @@ -263,6 +276,7 @@ public sealed class BSTerrainManager : IDisposable if (MegaRegionParentPhysicsScene == null) { + // This terrain is not part of the mega-region scheme. Create vanilla terrain. BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords); m_terrains.Add(terrainRegionBase, newTerrainPhys); @@ -291,8 +305,8 @@ public sealed class BSTerrainManager : IDisposable if (newTerrainID >= BSScene.CHILDTERRAIN_ID) newTerrainID = ++m_terrainCount; - DetailLog("{0},UpdateTerrain:NewTerrain,taint,newID={1},minCoord={2},maxCoord={3}", - BSScene.DetailLogZero, newTerrainID, minCoords, minCoords); + DetailLog("{0},BSTerrainManager.UpdateTerrain:NewTerrain,taint,newID={1},minCoord={2},maxCoord={3}", + BSScene.DetailLogZero, newTerrainID, minCoords, maxCoords); BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords); m_terrains.Add(terrainRegionBase, newTerrainPhys); @@ -304,26 +318,26 @@ public sealed class BSTerrainManager : IDisposable // TODO: redo terrain implementation selection to allow other base types than heightMap. private BSTerrainPhys BuildPhysicalTerrain(Vector3 terrainRegionBase, uint id, float[] heightMap, Vector3 minCoords, Vector3 maxCoords) { - PhysicsScene.Logger.DebugFormat("{0} Terrain for {1}/{2} created with {3}", - LogHeader, PhysicsScene.RegionName, terrainRegionBase, + m_physicsScene.Logger.DebugFormat("{0} Terrain for {1}/{2} created with {3}", + LogHeader, m_physicsScene.RegionName, terrainRegionBase, (BSTerrainPhys.TerrainImplementation)BSParam.TerrainImplementation); BSTerrainPhys newTerrainPhys = null; switch ((int)BSParam.TerrainImplementation) { case (int)BSTerrainPhys.TerrainImplementation.Heightmap: - newTerrainPhys = new BSTerrainHeightmap(PhysicsScene, terrainRegionBase, id, + newTerrainPhys = new BSTerrainHeightmap(m_physicsScene, terrainRegionBase, id, heightMap, minCoords, maxCoords); break; case (int)BSTerrainPhys.TerrainImplementation.Mesh: - newTerrainPhys = new BSTerrainMesh(PhysicsScene, terrainRegionBase, id, + newTerrainPhys = new BSTerrainMesh(m_physicsScene, terrainRegionBase, id, heightMap, minCoords, maxCoords); break; default: - PhysicsScene.Logger.ErrorFormat("{0} Bad terrain implementation specified. Type={1}/{2},Region={3}/{4}", - LogHeader, - (int)BSParam.TerrainImplementation, + m_physicsScene.Logger.ErrorFormat("{0} Bad terrain implementation specified. Type={1}/{2},Region={3}/{4}", + LogHeader, + (int)BSParam.TerrainImplementation, BSParam.TerrainImplementation, - PhysicsScene.RegionName, terrainRegionBase); + m_physicsScene.RegionName, terrainRegionBase); break; } return newTerrainPhys; @@ -337,6 +351,53 @@ public sealed class BSTerrainManager : IDisposable return GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ); } + // Return a new position that is over known terrain if the position is outside our terrain. + public Vector3 ClampPositionIntoKnownTerrain(Vector3 pPos) + { + Vector3 ret = pPos; + + // First, base addresses are never negative so correct for that possible problem. + if (ret.X < 0f || ret.Y < 0f) + { + ret.X = Util.Clamp(ret.X, 0f, 1000000f); + ret.Y = Util.Clamp(ret.Y, 0f, 1000000f); + DetailLog("{0},BSTerrainManager.ClampPositionToKnownTerrain,zeroingNegXorY,oldPos={1},newPos={2}", + BSScene.DetailLogZero, pPos, ret); + } + + // Can't do this function if we don't know about any terrain. + if (m_terrains.Count == 0) + return ret; + + int loopPrevention = 10; + Vector3 terrainBaseXYZ; + BSTerrainPhys physTerrain; + while (!GetTerrainPhysicalAtXYZ(ret, out physTerrain, out terrainBaseXYZ)) + { + // The passed position is not within a known terrain area. + // NOTE that GetTerrainPhysicalAtXYZ will set 'terrainBaseXYZ' to the base of the unfound region. + + // Must be off the top of a region. Find an adjacent region to move into. + Vector3 adjacentTerrainBase = FindAdjacentTerrainBase(terrainBaseXYZ); + + ret.X = Math.Min(ret.X, adjacentTerrainBase.X + (ret.X % DefaultRegionSize.X)); + ret.Y = Math.Min(ret.Y, adjacentTerrainBase.Y + (ret.X % DefaultRegionSize.Y)); + DetailLog("{0},BSTerrainManager.ClampPositionToKnownTerrain,findingAdjacentRegion,adjacentRegBase={1},oldPos={2},newPos={3}", + BSScene.DetailLogZero, adjacentTerrainBase, pPos, ret); + + if (loopPrevention-- < 0f) + { + // The 'while' is a little dangerous so this prevents looping forever if the + // mapping of the terrains ever gets messed up (like nothing at <0,0>) or + // the list of terrains is in transition. + DetailLog("{0},BSTerrainManager.ClampPositionToKnownTerrain,suppressingFindAdjacentRegionLoop", BSScene.DetailLogZero); + break; + } + } + + return ret; + } + // Given an X and Y, find the height of the terrain. // Since we could be handling multiple terrains for a mega-region, // the base of the region is calcuated assuming all regions are @@ -368,8 +429,8 @@ public sealed class BSTerrainManager : IDisposable } else { - PhysicsScene.Logger.ErrorFormat("{0} GetTerrainHeightAtXY: terrain not found: region={1}, x={2}, y={3}", - LogHeader, PhysicsScene.RegionName, tX, tY); + m_physicsScene.Logger.ErrorFormat("{0} GetTerrainHeightAtXY: terrain not found: region={1}, x={2}, y={3}", + LogHeader, m_physicsScene.RegionName, tX, tY); DetailLog("{0},BSTerrainManager.GetTerrainHeightAtXYZ,terrainNotFound,pos={1},base={2}", BSScene.DetailLogZero, pos, terrainBaseXYZ); } @@ -390,8 +451,8 @@ public sealed class BSTerrainManager : IDisposable } else { - PhysicsScene.Logger.ErrorFormat("{0} GetWaterHeightAtXY: terrain not found: pos={1}, terrainBase={2}, height={3}", - LogHeader, PhysicsScene.RegionName, pos, terrainBaseXYZ, ret); + m_physicsScene.Logger.ErrorFormat("{0} GetWaterHeightAtXY: terrain not found: pos={1}, terrainBase={2}, height={3}", + LogHeader, m_physicsScene.RegionName, pos, terrainBaseXYZ, ret); } return ret; } @@ -400,18 +461,69 @@ public sealed class BSTerrainManager : IDisposable // the descriptor class and the 'base' fo the addresses therein. private bool GetTerrainPhysicalAtXYZ(Vector3 pos, out BSTerrainPhys outPhysTerrain, out Vector3 outTerrainBase) { - int offsetX = ((int)(pos.X / (int)DefaultRegionSize.X)) * (int)DefaultRegionSize.X; - int offsetY = ((int)(pos.Y / (int)DefaultRegionSize.Y)) * (int)DefaultRegionSize.Y; - Vector3 terrainBaseXYZ = new Vector3(offsetX, offsetY, 0f); + bool ret = false; + + Vector3 terrainBaseXYZ = Vector3.Zero; + if (pos.X < 0f || pos.Y < 0f) + { + // We don't handle negative addresses so just make up a base that will not be found. + terrainBaseXYZ = new Vector3(-DefaultRegionSize.X, -DefaultRegionSize.Y, 0f); + } + else + { + int offsetX = ((int)(pos.X / (int)DefaultRegionSize.X)) * (int)DefaultRegionSize.X; + int offsetY = ((int)(pos.Y / (int)DefaultRegionSize.Y)) * (int)DefaultRegionSize.Y; + terrainBaseXYZ = new Vector3(offsetX, offsetY, 0f); + } BSTerrainPhys physTerrain = null; lock (m_terrains) { - m_terrains.TryGetValue(terrainBaseXYZ, out physTerrain); + ret = m_terrains.TryGetValue(terrainBaseXYZ, out physTerrain); } outTerrainBase = terrainBaseXYZ; outPhysTerrain = physTerrain; - return (physTerrain != null); + return ret; + } + + // Given a terrain base, return a terrain base for a terrain that is closer to <0,0> than + // this one. Usually used to return an out of bounds object to a known place. + private Vector3 FindAdjacentTerrainBase(Vector3 pTerrainBase) + { + Vector3 ret = pTerrainBase; + + // Can't do this function if we don't know about any terrain. + if (m_terrains.Count == 0) + return ret; + + // Just some sanity + ret.X = Util.Clamp(ret.X, 0f, 1000000f); + ret.Y = Util.Clamp(ret.Y, 0f, 1000000f); + ret.Z = 0f; + + lock (m_terrains) + { + // Once down to the <0,0> region, we have to be done. + while (ret.X > 0f || ret.Y > 0f) + { + if (ret.X > 0f) + { + ret.X = Math.Max(0f, ret.X - DefaultRegionSize.X); + DetailLog("{0},BSTerrainManager.FindAdjacentTerrainBase,reducingX,terrainBase={1}", BSScene.DetailLogZero, ret); + if (m_terrains.ContainsKey(ret)) + break; + } + if (ret.Y > 0f) + { + ret.Y = Math.Max(0f, ret.Y - DefaultRegionSize.Y); + DetailLog("{0},BSTerrainManager.FindAdjacentTerrainBase,reducingY,terrainBase={1}", BSScene.DetailLogZero, ret); + if (m_terrains.ContainsKey(ret)) + break; + } + } + } + + return ret; } // Although no one seems to check this, I do support combining. @@ -452,7 +564,7 @@ public sealed class BSTerrainManager : IDisposable private void DetailLog(string msg, params Object[] args) { - PhysicsScene.PhysicsLogging.Write(msg, args); + m_physicsScene.PhysicsLogging.Write(msg, args); } } } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs index 1d55ce3f1a..e4ca098b2d 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSTerrainMesh.cs @@ -51,7 +51,7 @@ public sealed class BSTerrainMesh : BSTerrainPhys BulletShape m_terrainShape; BulletBody m_terrainBody; - public BSTerrainMesh(BSScene physicsScene, Vector3 regionBase, uint id, Vector3 regionSize) + public BSTerrainMesh(BSScene physicsScene, Vector3 regionBase, uint id, Vector3 regionSize) : base(physicsScene, regionBase, id) { } @@ -62,7 +62,7 @@ public sealed class BSTerrainMesh : BSTerrainPhys } // Create terrain mesh from a heightmap. - public BSTerrainMesh(BSScene physicsScene, Vector3 regionBase, uint id, float[] initialMap, + public BSTerrainMesh(BSScene physicsScene, Vector3 regionBase, uint id, float[] initialMap, Vector3 minCoords, Vector3 maxCoords) : base(physicsScene, regionBase, id) { @@ -76,27 +76,43 @@ public sealed class BSTerrainMesh : BSTerrainPhys m_sizeX = (int)(maxCoords.X - minCoords.X); m_sizeY = (int)(maxCoords.Y - minCoords.Y); - if (!BSTerrainMesh.ConvertHeightmapToMesh(PhysicsScene, initialMap, - m_sizeX, m_sizeY, - (float)m_sizeX, (float)m_sizeY, - Vector3.Zero, 1.0f, - out indicesCount, out indices, out verticesCount, out vertices)) + bool meshCreationSuccess = false; + if (BSParam.TerrainMeshMagnification == 1) + { + // If a magnification of one, use the old routine that is tried and true. + meshCreationSuccess = BSTerrainMesh.ConvertHeightmapToMesh(m_physicsScene, + initialMap, m_sizeX, m_sizeY, // input size + Vector3.Zero, // base for mesh + out indicesCount, out indices, out verticesCount, out vertices); + } + else + { + // Other magnifications use the newer routine + meshCreationSuccess = BSTerrainMesh.ConvertHeightmapToMesh2(m_physicsScene, + initialMap, m_sizeX, m_sizeY, // input size + BSParam.TerrainMeshMagnification, + physicsScene.TerrainManager.DefaultRegionSize, + Vector3.Zero, // base for mesh + out indicesCount, out indices, out verticesCount, out vertices); + } + if (!meshCreationSuccess) { // DISASTER!! - PhysicsScene.DetailLog("{0},BSTerrainMesh.create,failedConversionOfHeightmap", ID); - PhysicsScene.Logger.ErrorFormat("{0} Failed conversion of heightmap to mesh! base={1}", LogHeader, TerrainBase); + m_physicsScene.DetailLog("{0},BSTerrainMesh.create,failedConversionOfHeightmap,id={1}", BSScene.DetailLogZero, ID); + m_physicsScene.Logger.ErrorFormat("{0} Failed conversion of heightmap to mesh! base={1}", LogHeader, TerrainBase); // Something is very messed up and a crash is in our future. return; } - PhysicsScene.DetailLog("{0},BSTerrainMesh.create,meshed,indices={1},indSz={2},vertices={3},vertSz={4}", - ID, indicesCount, indices.Length, verticesCount, vertices.Length); - m_terrainShape = PhysicsScene.PE.CreateMeshShape(PhysicsScene.World, indicesCount, indices, verticesCount, vertices); + m_physicsScene.DetailLog("{0},BSTerrainMesh.create,meshed,id={1},indices={2},indSz={3},vertices={4},vertSz={5}", + BSScene.DetailLogZero, ID, indicesCount, indices.Length, verticesCount, vertices.Length); + + m_terrainShape = m_physicsScene.PE.CreateMeshShape(m_physicsScene.World, indicesCount, indices, verticesCount, vertices); if (!m_terrainShape.HasPhysicalShape) { // DISASTER!! - PhysicsScene.DetailLog("{0},BSTerrainMesh.create,failedCreationOfShape", ID); - physicsScene.Logger.ErrorFormat("{0} Failed creation of terrain mesh! base={1}", LogHeader, TerrainBase); + m_physicsScene.DetailLog("{0},BSTerrainMesh.create,failedCreationOfShape,id={1}", BSScene.DetailLogZero, ID); + m_physicsScene.Logger.ErrorFormat("{0} Failed creation of terrain mesh! base={1}", LogHeader, TerrainBase); // Something is very messed up and a crash is in our future. return; } @@ -104,44 +120,54 @@ public sealed class BSTerrainMesh : BSTerrainPhys Vector3 pos = regionBase; Quaternion rot = Quaternion.Identity; - m_terrainBody = PhysicsScene.PE.CreateBodyWithDefaultMotionState(m_terrainShape, ID, pos, rot); + m_terrainBody = m_physicsScene.PE.CreateBodyWithDefaultMotionState(m_terrainShape, ID, pos, rot); if (!m_terrainBody.HasPhysicalBody) { // DISASTER!! - physicsScene.Logger.ErrorFormat("{0} Failed creation of terrain body! base={1}", LogHeader, TerrainBase); + m_physicsScene.Logger.ErrorFormat("{0} Failed creation of terrain body! base={1}", LogHeader, TerrainBase); // Something is very messed up and a crash is in our future. return; } + physicsScene.PE.SetShapeCollisionMargin(m_terrainShape, BSParam.TerrainCollisionMargin); // Set current terrain attributes - PhysicsScene.PE.SetFriction(m_terrainBody, BSParam.TerrainFriction); - PhysicsScene.PE.SetHitFraction(m_terrainBody, BSParam.TerrainHitFraction); - PhysicsScene.PE.SetRestitution(m_terrainBody, BSParam.TerrainRestitution); - PhysicsScene.PE.SetCollisionFlags(m_terrainBody, CollisionFlags.CF_STATIC_OBJECT); + m_physicsScene.PE.SetFriction(m_terrainBody, BSParam.TerrainFriction); + m_physicsScene.PE.SetHitFraction(m_terrainBody, BSParam.TerrainHitFraction); + m_physicsScene.PE.SetRestitution(m_terrainBody, BSParam.TerrainRestitution); + m_physicsScene.PE.SetContactProcessingThreshold(m_terrainBody, BSParam.TerrainContactProcessingThreshold); + m_physicsScene.PE.SetCollisionFlags(m_terrainBody, CollisionFlags.CF_STATIC_OBJECT); // Static objects are not very massive. - PhysicsScene.PE.SetMassProps(m_terrainBody, 0f, Vector3.Zero); + m_physicsScene.PE.SetMassProps(m_terrainBody, 0f, Vector3.Zero); // Put the new terrain to the world of physical objects - PhysicsScene.PE.AddObjectToWorld(PhysicsScene.World, m_terrainBody); + m_physicsScene.PE.AddObjectToWorld(m_physicsScene.World, m_terrainBody); // Redo its bounding box now that it is in the world - PhysicsScene.PE.UpdateSingleAabb(PhysicsScene.World, m_terrainBody); + m_physicsScene.PE.UpdateSingleAabb(m_physicsScene.World, m_terrainBody); m_terrainBody.collisionType = CollisionType.Terrain; - m_terrainBody.ApplyCollisionMask(PhysicsScene); + m_terrainBody.ApplyCollisionMask(m_physicsScene); + + if (BSParam.UseSingleSidedMeshes) + { + m_physicsScene.DetailLog("{0},BSTerrainMesh.settingCustomMaterial,id={1}", BSScene.DetailLogZero, id); + m_physicsScene.PE.AddToCollisionFlags(m_terrainBody, CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK); + } // Make it so the terrain will not move or be considered for movement. - PhysicsScene.PE.ForceActivationState(m_terrainBody, ActivationState.DISABLE_SIMULATION); + m_physicsScene.PE.ForceActivationState(m_terrainBody, ActivationState.DISABLE_SIMULATION); } public override void Dispose() { if (m_terrainBody.HasPhysicalBody) { - PhysicsScene.PE.RemoveObjectFromWorld(PhysicsScene.World, m_terrainBody); + m_physicsScene.PE.RemoveObjectFromWorld(m_physicsScene.World, m_terrainBody); // Frees both the body and the shape. - PhysicsScene.PE.DestroyObject(PhysicsScene.World, m_terrainBody); + m_physicsScene.PE.DestroyObject(m_physicsScene.World, m_terrainBody); + m_terrainBody.Clear(); + m_terrainShape.Clear(); } } @@ -159,7 +185,7 @@ public sealed class BSTerrainMesh : BSTerrainPhys catch { // Sometimes they give us wonky values of X and Y. Give a warning and return something. - PhysicsScene.Logger.WarnFormat("{0} Bad request for terrain height. terrainBase={1}, pos={2}", + m_physicsScene.Logger.WarnFormat("{0} Bad request for terrain height. terrainBase={1}, pos={2}", LogHeader, TerrainBase, pos); ret = BSTerrainManager.HEIGHT_GETHEIGHT_RET; } @@ -169,17 +195,14 @@ public sealed class BSTerrainMesh : BSTerrainPhys // The passed position is relative to the base of the region. public override float GetWaterLevelAtXYZ(Vector3 pos) { - return PhysicsScene.SimpleWaterLevel; + return m_physicsScene.SimpleWaterLevel; } // Convert the passed heightmap to mesh information suitable for CreateMeshShape2(). // Return 'true' if successfully created. - public static bool ConvertHeightmapToMesh( - BSScene physicsScene, + public static bool ConvertHeightmapToMesh( BSScene physicsScene, float[] heightMap, int sizeX, int sizeY, // parameters of incoming heightmap - float extentX, float extentY, // zero based range for output vertices Vector3 extentBase, // base to be added to all vertices - float magnification, // number of vertices to create between heightMap coords out int indicesCountO, out int[] indicesO, out int verticesCountO, out float[] verticesO) { @@ -200,16 +223,15 @@ public sealed class BSTerrainMesh : BSTerrainPhys // of the heightmap. try { - // One vertice per heightmap value plus the vertices off the top and bottom edge. + // One vertice per heightmap value plus the vertices off the side and bottom edge. int totalVertices = (sizeX + 1) * (sizeY + 1); vertices = new float[totalVertices * 3]; int totalIndices = sizeX * sizeY * 6; indices = new int[totalIndices]; - float magX = (float)sizeX / extentX; - float magY = (float)sizeY / extentY; - physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh,totVert={1},totInd={2},extentBase={3},magX={4},magY={5}", - BSScene.DetailLogZero, totalVertices, totalIndices, extentBase, magX, magY); + if (physicsScene != null) + physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh,totVert={1},totInd={2},extentBase={3}", + BSScene.DetailLogZero, totalVertices, totalIndices, extentBase); float minHeight = float.MaxValue; // Note that sizeX+1 vertices are created since there is land between this and the next region. for (int yy = 0; yy <= sizeY; yy++) @@ -222,8 +244,8 @@ public sealed class BSTerrainMesh : BSTerrainPhys if (xx == sizeX) offset -= 1; float height = heightMap[offset]; minHeight = Math.Min(minHeight, height); - vertices[verticesCount + 0] = (float)xx * magX + extentBase.X; - vertices[verticesCount + 1] = (float)yy * magY + extentBase.Y; + vertices[verticesCount + 0] = (float)xx + extentBase.X; + vertices[verticesCount + 1] = (float)yy + extentBase.Y; vertices[verticesCount + 2] = height + extentBase.Z; verticesCount += 3; } @@ -250,7 +272,161 @@ public sealed class BSTerrainMesh : BSTerrainPhys } catch (Exception e) { - physicsScene.Logger.ErrorFormat("{0} Failed conversion of heightmap to mesh. For={1}/{2}, e={3}", + if (physicsScene != null) + physicsScene.Logger.ErrorFormat("{0} Failed conversion of heightmap to mesh. For={1}/{2}, e={3}", + LogHeader, physicsScene.RegionName, extentBase, e); + } + + indicesCountO = indicesCount; + indicesO = indices; + verticesCountO = verticesCount; + verticesO = vertices; + + return ret; + } + + private class HeightMapGetter + { + private float[] m_heightMap; + private int m_sizeX; + private int m_sizeY; + public HeightMapGetter(float[] pHeightMap, int pSizeX, int pSizeY) + { + m_heightMap = pHeightMap; + m_sizeX = pSizeX; + m_sizeY = pSizeY; + } + // The heightmap is extended as an infinite plane at the last height + public float GetHeight(int xx, int yy) + { + int offset = 0; + // Extend the height with the height from the last row or column + if (yy >= m_sizeY) + if (xx >= m_sizeX) + offset = (m_sizeY - 1) * m_sizeX + (m_sizeX - 1); + else + offset = (m_sizeY - 1) * m_sizeX + xx; + else + if (xx >= m_sizeX) + offset = yy * m_sizeX + (m_sizeX - 1); + else + offset = yy * m_sizeX + xx; + + return m_heightMap[offset]; + } + } + + // Convert the passed heightmap to mesh information suitable for CreateMeshShape2(). + // Version that handles magnification. + // Return 'true' if successfully created. + public static bool ConvertHeightmapToMesh2( BSScene physicsScene, + float[] heightMap, int sizeX, int sizeY, // parameters of incoming heightmap + int magnification, // number of vertices per heighmap step + Vector3 extent, // dimensions of the output mesh + Vector3 extentBase, // base to be added to all vertices + out int indicesCountO, out int[] indicesO, + out int verticesCountO, out float[] verticesO) + { + bool ret = false; + + int indicesCount = 0; + int verticesCount = 0; + int[] indices = new int[0]; + float[] vertices = new float[0]; + + HeightMapGetter hmap = new HeightMapGetter(heightMap, sizeX, sizeY); + + // The vertices dimension of the output mesh + int meshX = sizeX * magnification; + int meshY = sizeY * magnification; + // The output size of one mesh step + float meshXStep = extent.X / meshX; + float meshYStep = extent.Y / meshY; + + // Create an array of vertices that is meshX+1 by meshY+1 (note the loop + // from zero to <= meshX). The triangle indices are then generated as two triangles + // per heightmap point. There are meshX by meshY of these squares. The extra row and + // column of vertices are used to complete the triangles of the last row and column + // of the heightmap. + try + { + // Vertices for the output heightmap plus one on the side and bottom to complete triangles + int totalVertices = (meshX + 1) * (meshY + 1); + vertices = new float[totalVertices * 3]; + int totalIndices = meshX * meshY * 6; + indices = new int[totalIndices]; + + if (physicsScene != null) + physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh2,inSize={1},outSize={2},totVert={3},totInd={4},extentBase={5}", + BSScene.DetailLogZero, new Vector2(sizeX, sizeY), new Vector2(meshX, meshY), + totalVertices, totalIndices, extentBase); + + float minHeight = float.MaxValue; + // Note that sizeX+1 vertices are created since there is land between this and the next region. + // Loop through the output vertices and compute the mediun height in between the input vertices + for (int yy = 0; yy <= meshY; yy++) + { + for (int xx = 0; xx <= meshX; xx++) // Hint: the "<=" means we go around sizeX + 1 times + { + float offsetY = (float)yy * (float)sizeY / (float)meshY; // The Y that is closest to the mesh point + int stepY = (int)offsetY; + float fractionalY = offsetY - (float)stepY; + float offsetX = (float)xx * (float)sizeX / (float)meshX; // The X that is closest to the mesh point + int stepX = (int)offsetX; + float fractionalX = offsetX - (float)stepX; + + // physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh2,xx={1},yy={2},offX={3},stepX={4},fractX={5},offY={6},stepY={7},fractY={8}", + // BSScene.DetailLogZero, xx, yy, offsetX, stepX, fractionalX, offsetY, stepY, fractionalY); + + // get the four corners of the heightmap square the mesh point is in + float heightUL = hmap.GetHeight(stepX , stepY ); + float heightUR = hmap.GetHeight(stepX + 1, stepY ); + float heightLL = hmap.GetHeight(stepX , stepY + 1); + float heightLR = hmap.GetHeight(stepX + 1, stepY + 1); + + // bilinear interplolation + float height = heightUL * (1 - fractionalX) * (1 - fractionalY) + + heightUR * fractionalX * (1 - fractionalY) + + heightLL * (1 - fractionalX) * fractionalY + + heightLR * fractionalX * fractionalY; + + // physicsScene.DetailLog("{0},BSTerrainMesh.ConvertHeightMapToMesh2,heightUL={1},heightUR={2},heightLL={3},heightLR={4},heightMap={5}", + // BSScene.DetailLogZero, heightUL, heightUR, heightLL, heightLR, height); + + minHeight = Math.Min(minHeight, height); + + vertices[verticesCount + 0] = (float)xx * meshXStep + extentBase.X; + vertices[verticesCount + 1] = (float)yy * meshYStep + extentBase.Y; + vertices[verticesCount + 2] = height + extentBase.Z; + verticesCount += 3; + } + } + // The number of vertices generated + verticesCount /= 3; + + // Loop through all the heightmap squares and create indices for the two triangles for that square + for (int yy = 0; yy < meshY; yy++) + { + for (int xx = 0; xx < meshX; xx++) + { + int offset = yy * (meshX + 1) + xx; + // Each vertices is presumed to be the upper left corner of a box of two triangles + indices[indicesCount + 0] = offset; + indices[indicesCount + 1] = offset + 1; + indices[indicesCount + 2] = offset + meshX + 1; // accounting for the extra column + indices[indicesCount + 3] = offset + 1; + indices[indicesCount + 4] = offset + meshX + 2; + indices[indicesCount + 5] = offset + meshX + 1; + indicesCount += 6; + } + } + + ret = true; + } + catch (Exception e) + { + if (physicsScene != null) + physicsScene.Logger.ErrorFormat("{0} Failed conversion of heightmap to mesh. For={1}/{2}, e={3}", LogHeader, physicsScene.RegionName, extentBase, e); } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BulletSimData.cs b/OpenSim/Region/Physics/BulletSPlugin/BulletSimData.cs index 662dd68fe6..d5060e3c6c 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BulletSimData.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BulletSimData.cs @@ -104,18 +104,20 @@ public class BulletShape { public BulletShape() { - type = BSPhysicsShapeType.SHAPE_UNKNOWN; + shapeType = BSPhysicsShapeType.SHAPE_UNKNOWN; shapeKey = (System.UInt64)FixedShapeKey.KEY_NONE; isNativeShape = false; } - public BSPhysicsShapeType type; + public BSPhysicsShapeType shapeType; public System.UInt64 shapeKey; public bool isNativeShape; public virtual void Clear() { } public virtual bool HasPhysicalShape { get { return false; } } + // Make another reference to this physical object. public virtual BulletShape Clone() { return new BulletShape(); } + // Return 'true' if this and other refer to the same physical object public virtual bool ReferenceSame(BulletShape xx) { return false; } @@ -131,7 +133,7 @@ public class BulletShape buff.Append("~CuQ0W%@n3(kO-rUYgV&sqdYKwltYu}{y5Pzxt93=U_~XU&k4;-57VPRgT53^NA83{>%9em#^M1eQ zKF|D1+KRG%_V;KS?xVz~NJa@n4b4|aL=U1ER z<@uGSc6olO>0jh|U(*ldxkH|t>c%7s`Vquf>M3L_Jhq@MHC(UDi?yS6QI zxf*^s-&IM7edVr)H7Le%{|3rkmCIeO(USSDid^cn(1|DK7xl?}SHn#3?3#a?{LrQ( z!uR|XZ6tndD?2&wU(n@R`==Y%eIWS(m+NvLfuZde;c204-oG%|;ZJojS0yPM1Pw07 z^Hrf$wj|1p{^`aWHhvH((pIz?L~^+Tc;@{J6W+!TZTJ`p{`CrEfCjD@o^O~|ZfRb% z{|WvVP@qFiq@JBjO?b?R$9%@@4!O;SkX!w0&%SZ_m!EH~xnZ8Gs7QXk8Sz>_H!mzG zQk?(IjfEic^R1s`3M&JHg8XlvJOAwXn`M9xTf;>- zJ#Cs6OFeC(+|55iITZR}Q6Wv90{o=?MJVRu&-`y*fA;o2T^0kD)~EA|Xbg`nj3Ps$ zebF%~mER4=7FOHY_5JgYy@f#Xvu#ZEL+!CUHiy6J@Txtk#bcJJ7M~d~OU*8~Sz>m1 z%oofqZ_i$@D-QzNV;9c2P`2Kk3AB`JGzQ+uE zGS_(U6yncNJ`SjOf4#@tbNsNm_xR)Hq2qgx9b}C&X5>tcH;ESEcX-XE9=wIGSc(^U zjz34#Jej#>N#Yu8>>J#a}`luRool8{Shzdt2%u7Bo=az$^jvX8%-zeL8lJCCcmZ$Nap0& z$qsFN(vQTa#S7pdWhr}7`h2O==S$h=nol&D^2^W7mcQ01f9<)-zd2j}e>>&>_gTsV zkrMNSAcKeSAD;!N2-nVx&s>{j@hqx>@)!qpJ8i%yQ}30b;C2xn2TOTnN%*3lsm@XDlqtog0TKY@lmx!qst*=27QU_KS z_VKysrCI`-pJHCKRCQFCT^Kd0C1`f}84sCV0W(&?q)M|ZNI4rKkrAot*&EVglkkbJ zT44B8+-=0%D(*329u@Z*F|UgIj2P1WM$E6`0V5Vr@d_hWq2fU!7F6+&5eun!r4g%C z*Hjr%fGw29T3-AY!{2klJuBi&1e%^q+9Sy5_)Pq&A^|Ps;x_eG`A{qpG*{WwTNNhZq~pMBQf3 zWG3n{dtQ>9Ow=pW&m{8@MD3UC;DqS{Qyw;Zp2Y-*>ZkBP%+fGySSK7T@FU2}03#9mAjsn=jtrj}c@~8ZnEQ|a-25&n6FGr`A39@# zIEP7|h)076Vy4t}LYO}H;qRveKx$zE&9McLSOEDQcbjpKi5JCvW?ble$c+0@BOWy4 zAyh(DkvqG&Oc*LV(oyc~Z#Ats5;7O=L>yoiKEUU~2j%x6`Q0VIyXE&$`5l$t{rr97 zxP%{;@In0UCZK!)2YwO#H1zrCTg!cm_ZtnNU|T}HXwL4;H2T#o9%FMTkZG)h!b1ca zLOw@xtM6ZbtSqQ?As;iWfhR=0l!%l^qB;6>Sr~EY#Mx$}|F}sdt-gN|!e(Qo9ZfF$HmP>< z5|(ii`!D!5mqsT`7f+>|O73}ti}06UNjH_MAKine``c;n=S=tQnb0<)J>*aO|0)yO zUeuml-_5m0HH7@cJnc;p?X-VKVapz^uv_Nd20&88`?uz3+W-IA?W;81+tS`UGVbkZ z|87O1qILIHAQee$=}-k~RjB4rm3ak1L30h43L$fi2aif~jd#uJ>lTk&KTXVY<#CwP ze_oKR&77@zyw*OAHmi96sxgR}o9hnI(UItOG;|NBij0$&6e#33&6dPcWTLF~_=mMZ z%^{CCRD#A3sBHD)1Mv3lo(Sd^%&N+4?9j;rs^iB|2Aq5M#f1UUM`CTOQW? zDJ`H)vtWJb>=m5y0V(g%Krh5Dd)$dVHOnrY=zjx&)POrXhO`&q2 z@z3f%KGA{A13z%IXEUhjn9J%h^M%ZMOl5jD%T7DQSL=^x+_i4@ zQb}em8|k5HPW=>ZhkjV^)5!syOqDLgbg1k>7O%9^r|WB}i3%)S6trN#j0VkcD058! zRl=2-Ybx=CTxJq*b|c%)2r&-CPavK!XOPYQ9mIC zGS>*CmxzDV&lZ=R%68Qwpt^Gx7Eqp9ESZvzpqUlgIOZ=?5Wg#cZd25x3K}%mx1Lgo9)?~;kC{>#w=jhP{)=)pY zPAFc?AL;t){ONeSI1YMYevVVHB&?O>paZM(#J|1%S3V;yJQc4|F^>_iRbj7Dk4|>T z?ubK{?|JUsr>Caul;2K2iB~~TL%E4poADYmUTdzZv#}QZPNgPdIsKto3pgqa^)!_D z6((4a4Z#2eT|1_FNIK5)5OtW~L03E~kx>N+gJ3nqU3aL4@~%5ni;OxD7_(Rf%GTJk zS$!?0N}qYcU=0t#qE0}cH{Bt;u)-WQ&~Jy45#;1#K&p%)QkiKELA&>&2$XsM@qGwX zp@7GT2BbhW3PAC%IKJO}oG-*o3~dt=apo|@1ZLvQIG);$SAG02O4mqw?Q!M<75ou; zjS&sveHa5o2ZNwec|%6D8fjQ>R3QzEj~YBM8rhWQ#p6b-&YHEdV0s^)nSVt~1QQRH zbwi&k-}3iRZp;_vNX+awWp=z|c1%K_w*K)yz%d@84K@Zv}5xA z5k4blj0^8Zz*x8gzxUsZpWT9qj0fSMncFuwF@hFpmimqXn^c>Zrlx9)H*;wXjZAui ztHtwg`lhC)Bo$2NLm}p2mXo}slLyWT+Ig7iBySV*FmSgBfSwT!-Yrt0=WdSQEmELo z2Y9yQUJm|iVe7#J<4l*8cu~(o=2A>$8oFQw=rYqhEHy9$f_mOXy3AO+1%}UDiYbg! z98ncWj|wE+20~hDPqRID%G@o}I47;8K|THU?4tYvPGW=8^bkNpOo2fTcsXx|sh*>%?8YcEdrqj96K3RLj0PwQs^eKR@(_ju zb%P#gE|%(;;LvCp;m~N=!I9B%HwQ+?y&M-UXBK~#!(w8&3=2n!X2sd~SV?kxe%^`( z^5}HLQ-T%D*lgw`=OnB*#OApm{-@a*2nFR6>4=B+hgy#G5N{*E<;kjeT zlb5nQVKdb_fKFT;;Pr*jcmK4l331*v#*mse20ISO8RL2%jFxxnWFmcndbFP+ZpS$<57o_xfE_bfSh z*=RT?taQG>zXHN1AAYbXUXhD~zfzM6l58{e+vuuuDNM5*c80QF0cBxgPKOUHp~uB$iV?>^>*S z)HUwZo|C(USbIRM0%RO`B$k~a@JX!vIEm%FuV_Dv{AYoJrq9IuYp);32sX-?j0;|= z!i90v?|JTrKh4e`V@V=a+)Ck$j zNEl=7a1C+;<{B6VE6g=8Q&pR5s@7n8AxjG)x!$)vW_|IQ z?1U@yV%%;4^2630Mfr@|`6!S1-|Alkv_PjsaBTLoXsWrodaTh+uE^6JSOw*=zXFvF zy3+LB)$#G&zWw>?3(FVK|9tf|J~q9t=*ZFg`zL0kx6Kd#(QfmD(BrdGRu~EJdY)rE zM~|3wV^Kcix%{74U>UxjS7f{!GjlK$Xz0c?is_XqmUr1daF#0Oh^%X{6mAXD?^i`|t zLq=bXy1vrrt5w%m8GUu?`f8&utgf#y`Wn>rwMJi5U0-MPHLI(PzL?qw@a<|Nz;~&Q z0N<@P0{mLF5#ZOUjR4=PHUj(xwGrU^)JA|$s*M1@No@r9&1z${v1@>=y0OOCC3Ew} zT4UERQ(Ehc5zfu98jZ-bj4iSenU=9jHX_q9Hp)ggEpxAIWTyoD#)wSC*fASf*1-B8<8RE#dJexZ7jVaX0`r1mV04$W<>)mJ}bglNi{~bX{S3B zGaJLSJHjlvq5(OuJFbZ0*&S*(!y#4)2Xl3}%IXevnc?b81FF(s5w1b79l;tiT#H~A zg0*7os1pE{0)RHo#@b9&13(%;ha`t9!NwXV2R2r`4&n@!Z7$s!tDPL!Oo1YV9;{Mg z!33gQ%)-h75$H65b=U-2Cb~mWsRNikz_fF?NT%rplmJK&g(wj&SC+32Ljrx&{@y+)G<7M%?!w*fgJ zW2F~4eJI$6Xw>MW$ta0}Nksid6HP>$PLX&ahb(f|$l zgvOktK&usKu!^7#p)sdVa=>lC%58?b^rjBXu^Khl{$QqEXNKFg=^vPBmV;MLOjA}s z5iy-18)-m2hCYpfGhksOQ?;XS+Yzm!u>#{4xm`3@w1>Kp(~TS$1}l)`@Ng}S7C9z{ zy^e;;UexP_%|e(MjEs6T=pm;MIb>oVY#E3q0hLS)Yhn$eoA9?8B{m^>v$pvKjc6Dt z@F%E4BJ7b3cvKnDC^(KRM3Z2%b?QBNEC+s@X5X+fGfwp~p^do;?IXo;&R=_=abTyw z4A2ef1taQKEuchM{ov7`~*V3t*3C-1-j(R7(zo6Oi|b7X+w zvSDPHJ7iQ^=7gVTu_^TEc9o{5PQM=Zz~_SVf!9}_7&vMY~SSm17=tjC4onh62J_*i2-cT3~xrj9AJUW z@LGm@PAM362UyZcc?lL*bAVNJiZK$ymbWq7j$e3JzzmDt9O;tS8JN;Z%Wlf_*RNPzYL2E-mvP#Eyh z9_rUS*sCUX{C1o|Aa9Lu*8VaF!XO^x4K^tU8W@DIg7eBiRKl>EPMQP6l^a1j7%(F* zWd`;$utR&beRMnKmH8f2x0BPo{FTDh_fg?U8r!BVEaS- zJ-M-?$-^xfVlM>3PK%(gNyHr*@`ol8@fRWqg}@JPn@YeVBb3ULDYy95{m0F`iQsR~ zk84RUFGV+)SR&1M8yn!n$hZM>FwBDVNx6q1bFe|j{pLLkfh>W{V3eVI_^Ua$p#KK_ ztbsuxgGjas2LqDCiHV~oC>u5*S~?hfaHJLfh>TUc?5I zZcmK$q&*CogY7zw_AsQ|)5Xv|{4H!xC4GQyC$&1w7i{}*x%w*zYOKsVt^VIkO{H%q zCR;|6pEPgpBY=oEQ=Z0tTUL*x$=Y~<=Ht>UV4S)y9>$-|BF9{?or6yK^E1dA(u%EsRT&{odg3@>L>zy zLxvvRl0%ka0)`|80kM_gh)rzac0@dmA&NYbU1|$`%y2jZA4{mQ@KkRwyBet8hA}Zm zHBHdIxEg#Gd;^<%`Op_7;4UA!k5j(9Out>f%N!KBT;zlbaU-@9`m1Y~udQB=zd7C$q1`hBqQga{{k6tZ% z0p-|hVTY0*2i+(_A6fTsarJREc%}efvDb7yQD~20oWSr83U=;gh}-yf`E1Br>;1*~ zTO*#~dcU2o>MIKJRei7^jx}^a9QNLVIP5ngK-hZtyBbBjzvsDsMntfqJ+LGbYV zlrY_2e_EfF`N;dk`_Bu3%w@Vf^H0lo^l6!s7w4amnSIPTBjask#nf2XTr-;b&8*FT z4CFxLo)1}?%9Q97z8 znrVE4Q(4?^B--hyp6H^ZdZL>Kw*(e#AtQlBTcwfcRdCEoY@lOSqEE$Zj6{--S&2=w zt*zJ$cG*4v&Qwviv2BO}OjO&sW>ZnGv5l)cZvAcN%1uT6#x}79Vrz1{*aD*!#x}79 zVuNz~PAL;Iwml%VDvj+AN=lWn?IB62Hn#7Qlp153*aER-xm|35*b=;5Y=J9+Dp8Tz z41joy>hw|w83q)*snlE5EYww+~7i@V0$3C1e zfny)s``|jCJ9351ao9ev(*m0Xr@36uX@IO>LJ3RP@lraf^3+;=e;*v=OEP=)AuC7P zU&mSS8FQ)INw^karWKW2%t;dEJWjbcBaAe0D3@bfPR2ayFCsCp6+c=aIW&I7_(Se@ z<)6@ib=JimUZu>Qir`qQ@Br2)X# z+3B)~`Ly=P$)H@XoTWd?w}=g4dY_bUNr8aH{jzc9enI~g_6z!t{W3%UdD(wnr<>4! z@Fw)W9{pF;-s1k_af?-X9ZdN+;dQ7O^U#kNmXv%uibZ#@L$npZAkcBfz!5b(VLUj) z7ICF>Cj0*=LWOu|Y==QcURIvqbB@Poyfhbh81 zaRR#@j+ns6j8hI!{Bb}cnCngOPto{8OXvxW!HRV!1}prb;14Y(A^JeW4vAwwA=oLk z)9pTKe@)I)OiyHLpY<~=3;?Vxl#XP~wo3E5D(i?ODY(att6!TPM^t1S4or{Cp-k#H z`l~O%t;tvxGM2*~>CBqdMq8EIxA?o)S$%KKv%jeWmZ2dhLh68-s5DnsnHQLsn@w(W zwcCVkd$q@G!hWFFgz0*<&uj{qtNmtE&|Dobn?mO53bP4yf@V{-xjJMv)tIX*&8AvF z(NsB`{lWIHq%#~$vGjwZ|5v5i13$AWvu8J&+VLoe*)s}WhYuIHmTp`<2hmuPv*!rO z*>jZS?8(4I3#L~%XOW!n->whB{foX|7X7{G_=Qtz(A{7bJR&TRq03~)QCLpl+68ui zQx{pF#R4zDsf$jIwjA!Ca;92 z)l7KJ1mb=(5r8ifXqX6^i4duus4^4PW}?PS)S8Jpb9LC54O=+70-mDiih7V1r-F}y z8C>iRh(dG+L?OB(>_rc*t^gy$ugqpjOo$7pJH_3c3F97dfANa@3mkUfm;;9$IOf1% z2aY*#*nwkC5bg`&umi^&IPAbN2M#-M%&8W~960R2F$WGib#&MXhmFDoW`S`-&sT#J z+(^!`bn4caD|sdK@pQx+i>E(x=H;oWR_ia{0z$5crMEB|n|d;}A0rjsa@L-ZP}pt{ z#>Y(Xu=Y#7G49m1f&`X`rPBl!pL!|_EUAIzum7%wQ0{%6q(9=GN&K8w3C%iY|GY4dR3Wto@TF3Ww~ zifQv>??o*OVEaWauiy?$ThQnXD-6!228Hn%L9wW6t1>#9)$(d%WlXiz7@h5EIks!M zR2w#Ox>sZCNR3^$Du1%q^j$yIyXl1sed|7taqo&jdbTAxZEDBTk(bup&vf;Qamy*) zfOON^vB()oT;q?WTdX+5%l_*qG_cmUT4U+{38Y+=z&_;#w6;THt~2C{hgDkN%@cT_ z7j!g`gU1hH@pn^8Z;5ds%dHgWm%n=rk ziMxF}$635f-0iyy-0fvOSE4#R(EiuETBnW_s}Pi*&tm`dS+p;=e{}*ePz7izpL)?q zfW_VE72x&%no*H(zkFMU1P;1fKVl`-<`{?`Qnl05sutY|TIw#-1W| z;dX4rKpEy}{htqopDah{3Uo42pa4LojBVWXBCcr=~D6vhoa+D)Aeyawze;SIRsmUK&P;J`D@`+>X=xrA|J!u7NrC zjSJAn{-K|(^4UPqF1^7jjg#GG?t@9b>=-hC0PmIjZirxs)M3=r1h*^KS8wFs}n_U$^v+tFh?Q5H#h*G_u|_M6!Q$Y=nNQJo>nE!}|tvmc3g33s+-`K7l4Q6mUY2U%k^+ZRGg zD7|_ez;LLT*SWcqh(xmX5e=gXHI5<(9|#}Hafl)F7#^|oo#G^M5+^BRfMQ;jZU^@@ zwzmS1+1O=X=dqso&eYVvEnXt)Q;nE6NgaRs);@hQ5w2FW)56!vYYxz~Xx=CMKS1lE zc^`*`IY3LKdEZV6)5vJvHy~kJ8O{5I6azGin)i_ivgkcAcNU$QLHB)SH>Wg;gb6-ZLiIssUX2N+(7RkkE{VwEk6;wGEHHD1Z z4@#{{V;j3)bygYMrPrIPjoaDhY9&_L>~YnJRW|WcO;}~0kgTw=?O6tJ!uN;PKeT`? zYQeG-NYaN)Z>bO+0=8bv5122zPW`^~<=yRgUBP@=y3!@ifqKqN&grx^2DJg1lrSK6sQQrtXDUqWv6h?__S&X;qbib2*g!OMXTxv z8n+V{74gRsR_Zo{>#6)r!E9?5$zgkgIm{I{7 z4FJGM^AtQT^BaivQHu!Is%SHC3I|kcRcz|1Y#BQrM?Ds(rE8~-K;Jm1miip`RMB4| zICk&HM6#&>q`*#OB}Ro4t3M%=cAWqRcgT(%O9+Rd@<~Jp{qTuvAE?{ zFw>ynlJ7+#PX))UTkg!mjST1H&~I`yGpdeMskym!Oj z(kCuQanu)+{QhqW|ATWcMB%tPXqEnF2{5ETcJ6I9KIk8xos9ajzqBbwza&^cLB0at zpd-*Qau`m-1~9J#5e&r47eF;Sxdcsg78MuVD?z0t!fn?oGg661we{`)1I-`g#`NaL z6c#X`bs$bXZCxiOpc*9f!R&`i2>Pt2fG;vPi`&)2C3X*F>67o+nb#!dW9I@YuNMam zsPGmvH%os{0-l&@|G=!LRPqPmn053OybYI;6qeTX5qPn14=s=meKzOlv&p89Pt(UH z3Wg#Gq^VMY)%p2obN{2D4e3Jq6wyX?RDmSDtt1b66K|-AfkmxF zSNb#md?rtGgFIN0d5t~KxMxAPhVt7EH^zogHCG?#323~q5W_Z#cj2{lK&u%>Qe@R4 zsdn)=Y^7FW`a2!^Y{=247t1wb?fBd#6js%45DRdFJc|g|CoZ$0zNjb_c`Tp(h*hg7 z6?rV6D-eSK2_l9wFCoOjDq4wH1DD&)p{R-y|7I?@;qnEch*&$8+@kov%`1kE2qA5) z61FSYsXlMhVatS+))@N3GNza=Wo`arfQEH+tksAHY;?t?gS><>9F(k@V zXiLQ!mrp#ZBVTuh-`lmP6cF%1O#9;a38;f$2b(FZFJZP;r=FvJuy_=K&mkZ~xFieX z7p7&4Jk%7&E6TkmpOlH`qw>O||Kz;zTlNd7LgKF#Kb7)AXUqIh09mFiH6qaX}HtEa({U-r0s?^^BheGq@2!yoxp#y8{aWLEB#iR68C=f9s zue@6hB`LFH4rVKJBiI>F?YZ$3!~HD%UbO;eYdTY}EG!?QN|<^DD`hHh!a4_bInBCo zy$iB`_RHw<47%^f>Q^gvxv8ACLx5>=oLi#NyA<49?@P64ab zf!NCOEjPlRbn-2N`n)Xa3)IJ}*O#ovS-d8F`N5+GCeb?5QPXd50z98*-|!=bsf@E)h3aSbQL#V_6IX?FAttZftOg$DbcQL9 z(;4=XIGsT$slp>@J@-wS)q;@TAxQ2@G?BAfRqrX_fQI%$c5gsppLHMPCC^)ctcX!oP+$Z*If%*3X}z$tB0rd#;?^{%_SIa)N!N}11a zk5tzGMo;%3jOYQHJ|GO~6SR(eV`jo|4^NY317~`WCTaQ62uBWGY|Y*?HI-0D_&V#a zp8#*5>q#1qtWqIHDb+M-NTdSNyzO6dq79m96EP zDo7zMFhOFp!U1=L<*W^MZg&1pI>e&U0NN=w7NL>0Q4qig3S}t)(%4KD5cP-@s)&P9 zLPW3{+78YmM?p^_#h@pVqpGdXJ}gb(g4z1@LrxRsp=1AYE+rDkaT?rK4Q{Ier(rvn zDpj3<=<*&j67+Cy=M5wnJG_Df#55Y|@7~3YW{h{E zu@*7Ev4V4cz-VkRRz$hu4DHy{CVDbGs&6M*(oKCJtJi7@gJ4kAjOsOz?YyO^Zhme& zV(OwT@fPptJ2)y!lNYH^i3Xl0^wg)gip>{;>JGjEZV73quW4*LMZB%)WqsDkYmkZ? z0a_+u3-9+bDN~Q6OVm;~Rv_P-I#OVB1%E@*_YMG-wd>0{9(y0UM@53`UHGpwyRai? zV__G_)Y)v<>Qx~zVejgNZncf*yPJU zxoqmnR~CAt=w!{Dg_#jab$_Ff5yU_+@2hn+9;M9OH&0(WV zjxdHH7R965s=*5ZK7T1f_x%bARrnV`Y;VRJ22c@TvA}d$8~?!>h*tp3x>mxEr?nZm zwUd*S>q8JNdT>5reg8T{me;C|7$){`O=|*c+XG4)>-3xzn3pGqof+Se$2A~quD_}g zgV5qiy2IDD^&a+BV&G?`;F8|0kzn~_zZ!&*q?A z>@(cfEq%rlH{GFFEBg$Gjd-F-`m5V|>$haPjD}VEW%6Vb?8t6dwRy4$LCCr0A8}sHg?0LZrh{}qt4z8cAhzz)8vkvY@I6L zdwj20oUs?}#tV3rpj7an0#0OkV3VE90W7Z_0w!dR;YRy&w^r{2Q2S2i^O<6a~JFx3GJ4sMIc+=xXW zDhG9Cj=K_@8CLr8x+%1xv|pNtToJR*7)Z^sSNxec{+~ z*(#`%j{E)i2s^+Fa={!b3Mh(EwN{kGcq1d~8 zAR`1&oNtkl$1DJIgo%3Z3;bp?N;SY7)ur>9gqp72Qvet;ip?*8Po6u*6j}@J4eexb zNFIniz?j~B0N@jhasTAb(6bC;X^+YlgIM09zyyNGf$1dZ&;HR#@r#i7LqtrD2eO9rdkqjE6Yk^NB)wMIr&RUZ;e!O-338gxp?&S z?Hu2wRPeM@Gv;Hkd^I|elOQKVN;k}a6hzq>kaJg!R+ek^L|CU)zp!=xmoOn=`s37x zR&hbqhj?~E0%w2>19&UQKzyuc{>~x8B2=h~r$I0*Ck|S}>#|K>iDp+9Hk&*kHnkn@ zY^$BwC%0bOk5akzk0SQpN?+*zhd;2s;KmC<<(c#a`XkPuFD(7M(>-U?7ph;)_R|yA z{cY#d7p&5=_7`-6@^9WnmP}1Vp}!@dz&Y9gG(0=6Q#89*wH`-}fZa-sfZ_+DOdO%r zP8;p=A(*tGDBuz%vNGAa4lG8|6@bnV28(lI4#NEd$#t0PDv?};5?_NakZ@UNye&PI`vXhwRFG3zb0$!812zgMa zIE#=PHzWTtlPNC(9GM|SG&?z|s$*=Bpy5kZIw`wjwl6t0l<-!Y`We2(; z!fm$JnplEXM|G#@5qg$;@HcaLs3kX6DD`!JkoLn`d{ic6-SDDb|G}=IJ0)LsOmYpF z+`MHfxk*fRU0Kv103Z*rVgq(qwZUu)C0zP|N|K0K51pJd!;J`Z8XFlgpce%FEP~oS@%QgW`tE4;&C18j; zh<0EpNr_dh+ksh~?Wo0Ho%LB&jC)mHr(|)@(SLA)A~-i5I67bnXah7Fl4NIzRF#FU znBpqf|KEuWqa`3`19@Uold2ltBOaSP|Pzhj> zx)$*YJU76PBsEIo@;WegFK%EfzXKbX+&VdSqf0a>sxe8r{+fMxEUsWU6;LRKQc3Qx{Yf*C zP-3H%Kbb^Lt?fB%HPoHixn3ms`%ju-u0*ok(P@O;xg>;PXi zI=mF2yb9oFl(#-J8zQsCW8j9G{M_t#rTD9N{qXBx~NFP=<$gwr0I2lJqsZG%IxIg)GKrPZw0dG z5#dT&+prNQqJpE!)|joH$8pXSoEtw0_VR)?q%?SN#?tR#X+Q+DfItI#Yo3-N;Q)OQ z#dekGtr$)3Hq*N5$4(cyca@whh0BDd@$+sN>z^-8mZhGb)xT(XkjHy=m0Z`eZ+MU= z!z2j4aJp$GcwZyDLp0vR+^Ofwl5e?ILwh%xF+9AMMBLGb-|Hw*b_@vAo_&Z6@8Gd9 zeQIoYIEHZX>nOkJ-FdA|*fJ8<15)iOG2hQUedbF;K=|ipCq36D=c6_K7g&q0nGI+? zxKQ#0UKmT?wG)q4Yy5DTBs(mL`k0L6Vsn~sQ@{2D_g^pSzZ$!L=!95cy^H%2oiq$H z0aoI``jY8MrP-v^uNM}Bn3c`Uf^bhyD$6DTi(4*o5NOLLuUReRaZr~h+pp&j_?Ogj z0S7}IX0N$wkt!9Bl_OvUr}t!hVKOAiuBnY#+hBsv3Ja^VjCHu~7{?__-*388_ zyu)!x%9eHD;bjr;Y1y(W#cS1q=#P{%a9Y+*rDEr=!OsHI)H zgj$NN*xVjs_Wj(17sUYYf&-3IBA35_?QX5j%d_#5{Zb!uhec15dz=g$K*vSP!<@Gu z%H2xe)8(<{0^XD?1Oq4LUX$Q0|%U&b_2 ztgblp92muWaUp17=Kx{ACuHEEuOKayo=$(516&{E_0K@&?C{7xk}tQE7QIo^@3iF4 zA~9SzNt;*3rmy5Xx0vQ-OM770g};gqSNWaAUj^@{xh5aqXY=Fa*O;{cn=1LBh0pTn zL8Hn{`6QOylA49D;XU)KncgIq*vyUB zxBhCn0zMOd2~rpbufz)mw{_DNgkvY%Tn}h37#uv|<~qD*=mi7s8G6CMdxl;x@SdR; z47_J*5rg+k9b)jF2_pvd8nojL9B86Q0FKS-017bDY<=Yrbj25Nw1%z0@f!NZRADUp zw5v>kqYO?o@i;J!G|@ngGfgyX;^d9E4DpN(+=O`0iPuu$q!X{C!ciw)N42_&)VTDQ z)2SijQ~ImWUkN88!nOk+xD}|{u1XCE!c{4XTSU*}s`TX-_1iSPWt$yJiQ^OqnwX1l z-iR(hIA7#VzEv~Fs!nvW#<=Bv0jtzP2V?$oiAE=SE;=HQ$@D5N;2nCwaz&p*S zNXs&(zYG1xpyX3EXMKoiAOp>u>NAx#=*oyvKXdkxvHYY?O(lo^*J6W#+k&H%^G>Qr z5GDGM1+__*0caLTg}*2gyYbZ5>bIkFzy-1*x2EEJNo84u$?W)NuPTx+642?@|v-gTAB*Sz7sD_jTaW<2Q zj{bWQ6M?=WuMgV7LE@BG_Lavo%mww zb#k_^SI+kJ$=SZ7obB60mcL^FZzt2=F(iRX^A6gR*O2pzB$mSu`SK|%oUrw6og70i zJYlWV_8W0W{mBctMmzPvI5O)&`Iq_B7#`uP^OS#m;}(^F&d)Cx!#i{V(cbMV9j8sV zoS$;@H71H2{GG+{AJ7R|__iIyhR^lm^y&N@@P9k}3l}ePF(nr-ay^dQ7#1Y+7BBjq zN)W&po*6yzi)^%-a&a`I%HZ8>7cV9dg5mghJ`epa)7R6ymREDYOT2BmCEA#@{@0xsf4a?4shquexIp1OJ8Z7-}c=5 zOu6OSE-(57Hz|3;HP#{p1Ot2r9*eL_!9N>+x%D~=PmkX-=!Y50w|Hzho}AtD++TFg z&MyUc;9rb?ES8;8R|11Hmbco;17ie8a?#0S)DXkg!B&`W?4dw>dAt!!VLAZx&CJO+ zfJM**VG%xsMI?~dVzwuT-$ID@Lg)26*A}2F{g*2b?mG&Ig;iGNS1E*#{U)zdc<=~u zffwT+7nRrNHSP5V&UGi}m~si;j29FG{!HbG)oUp)*ZRzK0rFYkpE?KpJbHT3UM~Nv ze~H^<1iWee{WefN+uwfLCsB{qFMiQZY+&N;`L&K*w`X}0@XpcyeMA$Tl;o5Bzexgx z{ZD*wB|7*|FY%IZCjUV~;i{&Y7Cwi*nz!)L?I}FZ!1=+MC>M&N!ii!peCFt)beLW! zP~SK`e#6Sm6uLhpaOe`Y5aL;8JWALnK| zjh1sWoyLn3HBDj~F%q>9GgylXFbkM+=bt0H*yHEsU%?UlD^SS4f?NI+ckcS5Yzpe{ z=acmhN}!Pc&oO>>+VvlB>OVL`{b}Q;o}8Aah_Ft+q9f4q6deI-n<;TGHHzmbg!ZBH zoSwUgknOu6LF`i~+EX|~orS-6saD43FS(M>;V-$7&*3kUOZZFZR($iJz8{j-66bj$0r8T}i`%Ij?FVDt^54V2kzuQU(s5=o+)$Lx^xo_z6X>qAceUWLjd z!t^7;@*~3UBaf1UMx;|lgq25xk9Ulpt^ZRKe)EMkI&{jRD7tMBGcMD$a}Pam;ZwdD z&b5zGku@IM$=~~^OB(l4Q#Nj=I%(WaHB?`sq_0UzH~Jvp`u}=r$`SGFF@UgJ1BeyI zV_~3VJQm~ceGT&4&fnXk^4rDVZO!uAjbC%SGVb8)ic|2R)!K zvfo&reJ1tUVa4^G@t)NeisYRjkuUEAn>=|ZNM+?6xuHPboe#bsnGavE&4VvUXW^62 zis6GLTA+wlO|!2gJjp+!Hl`H>Zq)c8y*RfbFqY4Z!v_yHtn8Iz?GDkb>&LR9Gdz4o#XHFp?@Ny&u+*OsA9WSubf*!K3c>1OBNtfmmuIgCKQ~{#kz)Al zcTX11UuWfK?s4Vwo7`fV$8U0v-Jg=$kG2a ze3-ssUg!KN@B!JG1P6r~&bJ{y`xT@t9Eafv535<8ubc4`P5OP=uOgkjzWtRX*!M3z zkA2^M-{_2=n96&f^NA@3Re9#+2l3UU8S$~(TXB|r$U7{)NHT1fm1vb*;es+MP&hju z@wXyKtqD^&EQ^IUFnPZaJAe&N?#bufr-!QsSLNxtL>T4u`)Dx0N(eT~3cRINdg=&B z!K<`sB9|)%3%16x?UnwqTdLbnkb=)>6g`!J3(eb}g< z{bAhTomTTyic1}4$)|+J@UX=Kt2hEKt(zUr4&mqH3=Gpe)WYs4;E=J>Ic}g z7oelKQIIA-R`AKkS!v1k@}?su;7T#rwQ<)GuN4ESb$xlca}i)c4`_N0PE7X~gW4e5 zIGvbG;IOgWXH^f1vp_J;$d!!1q}|HMB1YsxFo?`S1RYSP6fH`riQ1>TL)EDR+)`L; zJS@JoNU1dSw?YsMB1mNo!GO6eiigi!)_{k{To!)1BGeY|^&P-PS7996QmG6|sv~=z zFFlQGv>G01N6zWJCEx1C6AR#qe(sCPR?dSLBpD8zmMSI4^h>5+DJ`-~lXJht>Zw=C z`pX1>nWAupg$HkqK`E@v`)Q5$_7aP4eSK=`$0)MPr+7+ZyivpvX0*hR!KdGywpT5sI}HiJ%9*8U1n;h4#mur z%^XoPMRbrGHdAZ{Lbc!>yGk{Jb{#Son}Gra%%yLZLaGl1h$Dg?1c@EHqmR@!adauQ ze*+GlBNcaQKf=i^bre08n&?A?UUazbNTACJ4{1P`vnoPBm$M>5K$n^vLO_=~9zwNd zis~OiV4F=kRB5J?IutZheL4iTp_YkqU>oY62zdaRA>es}c)pWox>5&q<_2JFruHL} zny5r!fEv{~0JU3(0P0~K0;pXK0aW`zfcp6^f?A6twTuhCs9Nq+)X5kq|pRWLNb&_xX0&5(zo2M`iLotpG-E_;NcIvqQidew8|T&Op@ zDTDjVb~724SGC52FQi_830Hj&vj*I3v9+y5a%WqtcNbt;@1lhRCsG5c{Xrf{ML2_) z4nnfj*%5OYr^7l-pP(8pJayyYF@^74hXLmWEQ3<}E9_(`xyQ{fxi^~()jx< z{yvXCSIKPoS2}x^4)E8V4VLNIKBu2l%8A^8Px@U2&0C4%@c0;VQ!o}s+j^^s#GeZi zzSJk(vu!8CJ4WFvhKUm+vCF)}~3r?-y!#Alb4%9 zXeOuI7@p)E@(djXIJHtYBHPocpc_xR5_M4!tR*f_KSYAmHysx)eS${W$u25*KAm-Fmr_ z)C~zqgU?J{zE{x*fJ5uoi_+ijRj9u4El7&+HYlS>=@e|zKV!c5Y z`4!GXflkFFL4OzDXUgr1)MEo^kSqB|P`lF+AC89G*Q7Qc0im`rbJ|U*tF7(dn@Qe) z*s0yD;YwbVko!*4ebv?n1s`|u{(bTH27n(vybYB+tv{YHQ-1FIliuR%ILBuypAV1x z&KpbBDLB)j@W7^pu1;n4U#8}soRz-Db#>HxLmB)ePP!p+T+B^QELrVNUZ6^sEc4z_ zrRJt@nKW7^?Hf(4i8sRevn`Rn<&5>ti@_j~GpSLZP4qRZ)60CkuisgW+Upyf=a4C<>i2{JWJ;C zEAkFWerx)C`hq_kea6_9*O@+7693MnPGnX$=za^BUK zW3l|cle3IlB|Xp|V(BD46mfE)x`5Es_xk6J=pjyF|`>y`;9wU%q9E;__Pel25?M zd#4M0a)6n^9VPj2S6#%5j)MT?S34jQp$WMMZeHk0s9*J84noA$_pJY*w0u{a^$vgm zfPq`l{l}9(MQJ1K<}YRGY9s6!Ymyo2I!E4+UtyZk@(n!tqyB1~5$II!OFs}oFSzgp z=Or&|VdopT2@E!S$!~5bh1fG~!FPO(BvY*7BPu6L)m$wp>7`Yzrj|fd&Zd++ISVA$ zq7w3PwGjYUTt}hKQJnjy!3c&sH+z{~yNZC&-JhX?%`lYvcw=c6`1KnKO*fPqWVJFg_pW+&1(0 zti~&x@%e*;zt{L&$hTm8ehOgzA0MB0junj0qWMw^_@&ttW2q+_&WUyj;eDymoVqkF@jMhr zAMEsa_!Sb}|1-V+LcSk2r{)m2*3*wfDkXLz(8-4La7c|7es3=|y0D=A1rgLsjpiXI z@jT=a$9Wmeg3f%o$4*K9hk#N#d#>7}L&?*HZ#aMdb?^V5{>ydXf4BZC9 zg)O}nIQ)^5U%ou~<E*ka?&o8vVoev)CpKHDQ`_>11*#3cHYLkc8C;G-BKCT;Za4G6;TVK){ zvsS@ajxQ1E6%6D(o3f<3|1J3B(UL}QdFp8tZ^POLg?Z(`+faOUw)~P){eJ|Bs^Z=y zZ7BOSE=gbvUhe{6S3yY)?uK`9jot6FZlDg2#nUqH)%D&Rg7$c9OKwMXsl6B7nR^e* zHF|Gow`>2eGnhOv-CgUxnoBU;P7GuLrv{TN*Vb*xOV+#8VL|v41%S|sR_ntBNkkYQ zNt(1iN!u9KKIPfwP2-2(f}Xs`Fbm-P^!yQ2&s;mtMbBLYC5z}O_NH^ub7OJse0oNH z{oB*?j|%_^Jr@-uW$F12a@M)&2~%(Zi)ZNzi(^}-+Qh{%JM?QM!!48jSE%=Umvnj6 zWcgq1L44>C>?tm+T*KNt^Q`&_%r{8A8YqCjmt5WdrdE70p~Kr6!y>$o%;{QV%s$ZQ zE>Q=B<2|l)%HME_>LfWzE!?yhPpMZ>swC6UVpQOBkC8l%mReV_Nij9l-y$$P+bj99o+Mh1HAB&e;{7VhkYgW2p zk-2Jg$zkYAH@W!=z-4+u+zgLg*{ZeAhYg zzY9e`mO7xnKyCCG@5Im9(Jv?ZD?DtA=VYm|$ha0?(mPuNHhR?iNZ$9&*nl!L0OSDb zmw+7a204KJBJ?FG7I(8hhOS4quU5-wexa$Tb`6YExS(UNkEWIX9{iu+K>eNg|B>I7 z|8+&1|9Acm|NlSA|6>@r`QM|)A?H;{PzUT;VTSyF$j{%6RrD2kGe~iKT77NfoDE|q zRCl#NOc()mtzHItM_|9u;pBf{AIw(w`xhwRk_8Y=S^fXt?Zr3N)Ojz*c>Y7b5A^zD z8WJ3OA#UWybNc1h1;aD!r<^apn4ZP)^VL6Jej)X=|Hp)HeZactn{!=@;i2eWvKOC3 zTEV>${6K{A*OQLIUo_^&hT~jy32YIa@pKCKy zwlyBDJY)Ww(SLik3tEhen!S7spM zM}7=-Bxi5X^>3T8zGtTTZ_TTZeZslmL?31n2(NwP#qnbP|9$ySxA(nY`qG!?-nc03 z4$m6pe=%#{E~ojG>6SBbh(yQ3Ci0IC2|7--I%0C&k8Bd04c-lBh4&v1%>d7z2M^;t z5mPT(NAcpZ*_6usB8Ba0Q%s-Ycs?z;$|{cM=r{BDKVN?F_uW?T3#TfQWNtIEt0es>lJgHU|V45jnS(<7JnS>Q56 zk9-#4kbd7G)W;VQ?Am~j^Ju(-0iR(nG3(_Nn7J2UyFG-j5FC^n?D@4Zx7B*Zg>vkP z5oa{$`T1FVe^R|@HoDE(X5-lbEXW6NK+waH7SsVi8^OzQ+5QULYUIwW@Hpq^G##AZ znZLh|Aqn#bnn3cnGIM}-y+?m>x91c-A+M9K@i9GMr@v&@hwStq(<_kI&s-6_?{vh2 zZ&SJ;+KUIolEwVZ}~J{3_<5f*SO$3dvY#BGYm5H>09!| zEuX+`kPFp;JtxZ69yaYfC)D2a$eksSJ z)Fe(7_n%4a4W>qeQ!Ov`pHR!HP-M}-&DE~toS3y-0J_JPRSB$?$pg!PIZh(nP^oSX z?fFI7tedMah4p&`+^@bcwJfCC;JjM}Pu4YiepC|I0d z9C!NrNJ+N8?^$||{$70OTW~hV1~nL;8;m%}6~n16zYz_fhe_3f*N>;`*A*o2nt7D6 zLh=%uzu$_8-QQ}ECo6JQP<|8%)=Twy0{y#IHN33-aL|#KFjK+2BXheN$Kd5-05DK!IKw=xmdF zMd#sO(WL=cR-fR9s1}bFY`y?R(EGK*ztFzAv!XNBHuV&^wVZZ+T(nxz7MFWh?f1|< zYJ7(AJWc-0{o>i>55}u2UxEQ&eE&K2!g;^{{G!o+YHYR(#9cgPkFVGD3&ZKh9zeGm zw@e&}oGFFTT4DDp9kFP4-t;~U(oecOt#|(YTvvK^HH4MXW2J8n0}lv)yAmzkQmOwG zy!{@)SK_43e|`aVZk9T?Sf~I?wf4IO`2Go$eE(6DTcR$|+8@Fx{BgQSK zK!uWQgNWw$i6)6qmDeB-8bsk+P}2B5O2UQTDm_bs@@0g^@4*8664SAh-jDEf1wJZn z_YE9@pRkVAA!P-q_n8NZsV9f{HT+FE`0<$5I!C8zlSmDd;1T?KWD3Z?r=%{ivE|?*lJ3d;b=G~Y#1LdlyAes z|AKbqiVP3`pdfBU3-uAm++1P6Gorzwcy{U+F2n3tkgvjp^8@Jfbj0&?SFP=e=)|K% z@vt2?h954#Z)W`wBBMI@$@n%L`J1U%Z}6mVt?tx9!}=M(E*{0waedIZ{UJoGK2Y6W zQI`+hhZI~|sIN-6YnWCFIpqmaqg+H9GYqp$_)7Jm|DgbDM0z+X5YKRCf2&VBEIeFy7aYLmO9vwX{Z z2yGSPS7UJpZaW^h{I&}ZT+7>y2QI(m(c+kjt-}MKdG5udOT{+e(XC>Acg+(1afsa3rqho8$Lnu*$WS&k7U@`T) zr0~<%vEx584ExQU0OnUbFz8xjKFrcLF1YZQp<=?+2E)C6N=WFds*uK3o=j zd=CPfP@org1>?cngEzBnq2q^fNOcILHV@;_>HujT#W&Ru9%dMq2xtMLpWKS^4N!Hph9On1s=d9q%mte*GaeMA6Hc?bK!{vF9c|8P*1AhUE%77@r$AJu>XeQAZ z@~k6{G-lpd0bS@PClL0Y-d~?AHBaEp8q-nGj69?-%G(1;WGBd_zYs>piLG4u3u$EB z+Kf}O958Ee-Q7=joa&dpE$@HedFV2mDm4VAg`k{ z$?KcF>IN+7aRZmWyh<(e8@JyLydbx??ZopDSsFxk8IgyL$ZjLD--sMEwvQmkxV6S; zdDLhb9gFauDOaY`|C1A^_m|x?&usErUxcK`1yGqafox*H`YTBsUBA})$lr(nG$IqW z7J&8Dh3S@EaeV9Sq3@Q+myT24n}X!)z0*$tSz*=WSK$j$rCcQ{_;KV$!< zvJFUmmMYHh(y&yk$>q--E8G9xa^Kn?xlpevFI_d(riS^ZAgW~I)tU?D!)j#R`FXJW zidv(7lS|r1n5{}5O7?{E74dpTxT_-K0bjA5-o`J z`k&B8em~o%5B1JCj~5J=@y^9luj?{qO@TIMkKa>jWi521Hk@4Ea;SXhO0a>x@gNZR^OMzTCHkz+T8AzE)VBxNc4Q>+h3)TPWA4Uk>&qWw zTd^+qr+pGgeX`euGw&EDxa8>ew}Gcr(>iRw9x4C7?7a_soYi&bE!ksxFk_x)hzueq zMQN3-z|Nu~H!%@uCGZH@Fh;UH(oB$Kr@_>9pld)freITm(#qSzJ5Y2FtM$CBV4(nV!SNOkYW$GG9c{afq+ zVwnQHCNpy~b1oy7jQ3sKs$`ij_*MDYdWOCGRg!A6FJ;*P?)4?_7tsedlK1b_%i>B= z5w1vkBU~?k;ojR-A08fUc#S*^5A}Uh^3JlJHUz0wHkuxlfz_H+TV1+og;|7Z(@k;n zGT#_IB7Bvw%=@B5r>(}GPUdk{_ytwl@P&u@-8iGU;q`mI=v~|6)is>x=^2~ppU-~D zr1_Fb)9p38zPO}!R%=J*7hsp5%4G(tHpuzz32&wuw2%7v{ckg#&Drr>r|~?IAI}pF zclPw|@GKaj70Gg7T7_dM_FzWKCqHLPuWxz#K_wKc@*qK%zm435{!AV6kZT|OPT}4tBV}@xLskud$ z?s}X9kYw*;>8?E~e&O>C#L``RQ-OXOp1SHjB=YGl-k-O&6uX{C#&*+a>?yJVN_QPZ z0J%vcYBtc`k8do7FBsDrOv95}nQUuifvj1hP#XShd4}>^9`=K!_>21f1ChPuhHq zUw2`2Z_l}{nXiA;h&bz?^sZfKDvj<*Mt3Kpk9#fGbNym{2yS_L?{REE|=e)t<{2wO&bgg;Q$Gssqqgds<=h$1gW3xmrd3m5>8fDzj`}&^t zSSVdfzVnT>(*Q!H#ADH)jgOlnkP2Ip_DIyyNQvjI zEnmyI(NHG+<#PBqPIYNcqc}mYWr25L8mkb?YQ_D?1fVy_VzzLiTykH9l{c{8W;~Ml z`FG#(ZFDNX``*mga$l&&_*nZw{t&&Y*3sA6%&PxwkTUTl>kb$q1myw^h@tEjn1dkwu-NfG-{%+x~hrbQ{-Gd|QO|&e%)9zWV zW$<<0boIQo)_dA^JQ6gPO_-PuzNpkc2ZY+=)ujX9$Sh^KO)vUJKntJ-kawAKk-gDc z0LGsE8zI7b2&*G(KG-qeKY{ai$o`HQi%(lAJ=OUQ?BQIR1w^CYOX!Q3@DEUA)!IQX&T0iTb7@8ELr zes+RdYO^D4oFi)d*clEzV0MO*O6LDrc?;uTUpW2?!Rq|@*YQ<2K{waPYM74zy4etz z>%E3$yxr6TSX5ol>@|yErjNahAlz%LGgPqth2^Pj#_x9U=@wLZ6bV`EZPkGV%nOAX*W|$HoC3{u~%i1H;FM0C3jk1<2!3kken~IkcZsv z@z9jvp>FZm!Q;SY6Ibqhb#Kik1%v(={kheXyQLn+uOdZg83bcN-+`ci|0&&kDz&*L zL}$~@r&D`Pl^oeWb!4XryJJx7BP?sbZH0?%7Rj zrSnzWS?I$;qCiFG!5L&Hu95qDBwa9eZub5aAJY2AJ=E#cjvB+*Y_pHYYs$!!+-ruS z;k$Qy#@lzLwL*7i+S)LQfNZw$7ECt>pn$&g3=0vCAiA59PCy!vWn*6VFZF5lFJGYh(qB zp;W~dbv4`dashe2#?TB9Ng9TnZH|bg?ES$$RIDw7f#|7Ov1ShlklFDEW;aBuZ=%zg zxeuLFmcFN^+8@(t)GNdVcZ3Y{BqL*Hvgh~vPlUQQ@b*ks(l|Cy2$VXKX z^$DVi++?Pwp)4XhT|^t}x-$XtO&ILzXj?NZNMH78LAq*f8QknP1F6iLe}9fZyOpN2 zdZ*S@Kg*1?XqP#?%3lzt*NDoxAn#TFpiGCvo{P)Iq0GnETDc3Ow3|`L4cg)b9{iMD z7l+a~GbQu*A+6%1lK<`pUkTDTS$eI@&Ie{yJMCISTQV>GG+37X@J?lR@#&2$?`Wq- ztJXBq%1jr{gJ&81+e!+|@hqGQ4+Nb5h#8x62U;_KdAqigGC!7WIa+Z&zp;knad%A} zKOL!cfwh(G$%UCw59vftO|>yYSFJ`Gh6pUNg;Q}3CB+;~tZ1RBNb@^e(F`j3%{9AJ z(RO~8tD;bQvNCh0t*B82n#vkg8FQnNC2v7n`!YZv1P7FzjPbP0<^w?i3dwo>A%2Yu zkiPMNvrPs|Qa79)I5w>bQ}sTLJJNWfuYc8I;JWBYWq`X>iYj|(r zU7nyNEWe(2xqY60r14Q6WZcbzjC**TWyXG+amZ#IKKqOrl2#mPlt{MnNaF|($#e^w zT1)f9N2saud6&K5`OcQ}At?<%xMN8wu*`clwdg=_-cv^!gMIy#>O3W?U`kZMXH{bF zfcGpb(N!-nh+Ce#Yv#y|HHeL+_44AY^)J*PI)H^#nTFlK>Y|g49txIgJwdZ7C*syu ztr2n8BH}hs^(Vzld97c?$g%{%N%b>&AY26tV3n0u&@(zfsaG8-vu!^`y2nYE-fSKt zH&K}%0!a9*<#Ue`&}Kg8^SPJLh95dxcJWLtxOmG8y)z73MqQN+-*-}4i#a8|#QXlh zj{&`9(EQ){;;L<-mZ_pv#VfrB@cxw_A#bCcCW=W4K=>uGq*TA7wX*`@Q-(t&T=G3$!BlPb> zUi)b?&r|oFUd9y9?7V+=Sx2UOs}V5u!PuA4EtX}iz8g-PncB8C)SBrr={{!D`Kv|x z>gt@|Xy*aBYOWt=R3D?Q<&euq)#kl5yX@l;9w}>7Rt7uQwUIJTmRXtE9}YrQr0O07 z3~Pi;IydVC1y=b;66C$|N?VrmU^m#p3U12xJz97Rro;a|K^6z_LT*BdWiLJ zm`~6}kHa%ofxsj=sIZ+TtlDHIpDl$KRd%#)>KhbZECDa*r;itF3|@TeLln-Q zLwNDD-%0PCJY0YYyFr5q!WO?yo{jHcROnnDA9s#$d6 zrZYB^{4Ns?z#<~JX+q|iR3ZGafQ27`4s`Oj%CA#6Y_fecm(8N};}-26uh~Tcpdz#$ zYs_~07Z+!DUfBijT<~EopkD{rH~1a?@BeH&>~~})duPihcx?+fKDavMHD>qC!|Y8t zi)5!+13-|Hbiy5LwcAl+ghpDScGS!#j!C|P*E%KJQL}&tY}*P&;A)kSt87FgkL7%} z@LaPFj)C`9-q}Wq2s0nz`r2%`PD&8lK@w#nPUU6Z?l4SiBe>rICa4|DGT;7#a;?qU zi%er+HqdRs9%2XVGanRK0@py27Qj%8Pjt5Z7RF3dmb>*cmS>!;hVR-&0tz5!*?opE zFMw1Z>^+I-KX9Vq+HGa!kV4~p`e~Swt zZBMN$prn^kV0-2`znbF*j1o?MJ8aKOB@emrc!3JWn!@cb;LXaXf*$e@JLT@UD2scI z_2XjH0a(LePk61{(ZMWR@K5F1leuF7XsOG7U-6QD^xYkp%Wio)xNz)2#?rg4PEG+r z9Vzf^89!C}X`J}8P@gOG)3QwTdVKgw@%meYR;(g5?1ew6vb33#*h21p@2RQH?tLLW zDK@^0;Ih^=m1xUq>&s1C-27(#bIhPujq_DDMr?cGj;m;D83#`f?&k@jUj?t=HJ_qX z*ZJ!HwdLu+{b~u^LCdoAqWdLHY2_)VVTEC)veINmf;`1EjapU!!kT+VaT`9r2C% zEgyqIz=o{E#OmxJBv~`UkO;MtE3&9vDp>*U=rt!#nOBHES1tE7_KbM5#iCCZbFREF zi#ak>-D-f%aU_B@wnU)LTc;#EGom6R<7*Y#Xb=B&4ade%s%2c8%CEfn@^i2B4Fuh1W$3hLrTXG=J zt1llU#~IHL&N@DBm3VXM^WP|o2UJO1nsau%*rI-O1@pigf581jxc7sb5A~e?Kx_FY z+RA&+>+7G^H!yAUq28nQ{bMsO?(Z7`_`M%BE-CP#oyJ87{_Rr%JcWc>>8^2d2zo6; zPPMySM^M4CEBftovV9Y8p#I4NjV`+CA*(T6bpDIU!B@TJQ2h%H`+LF*ySX3GnyH*? z=y|d`Wc~+|-N6IRZh7~Oyy$P^tttx-uXWt4(9Qjvhx9O7~zW-N6h&DsoWLq}fQ77$>( zIUt?a2?E+*1-_@wfA8Zt!yiurUwc0SRi40mz8`UmsXI9FenepM{fN*R?nhXl-fe+8 z&vs_;8Xu_Kj{vCGg_5Wm-5oIZBSI7JNBCyaEf`IIxPLj=9Mqs>8tyU+Kg0HsWV6hE z@*_n_es#ea1<|Gou6%C2T6&N9`^-MNWFGF#>N=wTIOyML{>ESB^%$uS<{^_O{_jCw z*_97(DPAYM7BmpP>_zUDj-W_WVlyS(WY2H`hnbo-1eZ*wPD^{LPR7(gShJW}3$r1D4;`t`vhP41Dn+jniCa(^~MwKAB5?va_K zYc}bcv*}DHiu<4Kd^1QtMd$OK1AZ9AnHRK8)}f6(=`Khy$)0$&zl%+OugUe7%oDr& z2l?*a=XbYY*~9!tOOW1%x^vzAoqTt_3s@hmxe3f0wfYH#a43N^LI0aVs9$3Kc|XjR zf^+{Yy98`=tMV)7n$^%>mGdKSvv>mqoh8VQH~D8yH^{a}3`N1P7uR2kpW0w+oPs}_ zct3;sI1e@(;48a>PU`bi$H7*fX}Q=G#)4YRwES+CX?Yix1%sbcvn=14T?iaX%(E<3 zeQLsCG4T19fLDuc#M#0N3JugTXr8|Ej^M_-5f0|BXa3_klUcm$#C}|cQA1tNJk`P2 zazAq-{}IgI&pg#Tx)QA-gFik4@)c7g7c_HRU5)`M0iqJ{VRMn^KmKl=nU_@XEAbzd zx0YDTKK>UI*6TJIx(c_qEyJNkX786}{K_|`pz&*At-AC-IlBv%8kzFMB5cH7 zG3$RRd&Rea0(8gl2PNzk7w!L~t3JV=u)Yhnh@wBF@mw4sV!9`D{S4NeJi2H<*_wHa zQA}7&vGft?xo={j1it(HNHo;Y7PvEf`%JH_q5O_>d_u}u19UY{v{jz$Gl)G>s8>D_ zBO96mcg()B{LYys|Ni>^iO~mHzX8{k^L|jUhTC=MzIXmRMmW~bUen3OKJ&5Vh#uR5 z6Vhq1Yo5Ljt<4X6!_U3)4>H;@@9lqN_pcQ6p7HUS ztDml&vhx$VFyR9v&+{v1*MEy|t(lsDS*QwDVmo6@JpLFq1{+Ro{3Ht4EDdiC{4_BA z`|}3Pe*O?gM;!TA)uF8ZLG9;1W%l!*GU~lohx!h2H$6AviD;3CNMy~xB^XLs?Rfsz}4X;WBb2iGMu6P z6V|sE{wPaqe|suoTe!alenqtQV>g$ewXr|Tt{BDm#Q038|Hk)Q|JPTSsQ-Ka@eK8g ze9FSFzr(ZRn*F7HZH%Zv3pR%#{5MtMzp2uDCiCj6Z1;5;+T`_z5sPZwT1s%?bhR;h zc9AUn56Nj0Z&`meY~N534*C9@h{*eI;`9nFc@p5iDP;UNQPp};bt;d#y?<3$8}k|A zM~hj!dTZLT7P>6Zab;WameF_?v0KK{@Sm{Wj{IR31%B1__KKTIj6vHMv+FG?hy54% z>yMqE{s%y}-z;$x;#mTVzO(CBaei-L;*_@Wt(iAK3y6K;(q)EN@rCOb-^hQ;Cddp}t-nUR!&-|9{68NCQ zATAk%hEo-vOD+xag4x;R%mP_P?o`B!9bc9?#`pjXolj)`_6ymmVc@~-)ZBiu)P8!8 zx?6xm#q_FlesAcxV}EOw=jR%R{pESUF3$yJsR4GiZ@DhpsG?{eKd}F6yHIL88Nbo7 zg2TU(_7?Kvr*$(se#1*9j$dv^&G6lp+0860Ied4XUp#zdEF8Uy?dYB3jibbv9ly_f z#*9M2_-)UQ-}W<)-=#LE8NatvSx`+Hzg+7JfVfB9E%+{YojSi6KEq$U+wl40w{>y0 zQN`uAW^Va|95YIdc7FP6^Dq8s7I^7%lBPMi@jhyzCi^qX<~M8OSEtVZ$Q1b}QVZ7K zB8GtSjlH3+kgkH^cr<#N(_Z8C>_iLq7@{FgD>5Gwea7J#G`7Iu8DGA`vygRocDvaW zWF4Nt6EscX@Qm=v-iwu*5)O=oIx>$S{(_5O*6Q40n&f`blk#)6!KCv2oPD+u?BvNDZUaH@B;xgJ)@GPgBe*?yq85_>;r6c9?6%);2?xeGs4iAMlIzn-yPLPjslC#EWUVSwu=R37XKNAy29P=!Gp!?Q$c-;?t4o#?9K|m zzShjoyQivcU8&l>FsU}`E25{x^rQG&^a{TVS91hfm&7)DodG^p;AL)JYLJTq-R!&Z zK!kkM#SM_QD2OMF`Q-9k2y;94?nB-_{PL%@a{)4W1wT`iKS$-G{+O=-2U7$}TI&ZU z%k%HKI#fP5Ew4Zz3-+F^KOhLzFAYZf@qRS-c?V>Xl;}4s!WZ*r#N&pEq8g3eXkI=j zqtZNme^^+g*C*JBxoIC0&&R7M*7nb3UgfaRxB2z!FK^-smO^Ho0|qatrUGMQeA9pI z?RW!-`y&YYgGVBj91C>qtMtX(#qyR3g^_4B z%)-M>i5GpE3e&t>yu}}%I8t6=UX^d- z@qrU;%k1VX$gm3qYxbGa51&Q);{3(m#rUiCOhvE1@Toj`nMkk09a&-)&C~0;&Besb z_Lu$fWP7yr1!YgPNBgz*Y}o~b^7%f$Y}%{xvV^eFTDSd4BHz-9rTt;4(mtOp&5Q^8 zW2c8oZJGmbav$p{3%^50G?;+tA|PagJx)mz*98UhyR9SoO^Y`4R6;9O01bcRBmy zsIy^VZ}qZ{wdHL%hx@iz4~#$IhdPNZ$w0YLQ3{o1s>DU~y7E-gT)~0qotIP1Qq!W3 zaHIBg2VzuL?&bPag0}XQ5G*KRS>~@9Ji=L~RwnDZi7N987k(Dg`*)i^XEPs~f}cdG zIP3XjKGjpV_55(j@69f2Tn^dBh9E+;iXUfN^n zeSSSK^efc9wwiuk9?P5fuhVgJk&6Qt7YCIHau zWhrI>HU6u6%@($p{=eINE>2(k{ciKUI6eK9KR3JJ{R8uo1+S3fRr~{y@m$au^FRA5 zgkFNW`w<$a{x-1jo@{hf)IXZi+0xRF+Q6I^E)7!;RZ0{&+l|Kjzoba@tjp#u12 zcfFDBd1sC)6E*PZU@DSsg53qg)i;4L%yv*j~HW{?7kGXp`~#vw1# z@AW=mDmyqVR;jn~WHN9e_HdFt7jk#{LRjPDJfuT4 z9zE@B`58bzGLo}@lskE~C|(-=fEgn<3_-AiZTVZ7H)MwKLhtJN-m~MwTv2%PrDI1% z{x6K}Io?bS4|a{D)0vEd!!Bl|K%v-CURNK*+^18k0eIS}eL~~#x)UEtMl(c4GEOak zPVg6hito867**%;=aRAG>p#Tzj9QIu?TAyWnbD=6)f5`W4pn5-n{{{y3!mx8QSVvf zh9Z(NkGHK`i__$fopx#|H$m;;=%{@^$oD>d+k(aqrMr~W{6y4vMYu7Vm)LK!CcaC+ zZ_vaMp3MPXG+ugfo0(B@l}2>9Ik?iQ^V(UHB_NhQd2Owv4{Qf77JbpB;HDO`fTK+< z?cQx5pBvgu-1UFMrsghC&<*TS(r=Pf)mi$YY>UVi31UC5HQlcg7I8@$XB;o?!!qdG zFTRydG-h6@l=g?Ho7zDAVv!#r(D?bF>_~6~x)8?dry9@TDRMXvwlBexBzi%E*QzhxG#ngd<1wlhF?bvw+u!L}diGP5eYR^Ap?Z z&3xVI+@i{Prh4r2p$$AiN(L&2Jf7MV*~L%v5q@Hi3gvcEhWSy1`_WE7sG>>W#vWzJ z$sO06`L;p*I2f4tj#Dc{_?|&ycR7Ge%#E9TiGJRnbZXONq9+NOAAf=;h#5B>ebhiN z@k7Fx4&km`V!FjK(CzquEj5&vjb4)X?QuA*Os_@~tx{1X~Z!aqB|3h;xX z%J^#x@RMaC3h>S6AHKaJ&p&`set*77Xx7U8XGYJ|pUB+z(4Rfo{@96Rd5tONlkE?P zFe5v8Oy6vOO3WX>KfhdFsy{Cgn(fb@y;QP4)Kt#j;(&Ac(E2%g3+nBB_-;O$_!uyn zY2``al^f3)~@KsNv9C>IAYbJ#yP zvvN9C01VlmMRRU2@S3eR!D~#AyS5luTmki-8JXjflVxUn?jpTJP8-Oua27yj408*V z&|JsQQo5VC0rYj-D|zdHWoGeWtu!eh6bTy8$>>0?yvQi%fjwA4G}og?;m3IY9`CV? z*s`THO30WUPqw4pGQVJvG7ALbM@{hMm(P%W=i429g_K9DOqY-#LY>)9L*D^9I$sfg z$hzTSH09hT+T1O!*dQyoqfdSy`#uiC&KJw;Ek30&_ZUK?m%b=H4YPIW1YVlbi6AC{ z608=5CNZZPulZURnybC`T5l;1+eW?S`LarR0ag`JI1%T90~0(q$(@P+emeI)LVCkk zs-;J+cPvCJLQoepsZjUd7kJflYy0?~HMY(QjG z0sAf6EsA7Tz*fOyRVdFPoBjiD*Hmq@X4%`e+TvOk*A&&#RBda~VC2_XzZUV?l;1|_ z9}&mQD4@EJZdY%p{!j-lFZ=!p12~@%1Gh1lYOb31W~!qib@yqkiXmveHnree%y@Yi zLV%pLk4jlfPJ;0~6Q+5ro9>bmHl)ook2Td@a@>Y2HtiE?wjAZB72t*AU|hKIgi>i1 zO(<#hq=HUQD(EEHD=tt$43ELQB^AZxEP;_!$ptM6O)5yJYCshi!Z(8E?vk2GB~?!< zscKS5OxbWNvo##AQc37KVyLX~24~B6wBv~Zz)-_?H?9WE(+{N_;*5tG?=!&*x7yU#>EI6vR{f$SFg)nIU3_3*LumoM=m&O^|3ieG}W{ zHrFUj&=&*cHrML&ls-kH*cXcFX|5|SuCSU>5~{3D%X67e%Ib_@7?Kaeg}ExOGlHV& zQA6V3%0S^#R4qH70^BD%%zX2yw3)bx6}l51oWQz>xyc+kP_{g={!GCdK-_? z$L?mA9}(&9cA30XTh(B*qljfPpPf@yCYX@QkaA1Kl_X(Evr0>7gVCuXJ5{6zIU5uR z`O<1r5*u=!kS{iM?KDLMoXvG}ZF1@Sd{@o-dB*-;=Q3=K8`@Ghx)A*{{c*KsXaf*A zp)p{^IFY2A;mAoZc2Xgd1Lzxn_NNdA@X8rf>@=>ih*h_YIKt_mBjK3JXbt!u%FZze zbzWusZN!F&pk^ghKclB$bdW+|C;TMvhjGaVu+IMP(bN_zI8L!#$SVOt#qPQ=g zcKeH$U?K+0jhs040Eq<%?NG-?NN3Va*znk-Km@`+)|F49ECiC>FU?Q#38A);(U>S| zvn=4)g(V{VLTnwUd;yV4%)1`sF6Gfrl)JO(PX&J;=kHVe-NBzh9RG*)LC2RcVDAoDo1A}wXS^Z5A>KecB5ee_10$iKt)}vsK8jG9~Bt;m>(4wo4brqn_&j_?>YA`4OXaZpQ6*1u~uXPPt=6q=* z8!DqOuh8yS`8&nmG+Gzp?*sgOh`-Tg+d!kMDvHKp8ABNWp9LL>4nN z&d46jp31rWhZaw6{|upKaW*S`=Mch*+iy@lgZKfb_GO3!%)^gvzp^03N0kEfXKECr zU|8W6Y#HYoy9;)kWlS&RcU*gRz%&-%ckz6%{FA}p1HcjtzUBAIVa_yHi?@WU^>)1O!}Z zT3q(uK3VQQ&EEWPET3O(`W>FRkJpP)1?dYTWMoxYrAV?<+k!6-OhRRx(A5eE%sxTQjG zX7X0*4MG@h)au_-tZHs?bVezLiw9R9Gy0L=2MEd1q z?=FmOd+Ts3;U;?@p{#WaF_@n0eH26MlyU1pWlJ2xofKBs1)#lY#wgLX7xJ!8jDtT$ zy4C#zDrIz-;txBuKjMA$^Y(pI?@#cfr9A!TeEKe~&TeNHWM>P|T7 z_)h>;jC^kF5tF=ApuF{{TJ)$>`%6*@)V;6*Z@?;GIzMt$N}nuS8on?-?%3-zf&UVr zAoycxO|nso)1q+fD5-hR@P2DXBJZmM$tBXt%;v2BPV(wdcjo#lvXYO2xu*dWKkaWd zme#P_&`lnMC+s$E2p~CO!*Ok>#14ZEYJRsd%NtBzlD;-D0*bB-q^}LK(MYdE@!~z; zhv8>_)%}i;<2sG8VQs{lfc1by42ltc!s&PeeWT^}NQG7Wh1Z?SBtRNrf5fYXb<`Vp z?i6Xv6z*g+5z+eTNrCh!F);AfG@F&dPe4oUu2p^o`d%(K%f`F`ll?N5K4<}4$z@M6 zUZhrjq*a{*mNzkspE;qR9=M^&#}sBZ<>Z|od9J|!=QAZGA2!2O^KVVz{NwxrNjR${ z4Aq7B0Ahe~{bOX|*H(`VcuVVW=7Q4~c8&nBYczdrO)|Pm5mzN+k0c|HvV9a}k23c9N^>E>EZrQvjQjh=_o&4xscq?-+i+M8}RB(s%9KRLywx3CCmioQm$oubGf%pQw* z2@8vzO9^8os03XohxeD0u8NlEg8T$PBQ!h3Bx^cnkCm9EMk=}c-)HA0tie>{_kQOG z{rpH)E}NSg`RSszX_824kPvL2;O}|EFbL6I&920phgrLM9s-6W7hM#cr2pY+E7?W7 zqT?S$Wu+ua5?%!uSbDO&e*U=3m#?Qa>lo`wlgzGGazf3LSLF3u{DBT$X{Sq@kXP`d z0dW&ngu|2;+miS&%>W(TPgnCnd+bLA6;O$rAe%at(14j_HTh)WxC%|E%j)}LGU!m( zZK~yki5w?14&_kC%_>@Ot_v;A1b^Rh)8e_UB$kBQK!uxCe{-eH+FV7MGjGJP8 zzEzAX`e5_T6$!hEzD8$}P>NJ^blw29#tw2USwB8@9?m=-sUbs|H%TH5p|nO~)R0GXvqKN)@&eUfi>ZTyHpYX}7AI5_2sj3mbRVAXX0SkPL%o41=8 z5S=+DmTD}N!JJi@L^Y(Qb?#D>>UVR&Ydf)&AZnpf;sdQJ<#B$@0ZMGIIRxq2L6+5| zHD_S11hlt4%uhRYn$r#@fr?Fh*QxyukEOwEH7+j_Y-*aAb3P;1L_%pls#Vdj%3VO3 z=XtrA7ADlWZtzEC`cj641Ro)UH1Y46b;TUM8JUBTJ{bOH(I zmYJN|AP9ytomx7Y=q5bA->KEY+bwuPF5GSa4m|Coy_xPLBs4I9VP2Y}Ew z%-k9CGw=V5Id&QVT1?9@0&u(|3OK5F0DoW`ClDmtL=sR{gBd zPn@5nod=p%<+Lnw)o1l_dTD|QEKM)_j^C5#ry$CP_k;9rZC zwU7fX)>f+rmX_CYcDXz_2~x!301U6$`c|i7d%ZiV*t6cvPWPbM=DHn0(tO;c{2VDq z*yz%>1N8QH#1_*@w16b#?|hc9y8>ym4>h}7#FJ)s+zF|5my<*rQhMf!)Wx;0jI%1Z zl2eG&3rA0-t-M=l%2Z_{nt_eyd6K`wq`#3;5~qnayxi(+2Fgnu@VWHF&rB(zx$yzh z?iTSPlrKzhHMj|ypHOpoceR;M2;lB2pkjiA4~d@oSIB&&2{^$0&2 zEomFLtEop@g}6cH)jFX}PIGF1N+?kBx%?#o5{LtUS)lQqEIi@WZ1LEgvm+1c%dq`R zLTaM62}#a-;*ukC!fAH!Kj0k!Ia@+ZA!1t7?BK~2JH9g69ug)de{&)xo_W7lAa7|G z>u3ZnM!(DqOk0aZ$lDq%M&jg57o(31eS9hJztQSPEn7M#2Fro+{8y6BxU%)(G-r~iG)kOKE z?2^aDLKGw}i#EYJI4dQSo3Zb{dl)qsutDkBp*f896$E4?}Kd+mWwzs`3_6-f~Wwuzq+) z6%1!(d&e?#1IC9zJT6(A)BhVmc2^PzyFvc#I4y>f>Bw23fClra&8zHyZP-SyBt zbJ)nbG&WoBR2SwL`5NY$-NHlhj`GfF8@`}OolCGD2gl zSlaBiIP!=NCmkgc2#l249OeSj*z>w?6eBz=k&!C zLiS1TbkA)`V9zNyVeEzGBrMD?V_zLNTp$HpMaExsS`FHWRdl}vT^(r|-D9(y=F0D|p*QRo%f+X^<#965{)E6iZ&7csU}%X&;OfhJT$ScsbpYTsEup^ z?OX*_C7T*4w(0toWZNS8ii8?jHL1f*t;wd&2d=4IVng?N%WJka-Q-uT^r3ny8lZ<3vrm>H4unCY4E0q1TD2t{?jiqHGzqtZe-CrWDGe6xth3%ugq3 zX=)Qah&Kr!i)(mWOrdU@A`Cl3NE;;>HW(rz1%SlCWLq5tL{EC6GKuGtO$+!$JZet; zQ7PeuQX4+cuku%on*52~V7VLMKF8{}{8PL=Md$grKbtUq-~%l&>Q2_6dwyum+nyB^ zSCE7B*b7|61rak$5bhp6ruWqKWv2Ds>NPbwUyX#OH+4b~oUaF@BBc?g6B;$i4b2jx z)6zDQ#zND%TG!O+{6j^nKkTKF`lWI&esL$Q4cs@esKO?T-pHIP=SaQ=Wn;&3i4#NK z(lvd@IHYx*&0hjz7Wb@J>})w=cgfnAC1$Lms}vuF&UP+cXir*TYpj^4l@uRR3L|m6 zOebq>ZY0b#QwH7GW0GN#e?np11^HM~F4NwF8ShuJps291-r-xJjB{qqBr$KN^G%50g0SUuo^ausIUWWsFO3X?Y76y-~# zzxtQu+&8H5BDG$$#*01@g?eNps?%0kz0BVR0YE zW+t|cD=st&=mvbY`rFv0F>R{KlP)vfQ>`Zj>$~I|6E~D>|GZc2MpEZ};3~&^W+STWH*l!7Boq!cI`w4!M6<8Y4UF^X?f;S>9{kjK!vi```G z1f)ovk=iY)Niscv3SI0JH)YmOH%AC=TZKBuF5@|ci0q0laAT*?W>Sa_c@wC%?$-w$ za1&_0KCKTxgKDpKAxef&#<^U8GPhvXGe({J$EcihiOXLGuc5C)$)E{;+Jrxi`V)aq za+JHCHb|vo{!{9^-7tpA$U7-4Pm@dczxX~-dj~BtEEP?VA_!**97qpKDRZ5{fo`o7 z-Q7L*v^#8c=(P>PKtyDl+3xAQ((I+Qhl`ujCj%6XVfzB7_88Gd;3pi_B)qu}kFSOc z@7>$Rz<%u>uKPH(CpG$y6W_crCC*n{mpD=_opHy1X|*4FcMB<)LE-;u?+k?>vUi5U zU)G(W%uLjT*OsTug(B=+JLY^T@0Xx_8)et_}Vd)M^+qAVHRo{VknX}_Uo-VMEff~%P6$;f6e@_56*M&7o= zjN7oCSH|{i{FE2lL+Y_*CDJ{XOSgC96<%zw(qWU_&rXqhaz$p7dXc>i`!>$-hLe%4 zUSwx7@^HgGC)J8RY{B`bw{dgB#_djOKEbKh@?>nsrh9KHbGH0BdY+r^U0v1_N`+>< zF;MPozTbr0T-FnOl&`z^@-ASX#c7ez)t&T!CxXTst+}JmXj)E?3>qC zFS0}1%toOVA+F(oXt#e}w?PdX00;^h5-B`eNbGp8kU0FFAhDw~Bo3cFBt`^@=LLyj z3yJ8%IY5yT?p*d7v@ zd$Q&xt=2aoN9p=OaYGfHF-1|aIs6`I$-m8@I=hHHH^uVvE6gB|Rb2GgYWNgTnE&(U&jfN?Y6=yS z+o6A1a{C@9&lKeLeNCPMMz+-2Bg&g)WJe}&CGUb6?|C6UUV@RGnzBv-isJ>0?7L9b z4Ak>|b8Q=U!nkR#wC#tljgL2KI~CdLZ2LPtvgalLt$d>JfNk`9yWf8A^AvV>(5Pb# z&Rm0JENN7LbW~@rxa&a9TybB29yjqhFSOU(eRBZCH{5p^iQd2R4R#%zu=>5ljj)_${1qNSP;mAZUtp__8-6YL z`hM;=d&9DM^S|wW^PVRlDFqPQa}`s&;M<&!gmiosY?Z~1<1dYm>k`e%sy)LZM%=4e zS*=?(D{Iot^V2J9b(3agNcU=15?h&GS&?q8O0TR;H`k?C&PUpoykUWEo%#pRzI~O4 zaGPp`l=eKPy_Kr|l5>i|nHg#sY;L4gqBxUIgbcm8@Go-oX3wG-6Z+$q|GOp1Z!+b# zlAiKqe5F+RdmbxU-cO)_x%Ng~Zia9Q#XS*5t|f3-oPSx~np~)G5_1f7_s0;O>QoVF zDQom|;H0j2A95}aub-8_Yz(g2gLfnzI(L^CTnBj^z@1Z+j?+f}$V>3cwMWNJ!F0^! z(MYS#(4voLTcpIMMLO4XE^iAI)})y;sYy&OTC{jlLffWndjf(_VS9qVT6=;M+7ncJ z1VaNNeEa|+zde%@0y|(td&r}Mch2FZ*u|ZmrZdB4VHP&;X|zD_Gmn>@0kj9E)m>bz z2631tBubZKf$)srX~;H7ic5vVHL2Hy;9+RjHH^7n2mT~97uWNd9m-pwWpK##~VEgyX5A_`gj7>*@f{T8H zwm{1OsoPkSj-4zVf>Y^%yj8xt>zvC`n8gMmNu`CBc$u+YJ<5qrhfq)&wB{a0GY#oJkg@c(=h_TWWBbaiRwt$jj8yzv}(xD!&oFy{8k#-V@fH z@Djw7@~n-gBO^$?C%ukDPWU?m_=u_S^{nhk6_hW-Ir+NMk)z!ulv9D0U%vrso6b@b%2X3QbG-7}AMU%e1jsMwC&{v97x!rAjca zY%dec*h4W;MP&W5mie41j(65W-8$9rU*bDGmr$h94hWa^AOwLY8>{4Dwqz~Av9OJU zt;_DxvU+P-SwmKDHOrxR1J0$z`xWW4I<@6QOxi9O{j4*^)1`9$te%z_XJexAPS^CC zzWCZ&Gp)78V(aO?K2>k1@15BjH;{VnrkUrt9evM^*K-6Kj)k_oaqlvx_8c1M-&j^V z6I}fnaSn{c?4|V|JLmTUFrXV=EhB;H7{4J|HN>*Db`3GX2YY^RU)?w0Hhlk{zeClE z5LY)CWzNT14_wyX)|&b9!=g%ZEA5+o@&*RWHq)lT#=vi}W)3#W-ctbpp>JKs2Djz* zQ|zBeo!<}=FN$YrAThFn{xHc)m?kwtR4gwm_HUGKs>>Hnj^h10-+lel+W-9TW$@_D zce>MyzF1wDf%x>I`|?5FeVZoq$GdOy#P5NLJs&LVFYX`x_onQBX?pA5SI5RiZQ0Gv zf0(n1GDUNp+HTZl{++WTjq;3hU^r&(Z)>c}Bo4wV(^bxjmr;K)SGBsUQ;|{BQ?@$? z`oE$)iT!O(eAs*TX6JL!Vdv7==s2bh%ADHsY`HgAOPX|*Y`SA*Ac5Jje&^E2Zd7ZW ziZQ45d8cC7sr|Nd!9M5w?=b;s7zV>?UvX9(cji9*n&Teh+Jw35e5us7WVUQZKa)5W z?MFbh$yu>)nfELO_K%Z`u5fEwy}7ws2%DgFsJmsbeBo3?cT=G$UJ@>@>CfHotazRp zZ2bey`QJgym97|Ur?Pl^9eQHUfgwQ$Vo2UFbSTsX?f+g zhSHf!zgQHyh-rZa=fHYR4gHnpF7p;zx15?k;C3W^3t~FIy~DXQI@%IG?qysy6mXoS1oih;r?aWmS@CVdV%wt8Jsedku5b&nSNwq4&~GsC`%dCj^UXrH;w7hc zZ6f~9YAf6}S#CEy+Bg14OW zpW(ionNeKwkN?bxuc~uu-_``Kh>`nsr-BU6I2SzSod1390Xenbb5;yHb6=vQ@0#x? zFssA0J@w$yX;SFCl-n4mb!RemEVuOgXzAUzZ6bXtqF1HpZSDP2)7#SH1(sM6)!qTN zIQPGa-8e92*B1=NY~o?`{8)A-Nl0_^2o?Rj4cA}Ukg7BqO&z)iX03y+3Qekm%LM-@ zL~?2qOv12iXc$#ObE6w=Y=u4S)Pf})Z}5KWPNv~@LzbQsWx}BETnNY08u;4Z6(%|> zUT1htI2Av_Oulmg6zQO$NGHGudhJG@a4wJZuMhJY9YX8#&hyQJZ%iXc2q12jS@OrG z)#F;gsa=Ft6xe;KX_|hFHoduUZlq-9*12iunxgK}>}rJ360coqOrDpiHxLcKhcHX@ zu>K8iVBP^^?@7@k{KO8Y{-nI;f|j08OYiyKoArlM_l}R9!{hdHTJ=VH(YI8{^$%@g zE6>ZK)TZ>JhlZg%g>sD3g|f)I|IoR7#n;G%TMKgNYP2k$ggYM^EfljY`r0V5=IV*| zyodptZ2uel;jSPcAsT;#;F*|I^}DnRiZ}am(LSD=~^7{XESLmYg-^JLr06;z|A)GqUhw)Bs)L~oZWendZy>gO@1_ONr{Fy;Kv*sBQN z@-^1Wu3g-wbymEHx~=KitImN}>DgO!=^fiMUc(#JGe$o>LlE*;8=Wv3J9;>)xKy|F z8E4iar{kl>W-;scfrHfclyl$(RjM{@*Ut|9JglFc{M_=j?OajY?sPs)$3A4I6hM9s z>nYBGAJUW`(G)mWY5MWX-AtNdIMx8Pmn&UD7~Ll}ercjl@M3O)N<9;?9zTtR2xHke zIH**cyUhEdcsS-;s6BHPcVqjW2zdtzX@;SpB@#QO5~JrGCM_DEt?d}V%pYSKn}|Cf zV14w;-38FZS^RC_`_#5__a(wU&39S(tXb2h&zcpOHDlJSnNzQi*V9_@YL~KZC7>5m z@CIUH+S4=SnZtY4fa? zs|^>l0t!Hjek6u}1vG+d7|*E-V)l=V@n7+5A=mHB{V^+_S?yq2)E9XAbDa1A2IJVz z;REps`;A|6sMp2q!c%IV+G0#>sZ{n8>^@?5sT7eHXv+A*no&pL(PMkf@ zA2WitG9TPUGx&{1FlJ@iTWHMcY#Z;l+%`EC+eE8r@+Nw+iq_JB%x6I|HWsGgko^)3 z_gk&@7q?pbDBJK_@J@}-6t=z7tYd*-Uq81lPW7F_SX4JR9gbZndrsGC+fd<2moKI*1(PG$KZShct9cKGAr{}uUn*eHuJD~8=*4~nz#NmcYiwi zv#8A7CTKlV$RISGn^oBSQ*uN2LKUzcBpV<&%hj@eo94WQ>(yS>H_#L%5wp0u2W zHmth$7d5^hj`fp=^hk3!XCsZTnb7TDT|CE<@Au}|jq=(ks$9p4#(pciP~`U!9lsjK zM*bm4=EYa#_N;$oae!)!?Rm{~8i$46cwl645W|N$d@Hgrq4t)zKQ10PfgsIth30yk z`3lk&T|%{S6R%F14X8c-)S+z1cz4_X;=%M&FX9I5#eKG=g~A_lSoqqjO12%{lHXB} zmO29~@fqgBFdXK}k)rhCwx})U%WsPhFWI-XqsC@{#F5h_Moo~+ocq&r$`1Df8QUoa0jDT)!b(1X>HxD*emX&X9{h+#7wN=)V!;Lk`mRj3B z?>;I2<$*X@UJa|5|EB#p`JQ4V-<$o?IaA8_On*ZL1?1z-H9qbt%w4=nF5r2ws$>g+ z6D86B^i zx3rztbU=?s840EBYr+sZ#I^tNB2)M0>qx1x_$qF}+VuYD`bCzE4l) zt0{F|`zng$JNfn-f7zzxi?S`h**1N49v!ehr`UYJg#OIV^(Q!0e^O0V`OJ2{=I~1p z(|CU6@YLf;eYx~%i5dShq~CL5ysYfz50_^v9V~AxZhx*k#i+c?&QPA@<%K1bC{Oc! zwil^bE_aY_V91MBGV!4={plqTdzY2?n2b9eR5D|8Q)3c??N&Z(3kQbxG9I(CPt^swT>+<8jUw+rIsAa4^0U&^_ZUjJmnG-pQ1AFKE%tZ+4TddP`~%wz}qwHpHj@Z*%-HeKy?Iea?rl|{wtem>1Y-{_WPmScX9jEO^dQg4ZyG^GU3~6YMuC9biR=) zIzLGj=|4jBTCQiq+gRx>t7;uTG@&qR)GheMZWq^;x!+CgACA8?D+&%rn{|k_QENz@?(YzppBTRqJG#GS_G?7nPpW97R_Uy{a+LO zUmW|-6Tgf3gEQbm1*SEHU#_OYeWO6iASw!@(B9i`^1gVKATMbncBdkk8+aVS3ARyU z{n8PSJ>eT7h#W+?l#C52X4u3$pEXgy9bIM)TRNuvG9fG`sF~0fVJphg*SNWNLvE%# z-(mj#E3~)lUE5oLZ-xPb(+a}G@R-fG8dMuZwLoYlB(!K&j9;OW_{F$PjbEXO_$4*X zlK6G3VfO@lDdwM!-8eCx{^aDdum*pNKEc(&OTI;d_LQ$gA+ax}BW4 z`vgc9!!*k#0ja&(C_U$WLKUxP$}hgTz%RYnWh^hjWTHyRqywiTOrVn7sa)K*HX819 zk>O5TOvdZYacU#SH)e;)A`xzc@`oEclP&AEwm{0mjT`iH4?mkWF)#F#6j)DdS1G(n zCujQJ&NI1H`V?@#$R)Qs zShih&G>*A*S{B*Tv*q_4U(xQ>apd1{XTaHffR=inAJQ+D{ld_!!1(ayerNOlP|&pj zZ|gz5;3qaRg3r0-$>{Umg9>7D_YI9Fdr#EAfD6VXdZQu|r;}a7$*xQ?dYq%=Wba9D zNu|N><*8T(<95SZu}(C+-qYr-@L?qbe?>(Uns=JspaDfrJsxaNgSuW z>V3iVSGM^~+d5n|`MOXla-tRYVelc+ix`ZYFp$O_ckd~Yx5`vx7~)oN5CGU2AArp= zf!~102s#!9mOv5MF%i7&29D9^`<@)f4>q2u6ea6AN0Y21RCYrpR!DHF5jnYRFft6# z%LXGEJq%2<;N0>?Pc#cF!SW;jNCMShT-b-cZcHt38n*_A6&jH=7f)*(oTs zxV-Vj6Z>CWzTdxC5G$7pwi~N+ReO?E+W}H?!5(A2vXC7y`=_Qr{UOZ3e289YK}A`x`HbbaW^OuT z{MqAAw!VrpmX`{BI}6VjK2ri71@Qj6$??VXjm71x@w*sbCXLT^lj`d`&b<|ws@!I| zRd2?JE}CxEEi5`6a6X*$o0GV%6Gk>3;l|2~1L=tU@--7qkasmdc`r)5YmZ==WnNT# zI4;kYPbvS+N`dCN$P@#>Cw~afyGnesJQXHeSi(XUe!f0zX{39DhkEX-tB0Tsc5Ye4Cd) zQeGrBu1wb&Q*6MBi)MaxR1ucg)9pnALW7|O#L=14P;F3Myqkupx-vgCV^f%gk`rz;d`%cfe4mtwvQ2Qgkmv;@-x|B-mstSEzL0DbyG~O=PpE z$~7u*&`(WODkHJ38hj;}RqO&I(Qxv2E_5@Fp9kYQfh4}`N(!+9X`NV}AcqFm4(Tcy zAC6OYxD{CV1DWFqrjj2t=9L{N{4R33pQa$j0$oR*Mx4BO0dE)aHw_$Ys^euDjp}S$ zXw+|njp5Zpgnk;pyS==HZzatABNF)*)rS=eAi}qfDx%U3;9VR@(ruxG#$BGTT;oOX zH3DBGHJx*fcMgivahf#>`D2ADWFN`M!{N2|d(ttA|F~Z6F;>x#@ETR$7|JZ{&mGC; zz%hk<;=bdXpyzpzSQuPQk8QV?0EkWljb#fB0BDJfGrJ$=?~eqxyVT^{s9E@Kd`Qt@ zD|Mtz@gqvd7%3*)wVkho&_iiWI!pV2Mf?c5poH349w#JhTzSdTbK-{v?s384Q`BcP zsa)%S<6*-701}!Z+0W4n;~~R$f-K(FYoF)4jUwc$&s|1C!m&O4L?2T;6HRmZ`@HuO zkYI|q8;MQ`E#eBkoi1vchh;F)T}IEtvE96#&r9#)K1~bqv2m~R5|uCj>E%;1&fWNU zwm81Rz1!@By$jITEWLZ3+J90KprYvvb{LwgcTM?x%8hEFl7)M>QgFTo(_nh0EQZGB z3b*YsSstB`#Wc!fA^1uaspj8C_Y#_?V4|_joS^r{l{ZjW%0!M8$1G zyr}Slo)IByx?l{|NE?S0ewZI)d-c|gK93s-CTovULGw$dWA!jhbM-$&c~ZV%8uLUls+p0J|KDAkbq>(KFKyJGHF#lHiA=1Isa(gh0hgea0c{%NsYD6{lh_1-{Z{ahIiqq7YH2QX=019< zGQ4rOMahl3h$7l@58HWxdl0T}q~Qt8EHlSAmM~Un6S66qPrt(tq{L1$-^nv-e6twU zP|%0`+$_7B$@?mCy^mVx2!#?E{$#_@)Z!Z6L=#w$Imz>cswUdij2A4qnvUwa((fRu zi=qfL=#6eOwr1s8D9-DUlBisxI;tkPs{x2|ZWOd-Of}9e4P_DiNJO_2z*KR?O6f|L z2$pdu8D729jtb<%sC~Lk^kkGgt7$|0dFv04vDL%~$NV8Qof7=Qa?4{XA68w|n~+Yc zR6*mXh=FNTkE3ctOpO5WhMf#=eB9EOcYL_1Hr8t8bld!)fYCzD)zHdY!72k^s)#et ziKsfN{u?eK`Z#T|9ye$r%aiSQFI5_c8gBGqQtq>gW`v=NWJHj5dhS%22yFU^x%CNt zHUeSm|7*3nou9N_8GRnl{Nt*3#?!{EY?@5&7MZLad6GAy@sr*nE3_!pJo)0j^cGp7 zHShb=CLPTB{`6D6261fX;iuDD1<+u&8Yg?wUzWz($W!SrtCSa?OdDIaCG=4Cq$7JW zefy<{qBba(HIo|Il$JWFX@wWvq$Y3Hw_4)Gcn!;v?AGLY)>8yg#wCbuO6%mC=9$0=s8kZ{r{+Y8}O>G^UhZUVV~G?&OV78%ZZX|rb?>ZP@@L5S1zO@Y>p&s zBqTu6IkqI68RHO-No)sUGJ|AMaSXegLvY7RoIBt!x9|+T7w+H=N=afGfeFGkv57-n z?S$AQrRi}{6P!{3+Uw(8U+;R?yWS515&y)OmqFaI zg$SKRO1FJ&EqO%pONnG}(9$apQ;7}?+p5@(%|XLv-M0iSTbx@HOy0oBlbS?s&cQtL zdNxdMCoFs$bIzeel#)*lKNo&5OdjCYSvZWN-_BTP?w5Z#o*+2dcaz58 z{1|@Po=N;P=;v|%dlTVy{tNQFl0KW;zrh2AWi@j!Sa`T*>K5F6&-1h3H;rZBqHur-R(7La<=XW&E~?pJwLqRo(_Mz+u6Kjs@om zimub+`@WG>#D6`1DgX7%(yZ}cdwXUBhc|0inLGq~tIu+g7&-2jx!_F$^8^*c1c$roVOGLcD5>;?>nz+I6mjRg4IN7J+JF@d`{PxWz{w2 zT3`k&@Ob7w(Fgzu?w8T;cB*GiiKxFhCIZR3C9*e$xY(S-kKA|vETkYm%Ao4~cn!T+ zu;$Z!Jxv8`Rt1esFteM=RxKvxv03&F#&kY3M(Cv1L5Aj}*8}!C&U7y<2wLs9p@^e~ z^Za1we^Eypu<$2E8XuF4*X9Yjok94<+>5vN#Cm=&(ueKsSzZv-dO^{_E-%=i6VblW zcdWUgZ*z%w6@212*}iSwjBu_kr{QJgj2pv#b8kVV1-D zM>?&u#;;w7Ha=DK8|!g4j-?@F#cP*yu;DkRg0>>RwLPfAOxr!dl48H{-k`0-Z(SAC zmHM?01WU^N#s`D8a=&#=fSd8!b-|L^e&hO}tRsLf-r3#i*`&&2bp>6&O z{|j66bPR7Ap7@Cod=9p~doG1-+y5fimQ?v$4Ysyw{|g3N-F*MCanPb8z}LV2pQfBB zy#9YreFuJ>7xWVw-Od^HNM#=feL=6C)?rQZjVSb~WzSGxUf51r09$uX5HmdGSQ!$LGcNuaKWV zDZf3xD!cS zn-_z3{N^Uaiu~r~K~sg_oI**$Z*IpQnBRO4&N2MvdpQ*In^&RQ>@RqLnS8l-7?rSI zB|P4%?xPS>5mGsi+t8+{$8Bg++T*r7QsCn{B37A?KWPuM!L~S{dVIURRr-(bw1+DH z@m=;%?LWRp4>j{8zqDbmo^PjwK|L&s`5TV%0NUPgoV)+{348OtwA0=Hi!uREr|;qN ziF=7>i{Hw#xZ0!OKUda#`P3>Tu}%7Zb?!8q$T!8o<%!!Q-7;1WV^}>KzvbAM9WBjL zmj2e8M;HEw>&WfnqAAWndwCysuFpG1WeODsHSsy`$`Rj=-}0hGWbsLSuK3Ck z-_p9I9JOA5t^|chN&BFBILL=le?i&QkPH5T@<|~;t4*C2VD=s9Z}|%NUpKWgV1{E=#5ANA}p|833uX6W+ z2e@y1F23l7hfq&5o8 z7#Cf`gfA)~ykQ&e=eU!deb$D~`O}tepwSx0$T_tf+lA4uMhW74CaWz=pa<aK!s>xMWMZ|W! zq{=6wQdB3%FVfiNub%d2za1nmuksmXPGeC2sMZ7=@ol3uUS)iD_-ML!#={-n2+E=L!ny6*e277s`f&If>Qzap0k~xT#&tnHIA#QgoPE4Xj zH57LytqrmfkO1$zJ7}(HoGK5eJxndUrV3X-c<1iR89%Ent1D5HGtsciRrDl~`o@1q z?ggf1mQ-TFf=~U8pWL=o#qXicXm4RTH`3hl8@4xPvpZ2i${qdQ#f!b&-zW&OQiKZn zH6?|M9vFvF*K#cVd>H&HzbCd@^dtBH46I|EK2^vd+ z+Tx(q3wlcZY>~2V#OKkL0L!VF0?W5h*F!O`=YBV6EEP%FT8v<8K6=cell!I7FAgX_ zzhQIGSQh*q{%&rp+~hOPp{cvqFR?~3N#}lH#=xXe5o>MMuw{@ zXhzSVI%qD1i5N7One0}%--PT|h2K;kG|%>%76;9hJ{~!ntNfk5u$P$m-Fyb9(wBev@V~=J)s+4NO8<>eE8!9)S zNn$j5>);IU4_bIk{lrWoq*emDW0OBvPN-nzRFkMeLGpfX1VXxlh285W+<4z0a<8?D z=wK1AQ{d@4SlYI!X*sY@uT}TJ-%Xt)j$9nt31X+9e~lM-@!ndsuaOL^4-zCsw+mOz zySDiaCj&7AMGttbDZk|uevky-PD4_Ooua8=x0%R!3 zn{|qj?>8I(0}%2ezaRVNFZ(zk5Ft$qQWQ*SE-T40vD)P&QMTC^PyTs94V;*bZ`(TM zb({h{%h$EB4%%DD#M9!~ZYX*6ex6K$R6K=tu@k%`P2P?XiHf(}$owHivh^3Eww;YR zBMBN$XZAWtZU1`#amHLO6GA7XVMnw-`>Lp+WDfF=YMef9#(JubCl$8cgoU-ok7?q@ zvOOL?ZV(b|8I&L%p%Nx;GmeXi+~p(66ArN?6F}&gZ}ExeQTGk3Tky3{R!+E-`VJDFKJ$GdRNbs4RrJz|$HQ91m`* zLUb|6S`=B&`K@^7AE5t&1zx?M@kkio{JDhje3+iW>F?^;Le$es)Fb`~42Pyv}Inwd+7oKOd9gHoU#$LB>NZ z+$3;e`aYmY(cj*EWjy5ENeUwy-G4g`?m5cTZ*`(j(?CE-kDsKYgAioDi`&?dvb4DGB84%1%xW zopC9kO$EyIYht%355?!#LFt;p6Nz^5mQR$HN>*a`UgEpsE=5a!nm{}1 z#a4%%jpn0UiHY4~u7)cG6Q?e6Q|{dt4y44Xx0B$kOXEnstjeQYBjt@u zA{HHDE#nS1vaT@_8PPp1XA||Y!$}uA#cJf%wXExcgfLmcX~3|0xigNgGeH9j&Y+(y zC#Rs{grA*l52yV8GCi~$_p?f zes-|}vKlRZe~qGKms^OYnc&L>NZ|I@E3985!_T(ccO%XEn=D*ouG#NT=>Yb7PI^-+p2$HrXfnWWiz5Wn{sPc zTc)>p(srVWXUS9w_pCk#Msdd5jst17i2!{Nk=h)EEsw%PvbGWiC7rdD%}SB7uZc`M z_rSvpN!BuH4sbU2dI7MBH#-~4|2xz{Vr~hFm&1&`I%=0>UUMr+z-Xj;zKB%KYwqy- zRpI#NuklWyhkvMX>Tp-Tk-|4@3+y3}{niIX-pnIn-at0sfdIuQ^FYvm-@<32f~lR! z4j6&a!+`a)XnUVVHsmy8%rq9UIKd*GzJ@U-lA(hL+S|bA)u~A7t`;k->>P% zTgP!k3;e7m8gC|3;P-1r@`egf19MMPjyK!GTQwv{kk!m1bS0z}Q&%%qahQpo+h`$Q zkktev6!~~$4*KUuan34^P>$pd`ZbT0GACLzYZEi;CZAVd0+y*#gBvjYq@WEwz37G?KMcE0QMl zM>rCAedwJ`fkidh1@G{)9v||CF9Jsjyc90nRu=T@OSq#@SG{X)xoLL!{Hg~GMQ?3J z|4iPBnQR$LX;ZF^^3dHxJu|o|+@Y>VbJKTo^foe8Od$`K#-R+&c64wI@lMIsl3TU~ zs>XsE_Hc&5g4;kHTjm=04czIsivkY^!9PBg}JnJgE& zF`9|K=sR5L&{Z>OnR`VL`#Iw^hWqK23~> zh`KIsKQZ{W>+P9$fGUn7jSbb{TMPLaH47cQv07GY78M1HH;nl4iZ%zgmBL!E$zV#+sY0-q!xs$hg)z-Wk=%K*WnnC4G@}qLpN_~z zN+P47EqYPIj*aomJn3>5$t7s#{o7d3uW{;jfbTNjzeXa+HXx`it+Vu$sPsnacv1mE zgSBN5nE>KRcb4;6)D^Uu$e;U!`;34%N?>hu^i`X(ME^n{5@hw<4e=$iHBr!F?x6jd z&7}Y~3BqY%gEmZm8e(x0NR-2lRV0v6hn?lvq*1IAE`rIy$X%RIfri~Ao6tPAR@wUY zpn>Aa*{f^abq>s|0jVhcYa(dZbJwJZWwT0UV=AyomS3hc1F1Po%D1nL9}<7S+0QtOi`p|b%CUE+l}ju#5PSMNuAha@0B*_}1Jd!d zW0=?>{xkay@^m&JqA^kFFq67ue^j#r++EjX4n{%G*xg&`X`#t%%fHdGiOduEHwG+2 z&;j%$fku7obJ*3}?LhSMWoi2a0#er9ukmHPFe;`s zF4d_qo83$Xj(5`EYk4JhY;^bR7JV}ANuy>zWwvTc)H3$eO&^R%2+bMW$!IZt#YrxA zGJ1{b*ZBOj#;3-lMc^(jXJaK7n>N{dtULy0Hmh!8B)ajrO67}06JRzz@3okT%mdt` z@u|eo_!M4q=%CLAUO98vJkZF0)E`cj& z@e)QAN0gtsv^9@TG!WH0q7Mz(sB=3(h*Ob`%Fy71>N1jW5|HXafV7#C)ocU2{>f8~ zsT+scq#PvPJuoJ$Px4(=P}P(ulUnRto5Gn%bXIXuH# z%kMAv6H1dCJyJ*J z^A4@2Ro8#nsvwY6hhAu}@qqEQY|qU2#{2jJt(F|Q6@Jmy6mJecHG0AP4NmN+ES{-U z`aDR=ut4NiK42n4Jjnk@7;n>ApsMd1EsSs4#5?AwPyfhlBj#lRZI2#AJDo(PT;n^x z%gJbeqp@laG+IVm9KYdwVB8141=l0e(S7~jku~u&ydAp})^s4TQn;G9cUN;iiEy;; z+x?dFl8sJYkbZB=*z3PzX(ghW2g$ZhL8g+jRHimbfiXAqrVUhJ3=H0k!GX?jkkKyb z_3u#EN%>@&2(+oD2PJlUA6@lkyC1viP5U3KnmvqHZSy60iU4wiz7lE6V-2JS1sHy? zsjatCwL>;g4x&M6F&;;8yH;M||T(9M@-7Hpjgm=9@yK z6yav2i!dDDIEPU&E_(Q{c^MZyypfm5qKBCpfX*Y}zM&?N1^Guu|LvOoUUdHNMd$zC z4kaOc_d<8ySMTm!2u_@b@h|3myB%e8kUXX1iIg2rTuGoH$}kaSn6M0;@Zlik$e49x zSdjMd;Qq}yVqe`s23$Tb^8g@>ODvv<&*?a6F*;7h=d3(sk1J2b=d2#K$JMl)msxB{ zlBX>QZh`fEO=Z?LKO6&$9~dM@YQQV=z$;t!K?~)N2A=)vAI|VT^0za*mHhVeD?E$c zag5p>t3hKM_BdAT>Lan;aeV!&zH^>{b%|xIa|hdkx3SLuTT-C@AiAm%kN(yVuDPxT zA7As1tiN#X9l_|lx4UnW!g~9*v7idY?1NIwUbAfsy>3&^zC>IXdEx&t_jSry}0Japt-(nUe%E`*9V^~3ko+8)^%Rm)>buF9{SPmnTZ|q`@N>z^A(XoY$5AG z6l=WsPiRf7Eh7GVdwY-|eP>hd|FZanL$xp&3kt>!K}AU}&;S14F#kKQn*ZhSyze)R z|Ks0LeXj)n<@tZ@s`1C`d!yRZwdguaVFuAIYtixeN z$ZWZhFeBWHD#14OT)gHgF3~F3X+kO{jgn>;(4MMB=B?EoouEM?w7K$3&~?WD7?ipY ztSKHhoDI6pq6|(LyJ^lYgDy_U2mOa*dK(G4M*N41vMpmK7mPcao)-)eN{87L2%Q&a zjBVXsLKYN~*P2B*`qHxPm|Z)YXMN>P(auh@3N#HVpir4DBRISPk)TkOcakwNoQ3<$ z=zF9WK$6}XF#LW0YV$qZ%s-^$a}iAb`f-ML@CKI9elKqcja>s&A7oiM&epYuXj~jg zKgX6r&kgk{iT*esojVx31!?ylq*QKwpKuUn4-Q8-=q+#%0x9jA$AV(RLA9R0eDm1F z#@J;zX!`y{z75K8l6>2p#Ykde0-F=hv9XI6&9+K*Cr^Ing02JSx#oVFhM2&1I1RS8 zXeguaXHOtFlG()*tUOk4wtqb9ev&&UnSN%JhrYLpd*DV&$~q5r1sC)_rSCVl7~7dAkKDjxXYmRr4*ZV@K=N6^0qNs;3X17Eu`w z>pZf5J~TRk99{5|m?`&&GjZ6C`7;GnGps9tb*P7ofzkHd>F3;mSg0wME#lMtGhRJB zlsp=>08#_}g@nQ-v-IV@760lB>_Zjsf^W<8Fp}!5&^pY2^~ms&3WQ1fV@hV%&KY?np>rNK^QJH&Lj<0LeZ=$dx|QLhN-XX~Lr#sMzF<-r!z=|q z!%Oi!aY#L@6u=9O>t1T8OL~-mtonnmsBuaoSWaBsY3@N@$iuw|Y|gQi!f9Qf;)|VI z3t93r;XWM}8#`0!fCy0=z;Q8=0BYswXB0W0sl|PFvAgdparaCq_vYY`W_D9Flc4Gd z%npb4tQ8mM)JuJbquP+PeuOM_!gs-Jcwxp#emN%Lav5jJSApQ1zI>k=%zjlz#v zSAK@-dyl9q`}KvpDYtQ7*~^9PTfRflt5%019HTCDI%~Y=`_z*jS^ArS4N#uwFp~ig zw~PLA2kM)7v98jvU{h|U0jxK75@5agWIl@Z<{s7L>b-erjlq4@4$Wvrah^)v`EEV5 z&XP^^tiYervrhn$^{k*iPIV+jae2?~nbfnp*iHR9J!?37Y4A(eeN(#jfo}n6Mm%a(CbH!!fh}^HQ+->N^ z=bW;SyQ4r$#K#>Pk1|M0!zFeoCj$2=FXf;?8lUrH``E8QHaW6_hUG4V1-eGuwghdi zm1o`l1=X#NoIQ4+v(@4vw2VX(-I!`}w`L&fCdQgLYBuB#Kn}c~V8QEq3;Cd!t$fz2 zOJhMj(l*0Ezb&lz@U_+j-TF{R-2gL^LDXCPU!Y#XP;VCx+N)#ax7TX;w^j4hie%$? zIvO*(riA=Q^d#rLUQZbp9W{?jE!(U`ph4AlNs9Se%kjHpMS2RA&}q>3l)Fq418#zQ zYdM*6kLfqawy}Z)EG+nbklQ53{Z^a1F65}xp4IUAX$X4CS3sD{RX}n`RDjGlBi9%f zKN?oO2^yJP8ZU_*f;bQNFtK#PZg4A!gp<<7v?i_~G3XKl>Sf2hF*+KSrk?_0HWC ztn=o+nYq~WuGVbBaBeOuaP7RGum2eW&NPstp3Dnu_<1sn#DUjz=XsWq!p)WFHz_tw z&~K2N-^L1{tn}jYrsyjiS0YAs2lK#7(@^G;@)LS%F5&VJ{g@aK9&*PfNR>MwQ+mk6 zR1LQ}X8J0+$plY7MeDnh|3wTr&{DdIWQcI3*C`oikKQ4NlG75&R{)}eDw4KO^gHxr z>IPmOm#u^S&$^O+kTGI|H^PlB14eVAD<^RE8Hu8GHG~W_Ikf+f3hg5Q#K7_>M5QW0 zVnCcS8={GUcB>^y3nr+WFdnBsjnL^jwa37E%V8=Av{8OutfL7pPpS48h&VFS=0LsE zOhn6e9n&%~5bNsLd`%=Jhj>QI(0p#?9BYkg!i7-42@!lXAGVo3kE z>u+QB*a2X^wwD!L?O;@1LMp7lt`hDHuiuT&b9~+BYGY@{FS=p5&s8k-*G{W{-+ap} zZ7g$lN;OdBf>LT^2)GMmv^#3}0YOJF1iT&i{e+^EgSId_y?t!sA~u~o>NoUCaHe6i zZ))de7{Fv=Br+po2$3|MW6&WygU}4^W*lMPF_2FL$rFBZOOQO}8)w-G&uApbIETp- z%Zx{Ut06HOmIBn)O{dEV%rxu?vKsf2vN9nUU$V^ygX;u0{2xxe+&~Od8yxh8snF|VsmsP_s|=1z2)Umoxd)R>L%;Bi@#A{ zjt*ulHv`_^W&ETo;lpaE#R+`qm=)MdM!jf^mcFyt&)lnr1%<8f_Wb@edckUW@E_Un zw(|nmT>IW;oBFg{dT%ccvWljPj{7JQP^N}Jw`IM+Q?y~r43f`KM)}C>KyJ3Ap8Ks*){Bu;L~Co?QtIQf zy7;oTu#q}p=A3Xd%_Qpm{ zVh*qIE~-T)vC{YGtEh{PvyS92Qv4KOY`<`ZFBSrNaLXHSDXIUbkUvQ5u6K}0Mlvx_ z<6g9FP2*T}l$&<1E`x)5Q(>0fQQjyZ;dl#1Onq`Zs^FT43M}m9Tzt139neg5V$z>c zqMuTk@$G41$3nm`y$O9V?Tf^Y#RghppupXC7rOhtn7i*Nitd%sy(+p_NB8-5ciZAe zODNAY2p@r>K;km}$ymN#!veh`|E6Yq6j6j9bH9n-XTUy_pe^T3>!FnK(vPsxx^HS5 zl_6md$k+5E%AF*AUns5X!9tZSYQP10?G3URKJdKMg(@}TmmAZ1oFDj#}4q10CCmI$(I`NB|i zHoB}_-V%me2$dLrdWlNYl$LC5dWlNo&9PYsHTny#D#Hc znZ}3#Tk5MxWjcLKKO!I!wlFAvLrBpY)vJO;lm-2|>a!ZrH-j-on?;LQ!)6uTZZUQB zTec{vMx|5xb4%>Ldo6~pe#;ZY*sVn<$m+@>8)Wl??E#CYtB+1fzs4R5Y>?I9^ILWW z{Th9(zE;zA3)&kr#~hMhiiROUuw@Tv9cWsb9q_nJ*Yg^D6bch=zJ+RYojqfdX`Qgw@*DZ zf!y9Ijc4v2UzTk3nzWiktJ7_TzhWuTs*?-;!*9BPxP`+r1kd0S8*eZgZC6@A)ZbTI zK=l28*#h#`7s&j|3&?kyCfaz`gX3)+Efm{-Yx;$PN#-@|=VFY*mQyqW2sn-4dX5_e z2Sm?)A53;gVO`E9k z#VlF@CKk2)LSu_sVt33HrmPeDH17nbgCMaZziMa#==;LxULW1vD&yMVe|NlzN8wk{ zhUpbCF4C=cK&o+Uop>#ZR!o4QURxgRvk>*v?8kO5T1ZK=3qc~B!Njur^F zI7D$(jBxHa&cEXNz}DAl<;kLvRPv`o$?_obk9Zn@wj`wfCWR7!6!(Z6?a)Q%X-SYbj zL1{9(WzwtgKzLQsZAz-T%DAA3zAN+z%nRy+76_+aLTHzyYy7;}$?EMAu!+Q?5rD#K zKwgzW7V%nkO{g*KF_Qol0w5a=p4EGUmc34yfu&2OIC-3`29r}+VCmwy)oC+CxZifV zzY_i5@-<4j0{Yi7CAObi1w^`1vuV*kvO$!OOpvbqnsmW{n-c64c~kpnY9O<(YqnU| z{t{gWO1VnTf?=Pq&qN+j1nxGE;&c8{ezy?J^!6zUcbk0iIU@?!*0A3)X(OM%v3w4> zOb`3EcA=xClKLxj9hl8kgpz09lvrQmbH1z0kfrppiWi^r1HB{naYzYVu6kln+4@by z9cmfgcc@Z8AKkqjXDvLDFTl2#C~y!<>}Vm&BiVi>3SQC))OLg6FqBzO8tuN~ zTfan@;|gRng4{MtpU!#X`Ui*IfI=BFFe(cn3zRldw#4BM+ED039h5;EQ-?A~Jg9Es zh^IvUcd#E0ff_p11X7o4V4`bB^4Q2ERYm>d4BuNh-x;GWy115x3X4!fJx;qTnBjjjUU28#)a4T=pG)oMe zW-K{;J|(r;u2njhPd@#r$?0Y1FflM}8E9Qp^z?ftfne||+o_ot;1LkE*GdeWvJq|O zl+&i*b&N!sQMMEhsqSi_KO%FZqY03GI#W7zXdlQ@Wb6!gklGTeKNJ|HNk~-B%eul4 z*>l5MEG)PHZceqZ5+KCKg=4gq-VoD9CYlBIS1h$w9{IBh#F$uG1q}ZqJn5S@5!ikl z%R`#CJP96im=TWFBrLyrXMj&=)7qlE%dmXQ9;X#5fF|R2hw~#|m(vn;INz9*oE6jW zk-Dhie5Z~UIeIc1BIfI<|2_e4WVCNWc7ZM5e!ts6tg%t#HX=ircH+t*6m0;9-9^j@ zhhduE&kD&o#)Two{fzKa*3v`-x}P1fD2j->=>=|%f77+j?q9cO^E5|Vzaw^mF$B^F{nlWtlwqmk|RVnmz ziM_35MK&g!NNj8HG5?{O;Ne35p@qT2G5?|Z;Nc?wp~aFAdHM_(F!*e-|MXcNsKX*v zWWjP>Qo6M3au1h9_v&etE)VGPpe}2;ELx|h^<2yyq3&#el-C9)z?#^wMTI<6=|A0T z4^{rtiV-|i?LVz3!9(-?rxk}ZzO??7R`$7cmw7JTWtdC%o$l`Mir)7`_r1}5e{?_K z?*75({h8=K7~PLX_v6w11oz;f8vp4*dsyf{tr)>W_5Ra}5Pvrr7fufekjharog#q{04@nlPhX zk$4qW4JQPw;QwA`zw7z?E;H*eo?!a%zF|ZnHk0_l2scTl31P%LQGAw=f(SOJbdx*I zv|{WBYZp6SCfn$f&?0)RpQ}!3pw2eW=XsAfGDo^)uDV{j){EyRa}6v|<8DXCpFb z6q%DMju<$tu)5dVrxK1cf?8;e-k`PAOcj-5lPRc$;@A?jVm;!Cps`YC5Vh5^Ezw#f zHcI1sKe-2fxvVU~Ou0#RC9t#PCiC-m)rpf)-Roq52i02v5h56lc$myOp`K_ z4NRw`k4a2p9tGILbaL)k!ZhxYY-48I1vMryja2mejX~6gN=rL6Z}N>1LYswr3hXLj(wnB1Wh+>O`HJ?m5?Ai znZ?|Ltga03@38*4aLY6>Ez4}bGDI(xD@WzAB-Zqp?9f(v%;^T6c5xvF6T2(N zld}0vvyn*bc72iPpYMux@|w_o##nha1mH9jD`%PZ9n6)6_7l5YT9-~MlljW=2=nzk zx8`P)!B~VrI~e=EK-}HRn%4n$$CNwy83SjxGR^NBFdn~@q)c=x={zY19B}P6#CWH^ z?V#L5_tBkzpWzlZOr7xV1CZ{mHh> z_tv~OTeyQo5${w@xvV9P1*4dOya@7|JBSSa&B05V=MX>d#9>sQe>38=HF9QlWc}-x zh?1{x1xKRfeov8#rSwTsu+za?@h^w>`){eL|5AAWo2AZ+j{n;5u5-zzZQtqYSN|Kv zFS$y5`mcFX2Y!zJyD3}v{e1s@Yt-5Um7jCh4kgDF+!pkx8!z_!YewHTI`Z8?D;;@+ zj$D4Ekk0g*asG8!&cA+0f3BFU8?e=~%QsMv9J3CsWn9HN@0NqZOVulW)%f~d2j9PN zd|mvx4eYCpuU|2~_gN1A((&@>uT_5END1EKg9c9?h?67o@M&&6F+bddZU{_x=Cv!D+}x;-Yr>_PKytyPqU@zM7W_l=68(E z`$c?<6i$1(ZtaP6KZrg86ZjAThJcGKFFeJ@G=XCZ&ZFV?Q)A8FNr-st3W)fX3zH`b z>1^IYg7(CMXXm{pM~+2VsSTsqP?Rm&K(2+s`}~`0Kmz~Z_egb0g8;Wp)75vK%l(S= zRqtVBt`-G}u1DLl^9wSAU9%)WSupRV?$yhv@y(NJ{65Pq77Pn%&B7CI$5_6`HwP~O zSuDTsM3o&0h%r@%=|}%KxxN5q_3eq>y+Rg&TX8P>C!n=!X$bi}zF6!0hzog5SHCA+ zZqwFpy7;Sp!}!%#iErcItQkiw===mM%a&RzeqF8TcZ|+^p}Xf2)c^9&)CP>4X;A;~ zfcjm3UylDruZI6E&uQ`h-T9V%o|e5KYS~~BDu0EO+t=!YCJRIg6QGA(%;>z)_||8s z$FltRpFA3hTrH*>4et+L2rmA5RP0+^)?OAhcPigmOY=Ra?6Le|r-&@vGni-uTqm$l8D+3?%vn(Q%E=jV z#{AkM6Dr)?SnT84qos(gjwHvu8@A6RPGkAz#@U-2|A5e1oMM#uwUze`)m9bEP_*jJ zwe$5sUN$y2E{CS^TNnGa=5D0u;>&(*Js+lcoEJRMI18F0+7N?Uc~=Ft&`0BA?qCFB zatwO~J*j1F(SQ!HS5UgaDeE!ws-%*KS%z}iExn4W9#d#STMupJF|?J}|MwMlqn0=K zxp{}zyr;UcSX-k*|6l`U?sI;lcdWK}1~(rg(~YKDC_t~o?-2`gg5MMW%W(sx@xQzi zU*y|^ItaU6_lAyQ@v#;TQ5+`9z!W@UQqnUegZE2I7+GW45d6;Y$)Y#N8A*s^DGNkLk)Y6 z)loxOL3pKai{S+&TeyuR@9--x*0DU7sI6Oz{p7)*wp^mDjir9_D6<{Y6+t6sUI&6! z)Xq)@wevAg95iCHbTDY0Ek!i?9Uu0MH99V9t@cIrouQ|6OxC(Eo9u0ZsB#pd6U5=5 z%!G8BB-H`KZOLLeOE>ZhC-L^_el{xIyZCKyYb9E5B*LTEu;+XuGxx)W@x8N5 z(@GzET-W?BW^8WqHscwhz6@u3etqfYrfMHstC(J`uiV_U*srhmQTC``I58yMdS0L3 zKQGoGdGee!rQcb9tadT`*Lt~`>0B7pR=G`fwIY2sAF1kCZ8edq{La~aXC-Vr7pYv4 z8uO9Lj@5$TJIj1lm`<#h7fsr(e`wTyUh!i!)Fo@-Y+DOID2fAnx^6wJx}0%PWrMe;+6M23EhFfRDjU2lW42_| ztgt$3D}%<_@I1$9xFScx0W)1vBbfV1bvc3b=p_Ww)2~O+7HOsxSxK!mnrUOqG)SZ( z?9Bo1jX`Vur2Vnzqo75Ub_|xsYm9vd`C^0o0m~G~>n*<|lqW-m`N4R>ZBb(lnPm$_ zBV89<@5h2=h4_&Sk{2!ld2MC)AF6YP--L;DeemtWKaA6P&re>HUDuAA3mBzO=Z@Nf zJPHxR{0xe|8YIvAMSlz-;1_;vf^au%dv$y^_vO~MTbE(~W*5rG^2>53YXkc&7-gm) zTgJn`B4icoOZ%ssVYx)h0lc+wm~BTR4g13++_&%BOUFUEIypGm)d{RSuZv3>3sYu4 z`&C$S@=tO#uOyM5ll@nYgNu__zs$PSec;M*et{E{{ePvgtB42vt`m>U1YHq&!0$S# zXG~*s5q?U~@c+_B`27I>A#B|Ax%)kyzPU=~aY&y;ZvK1^dF3AX`)dlm*2|M|oy2%o z-(E9B2w{VTi@MJ1DBxPO3=W?u%x;-wyk)kS4ZR=5O*UEzITtP&PBZ|r4I4jt99$0q zMTB8dLC`hU=yzeu;x9#CUlTdrVCNPc6~M(;jvOt>1~nKvI#lpvh8k16$zBkU-!;}o{t1Cee`)VO20EWA_fZce~|qqdLu1mD(?dBd)G-|qfEw(yG|ectHn%LW6*V}FP9nwFh?hkr>SL1VdJv`Gu> z;!TBpgYVFWEgrO%qZF|;x8phbmYCgR1@Vmd$Q1m}w%j!oOp)W3SE9+y4SEWCFSi>M zlDE44(L1kvJm=Pv_GKJcy*2mcxo_Uu7VEj?+;tK0rSVsXueSYtUmc~HW^~=a(wvNO zP{uMm_vPAokHpr$I`@0Wk_ToWQiC~jzh&>S0&rqr?Id0s|X z`;mm9p!im?nXP~)13QNej=62fG3_+2n|nN4_?(`m+k+%eu?Jyd%xl>5+1WvIKjuLD z-nzKvU(Ks}p4}&8i@kT9KNnZ=iG8W4DxL0q7%cyywsekO%7p7?GcPN4`W`zTz zdQ$#Qh!odR3uL(_3NzZyZOj;AV~rv->9SmxlrHTuw7NDT^_FBnhIAGhqdkDijuIlGF(v_)>efp z%EH>}a7B4oJ3m}e5!Tj(D`u-{#>>{I9h#vQd3|?zXb zUXzgeq96%^N=*4msJSUo*7oP}a}72%W(_f8@<*i+jm$-p$z0&G3gXo>n5C6L50{~|KJ!P!B#!UTh$N!+0*4kW!%O1^Qq(|)$UR4?XU1Dofu z&c8cjD9Sd2lUngg`>8DqyNUf&E7S*teqc8E-uZsl5dYWw?$5)Ek1t;3%o!1^_M-r+ zWkpJgNf9CDi$jy6LG+2<+Bqh*3DN>xLNBZ;ggD{;piMjpO3N5|`kX%MVAD%ig>R#- zOCGXakXXHuOV{SGV`sRaI&9gK@96M3>*0q>1-^SMVhG! z*dg~y+RThI;2OedhpTne_#%|Nm3w#@;e|xz0!`N1UndNp(!-Y2Bp=NlM7z3-<7e|S z(M~KRsLeU6m)uEoYYS_v1MV$-UahlB@qj|#WlzXloux<3ca7IY{6xbZGfFCeQcBXb zpCnzoSSIxGNkSPJ@)g9%vc9l4rfDTYDbdECDO_j8fw`3Fdd5!<26n7(j+OygEsIVN z3xx7xT$qWsyozr^g4x)6^BY(NkNfWMe?N|U6#0O8@4*v!VA2BJ+f~>zxXNLHMB|f$ zD-p1MhIXQ*>I7*tNE7mI)JR*dO(c_D?^Rf&X9%TR~)q& z0mvOBpy}GaM`M{pXG(t9Q`S;8JW?wuBb9Hgm83~@L@i1K@z$-TF-e5fO3F;-b(b{I z6cst#mIJgR4Q3vN2vJMXFno7Cs!Xk~I-a3DXbQCgw8k0Lp0l;f8^E{-Vjuk3$EUCl zgwI$HuD)J0co=?~1Fk1p-(YNtzZf8?8P#QsOMxDs>AXcB-pdkNUa^BF2f#9OxTKH4Jl0qzF&)7Z> zzu+w5(7q{l6AT3}nTy0n2$I^{jdJh}<<9Bh+7IwPt3tGO#juHa%Ra6vY+WS4?OKwAm6f!y7=ysJ)3?3pqa07kjR& z9R2~jBR2pzSfzy<&V+SUVRG#B>)~@D!rd)r!@6pp^?OuX_m&Zbt9nGv$xxY@CJXB- zS;Nc$yk#*=7M6BcXH9{?Uc7hIiF#VmGOrFH-F9qh1_M`Bf$oTyc zvVMQiL7h_XA;x=%o-O@4AB(?~E&Y-Yf2xB?{9PAT{w3fbm<-8I#7R57C4)y=%TuR( zCH^C_DWxDvNW6dMDA(ez2HhG)H<4{A4_@(motPOYfflm#$_sicQO-C}=F(h96f1JO zr|a_ff{A|U;!~EDt@!u8m#>yXKGAZE-|jI;NmX`*RD8W8RT&IL8?_%j=En2qKhEZQ z#rKChzdvkSz5Fy}{)98=|FDGd4YkZ*W@?2Cio#S;xWEfjo(5y8SVK2OoYF8=8ZIac zQ)S_T@-S7d@tLX!7t9V*v$dW0pP65{pfXHVh6}2~RF!DbRCTyuewdm+dD}vH;D!m3 zJvcm>dN=xA`=175qGD-Pn5h~36vH70d~`{zOz&?p%98xWZ}_|J3CjQLe|Qys19n)a z03i4d=TD0#7vGHQ%A%mz^P4<#U0ED7m-s00$aQ6DAlH>;K{Ku^%Y)_$AHSz^T{%0D z>q=Z*;<^%7m(}L#a=y8`tT9)Y3vqRcAD1vyA1s3Vzj$&>P1-NU#I>1e#!?T$mMWMO zZSLT)1<0^rRCYn3zaaK3N(NKvb7}id-RJt+4@ z$!Flwm^;kVVjv}tlat?YB3N9xSUw0blU|eCckhgX-v1~=wQ{6UBM&@zCYY1ap1wW$ zMyf#&vt;Dou@nF!d4_?@0Vj9`vL>QXfJ_m^d*CFLPn=r;W^09Fi4^b=ysn+?(nR)2 z1CG0NE>S&q&?RUXY4atG%~7To-@2Ot1dfSVYBOs~vfU?xdRzdtWxLPddiJ(LGchku zR$dbGX2;E>nfXag!ji77bnlgX7DRYx9*<+Mm~pPHa&f8@2T(=q<@2%EVd7M~IMwt8 z$ZtX4Act~e62E9fVPz$InS4V|8P8X0&sCE)>ur?iw(59`f1?jO!UV{bhxchgxK<#@ zr>JLczgnwb?tOqG8JkJw5|D3Dhm4;Sb2>l(-BQpiBB$1*VRK9eXI1_!jsb3q0&7>LtZQ z11=-&+e&!RPF8e&8(kpB&)&+tJiy`bR?r{P{F75s(;1YhOm78U;eE1-i@ctC-Au-V ze5YWqK`^jqO#PS_9gT=MPb0%EUV*$Hh(>pch1x&Lg}OEZ+Nh_sS#(-(N+Zi0?v37e z%IXT1i&d#q%M!>xG7!$I8*R3+ow-1OZPi72vpktcn0DKw=d?*TidzX}8dz(F_JlGW z6;0R}Z?p`15Hl!jo^!D3M>2G*K!FX~safI`bH62{!nx1SnYt&O#O}I+JV4*b`ic2q zgVxDkXj6wpqt9X^c^fv8ckT)BTl$74kqF`bvT3tQ$Ue<@NPL+4pzRYCq^4G|v8818 zkFA+@){AZ#Z^d7^Kie=;!8$w9guyc5X$XTm5fyvT)1J*Ksmy&D)fm5fGlswX0qL2m zF}J{IfXQTfRS8o@>H8&4qJEbwvACCAg2UUf!#{mT-(YoJ)(LJ+$V<^=3r4v#F$%oT z=~BJ3LDkulo*rOUNBiQxP<2=8SwXt2FdTZ%PBmE3pU!+P zyYP55c@}<=OOqNX#m+!2LulTJHO9ktlC`oYKs+4!9!` zx%X_c&cknWb7j84oM|-jKkqN7a^}(arFLG zO$jwwQ%SRXrIls_*t@sVN zfx1kD9LN_s9*1IG*=s5VZ)9~ZvHD4p?UNM=0z0;GS^0!>$MqT?)?1|`QYL+FACREPl z8l~3|p9+NPacK6H($i8CK&+`O<~6b%IbNT{p(>Ypy*l5SeYEx{QEePE*y@_z=SIZ1 zNy>@ZNxkp#Ixlpg6LrpKMxE2U^O;p%3VS}Y@w!L(Q1|qXD3xo2e16sjWmn2)N*k!t z(vd+-^IIQ{-o&BOC_VjVitHf7#J(CAxLOcL?oZ5rs%*EJ^&R8#Q~vl@p|;f&XP#W7e~fqOiJ6kx;rcsaoRyB8Lfi;S zU@_Qij=kY9xH7~}0tnKOB#-B#QxUL%!+VU`#G&&X^8&!ilLDUA5=MsFAaUq|eMs$g zjg#7^Ms^l&Y^=L9zEBxfTs&yH}q_n3`gukfpuHC%JY#cJ5*5OfPOoyJJ(U?3$Nn^KFX z%zd<>+>YZ>0t$A-A<=O!9VOdCC=`Wt2zriDrl5t?k6k*Eks%QpIIp4=(;_m>cm))r zv5CEgTwR|`j;AO%p_WdpKE=g&Sq-^)hqUU)w_HG zxo@Hn&TWjkE6}qtv4eM0wbU`{AOxQRA^Em{Xq;HdbABAvH~s!+!^l+mPu$Z4Zy1%5 z++&-*4XnUM2PVe7gl&=W&)SliTs@>E*q_*vPKIUUM)*>f@0@tWY@i*=+77 z%!F`YS1Bc9^havS$R2GE7GGE6G$NsbjamhwwA+#O0#=;?be4k5zEK@8r*SpXBAP^j zkS7p)><6|WJHkO0q9%M!?%|MRL~aB|e*zfEJ$=c&O*P2P&STfT{Ng0@1x zbvfd=!F&DM6au-h4gAJ-ZT}b?jDXJEmw$V_^Cz8DU#N{2w&@W>_eMz|R=KA2J zurmfR1>WDz8>I-HM@ zP%|zg=c+aa7r8G*{`x}nHHlZ@88ygPHm_P%zo4mUnS37y9c&T>R6m}l%JGMBiK|`x3d`e4g$k9q?Z&>4nog2fN;;?gbNV?7~VNGe+ zxh}_XY#`fUFYv!?_6leDSU1x$+p?}xe zAQkiP$_1$+|E`fB<@tA=4^qYcT^E8>iGSB8Vgco{`-Pdxqnw-kgD+SiUq0J z{#`{us?xv93sP17UByAF+P|wLNX_@}Dh*OK{#|83YN3Bud00~)-c=FQEcS1k-FKvE zNj&oq&~@IuVjB4(@r!Sq;OV9gMLgYUJZ|Y2a;mP{A6o3ZPSvIUwx(Zy`bH-4-)I|H z46$t0S9E&pxV#P9K@vN2zvNIOXxL7S?EG&W{`t5G3_u{;tOR4-T5zG5lOCFP) z<4nJMEV#XJ-cZkn5p-ofY|Fff_}-mGGhdQZk$(gDrZL)dv0=0eUsN%UW}QN^@qn9h z|NM0WB|=w-Wgoj8?)C)t>>C`=1cVuA3Is*DH~F0=+;$Q3bY`+nNg__-gg2;L)(`3o0McHTX4tT0{w?)loU=wHU$ zxJG9@^Moex-0!ua!Vp8blP;BD?HJ2ILC`&hiszbnzfk{wBj5j)J^epQ|HFYrFZ5aZ zzk3w4y$$(R8l-6E3$nWUZ!n_-E@lW^Y`8%GGgzk1onL;{b-(jf7qmFNJ|y3JV$$^~ z%og>^)k0$t3v!ZEm7hR0PS!@W^B5=)I~m}9gBbtRMJEHDNNXMh@0yAR*J3*g+FV-- zNn)o4wdH!MaAcmH2{cv`A||&J$;>-1MPCMmk=lidUayZ(w~5B(x}+2)xy;|9wW>Q} zYktQFiT7H)VzlVHAtdVm?TiAywg!0z>o_QiiI-{Q5vhmHs}l?AC%Soj(ctuJ3V;9L z7*(7Ua^qD6FnWrH9B^w)2k88@i}$w z1gDO{;VogzAU!u8O|MQ?YEGRy#E~BvZ%KusZ61%NR}<_fH+POxR}JY0X8I()UZIqI zV-ATBIKw!P8iI)x>B}f635hKoZroX{fH4+E zos5{)hmyc5PBbV#N&co5DZd8gCzn>KMVI7<#)3|OqHIjM31a}k-^zT9lb2(|h>(m# zguO_X)qCeMI0CKlw(rL0gl%iDH9d8W728%ofNN-9`wiJMkYyPX+SYs#r#r(g(b{YO zN}@k}ebeH8O#(%{y{xz@33=s-xL+-FU!POl;)7+pU4QucBiA3herQGK_0Qhj@$B5e zO9EU6h(5-(YC{b50HMn(^@sL*w-;M)xU)_mDB!0CL<*S;yngQ?y?qYW!ddvUVs$)7 zKs%zasS55o;aUd?Lu@;C(HWIk)0T-ujybV%Vm-%+Az>M@ZQ!eWC5m}eF+~A!SH6js zoQZ^b%KIgLlEoA^yj|xLvRtQwG%qhK-hGglb(G!S4b$iOcpGkmVbvs7j&L#N+sYHs zvi%4X)ojJcuI!XvYB?KkJ4rrrLCl@=$|?PnJiGSV$sGlRdFf_zQ*22tti5);y96j_ z)koG|Ykfxlj(5}b$+7sHZ?Vz59H`Z2rb5*)8lUsx6hOKz0i@wVe9lXg%8k!?`?4Z~ zuJhLqQEKK`Jo8~~dhev!O*^WKrf*k2d_aQIX0B3AJw`}=jTbA^`(4RU5_L3Qaf%2M zE=JXi%gSeTQSDdi!^&Y@PG|qajBRtD%e=B?0cjWBFvxYy4QJwQI#A=pvmF7i&V@JZ zr+FUcDChF@#z9LgzTxD#%FM|%v#**7AG>JJ^Ckq49K&AI%$NJ#D(c#>1ZRlxOc&^p zC~EQ}56=$pt+EU{QUJeOq8VU>VMzwHoOZ&B7;A~-6`x)4vBCVKW|Za={*K#%>R>~$ zI03k+M{D`%Ql6#%dFvmox$ZXkYd&edQjY{rE7rVY-R}t=SDawYUyS)xe|*tbu(U;{ zvg4xsrLRCOUIe-zhQYFnNl7yQm)9ruM`w`kBebNC)Tt~>O7ELc`&BO z50~)+W&bbW3{!n-Lj;vR7;mCi5UoPF1TNt1MQBWk7l{4f!r-mGZ>Y*b_|lGcvCMQH zonRK}xJLr$c!06~nqV&W@==25Fw1n=qRTd2p5P*h_e4^RynpezGt5PZ01PZsEBP_Rwu!OJLI9uFQ%myW&L^T}R zF=&La+-Zd6F27-q2vp27UW@cLa@u}I2N)9H&g$GESOP&-t%o+?n{S`6$Eb6aUv3lPVzAC&?AUQJ_iUl^a#rTeN~hZZIfKKQ7$5!qqt?EIT$NCJ&>*X8 zwtqI)rGDl(pR#7J)6bsZxhZQ_;l~HW?>8GhiVkqRtUKZG^5)z7bC$JM(Fh)cDnHr4 zpITd^1MWebUJ5Gnu!Ko1t*cq{0!bVPwq5g}`er8-hr1+d&N^d|{o>L2W+;?s|Ke#x z+R1`QdL7C62mV>OH($4WT?bT0cdWuWio|aDU1sue#3FJ2p7+a`mjD?z`4~bayvld< zNOWAqF}`AR9+%jy&Ty{?DTvreLQR_|d8J(DD+6@IEeqaw|aA z-P?Z=nK?9P5UJf|6hP<>-Pu+EYg03&bnoZ5hP#=1{E%|srQ<+0Z`M`Qle+%Q1fkpQ zyvu>gx|!Nh>N3sfp6!nYSvMz8Z!N`~gmRKT3Nayrlvrle+yAafAG8ch1}V{Tf{UcQ zoV6kUo~tzlo9B{c=4@2g~r4mEgyqFHguQOH;mu}4wEE&11ad$ zYUJelI3&>^9jD#{t=xJKchNjGpc}$Tj&;87>sdRHhV82K%doQ;(gz8_r7n9 zdq1S+EzJ9LH8mckwvs1I?0&|D-Uq5vErWg~#SXWgGb+T- zs(FKK1RxTIB93*0Y9zT~{|M^*dPXCDC{xaZ zwnGZkGg<9$ZSwv2oYMkf7x@ik#<*%@qi>#Fe@ZW)8x<751n=I{SL3bV6^@O_)zP%f~{fnc^hvyX_37HH>d^T zZR`@p>Gq8`oKYCm^gK}Lxj)EQo2iiCypP4&|{;3qis5T z%n1hL{uxtstlj>>-yqoi4X3zHWnvyIAhU_&X0Okc9aza`xaX$or&Ol6k*EL6_%;4h z23B-6;GUWUT<~J&-LHQe;IzMJhVEY$aJczKuvIasR0nq4dfpp+L7Ck8zBSj?^c}&N zq3vLf&$CGJjN$gJ$~3Uri&rKC2bUN;jAu6y2%Gz+Y|$Z}u=3cP`>;U?k8F~a$1k|D zF-7wM*b}h&-W3@5k?EYgsmC9=dcztk?fom&+N%9`|EE=Z$l64upaB+jgm1O*|4NXq zaG(ZTRWVea$mJb=$wh;I+~lKUltfqcckJ3py{eS@8?r?1Nm=U;UiRXNejmD~f+3Ro zLjz4mW(j!PW2<2=K4yC^hK0sY{@E31%($%zbICdD_2zz$U0Glen{vPSFV{qa&ou-G zQ!t|^jezeGL8n!}#t3)`6g8!`dH$9=(t5)-o6dwNDt9+ax4~+S$*B&M;wF!f1sWm8 zZG;@2XOZHWzo$upOGe0_5q8-K$r5NIWWvl~q1A#BGRauc#0dFq87ngTotKT1(a1j7 z^yB2v&P&G$7K_}3jb?H*PHd373E&JbjgJ#XR$v|Eq#0l}atuE{c$iP>!mCZuTOACp zgMt_H#a}j8a~cJC^8$q~QT|M1yw@mCL-d=R9Ok2jU$#0Dbt)>cG{nE6ewU(O?fd^9 z^uLB?)HCTO5GM5rHk@UA^^F#;c~{>kwkkk(z6C|~z4X1eJ66#3&Nnv+JdU?C<acEp*f~F}s|h=6!n%cF=fbeAKJ2Uy>lTNdi^IC6 zu(K(wTOM{U59?B4XDX~~4?Ek#x_iRTd&0VV!_Irdx>aH4s<7^Xu=9bi?!mD0!LV*k z*tsUGTNie&3+vX0o$JF*z0@Fl%C4I>+Ou6ZZMJ8-ZrWnccHN{yjqoYEZqjLJ_>^5Y z>1-o>%C4IvE*?H**G)U^*{+***|S|Y?co{Tc=(h(ybx~MYai{pX}>+&b<+WRw(F*Y z_H5Tp&)BoB!KOj3{$ppjhMSJ+`qUuTp=-Dla1^}hgzioXrxxjhn1yc|=H@?kR^L8< zTF+Z^dj8@WJ%3?D&ztnsf9yQhaFf1R=Vk1p4FbnqS)J~m7xdciy;OT!I(l%b*7O@zH1ZqlkBK6T1+E3{_;8g7c&vj7b? z74eK=Ko!t=@E%4zY`aTNcP7qvP@pbqV-@K}=H~tx6kcSqO*jF8sR;89#Vi>Ex~X;5 z?MrUEgYU>pnX=iWvvVO39km>WsEhc(-i^{R8!FJGgRI=VA7FG>SLuGx-FwwVyl+#( zk@`k$tA=`24=AvUL%nCBC*t&~i52(%W$*pt>nhGWfBm>J7kte*0s$fsK}nQWuASh- z-quD=SF&|w87y0ptzSX5NjFf3Z`l}-_)#odjx5mO_!_5664IrK_w^;aw3}U=lmw>@ zK{Bpog9E~GK{wcm2u^bysi_Dm2vE`Y{h2xU>RwqgkY-=6?;mT>Irq$&GxN+d&ph+Y z%=1Io)w8gvyKu<8N4U|m@S?kL!i?0@%X_a!z2TXKr`Mw%@Op$ruV=zsg)<}LdV7UX zZ?Eawy}}_yRjUo<1-caKQlv{Umujuv%S&}(+d02ni?MQ6K>5|Ie)7wg>r$>ur7qRF ztg$=@ejvVVrUW5LCu2+ha&`tCt4L#B;JZJ2+jM@GDo<}z-ql&N8=#8+rQ5^u%wBQ_ z{OQ$1)*g%tI|{Szi$?nA^iWuTRXK>r>aVKQt)~kAkE?kKUBXk_)i=3Ydijy~{MQ7& z1R81mm2I(Ff5thxTjzXlZFN0iWsY!WpV+Y@JBuRlbgsF`NYa7_Z8qcl0XA7LML@kw z67GS)jOHFYu$(HV*m-dN20D=u*?S~d?b`DC#2@0P4gHz>@5|0`_5}XO*}0uYnD(_@ zW|a`#o3fyFo#_9s2W?k8yXnkF_;~jH)gHi@+FwQgP65vE#?I*hLR@TvhEZ$m_n)c=4^|gov3IJ1lj8k@xC~VhkMU`I;{n`?m=Av- z_|QBxAm#|f5Kc+~F$JGWY>6zRF5^yR%`!n)tzl#gveqmUh-e=Q!~sA=uUGFLnSyW7 z!WU8*(H{V!psQS#1g~;g3O>FEKI{c8e0&(dcd|XtdHjOm9RGqOXludFETG|H?kwnp z&2*roaQZEDP*Ffdbz=DHQ|E;4F|davo^_og=o-LY;15SeHSN3Q5}t@poV0|9_Z01w zXIDcYwFP3#!AX5Ci%WajY=^}3@$dKbXQgulGoHfzSqX*>X3iM0r$iC4>sikW{x3cM zUwS^*X!~8!^WuL!H$6kYXU%6fy&2XDcE$xvlHNz(7rl3lR9lv9JRvrmh8K_Um*8)L zztMDNjWeQH!?;wd8%CT*aV$_XmW!!DRjBLDB%8jl>&=|@Jb3kR$obwiInJXs6S?@r z>=>QSuLm;uHEeH+Uk_kf@7J90jQ$*&SXhtf_!M8O-rA*p&B;uDeGCKZB)>jn_%)oo z&=hfqBCxUc;ZyqX7`FJpoP;)KI0&x@>xJVWLP;$hprz5?YQFtvGN`7RTXiF zoyo_mT=DVSaaHTX$0b82H3eqo-!swspT9mky$ABk*^b5TBS8* zG=Ey_G@nDNNw^wIgbgnWUh;9!{llfy`3?wnix6ZrUBTbF#{WlYLbCTe%TO`3~ zNn>n%gn<@c6^UQB#CX%#A)f?`WUTT?cKfY7tio~vD+%x_d9#1zLgcWl;f*K*3gd;w zg{il)G4Xp(x+}q-HOI)6d$m&po(GnonN_- z^160Z;%;{rb*UNwbo`N=_B;G_Qpl_=Fx3K(v)Nm@Xw!jln2}k01@Dt!Ez&da$Pq{e zJe5Y_!YsTjIF$i78)}Tvh9RS=VmEK5I~VZFR)fLaie&58qoOI@@r)`h z3eu{~zyNNzCyyIt|0&gLQ$@1;%B(iZx2>76{P)nLS;`mvDc;*H7?Isq6H#acEk+KX1y;=vb z3<^14FJIqOo7nZI*^CBbT896Sq_Yv2jIri&*A}GG3Y5kOan}~6(pXKpt2Cn47Nycy zT)MgVDJiyoN{V@08REIbKEq_)m0_Ms?Q^M~Q6sEcXrFN{JIi{X4a)v3ckaycAniMA zzJ2S}+4&FoyG9~@#UgEcu9gXpA1!3SptOUQWkEfQxmu2)#|RoVCK)tw-QTbjBX&}7 zZZfmJO4mqh`eMp$G_DW)t&8fhE7hl8rHM$9KQUF;})svH%A9dUd_*%E2X*L9OW#~ z#N%|UprvN(ZG|ZuDl-l;s}@aj4Vd9FaEOd5tJUgrm`CQ^R?!0wxD-+bifiSNpyqVo zX9#sjR#6G-S_q|T<5~!RLP_U;p{xU2K||X5{&&)T>o};f)To|<44NzD#pZ@_8G$lr zH?3uqZfb;(pB3zCfbp5okVw4rgb7HhT#RZijSOC6yxF2GttzcWUXxl?WUGp#0E`*6 z=9W{Vq~jITRgI@4aF5oDEF4jrTASANB!7kf2~DfZ$(Y5TG=uY{@{6S|^#?vB@~ysZ zfA+3p_>cY%97UV&XW1V~`LB-r=p76li*ZqXpc72Tt4fTY>QdvUdZF=C?c&@zUR7ZH zS{E9>)6E&MjY__RE)_SDWinqqg z6ahDaO`qBNyIGQ!EO)Jq3g2n~2q`#mVi!T0+pcl5&RurGph|LxkxAs!Uaz?r*#z|j z_mV<6!@BBu897l37PgeF`mAD~_zh%4$ zM+XM+L^*(mB;8L8)(qkXiTxzJ*?;Z(#FEH*<4v9@&&0)z9*S4qQ>>>W@z(BMk(|5c zE5(v@7MePdfXp!6IBr}en%_pCZJ=D7J2TE&p3*C$1mCPeTXFil@iEe+m-J%*uv#V3 zac`F5bSrM4Hy+@Nh7r;I5m72y&^T8%#gL=Lv`c!>E#e#b%F`l@$-j+}=42Zr_>SFP zg#7MfN)kQdITG11F*az!Iv4w5wbZ4BffHpPCx_GBl~mH~lnW|yi}|=xw>9pSjDrLB z2El#kN{>Wt=DUXqA+HRddw*DNI2jliNKbmZhl$K51Ek>8ulyS4##FlmQ zKR8zz9@uc~_RD5!klKRweEROoZ2KqJBeIK%o4JfGUkSS0%CDNPHaf%4x*h0YOWOy}KttT{5rmF?3oajoM!s&YpGH5H zj{qpGo$|O{*}X4l-#ME`RjFnLjhx;(Drr4teRRw8pn>{Y(d3hIlEy^KT3XYhAEw0s zB9{y99NR?W(Hj#1*@YNIz5INZc_2&r@_WZN3lq zW%O~&FC)~RXIQ13`TOTQXvBbWm_`;Ai&a`{wrSRONq%`?G;_WT=7W#D9?Sn;udy7jFtUbvnSCs?BntO{GA|i1OApbI8Nr@G>WVT(EzovT#HX_5clX8d1kkIeJ zwBNmP2{-uEh`-D?H7B(byLJ(?9*H;fDiMEzc%S2CbdY(GxJvPteZn?u+(*(4Lvd{B zYRE@)dA!e|$;8Bn5ncRcZ0PV`hj)s274ClH8y|{Sg>~N_uL|jYG7cTJG@R6L7>;3S zEhqVL^Fui2<`pBg`N!7Z*hYq-ev_|dXVToujXfxagJBQ(?WwI|KgI_X!4Vy;m1KJLZ=99r$0`Ca*?Vt}-S zBXDsYX}8qCja#{sm(7)sknxei4-(a6sD(@PbZmo?jH`UvA)Vm2EK`p0hq5ikMlJlg z>%;uGq}ha-m#`tvaH6&3r}{(+$Kces0{=bWSPLElFL56oN}yv*;kx$}E$=KQ_N>+MxxGb~7TUQvu7kz^t9ojS%%J|*pE z>~>Rd!rNl90MEQF@E+F7>dEjO^OW*AlhS9Fz__l6W(Ma#A_hM?NH^P8og)U~mYw$g z?N-9DfhyrKF%}c(zieB&w#GKJVcHxJ5NlVYueSRfiJcG7G89I`{zHO zef>T2_}DqZscZUq2|izVzy;xO49lXtDW?gk%f( zE5ZZ{Shmw;Jb_drwtRpee*&4h@{XSLo&(2lz6{qf4zVHd77Y|6XZgdFGoRXVUrau^<7&nqn=6-Y`5mYEb=OjU z$q3Cv4ax6F@Jkx;E&oPVDKg(#N{04*{Awpxi#${{Bk>rUHMgCHx}Pm1{f509Uvj5( zy>_=!U%r^P@x0ZtQJ=k{Cn}JmQFjZFHos!@(DO|M6kK-HblL;j8y+FU^|a9-7Sp%p zpUT9>9M|3eaBjOU*B4O5%>dj>U>5-#$a?$bhDj7-qB(6{+FJZyk!W`5`;=`CJi=#Z~@NN30RKZ-sg*G4t`eh@34 zGl%F*aNk_c<@!o4TdJvdD?R91FjC#voa)AAwdDqo@NL@}mM)LV8^yuOt58y>JI2)ZojtszxPXFi-n3?&=Sut8tl z@Q7@lB&O1B1d_u81DnRQGDUT6vkNdH0Mmzt2^P97`m?Ik=InhUXgs7)tMT_EcL4P)!s%D0B~#*_&6 zQpPMu9;O15A&u|AV&mO`c6|3ZUm?k%EXbCLiFf4O^OvUZDbp}id7r#*$%E)htTXQCeDjl-Z2C{G3YTa+DA@2fV01Db)LBuQ*-bfI2mj@69 zkbP4|d4g0nd7T}9pVjsO>a9S~3=&yBs78?wmI#*2ly zb{}_ocG5u3*kYl2$uGMXB?Xs7Nx@}NQgB%$VFa)5u|HECJ)Jz^QYB;FE>Ki7qBk4Nn`XUP z-O6eqRgi_JZzwOff<&Wl>_3<)ADAPzq&>iGT_Y4EUufiGpW$A`PwwjuY&jIR~5pHjeq>A zqW(FD26UXY&?C$=d%)3&!&Qrjk-MDjX#%m}W z=CoobcxU1xpWxs$6#}R*P~5l5=Bj`w#>ptoi)0CM{1&qyFkzV&dYekTO{J>Wn0~zQ zxuoJh`b99{40${ypX!a|)1SOJyL@W;Gb7$cf7V{UJB%2tQv0*@G*$lM-jr#%At}C3 z)u)`Q@1}Fqr+C*r)uH~Z2g%%DF5ORL?MSKu$}{?nbcv&**CjU8;aFo#S}Jy?IQ%n>OPL<@P)ZY*r2*Fx9^(vf9(eyaHe2$o$~w&f^PKl zaf?Nf(H~3e5k2qAcvjE#XFU?637HQ&wGlBPyVU;T)-5Mz9=8 zN0q^t-)GC)|6a<=w&fi;@7>EgFmrhvkI@0s;A{SacQ3E^y_6Tg7k&@r9htd2qE`Xm zJYg;8k7j}@iLZf~zjxEy2exj4t`pTWmShPwS>9k~dD@H%y0pL)d8P9`%=8;v+<}|< zr>+rQk;I;>bEXZ0WO+lGE5*p;-xy%5sI0!+u36!z`Q?+dAC!PWTR< z7n*0ibYr(B6BpUUx2|OLRxAl}n1`k2!LL|u?zSJ2SGA|2RLa#2gvxQf+5%OTd~5kr zMJr8)D@44l5i`R#d$&Zqy6_CeCHG@o$f_)VM~>f@ws)dZsqbw5|NKmtwlK`XZ+~&q zEd18?r&x5;bRPZf8NNX?{wB96agTWw_O^#Mdvj>klu}GOyrNuD#@`Lh4sUw?-e>-|o_l}rKm9)Q|KX0?B*%K<+1W56deY&# z|LWpiD8;J^VZhUa>y$ZORXDNMlqtIL=2)YLDax_*%lGKh$e+)vsP64WeCeN)fR`pN z=F`P5F}Y*Ssl&WHomYQ<6kiNlnCK||#t-*g&rLp~000dO%uPefHW#%OBwqMawMCy6 zF?I9d@TG}^sWf&bHO1>gK0%GKB=ODE%Y1yg;M|39Q%4dz{$q|Qo?Mh)n%I%bHMgf3 znM*=JPzvuCCN@uduStID{i4Kwoc3Om_SE|&i7ThQ*CagkeraOz#$>Z>{hCy#-Y-x5 zY}$KGnp5w~6HiWiuSsy~eQn|!v%dGLHry0Oa>-8IJuQzWzMu?b`ZbB`QtvM@IC_xx zNx8H+GL^G6@yU11iNkKy*|npfB(|ymKVT&Yb1VU_H@C5c+&Xe&oVnsaaH4DcdVcQU zc|3St7(9>3$9buWYi$Wye*?BJK?6vxB~Y;7}iQ( zHSw!4wgRdOHJpMER9yPA9a;8@yqj7JD23)td!81*uA(^OJJt~4*Wu3X1?H{mmm`z>+ketBfNW#@0N?)FQdCKEuxG-*dTCwalzG2UX=u>fle|15d3jypCX+1n(y1!T>k{ita&1O( zZCzryN#2l=yrC}fF_NY7zjlq@Sh&~JB`!3%TQhRE)+OHf6Jen_Be}UQF{I?i;`+pk z=6wlE7Isd;*4V40HqlEmmGsZi!Zz^9AbyTYULN~GN#;L%WH#n6dYU1gWN`jt{845~ zOUKxcS(dgNGrK+s{x2g=X*uV(u753_9?y3U_GLSzt6k^d)7egApEDna>Cw@-(XqMF z@izuP*jqm3lhRVYz0w}(lTi{9ms5%u=Q^7!Txa9?&io6U`77MB|Zm5uY;b5ub8(M4K}NZjeP%a<{_zZwFCW zD&bg@b-RH#vwm}!MuBW2QC>!P&U8sE)n1k?VLzL3X&uc?$n7c$!tDQPdyA=-RdSyH znaFT%*E5s;Eg?Lw=CMJ?<8zb5BjCe4ww-u9I=O7)#Ewr8-PQkK*-zR(Q1<4I4|Bh$ zY?K{6>Zp^PUsfBwWA5fni+cnA^mwb+9z@bxy~RKAC?xN)A5b1Ds|dB9XY$A?=M<(b z{QcQk^V0W6(nL*@f$~!Fv2se@yyo!i1o}JdKROSyT6Gg4FPEg*Z|bnq3+&8-_BKQq zMP&!uKFmU}zQp&mG3%F?H2KTwIWCaK{hZ0peI%x-5+>Vc785MFh)b}jY`_#zo-CqX zMbt9Y$R6hpC+|rXkrJ!4f02$s;KAMiXk>!l;rdTGkM`$|uDdfo@E5+|H8kBD3fA5A zy3wC@)0Q>{9!X|zc0Sn)s9F8zEfL1A@b5RxftwgsV$9-e6Srl)$SCDfkJrRa@lMq4QX*k;j`7^QKg9 z$``U(WPLK~`|0>UUn|gT;hpFjlLHxp z_Dp}~d%tQ;E+I*}lf``h4HZ|O_%?U5EBbv6m>c`=>Eo7_?cHM(2=37?ySL{7UGI62 zYyUkDag}lQN?m-@?Usz9Bz|)e8Jlb4in5F?2pD)p#RtQAw2Eo_6JcM zh|oY32O>0tiO|6A`-)K4L3eHYKW@H`_2twq{@L|s$}5(BY7=AE8S2aEYqiL}4?NF5 zX)-ZVr&jRE?;o4x`~LDJpbe*OiY1&6=gjq1Eb+2aIMDaiOV~JtUY8P`ZkThsrO=07IpQ!u7cU^mc+-|iSo1YptT*jz z$UEgt+iV``)p&+G?Lxv?r#aA_4SuAcpgQzZ4D_u>rmFQDfZZmH0n}=pM&S@#`rE;(a}x6P7*I{y`Q}YZA{i8G2&l1E zLk+Vowl0)-`)AZrwFED*u{B@aW~4UU##S%oY_xGH^Ip|b4R(L63ASw~Uc@-e_ae2J zwKXQ*ZZwSV@Vy5C7q`sm%!4>@vstAq-5o7fSzzmPuL^f zjlBX^3kvx?NF1|Rd++zd08cngFnY5An!JzTkOjhyG7yEu5GU3?Aau3$AqGE?*H1XVPsH63^&1WGse7^M`rZ`mB zhGPvwsC3uYu2QbQHo4k=p*pS&`-fFybc`&u^?H4uE#$*0B!qI5zVAwC3uTmJ+uF5> z&j;U^y|d#ELBmR@pECS{Y_DO!-;fQ^C%6&JV~|^fvK4~k1pxM!=X*>2ZTX?xr@cEv z{#{`Y_xXHIs2Hj05;+Wd7|1=Csc^q)t(_f!x9lBf&)Y0zAsk$cvuK&XvT`MR4PPd) z>~5EB!@DDA8wkQyiWGZ^5l=1BC^JK@%V!t-=2+q)wl+61&Yk>UfvQ6QRySiV( z5n<>QtzvesP5f28X^fhZi~+!wPiS#(_vH4EfWE!33W6aaNgVVp_ct62u{cC7$H$z6 ziJ#Lv8=ff`dz(ygNvP>D19eq|;5y08tK>kO8jm>JGouXUP5OrzY2(iB*MxRw&)2w_ zZjgwa(F>>RX6c3hnBEKiF~a` zOZ_XoLZ1!A=8(TR>@|n|%@MCTLT6voeu~xOb(=ZU78gS@y-uer!SA%68Tbi$kOOe9 zX1px!?EW_RXma*!7k*~w*2A@Db!)O$KTNN_Yp163)B8+WQy9SF#UjCUYuPg$D5+os z6^Vt8@Yx0a)ePZ34>5i#%AVQwOD5P*(m7z-&lDAiKGpj;haoOhU;B<}&JjCq*66!D z77Bc4)+TnaimonC95|kjQR^KMVoCZJmK|w7e_3Ow zJ+wc2IOkKn{_|q2jfSr_8n%j0`-YRddfPwguSVemD~R*iTL`w-&fZw-#TsmG&7rw9 zN7`6HjQIDNW)NZ#9|cA7P&FAH>oASsmLaUW)`@~txx2wox>hK z62vHL53#0ka%6Sw2xVP64}JC3d?+?Dm#%e{0CQWrFaP-Y3Ow z+ea+BRcNsRO}$70>JtBN4V$N`ju1l;RVFfD1FPMSO zN`*20L91&L=(}^|p^y5@&9`Qt4|;dlflcU~6vLj4eztVo%^`P_v+L)gm)jyYxq*zd zZ%Y;_uYEJ|n>7G906Ly5ry?d#uj7Q-*+Yve=ZECULWG?3J5Km(zJsY)%|53TnXWY7 z_IJ(xsg2YjOqSvw#K`$WMQs!MRX*}u z*?7mFFp!F3k!}*q0`W6~OY4I{VDrayP}2SS_75+3(eHJtm?*V_Azj(Z(UsweyxW1;WxRi1xW3B07^L{-@v?RTWXLs+3Scs6Qar@`~| zwU$35A-NLBQo!t;0h;I_i3pDW&3q_00B|}et4qCb)$n2kbby%47cGlUFj&~9bAru2 zx(*C+P*LdlhYp^ z)hvMUvvabOAI|;SJaS@x!FA{MMM~=U6OLN5(4$NI?`w2Iu+Tq3SHWm%#$cM4F$(7T z+n4yu@AZ};Y6V>L42u+DwsZSZBUX3usRl=t-5-gL9j;o665o>kDpQB8yyMEi$uS=@ zMCRBbf>-OEZ>s)n3Dz|@BEqhsziOdC^H`APw__)k-+pX(PHZ(BWUmW}WZSlLf>r5x z)yO9P$`Tf~+VwKF!LnO!EoWaqYgd`^8h;b5D1kqZjLWCjBOr&V3C6_Jnt6}J zq6ZdGghEJ}Vm^b`IQ5v)uL(Q%UTunc+PU|W=Eo70g;nd1br&qx%0q{9&&B5L3(h?k z@S|%-Dc)d4=`Z@GEIR7!IjY|fQq{uTSp0{(u{x%oTWK;0jw%4I9y@A5ES z_G8@@SKHfW1?DqiKWV_c8qEaeL+QYr1CFESyP4rIT%G+j7v1Wq69lpB|00$>*>-+Y z!n5C}I(%xg8Yyqhu>*WD3DP^&oiszTG51e(+|3zdDkle=@jVk0{- zNqjqZ6e+3WHnD94uEZOakVaFXnD$YI8Uqf#T|$!!6(R@%E_hng>!`osX>(%OFjmpk z8!mf#+ev>_v9Q>=qr@=rA5xH!aEd8kBgb1+O#d8SRg&Gm%2aBA>G-{10~mQ?RWXcw zuKb5l!w_4H$Xsx9z}ohTwg=Wn2+7!~(pD9l6JUQ`v0wS>xWDGriM1Zi+-io(+DDu{ zoCsz4>j1bg_b3w&F|W#=c6R@Q51l>V<+^NLkrUI79s~7?Sm;OmGAq=#SJHO!Bd|qJ zf`KWf(NyR)j0BP)_$W8qm*Yq@8l)_}furDg!+2M8BD@0W zx~ySj+cByDmvE~hgDa;#@fWsYQ!6rIu>jJxR8uiA+l4WYI86W!Xvh9?&uCVJ2tG5w6rLfHTq z<44zBrCF5NcD=v41P381w%}F95QTFDhtA`Wd4any)yEdx05=1J-e8V(B!EOS;)YiX z^g&%|0AS^-g`^dA9S#lOk}P?M6l5jSfXY|HDy{2q&TyRpWOWI;=QK5B+s!^!{q&5b zY~K49`F~-SBjqIulXekq%sB5pEfI@Bn`Nae#%p(XI!~S&qO*-p_Se`hF2^C;Usgb1 z*G3Rape_bWy?{;gqeYoUKD>YqjaC%vu3 z?5uiQOZ;0%F6Gq4+q!V^>l_?-TbFuUm-wSd#IO5ICjF^|&k9dvCCyj@1-(1nNYm7w)*h8*Oyl|XM_~yD_xnq_0Z%c}0 zuGhHKSMZ2j@0KOrqRuD#$dJ|k2@avrlOjg=fHtA)krp|PNqm8wIwj+QgFWh4!$5s) z;yTi#YZSNvDbASP%=ucKtf}32)xxG&18(&i3i*>Gtg!zkWy`8AwyAi13>9LDZgMS` zU0>_9yNP0yq4eXmc3=e1pZR_G!Q{P<(9Ps#-G@DXx?C`j zzV53Tnp<;vZcW1J{xSKH_O6gG4({^1(Q17@mGH|tek$dcyg#{md84xFY|O?Ky(+r`vQV^@*qRIjfr?(C%`62jICxl5v~_HehE6**DfA*m1GE<)LTe3dMDZU zYF_DyVsy6z#}=_Q_uF$9&Rc4A?ozeX%g#J^q3;+;W`iB$J4)PL{V?)Y=>O`2a%oPfSG6>OGEQmVi~cb4VuTq zhYXs3kE9GVr}isn!*{jxX8f<3wfCbRo`2A{=vS)m9Y&u*}_obf5EuB zo_rW`%xbIl?|(qU<}@&OjT-mc$?o5;>uCvuDt55Bkz=Zh8q30d$%z+?)ttm;T5-PX>7y&+jO=V9b=Bj^D34O=Wq%i zoH;fw@oHJm+XcP}gyB^!_013F#at#c@9|xxD0`H-=qd3Wm>8kf3-A;!6IR$7f!JYd zRmGa|)*@*TYQ2=`pX_VmBW$6#J7Qtt;$w!4`m2_+F_6Wu8{0#iXytNrl7q~GYe%Q_LW%12{7B=^IMYliI1V6k&22a_w_y;StJ}f>#QD-x z;23K~7!@%5U)4;kMk50CgbFG}#*bkc%!V5gJ`Mn9?!m9U@s9UZ{UrRhN#bXc4?f0E z&$PieGloU6!n=RUzh6x9B{OupkKQ$BFmDUo)*k@om!hNH$CD{PeKY=z{p1{;YmbOc zoRa4s)yK~#JX30FD_;}vp6M79dKCL&WsM0aCnn;gJQ zLV*J>_x!FS1Z+EUd){KEOl_GEjhx4@nKObEHcyn2L+gSR6UjX904q781vVQ$a0RQa z3K$wjEo>XdENmOcfen32UTvI+8-T2t{q1-caigP)ajfGihBcKNT?t#c;$ACd3LLg? zuR3CYxdYvzOw|n2WI9PEqh9sap6WJX?@nE|ap~-&3A$@Y>n<@{_uavLFL$p;EGnh_ z6vRL2&&m$jm?NFM?ddsJ2`W)#=q~Q4yM~(X`?!0?yxi-Ng-1_MC0D(v4(@A$du?#v z5Zs%Cduwo)VTjlBIdj!cV^2?rt4hK))7&G$-3{&q!M!lJ7X|lX?p{xcx%P7H={Xd< zITGBD1^0pAej>OJ2KSS}eTci)bJ|?xYR2m+FxNtJUC0%@YJAAPv}!AZ@G^hi5v<)| z>v}pEp}tTS1YGOOpzp@tsBX!+@YI{i93?!LV1DEiyY_zQc>cMLgALc$4+nV%+1U2<^0+p~UnwjeVOv-tfcay%#GODJw z1r}a_HSKV&DzClud^6zG#2FMYW*%QVM+Cr>i!93flu?qB>pr($QtD^Wd~%mLrH7GI zahNE7VNzjnjo%E#%66;B);dHw8=8_;wLgotmQ#v@%2vKsoX`CB{p7rb6EvjLR>Fzq z*K|6ih`X&V${{bDty?IE7_{VT)}vnJ%fXZTNjbsKA2cfjkuORJ_1PB zIFfytAM!1m^k3TYI&WD+D3Gswv$={^VqVfdxngt$t@grE6oW~rl--vvRf0Wq$Uy>dr(s-SQ*v$0THL{fb0zO|+ zE4hy!>#jGmNgr#_+vjmK19@ss)$;B8i(yQFXWsBL?affy8#qPq#<}X=eLUUHN!JA9 zN2#5kexz8S8y`1Tu?qN&N+a%don3!M{_bAF50f2u?%%*OXuSUcDiEMR+iIeH@$iji z`|=wP+LwR8yGIA?hZ;PsUp-+T91`zaL|xUE{gVE|eGz#%y1<-hVb=wj*1=aSp)Lg~0pxzaOwOduSWe z)kkCZs?gZ|qLP>?xHfSu&cY#ACa+|Vw-smPy92eO-^<7(>?@NX3kp-F?2Jsp1RKLa zCR496okYDLsA~iV#gK-Tre2nG)qrE+G09jb;?Ysv2jkHZ-A|q^a}8LT>%>_y*Wg() z*U5C5D_z*N^KBhKnAxq-|F&j77{lV@(rwbvfIoD-gUW;zbll%3?-#(7dq=qI@V|NO z;n*#O!*c^$irD8$)a`54s-%-!!;8gB8C04mlK!1r6af7h@t8UA99ATJvq!4{?qB(V zy??#e+~2*3^J6Qpy%H}|DR)}x`^q+Z|9Yn-BuxtE1cXv@rb=0AIEnieE%zw(>*bcS zyq8jRLMgl*4DCFe1wzXj!p`njIG=&eEQ-rs=(vtT)36y^Uf=i2Ik7d47|Lp4uuhG+ z9H8H|$~@}x53bnRCw!H?uYa-#v_nbZq66~o-n+!!sY2`>88G;K+p?sfWvj)}L2b78RaH0q%=@DIWUSIH<-A?k5&e}XxYY8>{k%ABGxMZz;fXLY#Lo}& zS5XX!fjsxKqGTojJv6Ugq!f5^zQhH0^%5VoUOP9`mGGVr^X}*AgcfZIwv^fweQBf# zclA>5&O*QAw0CC_ubA77`|Troo$xyn-krsM$B1_)CArtD7t7U)n*=dnw&3=gl3Eb@ z8i_9dnUUK?NseGY8zbp4K#-kqADs%cJZHLu;~gC({;E-l2b{r_N=7q7QnUOV{$n+T$rnu-1u zrDUt>YZnLfmmm}XleF3-pA?i)C?mlrZacXPr9y> zc&has{Kw%iaejrH_~|FoLh+jSv&yeJBjMDGkQ(I*2|&n7BH);)8Tt|bHTw}~P7QwJ z^+T18e?(Z6eJ&Es5r|c8{|-OjV7xgu|HQdS0v=SG&t3*1FD3=?ywU6n-JiXBYVh`t z(r0<4=9D4BrF9_4jyR7z>BRx(@Brn~kkQtyD=VT-aPc-K(Y(N3*moWs4`k2uYaGvAr!;(l>&roex2>S)K{ z?^C^~=$c>lB79@+t1Tz$$%+@!?Ci7!x9tzI7-V5|9CqfP^*-W%N#i3IvJD+n_qE9S z!g!mTt-ngOk@F>Ov@}vZhG$puaTPl5_YYqbs~N8&qTEn@W8!C5N&Nt8hEdrR>@@Ij zCQXfzqpt#3 zg_t^A)Bd{Ny=e#=(Tnp~R8w&}cS2eOUlpEZgWj@^eyQ0nWIm>>j|*g>@8)UfI|=&2 zlGniB{%QE5ubN-B_*1vtplBCHG(DM)zX2iaQ2Mm{>lzae zePT{lvZA@MQ$ytTr@kK`Q>Bekp5eK)-W?={)hbbp|0qtb=iM{W;)%MveBNx zb~?%XJJt57F){ra`bMl(9!a!*KfBEw@oJ~rkhD{p6ZBTsOca6yIT!zUV;acq@Fa6VGOqcf^&czscVX@(DUXCTi#r{3&Pz~sOHrNX% z2RN(m^;RwEU$vCu++|gRc(b3WT-n+lcX!u)Cg`=+k#AV+GP< zMm0AKEj(PJ-+TS9sD^kbCwCxzdF;+baqM3E%Hu7E;w!@OmLu^Mk$B6oAX8CLiu#I{ElV%F(8UNFw*bZbO1cF|aZvgb;>=saJNESHQn;^L72N22;nObp8_^9#u_EdYUyxiNtgUgr$FDAz|%fY9`9wbawx5P!pJcY?oEf zB&uLCvXV{D(GN*uiWRtwX;QlL*ax5_^$~=O#Yd4w=HUT?*~ftts1bd2!xaCC@2siB z<;I*YQk(FTr6sK;=xJlsgN*?;Fwb@T9_J1(QuTu8#sBc~ntP-TlPyX=&lH|*O8({9 zrY(jhX5;7R=P=U$_s5`syNS+SOaCuEe%P4GXM1K$d0!D1^1dR86T5_vh-HsSsu>lE zqB`O@O4h}epf=HzEH~6|UMrpQlHi*(&j7XfI2bHurW;6M(KjOy zB<8{=4etJ#$q-d2Bmf}#p>5(w*Cgy`n1Z|dC;I*+XB7$qxs$n?%h9>(7(to(zp7fu zE$shLW12T6;*f$`*yW6NuL)#oM5Xx=$k2_rnR(Z^pBL6Do7Y z?8Wad`=Z=~@JX=jbT1RfLUHcy2)NpSqBFXU3(P83+J&Pu9AieyZhhMCmXD_zVbsO7 zWVCAH!a}IYIpgjjpBlbs;}_HJ$f$kz+s%#vSedxFRLfPmOnLOmvUFDC={&{~PxV z+bEekKIg<#7Q_FN)bM}$14d1j4HJ!hyT6d6YKA|89@HB2v*iHeDXPAv{go6!7bOXr zw%lXNOsD3Wi6k`}a<$aF**D-yC^0mS2{v0bhy?SqCe549j!2e?VEG?D!l{@Jh!gO& zEjaVpsL~$*`?^0E+#d?=`-1y#bB?Le)x6 z!BOX4EpO556Exa`Q0axcyief^=AA0^|4JZp;#0D^z+Hd zRXd;R1&d|o)wVO&ZSKEEne=bdQ=UoPyy+=_=P?qd2BgN%_an$`qO)v2Yvbh5ex%Lh z@hSeRY?zYd7pDKiUd8etkMXC&*MV8rQ~lW|G8h2)Q~rqb#`wSNnB5@&nw`n248h4Z z+nvN=LtVrGrTQD>#~9D3ZrQ-C1+vq39{ptQXxR&!ZkuCa|5R_P1r}aL@LU{jV47(B zD5u`azf^zL$ux+`4~I%z^7}cd0WtR+;0F5%^j|E*(eD@Ke|*Ev>Wv4J4AGQ_Z=qD5 zEll>=r(QmHp9#ohw@m>|c}BiR8e|flKt;9@MJF8jG{u(mGBSPaz;}ddV@n?Q*L=I$ z|0KGaL3nbzJDP+m$we%F{GLJj(0{(~S2?dAfBj%~TZxM5G>HSLMCbLs+_umoL^T(h z!&yRe#$umBRUTHLJ5Q^;hRFKA&DLQ>Ue|bD`(^$Ym701!?^C@d08ogz{r z74i{!RU9^;IvkT~>433e8}g#30cSc*<8YYewv8c6QKrozAvAhY8rQI2BU zu9L>0l2~#kKKP*a+1;)?!`c4rVgFJsLu>iG9yP3lQ7G1akyX`w%C8TvHycTDOoGwe zsfbu1Sxi72p&}AwOm>&S@9OfdyQtPl8yg}JHbx}QzICyg&jONBe^ivyB`8Uh__b^` z+FHo7Wnr{>BKMk zB0{={legCUCOjT^fl)sX3RF?B)BB=wO%ko>_DcLfmyO^0At=##6gTT-Z{8+n0N!Pj zRrq*SUka&Bi9arf=c0eOEG|GoOo+4(Q|6~CFN46-Ih=N0pHjJo~$g{TjksLcWj^q^=iUZVPqzom8|Hot63X31@wMEluBSp>@04J%6b+Yq(Qwyz$+mygPViX- z>sM4}(Bs+9Q?V9A-(7bh(PH9RfNw*6DEg_uG$!oZc*PQC`{$U3Ruw7W8LG0$FcpPb zCM;jdSP@HEsBX1FIhm{hpd0Z2(5F1=@`m@fd%P@b@kL z4)8}{tuHG`7C-kh68&iB;-d@toL#pv=Bu6V+bG44?(RyUee)0-NZX$ z7cHxK%-M6T$@c^2(dZ+&(Qj#i*0A6CUf6lG=0I-E{!%Qj6T4fqZz^s z$D?~s9*q;Ax;OrGeB+_`Gx3c_obOd<>#I}578-n-Yk2V3sSpQ|?3xTdgOa+!TB9zq zanSi*c@FiF({+DJPB;A1XHK#|68!Zfr-)FZ3 zSA}TH)8e7}xE8sn#%v@uY(m{MtrgEOVv~KLD5TR{;r|wmzvFGcdB_Xwj1_xX?T#!#s{Wo4gw@)K7Vo^Xmlt{ zxYOtnP9u%Nz#)FH*PVZJuhWD=V*bs?lEg=T2#@QUeD>npCHZum&M?(>_L=SHC-lil zo;b`w2{cKL`qnkyx_ql|+yc#v<)J!;V6XkUHZgki3#EV^b{s6$v`hB83C9|TU1y)B$hQyY+qm?a)AM^kf!R02858uXy zIJo$;02l5|Jz%Rnt!n36M5I^h#b z6x3Og#lJ~^G3I4uoJ#DThZVceD9MEP&Lq5yn$4StXndfRlz2-hzOo?R5{|DdjJHJM zD~qm+x46?b)nOfZ`&o8ihRa+~&Y?|yIr?e&QbXzm!M071ZUHYxY~sp=po? zT&~fPTV0q+6PV>%m#mgw8vAV#kE}x~itQs1Qxwl7_8H(QhUZfIOgkuo=Y{rpp`KO$ zbZ}JvbZ}JvbZ}JvG;j!9kO>d1%7BL!WxzvgGT>1oro*F#OovB}nGTN{G##GeY4DUx zgQs*FJPXs{ncD3=hy4R}&Xc|k_kh5T+f~Hf^j1C^nKtUXF}G=qH1W*QcfMRV<*$Ms z@|%p}KuDIw_5IO-fc&N@HZ_cp(`Ymq_p>$QJT^>d_89Up-^Wb?K0a}jz;4kfyO%yj zn(SNQHTp8N8$Fy$uM6Yyz-x*m4y}+Gk~NS^-{hTneZ&f*Yeae~_Jg!BAbOT^*ZFKF zp*R3-TV=STQaU)?%dO98J!pQR(DjtvPf9yq!Wk6o*RR|e91Bd}FZ8o{&1y1PrOi)* zkACp#Sg0rYVrqQFqbL%ve;x@I(eY>jOXylaBgkWI;=V7<3C>#diQo_RO}8x9&}zd`5lAo^uPR?9*P#Y6P(4brlZ3RP<#yU5ABfiBtrnVz@#`I(O{hSkVO zDJ$NMhBvwy&Q+*^+;caw&J;KZJd2N(RTXS<9-7+d7c+b0Q}0an&)E-r{Z-{mLh=Hg zL^JK(c;?=J%S-OVkl$ZhZgaDDP{88XMn$M9uT5+wADpm%&aPujd(Un+A02Wx5XKAgDq& zvo}y)`Ktu)iSVqeiLK?o`msqNY_(*ns3VE9y`Q4-H!zKW88=5 z$KP^FW2(|bPX4RY<|QGRxkLP@TN1Iw6rr8E*8c7xUI?`cwz#7h<+ecDym(Ve+Cy-F!=n=LJfT=A1Vs_%WvbT#mukT#+X5CorR!2@jvGn zg+KEWg+J#iLj|{gFqe+C6ycQa`UgSlcdHv2(n0&bE7VEOZ|(YZr)Qof2jqP7L1~ej z%xBv9nL(hl(Pwi8eHw)nLzk`(5s5Us?qgh0Qr{z!Hj$EMhuAeZe+E#bL$?~8OJ4U$ z%r==99Q3|CU_O0J*C$lK@EpHePr2QC!;+!d&)e&p zBt)$ht&-iQC?%PKUX0QnLE*-q`Iu3lrM|+p_Cha10cFvw!@49 zBpPH%-Uh9_Vh7<6X!xe34|5G@MdhT?ib*A>DL+Lke@)PLb@va*EL!ouEUd(0dL~k- zCH~sPj*z7lB}>mXCLA)cM{d7Jy2iT=nV@4vQJi_%wtrVBouU$p;IdKQbgHxaAqb?) z^o2VaQwKTS*NJ?B;k)58so`r+V#QCJ(OMS)6r%MFC+nY6nveDtLCeaixT;o{4Z1Wd zQ=l5YwpA(D>+(5Wws2YdXFXM0(|EB6b!PhP8re3rnaSjBm_DHJ{jR+7KX%Z;dm$L^ z0_uE|Mh)|ZI4RS8EVzfs3smz0sPazR^t?oH9|`WG!F@cqPXzZY^L>6^D7Z)LeV!XU z7X5rnETREdB?oVt7yoEe;+3W( z!Pyylrt{pW+$b6Sg`H;!Fp^(__Y$+tdXK!7ocax4wE3svwa--Fl)MyYC%W17R~LBI z#Eq5GiN1fg_wN?p<^7&dSA-Tb%Wl5{Ea<4s|HEaUY@5gWI8-*UZOkmv{DaB0nr+|g z=h=&U4_Dv>nbU9+wEL;Et&*oU} zuc4x9v?Ip45}j+E?i;k%*D*@d@!Nbg$_M@%egBk~`@({L0u(%4LL=p$wGdqg%30*h z&xuzqa^s(I{mMlJ-U_@87Iz&iUf2H8rp?LY@nh5RZTpkKmOL)M_BWD&Q}KfA``SdM zNe+}S%*e8r;uVyU&oFe zM!$v>48wXkSb_`A%w*>Ue%m&}*WHh`=1?>iZ8-9k-|tp@RBV`M)?0z))BGA(zbn@7 z);z*R4i#i**>Z@x4DWim%lq$jVIHn%rabQOedDd+s8jkf?}vDV!OKXK2?PLj*I^vr zQI>Xsh#0TVOFU6PX-*fig z$iJ8+SE~PtJ~!2W1HrrW{tL=u%3P@ZVsQq%e(B7)7I%ctCQjn2o7$}~6VO*(Xno`2 z-X{Mo^un(cL;Vn59)t0->_crAl;iahpTc9qIsSOrTOF54kEwO z7C7_Wc;%}_rV_E#ZDA%BhQf)#XjYo=4E(}J8ZXb5p+Wf*x=6K?pzc!J1iZRb%Ho;p-4K_f< zCU6fidBzjn=6(Awf&vJZlb3stvyTB~QHIY=D}dy#L~*SuzzgqqqB1Cepgheg1how8 zMe_VLZUH0#fG%B}dys%m{HZi%>B|4G40o^@-;p;9`R8Q!)IEcGLNE|w4#{a zL0{Kf7+d^=>C(ywZ(?9!WdwdDx(-fQ>EKN?OIO4y@HTGzoqHX)j2n+tV}*!zFLp7{ zfoZnfBo|~GxnNB;)AdlS8pr3z1vW=zc5!S?wv`JcO%gw;zzfH!rKxhI^3;s@9b-XB zhz2UD$TuYw$Ep{ma$!B6%(c+wLOf7?<;VutINhI9hvAQocA^>A5yn&cA4^sk5NSQV zpkv(Hk5C?=nI#`+J2Z|mgw7Z$-2#sM5DqwWK|W9+n3S1uo-!P&{}pJJ8B%g+8Ra!J z54UrMA$h{+WYa(IQ-AGOXUqWqcj>Qv&ynjDBO6HD#FbNeg&}VTLiv`WORi@zDU^>84rfh8h1fjg7vRFhn1OIRnvV z=bNYu%q3u$+=Q*cSO)LiQ&t$H=Jrh?AAUH~czVzl{hr6)mUHAc_$o5^vJrlq?r$&v zo0vhuXl{Wg4Y_fWvT%tYgO;<#=bOJg=lC4q?Ck9EnMjS#)3c9HsYL(o$0s9i^xenj z>8V`j7@r(AFh0kG8Z$meQsZ;%{f^J^Y2))d|1|sfM9ZGZA-5`mT~YEZAbo8+6%lE+ z`H^@?x8Wl?+!XJ{;Z9!L`J7iCHOk;0Cl#SFF86C}HJm&W@B8V~KkbXZrJZD>ur-9X z*5F!Htd0yXXJVRjYB0!*8=n)I`Qr^I$cbQTwC$8rECi>3g;IMNjwqi-{(Kmh~ANUi}pR?rm zf!LayW4UW#vvz(E<3DM(5rWy_O3XEthjrsJ=7(ix%@21*nA0vNOC(v$d8WgPOew*l zQSTRKzF%(MYfhNN@W1%GkfnG#?1=?qqdl{*$5 zcZa=3e9WH+W`!w=yT}@hnMJYdU!$!QKRO7-p;$L)!cA4DNfJ*vr42HRtQn;D_WY$V zzDlzdE(H$G>cK#Cq(hT;$zsU$k4$;UI&QTqk*>4|gW1A(Wtc8<{kE_XmRBiABcd{^ zr~EZxOlHD@8Le&(_ASge3b9QQipkr@ZYcRyxPK?EXX1Rxa+EW&kJ&S_pE!qqa`Zs4 z+A>L$D#J@!vz-lxY=bys#5cyUgNyCfADI)MgmXB-LE0#buzzK<8NV0gky9M_khFE% zO@j~jk>?Dl3G19c=0r4ki0jaOvxdu2hSAaCJmbI=m*!>9JFy0vela4Z zlyg7y^I#aI*(wIZXqbB7&5 z;|!tSX8eR-&KN(;2n-=ChVM3FG4sAg(po0_JjQk#<0ng^TDg(b$|b0AT+a9rHRAl^ z&2m&z89@Y7L+iGh{)1gDnKoRxVLY}jV9DvUG`2UzkKOmckKJSM z%8%W5;m7Vd`0*4m;c=?$#g3&&>V_AOi5Ca^ckf9)&*aAkewsl~h94VdJoH|eF_ff< zHF|>hDaG*Q6XMC;htk4tq;FwZ=flC?#&@!NYTcgN60f@0BbIb^Q4cZHtCx7y3%%+^ z_A4uoE=jHjmufv2w(xqJmYBd!o0fQ+7J8c&;bNR9&-qI?l_=9Ir%a=~O~r?+h|RL8 z*xOX-Z7PCFZ6>p_{9!T^%gX61OS=K_Zu>Da$;(^LQL5HA95}3B{1P&7lfiGYW=tE) z-rm->{kk-3g?SXkx-%7^ZKCYQ+b=D9hOnAJOv}YDF{@~|=~tO8JAE=}KDKN&>ZEpDmR7QUD2>CWFK6SR1r(JRZxKeevDlf`j8#hV7C@q-kUqQ;7Bf( zjN!#EiGgm7_?3Sg$$cZOD_?sl+2F4JlF1s&3X`a9f11rUtuZrf_k#M&W`Mj)Tn`uY z*A;k}y%9@@5Dx_OSzAO)@)_8AKS!yX|iuNL^(gmF2g7O4|$uz3Ou=44R~rge%7&zpG1+66f3$6 zEMaw4YVnl&Mx*~s;vT6Ht}XDcE$|x)z4e9u+9L1TBEPZNTVD*ix}Flo+K&cEN^Qp2 z{y>fB{k3w)NXc!V@Skf;+=1hHp<|svN0ULvI-!GFi01?&D=|bg z*4|G+9G$I{wab{XZ@DcXJyeJK+AdzY)@_?xo4DQ79zq6M8?j#ooReA0TMK3d{>q4r zjeNI_OTNN>)4x*3Oel_xA705)ePzVEEj>1}p|=9YBPbwXJL5tTQ(x#aC6Fe(S z>{yHM(e^%DTvYfTHTa&PVc|45*bKA{Bgh}dE?oATj>_2b*K;TRpMce$ssV4bE#x48LzhIg(T0!sbp@Ds`E`ah)D?M+tfh)U zu)@E9$LqKonJHR>x8P+rIZ^VjwlmS0Khz9&jW8$ldu{+anr+kfg;afv~6#8Ce>HDQ5 zzLB;nKNQJ*ErY(l@`4%BmcGrGXVLcrOiafBCVnpS?|-tNB!lOIYz5C(mWm?}^iGS^Xa-74Ai*~8uAJY`#zqyoCs|FSaq-6oSUedD~j`|H#bTYgdZ@znNU zXIZ}!yO(4*)dzGSfa|8Z#D#!jO8^`jIgB%>Pb@y_A5O}9^4;=;z+qWPxYkbg;gjB* zUP)H2*Yr(c=M#bs%lg{Hp%YUjhXSa*@0w;kAz1xxndzaq^70h$>R#-E^I9B{dRUwtbku$w`;CpbB%EA ziItk)CFWXWuEi#Ox%s`+To;+^60Xbe3%IL|&i1-{Ok$_G?&2Ej-o~{jwwr5hESBI_ zmxw@?i+jQ5;qC}9Wqbc0bMFIRWp(9yC**)fJo-GxMvEG2rs6amX`w<#GNEli6A%fU z0QsjO*r}p*YD+b-s3f8x$m7Fdn0K0vGb8tU@2l_S-mC90^Gc~GBe z3fj^RRpf1-uQ9al1s&x)4?EQixHdPNSqd}Rv5D1nZ6VK;X`9J$AUd7;kuQ@!#+Vwa-yTL z0z*kN&bihzIS{%|rEPQ#4GR|$i7K$ifjxHEDN7sHw!I9F=60pij|=DN&S8F5_H;bW z4+!7OPtdX3{yxdyOg;R3@E4&y8eGk(-?WOz1WQW3yy|p1rc#G)oR;_M0Pc0%5GHFs zi*KiD)cX6@i;h1X3JvybQ=QgSr#027rueFG(>{k3KKuKz&oru8RCF3ixXyJ7EqqqK zX`kpQuldSUW~=+`_#XhY%?~+R12+&mSXW|33T8#a0T77)l?T?ZY5CRMw|YAIsVzU)^Bg}xXCHri zIv%Im{GfA}YVUl+{yxfIOwgt2c_Zn0dn9Ru(}cQBq&pJg_Vq`I3(oJUjO7RC2em^% z?Qna~%=Y&Z8(%tpd)vp;iIIY53Z4-=x|hrr8Hh(hR z`7l?qqE91PB5`S0Kp+VJCSxyeK!A+sQC(Jz8E!w&TmgPIY^37qM5}em=atJlyB2`*#XF z;tIy584>)n-3TbJw?ng1pm^Jt=uB%`)D;?Gps1ND72-PY-j0-zSGB3Vr*K?6`LJ`o z;nt=$@qIeQcnKk$Uhp8f^MZYiR37a65`TNGk7_9BxP|*@zk^n=7X3qcSa~vB_NVb6 zJ(G83`@N3;MoI9Nqd7OuA$O~7?p0CPpN>VTpi`NTa)kUeV|CJw=NE@*;hF{LoUm=k zr6)JYq8%_6u^~55qd<{0c9v5`uzV;qID0CG8L?A>y>^K3MtnXc zh>vhv7Y}W1@=89D2L`>8%Y1$c4q*uIFLF|Z57oF-bUoeZ;y9CXh@Tfn zPp1%a5>=t=#YwN{B+B7F*8|*+ChY~DnD_xT9E{+Vzu1()j|>v1v?(@7I8%jRPc|iv zPs0ksOR&sV>aJ*JUsE351w~H}mk$LUqOXXa@+}H-Z*JI43^?^a&T*O+O%8V0-%fX} zewub(jte{WEZcjOBO_^5-||>!GsUaB_$aZc%MCO9#H*Mzsy^aASgwl6rFnf4C$4`NDF zl_$)_%1rt9jKU*1ZltXw>{R_>&*d+NFO11^XN3!<1oOjvYPG#9c1_Fi_I)a+h)5l} zNM)v3O{6Z~6dG7~^Q$6zn~v=@GWHiO4y+Cbd0D4HZczwuyWe~_i-GxSB|DD5FtYI> zL+i3@JZ%2fFe`8@^T4x_L~Is{prZ&J1f4VZi-C?iYf|27c^oU!cO7~fT^d?9E$)5D(`y4e?XRdfAhZ@@V zsk%H?q)s6>OzT#JL9JPO*vO+SJ*bfA!Ah^pywzu{&M~#H)97`NRb+$_7!;w6TWBq1 zi6lN^{V@LeDebpQ6I+_kC`X_8*74uN#C$P33ZeLO!GbLW>sT7#MHCBM0~MP_O!V`D zKLP(n8u+et+$_*aZGgS)hcT=&h&>(id72*}h6t6j?C%`@YCLxEwSK=I{XP`^KFlvG zR;l5_+kO%uU&R`EOz`d~o@vyn*bYa4IH5%}xWH&oteFSO)|2d59o-le)N=zBsBy2} zR!20TwoyT%*HgF3m7>hv{so%OuE(5HoX0Y0(#gv5utx&(qM~4rmOU&jEGu1)vt?_k>4&xI2~(svDGA|o#B+do(w(O{4kK{@-z z#b$4|(K)Tj`J>?;^(L}9e+sMfr;5#6Imat$eS-abky)L`Dw{UW@VtIjRAg4c#3Ez1 z=M`SZ7ii1%H{2sYLz|4~X+GzKEptmN=!SK^36?H$s769kNZ#>oYS_83WG-(Rh^p&Q zH~5C_u&C0uM|-|IhTT}yK&O_C&<56RY_n8s6$CbfVYe(`_Mo{qlJfunlIvJ+JclFJ z@}9JTTj(f1H1Vn2(U%#p{~JYFvB?d;smx8BZ8LeqHs4KI22m`|E9n$rMl^nZAvCSy z8vfPyi@bGcgG&*&j&5*StjnjfYx+3Jq~v0WGr0mv!`YH^vZ)k;s$D_Vqd~e^6CwR2 z`@5dM5@6D4LBY1}7JJ~t7|tA(fG&vwbKgR`NcB`ao*%S4(qqyIUJN#;GOxZ$Pdii} zLEPkdVX0WHnfC6&z=^^(1;UC)%TBg@4Pqf|^dl44D!x=}jGY!n>~v-3CkJxbY3n0- zxaQXE3acJDJr`}=MRew{Vpl5jP5U_JVBQt9J{q<>5?1X>$Nm;NneL`gI6a7monl>; zaEg{k*gt&A$?jokXrxRJc9fRN|F^r?{C|^zB{*3YO2)1e$ zWA5XT{A+VKZ2lA^DM&aHQu--x^Y`I3kraK>fjDzPlWTGu_(@&;@4qm>2E0 zakOeo=eDB6$%io1n|muZft?6Fj)5^ipANq^r=Vq%>tSu?FOLBfN<@QLZmR4tm&{Na z;#5(%97|)dRqo!mS8STu-Zk^%PK>foS{|nxNUEzwTQf!(?Z>PsW~Ks;HP4X#pMB9WI_DZnf#&d3nv}q%xn)`L1{I9QBRaA*7n@zqM{& zuy{^jcnwz|W}z{?j~P*tu6bD7>c9C3B`bD^RZoUBd)ZBg6;DSS>Ppwg{JIjl$!w$1 zb2shVz?E%{db*svP_c^_Y?Zzl-Dg=NWgTne-CwzPbDx=?)}VSF1fgTO(S|X*G@qTb zJ{lQ@oo+w*XJOjOW&)g(tViK$nRf{08&)zPj7(9`F(l?^G!%56(lut7^=v0gg7mO% zm(N1a1mGwbpr^~A7!(+}yUHvvO?R)5u||pM`M)<}ntRA7X?a9RWzV+V0IPMA%ux|l zUJO=ewYm7uK4p&|mGK}B^&MJq%_=yZ%;A`%+0_0(0`@;AU=w!dwuH6!vF zl~-IN82qHbsx#}+e1lS633Wy|t|rr$MQ5iC$;mWx0yQnN$s3mis`AG zmmgHl36{?SfN;5y%!2y}az8KKDdevVU&!1=U|zIS!fo1+-zhu?i|MQI9MDmEx!C%5 zf;S`T3U>9*liY|}wtmM^kBQ-M1dVl>$&ubRRh3=0q(4t(u5+d5K2vJ@D6iu$r3`ZF zoomW77ZGI2ZCo5FUhax>>KRxvt)g(#m$|oRXWyDi@P|LNo>0)y_YhY`-#^{wAhc7d zyYrhePhT>6;AFi08sU_XyXCPnD9BSq#Z@2TvDkrof_M0`Y{{_`WCWqeq&E%yR z^Jw7xX#7#Vk#+l)sP)`)CRvL`Z~jRtpZt5h(f4;>nzfFP9(&1t{#2z&hr(6GoL54$XoLq5^KaG6Tl0zb zm#%qaC4z;Gx{A(I0mw^Kuu^e7Y*OgWe7%`Z7_OwPdZ4o`TY+ zh>*xD!-}tmRlCEQCs}5z_6CIWta+OAFCmHIIh;`Oa9DFtZ~E-bbM~g+-aLfYpxzAF zn|ZCh*4#JMJn&OG zpv6;5=-R-?_1q+@IdA8z__T-dD|6XCW=ci8ciIp%){CZ>;SaG~rZ{01)UGDJP_VpS zhd_v!RokdHH`tpS?aeLr#t&-0q&MsBO|!k(py_nR^|iYgo_45nkBM z25v|+SpU+U#36d|YYE%>9Y4pL-GAp9ds$5Lv|goCQyDVlOM1~{&!rp(i-k+&nd4|& zI0Al)XYAL`eXA~LA)_w)zRyYzO#GKFa%2{FXQq^s|5JwQicp(2q0h_Cpg8e$S zo&F!sPzQv3M?=h}|L5OCl`wG{gbO91^cU81 z_g&pYFK%#I)vMtj_4uVLXQfyN*9xcXSFh*6zcP15S;g4g8J$jWkK8{0k-B_$OkKX) z#%xi7R8%OxM@YVA`E=X~EI7FMA$^8LZXkzOod3$+HO4?~o{othr;WcMPTD~48XHy% zG4!oboYVgzldAEHdRD!Gt7<*_JiTrFyy#{j%%pK)if}Td%Jxcr1s2}qe?R}3%a%kO zEH?axYbCI+I!4ql?vHTKJSXe~G6ugbNAYl$NRUA!fLH1j-8z9>KqK6`P;**4lU2%5 zsRBbs`uI^EL#{Y~QgO=??RGROP+V+2iGVhoB~npDubByq_9X-)W(>)jc)QDKdX z$>p#16T;6m!jI9+7jNO}i^4^N@MAdpyzujRJrgxFR*%CEuGVh=1Sk5yMZ>RoFMQDN znxg^6Dk1G(T)JDq%1LaKD^BqHFDfUzff_C=GGUFd78o-ABe5jxi2E9b3|(M}5dK&B zfge_{matY`PqX1QjU0B4@R#d)n6o~+VaVJj#Ev1Y^T!P>kQ?Z6F;)Hr&Kb z!*Tv2sWplkYIJr&MAUear|yLd-r%YO*VgKehHxf>GZ3#NHDkpEFgGmLfRuX27lf7P*09v@!ey?t->}4j-FiE`o zFG6-?<@WABmy7J_SVCLgn6B83tUauwAr?%NMyj5qma2nXNhR5+@G-vd)_4|%E3 zkDgD>tm*-sW?2w`k;QrUXH~7qJ`*Q~ZO7)eBR~*53sAdeGCoTP4D&G823S8{M zES;`Q#3RP}aKC=f?r{;zCg%tDop_qXTwZ`z3D$C-KgImtpu6=Y{8>`SpIaadKQ)6t z^)ms@6qfv&t^!;@Ey;>ayj4)tM)m+n%gDOtMreQs;=mm#cWp%>>mGXB!Y4+5N{F5` zs{*CJ&-KnFt~Bfg7T&Oif{lX~>#xDsjmn!=iRVO3YkZlu+kidUO8tJ@46lUYX?WA^ zQ2HQNvAilxW9>(Dvz%@vTc74ipCr@EtigsMioo~kU(gHQY6WpSdS_dWUbPkT*g3eE zR*buyrZqP0Ge^6eZWzemZOImm@trfdYVG2xt%PJmK@ABTj_}CLQ~d6!Hwcogfa|NA z!NPh~5BhvP6)d`GY#H`S;G)z^qEDW`T7V>(UhS}R_9o^mJb6MYO_knZBkVsR4ZD5ql?a~4q$1}2M*{F69w>-Q^0s4Ct2%DV zaB&k=H~y2V*WEAquO=W`-^%eI+4=}~t(4PZWkQ0A9R(VZ5KCMAKX|hVBXj?MhtpFu z9TB(mH~te5)!#<@$*N6y9Z_@D*NG2ffF^I+%yR<=up4gLM7~viwXii3{6M0MmdT$9 zW+6t!8-B_~3ngscG};~SOb+Y=B&b@;R{be&HiU8iIS#ZI5a3r?Ymd5$Z=-YSva9A7 zs#LYN=&=JvKydSOerZJuLOs~C%1?N+Z=>?Rg=q|Sh$`G^M>EP+j+aX{G5sw1IwJE5^pds}gww-F= z0~SJ)0LzRZRTAQ*pPCg`7XwRJJu^tn3#&_l)cmj-=oW<4bAnWPSUoRDT^&}>4^m6Q zYZe4cmXWATy-HaNtF2e{MAr&Zjn={q*20a}!Y$Uqm#l^L)hYldYk&~ zmyC_NIn0fd#4U54C2Wl7>!#Po#`AwAK43ggw>~Fe<2m~1tCY$py`;E0o$^=WyuDk- zfH@Ei@ijMbP5-tCoNv8{_rtnNyTAP#``!PoUq|_gKu|Pv=Iz0!LP@T5y!>Lr7&^GBxpN}NF z`dbEoE2^lsLnZ5}pts@}kA>5UOs1|lFHd>cSP0o&iAk@03%9KG}EL3;|H8GIK`V@xJhO%8;9Byrm-RCI||bq!V=)bGK8 zDuN;O4OSeLhqZByR|j0b$H2|3{uz!2hIWU9l#t;4H}oS_!;0YbC(}k%+~f9a%3qjk z;V^wdTpoz$@G5%Jq@rf8r9&`*d zu;EoR@M>UaNG!*e3s3e_JPpi+Ars7E^Ot4{y-JTjq!7 z=zOL^SRkWVWXeVfAdR*2`LmXSjW_G|EAySnl#`Z$lc=x@JC{$qpiRh=ow{a|?9>&T zIm;SUZ4H;t3~IK8wIu=ZGb?8WRbR)XC(BIjya0np<@^BmhUE)_nn%MA2Nk>U6Ah{! z=a^AYvs)1?DxM4%li_Z!&NS9Mtuu`kUE$&)#dN}eS<@HR!6%Y?W`PGXaRdZLuQ`nA z8#_J#Bd7c3DH+zO6G#L}J0#)N@g|v$@sU@Dm}$FZ^O*PQv~BwcR7*-_M3aVfKGb`J zXX}c&`?0%8?sQUw-&DxMLBf5jBrA+7IglOr68Eg|&Na2S4X%opw<;{bE6rk^nB+xyL8h^q;FGw4YNLjSf zQ8U0TY>~8Asf;=C`W(KuR;byj^3z%$0{wtJL z6%mTLJ8JkXGVqd*MR+!M-1p)62B7h&-dS4efFD|F@c0iAHLNXmdQznaFlJ}~WUVeM z1Uv+3W{0{gwh{1s5w!HCW@T9mtXZ_yF9xmkD?q>h*ckM6pjY}Gd{y0SkMJ&KSu%at zJxe=uCm)+5dNGEsFXrja+{!N>JHJ46q+rfi~PW&gD9T<;k zu~g~J6H9-6taS3R8>3QNT`Au@kht#SQ$;0?sHxM{R)18>^JC7*EV)xYV^T{mNO#WR z5~Va><;+&@vWDjx0Ug=x%SC(NmETEcRnI8)B5YxF+q|Yk8-IJyC?y7NOtV92g#j}^Bbf^jJ&p? z;x{{VQj+=7O8K|-hn=b;w8K?0cW&c1 z8mn?H-B_7&C6AiKoBbA)&NwuJyoNQqodKiBe|gH5wEkewF`#GW8Xcs?6~eUpD!K1r?gDp!pi|eB|1M2G zq2=70{R!KybsSS6JNoEOa(WN^p|l~hd%Z1xW;^GGJzB~6?lB#Vq20&w>{qDrABza< zv3T^%oBioWk>0#^Wr?G}WojiWD>e1d`;1p2D84+0>!V%?wKTBzPVN~35T|`v&#QXv z$Jfu}g|_Mbm$_1IJk!kBt5I3YsYt#&e_{bDN#3^0Y2t6@#y$mS$(v{uB51gIUep|Q z*V_xI!oOJ+iAHYWB54YZFw)s2JGJVCrUVM?Bt?)G`ogrsj1wHf&U(W|($3z5b_ylv z%&1(a4SL53U3Um`f$bB*tkNw5AT*sc-$_B53Cuq{Fz5KdPL^aPf*9^v)Uz^k;v-Yo zDzsdhHe#v{vr=oiQ=9{@TNO)XYKYI1^Q|CWnK@aeZ=09X-7H^;=u}e-(W{>y{@4G2 z61I341vfy&%&^%P&p_sfi+K-xoAczyr(eB#)<+u5u6!qL?Ma%<4K_?nxP9HA-DlM;TJG4hn8+B~XbsxJ8*JY}L!oI~^xj6! z#xMAWHqZms*r17eVTVR6*rNS(uw9$!<4e?Dil%;``{+(zhiO-b z>JGMBP2W&WbajiK(Jedi%e_IyeL9*)T6pXWJ&Wd%J=1>Nt2^5nSGv9Sokl|v2-6+w zHK^%yil6FmX)Lquk2Jh`ihH7!+rZyo%Oli*4p!IOr4$IhA&o$=Me2av`B`_QzaKgy zFVdYCPH;z(enlyBMo3L$|DyZ%Cft9}d+AHemFY~n`gv9df06!N!T(!h{Atjil|%@N zg940a@9;AOiJ|)Qzp7^_vFqO(we~jhT+ATJ4E66q3P(q4vm1jZf`_eRd|?hAXb`1# zrx@ke(@p~s(BI^z+S(ec_d30^b|`(jZlp&%$bWQoC|?jGX}%&wIJC4ygG4h zA%E&MS`30M+6%Q9NVJi(RB@Qs3+LnE$L4(oFW_9soBA=B%FDG@y#0U^wAeNxqG-dE zB*|gTk)U=#xXRaI#3_R{LpqMwJ@D+{f!CgU?ErSE{p10Dcd%xdXp~nK5@d4fbM$BM z*&n|7>foI;>X57VjFo>Lm*f!D~Q*e^H8ile&3=8_@0^dN^yj-UrhvHKld^K_>8 z;bE=xvd8!>PxI%dqx`vTfIntzUDMBzPZ}nQ8J4Do8sNf@&74!@X5pmb!_E}mnxO|$ zpTF#ts8g*f@O^61MMJATMU+EUGZQXibh73k3MlvJw#*T=^O{?sTd1nR5{;m*Qf`oX ziBK(=#(92<|2Or&CgR|!aORtwcN5Jt{!o}llSRSCgDT#L>_L7KnSr0nwLSu&f0Mv` zj{nr5Jz@hwqz#1yL3HA=31(*b_$TK*0ca*Pe?lSkp@tLA?*djN1Kn@DlArJsm6e<$ z=K#L3HZZ0nZ&Rb-n*`~O5_()~g5#HtRS6XFu+K=AvI-@xG!FC8XH7;%W@x;L+xpw| zOsbM(8~z`VSiSnxXj$;q#i_!?F!k@NTk-=e%71;VfD-r*q(56kSGrgNO^H={=G?zb$lZZe5kGD?G zA1NQ!6prZ!F)qk4HexUY!B6?U<%Oz!&3>Q7i}JE(>^U^1AH4Phsy~hY&3z|^#_I>W z+c)?!tiWXw^1+CrRHRK>-~3GqleIVV6^=f*T|fvML|boKWI4yKrd<89@5w0NgrUuwh zA9NH;Bq5ZJok=A%d(bI)ObOaeAPX0x-IVC00A?m)62r_%nc?D@k)X-38aoRg*3Ak! zB)M`lB`_0SxME(=DUC!}H$O1pI9$9SFu6E9CouUJm1kh0FN)8=x$;Y|d80m(9*opN zy%iEjII43C4i#O{Y7GgtF64;h4U2lj%D*o4#`^`p#SP@EkXd z=MK|&em(5e*y!;?NWsELQ1vh}Qx{J&-&`~$@Aa4B8npyLTEjCqOV4xg7;sib2-~FF zI=uOI3z1b_LRHOPp^y~P;IHbr5De8E6y_@Wgtn>yAqFcR2qQ)+*s40li5BV!h?oef zDvp9D^yxV4;H8s!^FJB4T_YYVd#3X_137Gfy%LxO@mj?`vKvxMZKPE&nRY4yd=kt6 zZt}S8S(vZcZL3Vs$!M?W%TuGl2+SCmEu->rFzp^}$lt(}u15jjFVO@yO#*NCNClyX zOnvh=H@>U7;77)UwO32-+iS&aM<}JY(<1Dp7&RszyV_N9!+hS*9bcX^$;X!1)8w9V z_qOFpic91on~Cwv$6VX4t^r;ErtOX%d(?e#6Ke%Ltl^2MptglAhj}2_#A=^-#1$l; zy179_y>aIJepk?MrE2#?@Sfx|+dn&PV}w3?zem%PB8Y&_D?$Jd@xNlXKGFu;>4CE! zvG)0`f+zYSnU;9&4BXi=45nYjqRLpQn7>EUoCfga@b|ev<{X4QGY2X8Z8b0mPNyWS zP}1E99KD5AJJp7o<}D5xu1?6z{L>nK;kJ&Hle7$&^{=lai3~*_+hIkMPi*Elv(iZe zO)~(dGR`0IeRCM`??nA7GPs3bsbf=6P5sZEnma`))$H3^VGK|RibzSZxBY$YreVWO z=j4@wzDzdQ=lb7Z5{9$vw@k15roD!#*S2C%zPQX}*eo$rZP~4V00L$i_UEMzIn-G-_cV zGFOI!+)L|`RXq9~(dhJW#?UG^8tJ20q#2X@m61&$E)k{>R$8ee(K~q{F6jP66qsXrHH}Rzv1LfJ6}wR4mV^f-09HNu+tHS z#L&ZftiefJz!hpcfPz@coce=kL#!*wj(UF0MqU4`)W>^L?sGq*>FKO1L{Ds>XIpwe7^Vy|`8|8j|L9F3u7 zaSxl|8N#2~eZ(EI9N*9K=sm7%vZL62Bdc2dFOZc7qYk?-LzCWQc}h6olN~do55R|% zr6`~5L2}8MsG z$BI7r{`IAb}^z#X{AXGHu$IMN=yaUr;9yauc^R)dhFsSjNYB_^2@ ze+9A0k52y?aqpz*6GQj+4un28=t(nmg-*P@r_c$_X)`;mOn3Iv!Ii1%z;tJsPkr3d zhVGO~Ovu(-ezSJD$XGV!EE*YHOs^N|bbX|>jxL8jXNNSYRUnTkd#Mw_&Kb!pd>p-% z*}qX{|D=<5BdxD*7Q+;_{DV~L;;soTz7Q4}4U#T)ir$DIL!`i%#e*rOg^!~SA5QsfLG7RzcK{R#kEKUA`M`N z_(G4F3iC2c-xwL$v`^fGnjhjPG$WJaW6mK=0vpDm4E6u177Kjv@+99 z>Cli#Y4Et19+|gV7MbEv5{YaM0;C!;e+P%x$da3M5oZjaHAg{ac)o)noWj;NlS~fM7WF_p;D_y?o4SPDZlqCS$ce+6@R*L zpQ;j(2-m@P%owy!Rb~w=nsdQ-WatU^nIXuGEn!;V1Y6AH@*Nq4XwNKQff#b+^7JjSwEXW2eMRemJ^-7ZR4U?$(jxuSmZIBr=sG7rsAxsp-E)-*d=&d@uv2 zf5RYvik{;c1J`guW8h3UUU_`rn)qwo7&N4BQ|u%5R{Vw3-MEly?J$_XkX9NOs*mNknsnvX zW%|H`rAQ9Y1%iTH>w@*)!B5qVLbyOz&5d>tPglNbZnQIEx-v-WRa77uDD|$aIxJA? zUD+Ax&M1E9HvZh*cj}vJ;i#V)s{P~CD0Er7<+0Hc)a6PP9zRE42!-~A0HT-FbS_ud zsJT8qzs6eA6IEzWY&dh;pPheuxjv?mg|XW|9o4fie)}JAxe&ucxqtg5x-8_f@OIZ5 zE@#|+rk&DMcjk6Y7^BJML#0I4nad@y+5J`&mD*D0@BqU`LnA`JzkMv|ajA2z@x8T6{coXO%uBcQ zhh*jBWY1$+xxD(7?6g*cK(hquX=IfBlxBfWUHbK!0QDfen>9#k9j!H7+p>BfiyJB2 zg`dh;DpPnD0m`c4m>CHrlr_$}_sWex9E3#sy(M|1v0OYeRh#)IxEBh{>e|dJT(uwc z%RbvOtrxE^SXgarW#+pZ>9v1Ht5%a;)=uGCl@;U zS)khiZXNx6Lbpe`b@cNQ-5%rC(T}Iw47Wx<_auq&PtpYz)N{i(UjMSt+CA1&GMi2r zy$kYt%Zuq;o^|I7U!yx$S$8(X!o1N^8q}PBjIZS%3E!vr03Obx~M8lMO-!sA|KZ%uyTacmYWrBAOpRwrW=2|1SC9DLjrK?E3_i zn2v4D@=MYikKU8lc(U@Azhd4Fk!e^&8sr_yCp>Sjr9d9X;6I?fKD9To{(o%{v9g|xC%~yfs!VKe!tzAE`SBT>C(b9=9^s0n z6&PzC<Pxc9;nG43t{o>l3>{j~t(DaA)oPjF$_+!G6Kj{#Sl)II;ybp6^~BmRF3 zeCb4o2w5?|!)Bwz>^kh=OJz3Ofnrr|W_W)g(Ros+ro4nvb$ z%Lop}8KmCw5*5h#cy#_LSW8(?^VHz0vFQ7mIo}g8vLtN5x1!i}aEQv(E7f$m_K)3u zy7s-2#jr}xTU+USYtHdL4#zB~YZ1Mj2H)cECGDko@TQ@b^NuUBoFVW^5K!v#N2P}YQMN)SFEqa7t;Ubww~`t-^nO%Q!^Q_@s>PQ7 z%o@Q6u)^dH!nrmGKl~~@mveDiMdGY0z2!4C0?|O&x)f%Y-De%Gm9*bcuvl8NdDaxCG6T z%+A-Plg!T2<9&$*RY^3Is7bJL+*Sw{e!4xVLW@U*wZ@`0(2(8<_8rA%Q%IRzFT8d3s-*7E1B(;e9cd|3N|CPp?!!)xd!_^v8>XV9I-t41yo{7hCP$ejg z_gBU?{#96UFzhP>rfu3z68kZ%B5uQT;exGUVn6Y|3j&&jX`XdNjs+K?Iqx3E~pgkpcjUu)&hkO_j;gW<;ehJyv} z>A_(2eZwFwpFED>)BDqJOLWlLthJdJ`o~6P^6>~$#ZG?f_qHhMOn7%O@%a{&d`GgJ z_x0|}-yl)YRxBAa@^`8~he|$<QcKa2&Go+?AdZ-O zUWIlk+K{SB?e^_P7v{O|?%29eq+VN_r2bg^1lz`1AcZBi%>{+_o*SPW0Jfb*w_GpR zgk-sYoNI-@vwgDaV3J20ZtaxpamsC{wP|wq0)xYLv%8z_yok#sGg7?ilU{4djLe@h z2X^PXez2)e>2Y~HvtjhHTR*e-0w3r8yYQ8elEC$n4gz~_??9inbEg0JclT@$dMfoS zfgU5LXa8yRah4$dL+L@S;b!_DK@YjZlo`%;#>S%>0w)ME3o)Clbj2{~g{w1HzXdxZ z#>zoO2Fh9S{uwL)ol%4}LKcfG5$WJolVpij2>udaIQuKTHB%n%9&A zyT)j;WSR5b7nZy(5m-`4rB`dZo^MCtIZyBubSbON&@HEj=>72gz)Jrv@a%G5ObXBB z>q+~GC5l%AqkD(si>=e-Q^nr*b9vro1!&MaZ@Yp{hF7w}lZ3A7RO#{(Z?kp@O557w zZTW^SQ(A`Bm;8OS4B@wW`8zt!xr{}m>Lk0Mq%BigiJBjr8CIPnoOI1laD`%feYF^g zPl-BfS!ZX;iHraMTrc8@1SsNMN!>s?)L_|nq6LC&MAu~7h%Rj-SiMLy4KMBq>-?8~ zzOemAmjHMBkMqN;cg$?xP=f2(aCp+|C_^Nu3wtszqTP0j$CU7l+Pchtf!EviE`PDl zO7U?NY<8+RyUN*~elmI}xmn|@%!Z@+3`gl}Rz;Q8s; zPLZZa=?VKZ9u5(364yN!EBu1+1*YOz*%Y&U9>oTKeFCZ9>l%(oJ!p%POU7+m?^MH+ z!Mo&x;jPXA_%7fj&F8CGcz<(~kv^C|enu2~sxE93*duKpMI_l_iW-E(qW!@I;p+V{ zfDfwKEO%?O{A1u>%iKPHQ=7}d`5mh+J2&7rme#NmyyxcN%<|Uk{H+T=OK!|xH-D;h zZAo*aaBDsRzcPi}tz0>U+mzrVrfjQst)R&<-Oe@KGRu1_;}8zQWHDpyvc=A9Cc5$; zM5UFnI7(Wq4907NCLfOQukp9Z@I7$j>FHy#^1D8uJoHp;-!yS>zBNwn+p=GK+y0XA zX=^EMi?^;vTx7tau>7gaGOG(e%eV$iDpM&SEoA*g@O_O}4xtgM(UKWYB((D#L=mC! zT{it>TRc2a_CqOd5>fsjOKnW8vGBsck{kcCY0$C~pWqjdTu#1na&flYOLk$>Y(#k& z%U)=`Z52JZAg2cpe!)mC#HoJx_|&5N%z z@VCXUUrU~L_B83(gOKGJbc|RaJ{h&;d~N&zc#(f|Aoe=`92~An7a7z_ACgDn)w1uk zwoRBShyM8VQ{`h!6^F0)&zGSOm@nI{&TK!%&6n%5J$aU%oE`Oq@CqConb4o#5&zR& zKmJ|#fBEw^1G0?jH29wi)XM%fhW$uBA(ZajC3(p4&7TWf3!#ts;Tk_&G?Tr?in>*( z+h4XN0QJ#|l2z#3do{16W^b}~XO2?f0VLuq>3JRZF?JKE@2eAt?`*3eK3V6B!tarP z>bIl6mv6}JKa<7ikM)K50|8b-fLj^1DMouP(P|{O82uG$2a^cDMuOwAH||~6dSmH| zcxySi#NQwpc{ig?Qk=s3gF;gpmZL5GIRXGLJ~V%Y-+WdoQ|Fpz>0gnsu=Xnw!HRg< z8?6_m6MH}c8O{;~-ObZ_b)5$b*zgNt$P+~fzxUf?GyMf@!NH*Aq_;&edB$=GKgOMS zh6Qsb6HvvFSOICIl~-e{dwF5n4T+ME9cYTM0WTg~JT<0WY;Z4uAsjDo^mJA;g;fn@DB0WGjn}=Jy>Abq*!MP|L||wqMJ69Ik-C?xTVj2MFTi@_k)t z8x_iWk>x|yQOfZX!Asw-E@&+oOYxFNs|Zrc@y%K?L;;O26k=T<8r4v!?0{$C@Mo#( zbKzg);1_8Cf6tmMY4k2KF&(_+0xwcolu)o->WA{7hDFgFaa~vxBYku^0kz92PI%jX z&Nj@Xx&x=tf8BRNt~O&TFix6yb^n|_Y+`9cg4RcZmcyJf*nHlm&*JL-W#BRYjWg|@ zjpWo6&-wkt>gcyV`fZGUZ*ad;Z;bA5iGIHn{jQIGo89l!4blBZezh9z_qLMZ5NqVj|M ze~$mC;Vh0~QUMr}HCy>}+qRyHC(-onCX7u`@pw-~pVWSw#Xw2eTXBR&$a8*_h%Y+X zR&j!4)!-gNAWrl40tlZ-stP>za+nV=%r(<7ynjAA02(du1JE>9Y$61KQw5$ath&Rd zpLU4Wxd6f1ShLlg)cUkTII#m~{{w-`HQyAcedOwnW6qKJY+*jC!wp9L^J#ngCRKgf zjmN~#c{A0GNW;Z$gc{D%$WZMGK0ZNbWF00l^moTMdWyHFV3te0j;Y{h?ug|}zxp@F z$X)Z%aM^D%WX!qn@@~R3zEaUY_@hj9-x+yj6-QgA1+9dUJ-WDkq+lb8-6N!7+WM~M zQuB9@!3rAFg~pS`G`3%-d9o`UqC7Lt#5T7(h=;AaEd}SO(9?>hem8U6+tm7`CE@ry zA2aZ@U}M|5J0P=8^LI0)EnTfQV!cGjiW*8~F0-=iLud#gYb2nyny1INckun~9dzyW zS$l6ZSbIi0t+324S|-bYO`BA@Y7=^f!|9s6E7KK^rm8cSa;6+o@9hhuU)b|GX`_ExW^cTY^uG_8;5Rg#KsYfv{V42d$5X92+W+MNO5q zcC~)dR{OD(fS1@8B>Hw(cngxxVu5r9S^gXkTMoXz>2av|Y5f=~S`G%SU7QVt*Y;K% zWt`c!KCIMRM>OU{OaiJUFtm38UtyEh;X5e0D{d9=h0BSm{_-$e?z#tma{F+sBv?VZ|fFJqN`JsEHIW z0_J$q^zz@Vi&m1O;Slkc(LSMd=Zx3ll9f&$y+VBVp^&YLQSa zg)>Q>@kPjYsK7Zbc31JY#1lv^0vk`a4A^|EJkkD(Ldf(&OSz0#veep+SyAVa@$ssG zt;=KXI?7htTRALn@9GLM=Cd}>{fEpudq=hUb(v1$qsbOBg$b4|FfdjCiH?u(I#8&# zuCeFx??4#BJh_ByMn!*@zcn>;EAGro<|$y~k~z3sEs2o^ht7`>pN+%b6Vz9xzskIQ zlGZEdu?apvUZAG=+P2LTcdd% zmg8IBOII9ARo7*HtTaZICEV8>TbZiM9Mb)YSas%$RMmcPX7IJx>)NUzk9FL`Z+d}c zc{JS?w*6Zm%%U7tbEyCT@~RA=hXX7uuaGSKjni}hhlB|o_|dK^Ox>jcoGUdvo7;jb{j)D?XBn+e+%E^ z#ToF-OM=BQTMLdCgsq3uO(WsFA2LJ2jX%`R$+k&+b|Ppc?=v~O2`@O@8#zrlHat5E z#c+rqaw@nRd*V!bZag@Aw|9{_P{4zKMFnTJ|0F+H{gCXBY}1or>k0V-c$@zan8brC zj+M3admS4!)r$TQJNnc}IdZCWO+VA^6$Fk%q)4pJJXx*b#l8^fK4B%7Nr>#d`~_w@ z&7nff4`zgo`LUor*4`B#sTyj@glkT*T$t zfGD&v+#xDi5DmJoO%B}`fQ~KcD0Jt$x~XeCfIt^2C9szZ96$lz(w3jmAHG01NmOT! zrK0{}I2K?en2w;TI%1BsmB-IgDBXOHo5x15ae99`Ul!zw!)u#p0D}9k)ByEn6=!4g zCd;UFf97v^KBy@zyYd#O<3e%jWy7l!u-4JLV~bChmpI{HRdNkDX6Wgc|#C{E`K0kVpb^pQ)QW zQt+1X;#OO;s!pK5SevtE%>rcDM96DdyAZL%7vcE`OQZYoxJz~4>Xxqk8&5F(5VFM< z>N3O2t;-a*R;o@i;lcu2k}^8;5R%|ST18wxs67=H5ayC+Vhd2YxBdSF|6$9?u;@GC zs$w+iTKQQQi_pLDzU`dS!tKpxEG+9IAM_L%cZRWwwBG}QmLo@0sK~kDs+l>Z3h*45 z`wY*|H+eN&RWkbWS5%?c(t-w1Du(z!oT^(ja_Be_grMEH4wxoZ@&tB}Sqz9}L*BME z){T>>r9}Cz9k)F${{FbcKb;6vkvq$pOenq1DsDBn; z^>zQqd;3Llp7nLVGktDfI`K^SV)wh#=02-5!e_nRU(muqQH$M(C5@=$7A zQzY!fNLH2CB+lD?(aS@ zo`~kN>+hu>*x!+}x3bN)UT0f(_Es{bMWKp^)R!n!vBRj*lj0fw8}Tz)e;)kX)At9J z=!gDy<8%54w4WX4EQTif-wGd*Kk?;gU>q;~?5fgI#-Jfq))Z^@7PWW#W&ImZTo1RT zI&2>M?ZT?m_Z70YPn+BycA`J)Wd5|wZ%xVC@QPqgFnzqwv#TE#083tk{FHM0h|PtvBrN z{;~J=p@RKfygPkve>!$ZSJUR|!H>_%zVYYw!Zb7K~DI87~_6~=aG2u>)x zeNEi<%O{m=4dOY!^zH;dwXq2U@mhp>VuJJK->uaERA8-*IsyJhrfOc(PdaXJD{}_;ZKYUN5VDvDep+GxUHEnP z&tl=#rLizk3VwtcEFBYD=dtzw{je52d4g$FcA(`~kW~A- zI1~!JqK9-2pk|o!aV-8kh&?20eL>jd7yLThFANXStQL->s^f`wFtln&Hy5@wRew352kdsF8{l{Hk{3 z5Fs)Vw-S(Act&-2j-}KHUl=(wa`tBE==ih|1hWIs({S11_{RNBYsX2%r}Kv?k;#?} zrSt04fY)Jp?(t$LR@AkLvg!n;^9sC7j~$;DkdBE+=55lhjDuP%)dEsme~5|B>#A})Dxf=yYaU(g#aNCxx6jmcQSvjIn0 z?urK+NZN*Sh;aau$C&=gIw$`cEt@AC!?F<>9`$uepo}u)`WUBl zGj{?Fe+6D!aC70c1*9^ORdup{lmGmK>z98+@3a)1fdqOg3-if2 zilH6%sCB)SGxE4C3fhX>+Y(b+?^%V$r8x63Dhvl0Shi^LsOfF{qi+v^@AS3t%U=qI z(hH@K_&rU={b7-E-!=?|^|7E~IINGeA7&w2gEMvvIcr7BA&QmtqS$OJG`-O)1(B!>0K%OoqPL<5?hmP)kLYqT`g@lck^>4gC(EnqdL*ax8 zePZWc@Y3ckMCU#{+PS@-6-ijnhFs48Nja`dr-$%Vv_rH`vcA8&ppn)T&9l~-ecC=Z(E z2Tco*51avFjtc!lw$QAe$~hFmL@_fU5eIX|tQ1CRyRwBokkZ_Q(Rkc7!}jMhw8;Lh z`}5PyH#;6q6IXPNn{JUmaIkPmG~eDopC-Dyzu8*EB0q)SVhr)Xjp0;deR5m{O%x169lt zjY)Z>(O5n!Xe%*)zVpJ`S?R?6%-Ct}cyT!b(D9&cX2_U27!_}!t+}*q0jk9DFb*pY?Az=y7`b@5FfXV%CA0G>W9>Dk%AWPo|3z;PHJ{)8 zWBB^Y%)U~5Z?y>)Ygbt_v$nUz^V*h*ekR}>!V25D!#rxb=-;t`5SW^(JmMfAC44x$zU3@)_T8ogAACXg7bIL@6<$h3`7^#fA zZfd7StIg+0XUS$w3}q$y07>ka>Z&qj%M-1C6h5E%G>Y2rCAa(j0lV>`>2nX{48!Q| zd)$7tLUx#0VzvxuO``0&M5{MW6?!~5P7x~S7Fml?hXD<9`dbjLgvO!=nV&)8+tGe2 zS0*<8Qi44SW9^~Zx}J&*=GtQSXD)2*Y`d4-2!Chwt$VI3?D4qKquS9seEd zr01EPG;%UHRoZgYOaGZhQRm%zz?0VJf4LHrvS+C}B9SS_p$*;n)oj(iBk>8V$mK1= z;XCOiLk!19(urvW&$7;>hHRl3)oNYK`w&91=m|CJ7 zYIfHn_7k=)?`QM)Vd^2YEV~~f%CdIh-U!AFJcg~kV#qJY?26OP$ubTBY?FC?E}m*_ zzd!dajYa``v+8i!`LVF@d@?$IkHH3vnq!Me(q8t?#v_>YuA1L=k;B)uu#SkITs42= zN!LnG#aVgn@8q|ge@*qRdFh(72>3aW`P(18M*wi)$QrwJ?pxw7YNmjOpsc8^sG-&) z8?k3xPsh=*)A2J`X8!XZ+L9`@MK;jO==#y##?A~4*>d?_L#VA5@3?NH|M;b_j9mRR zo3lti9c|7iOG=CB(XI;R69UmH6cys^1762d8thk?(20vgC35w%35_V%?WN^v&S;*8 z&P5IOPOWNBJxW@A(~3ZKV-Is4biQ$^_xCC zf2uSN`BS&?*LLqrJGSmJ(V<4**rKuT9BrNHH%>1y|8SlGi@dx$e=BL`vU0PNnKxI& zrHxGdbygdR4lO&(PPpMOqK0TAh4tt_+vmn8Kzx5>-1sjLH@|BLFQMSB;c!DdkkU(s zRwHgc3^yMq8E&V3Mqh|*%iT<^rBnY^C)RqJq#eo;*}xw?%T!?3m)=$g|LecZB7Aau zYsKw*d+;ol)JwwdORExZ_g`A!pQY$cHXiJ-$6A0lEKGPiN2Np*t#1$Hw9oxzBO{k& z(@PMaBGEl`-dF+P8&}`N`^og}6>;w=e_fFDo=Qzw7gSH-H^y&_-)a0#<2TMPOrU)F zx?th-+-_2Tdn=*VD|Gl%$2uFUagp{FV$x2ZDiuVd#g1ygJGw#fCzYAlSNO?>mzqDz z<;98=2a=p)n^?9x7P@^->|3f^AhuPa*`TXoSh3v0cn)=ybib~`=KyXdXT zOP|N$(?HqXX5$6y{qAGW5Xx-*1Z1qoT*urr`><;3whc5-h9>b`oB=6 z&FR;_cgMJV>97B<@%NLA_nOxqz<(*?2pD^ETF2fo;oj)Sq?zpge|-7u`>BzBz}>@P zPhRbR(QqCGgyzBbRK%fN84=`Y(UJE*xaRN=kJ5YNFuew|T2pXZOpm z#X^6*B;GNV@I-skIA%&8C&q92E#QNe#SF`aT0aA9aO1aS!g%@eUO182(wTE%(VPOs zZb55vT})b=>ts^J$DAb|4djZJ??-W5{-fcWyg#YT!A}||%bl7w!mhum%%i7&Klyk} zopd~8Sa#!4+`hkflJS^`f7d?4D=E)2Ac%`LCShjZn-uuVPpA`NUSr-+4LpUH?J#O|Fl=@5C+!BIo>pf|-%`HhgjJGb`}A-z$w^P_wz$QZi^` zN}dFl*nzYQq>yz99LS=eSC)1wsMs2`ZYsMc;ce^Suz-c}^UvN*$`4rdCyMyRD8UZm znegu{aN^ZhayZA^wiP$ru+NsH?sv`$I&^*@v{nhAQP$UdYe(0HbHb|KmN=tOlJL{j z_*I8KhdR<#TdUL7I6s1fAI0OSPFL;a|2A%KaJNrZhkXc4N-E=E;n!dT%&=y2lzGA$ z_x{GhJ{DM@+VmSOmUM&qcEDH_3-1a7W>y zued)vF9d!<(-TaGi?j#{wPK2qv~=HV`d49I%IhpeBpv zE`#l$VEcnX#d95ljocs(vMjzR6~RTVEyQ|#-jXNxag0Pk4@<}f^y-}jUWuaSw;bfP zZWIZ;r7ybSTnQiY=QEln-t4zk;uleIyCK@T6}KBjA8+j^XQPTK@_Wl+Z}!WhwGL!! z^=AJ@4}YoRlo-~{&%D{c(y!HVG`EgpxpibFtAi2~>u6jA2{taB0b&WGzI8!@I7zdRt+-rvCBjt$h(IiJ_mci@RfX44p}&+*8~>OJ z73*HLyO~Q&NiG+u0mM5e{N;*wJm(}&{Q)(=VZ2#g#);~`x30YpF>sc$b=k~!qTtn6 zBJ?f0a{BAiG=$5pEb4e>!?g6(R~F+9S+VKz?sDm2dc3EWU5S93xKeU%*|T>d-4=4a z?8@`46_azzp1ou0x?uH{u!?0@o@?FXIa1-XciMC0y=7O<&~rpXAM@(@~BZM#T&oE3!Qfzou^#S z>e5`~+aca@{?E2)-O)1=SXjnoZP)fk&xYrAtwUG1E~t4E6?EwX!mGFaMg9gA{&CP$ z^Je(ecQp*%hv{(4W<#edbb!gi>R%@rw$@PbD1e>*NnZ0?& zr8Yw+wP%~oeu8~E^U>3nhrXR#6iew~=^gRbd(f+%XB#R>%scJVaT?X|+8C z3D-4Qj1Az+iT~lHw}5Swf#=EN%=p8vf6wN7uFlsPFF(yDux{=l+?JndzC@!~gu)){ zxD4gCaSyJuNG?f(`PZ@-VfB7%+Mh~S~(_oLW=zC23= zjBnut8fZN=jtpL%Kn1I<&e&vBQ1)KyrD1RQG7AQ3+ItcOL)d|cex|*=Bd)IUr7b^Q z8{82y?OM)m(AE$24|jXKYOabAk{FYa?3aKT=e#1phQ_S@EyEmHylY%vAHO8(YtET+ z>uU)X8xj1e$>@DJOYf&J1B@s4Kd}nOIyy8)?qI+BXVVJqN#Lm05|8>fauo*=%%#k9 zuaocJ+E3h~D2}Y%wS-+kUD)`V=N~N)(QBH6g=Tiu6MfYXg)H{!InY;=@7vWy=_0B%ii|C;4g8IytJ@2 zj;%a;@%w4sd?G#r6A)L47WChRxhR0tcyEa1*2*Zha@kASuUktwNc0jy0<)ksk+m;Z zDn7*p@AHFPn+WQ~%wS_nTYf-rph@P21U`okQBD-`#_Nztbk+Of$3#IRa#`(;qSN5y z$}Fojd;bS9TU6<%XSU^5|D@Q^aUC;>CVnUHkf^_GY;z|IXKUO{`JNGhEJ!v}(W&x>5a{!x@q8|!_gd-Z3BXT< z>tjS24%f$nuj=V2bSqjo>6L5Q6SZXVCw?k^b4RQWRzr}54!KCdIPo$1!>pyvjB;|I zV)+4A?=Z9Oi0TH8#FWR;f zTzcET#otoP74M~=;2NqrKQd`va6#mwa0-?^>Z1^TM|Hp#KWhvLwr2d4?>-t@4*GCX zf@HioS?S~*i4!k`wgV9nZ?c>v=ArP-84y0jFl<^~w1m2AIIyxvum#WbY>~rFeqbQu zhuq!zG3KXCYW6`v#c=ZkBCQzq7h}EtVX$3*r>l;g zCpl*tg6wR6h%kJuH*?QXP^pcUsW~ku zT3V07wAj*$4lDQl`99A%N%+%oW_R{pH0Sv}|NnoV@AG}WfAqCeSwvT4Y>$Z8N?
bk`s4TY^E+$(ig?bABL^f4*2v7; zF4cf@v(Xu;0Y6Dn4#-bQNu(xtpWLl^V;*?tt7OdZsB{c9kSg*Uy{% z@?Ad(*Wa3JK2kW7AvSe_YiRVszQzWXT=>FLnOCT5Z;JP(7;kqgjrU2c=u0$S9UK(lHns8zPFv!aL~0DW>^K~ zjQ4j`g0Z}ue#T7dl!cZdyl)#3XTGJ5?0I8s^m6n)tLwehlbqs)?uUdj7ThX#m5kv+ znBb`|{zMzs?N+X9ZCuw{xvsTw-EQT&##1GbwRqj$S6@NtqoVH^-TKA~NJQgJTf?!@ z@8nU5pvJ5I@LP{5*|#?E>pIfQmvvet#FVB<>;9U>l?~aQ*q+}%K4mR z#KsOR)CbTUpK8ZAC+1N**g2v_CVEO6X$g09GfJOmKB%ujez0$k%3q`Sk~mm_rrz%> zw-9i`KTF9(Bd16aodo8XDABQ8b#fXAHP}R;M%@r>Mz?@zv};O$E3LNgi~` z=@&)dA9Q}Y7}QU}e`r`Us?`kjs99aEpQbj*E07b^*YI<81EwqN!VRE=5e=w0s{zx8 zH6TjzP_Y+^UxYsNR4;14d2or6$k1;>mrI!BSz$0t-5;SECHVj+U6^_hNslo3u%s=U z5DnM{ciO(>L%@$SXv%;Uu1{+gsKl-4qO)&BKlT^x-vJf%wklG<|8a?jipNJl#U@=F zX28DYHE=n-td}5MH>UfPK`n>VpN8#8Z?NKtdj%0$gNGPd7em~9ijI<$i>?v3*MNuh zPz6vq!rQ?*yC$vUn&htu8-BC(Ouh7+I7ELKx#U1JAF*z%$CQggvoHqLMZ03?-}%pb z=E;b{Zf8u@&LF53n+FiWG4ta|`NyzTE$X>Q`Kws+B~tz>R=asb%CF^bI3-`ScBL+n z^_99r)?df!E>ZGVu_jBD{8cPSS7?03Kd9PQRS+qDE05j>l;Wg3dLLNF`Wz~VwKY_b zdZA<}sv0VY6hj4(qEyher#V#3B68;`8cNaE$ynbSfJT}obndfl-ua$7!7!z-QY_(< z<(f^}&UcG(m-{#~_s82a$)xSOQwqU5&HhnZ-W8%1-dqLhW+s9$LTW~`16ka(cZAkD zJ{b1t12m_;))OFq3`y_^fiDu+G;{eUcudH=OxWv$y+l}R9bpmJZVMF;ks(QO)O4)r zoq(jfmrr~OawLP8!Tzo%M_N#UAYg+rk32=HJ#S2&_zhkHl5cSz5tAash5w*!R8qXh zF?b5J7R1gmDWW*q6RH-)FK~PMtV7_ECnfJFL<}7N{Ky-qMG0Sbw~3M0$uk$*G2=M^#2}VDf%;RD6o4&w&?hT}4^@S=);HqOK1?*cKR*Yo(Z6+p6s5 zP?P}~+Fb_@z@A+Rf632VJy!|&m%RB!+T|1NY$AHthCKg%M1M4xH#js#Ax>W-1I`kI zss|TDFA7T_;gG_k`eKd}$>YqnX~%;kkH6u%BWJV#8Y`H*hU*4Ewg{~cB3a=WTO)p< zVrjdF<%nP_8BjOTj>T}G-wE-&_xcjFn}yQPqz2#khx z&3f#|4|!I^87fVjp$gG`UEG?^XD{!~D~luN22A-==$FL)z^B4hQf?Tos*G;Onn@Ke z;9={Of($(j@gR^A&efq-ym@Tl2>u>xPldgSNfW)<-$N(9zG#l|-#PmP^msgf3jt62 zb8D-keQU?$=CfCW565Q&Yf*yDXBEW%_>8JGF*zf$E?nCSEtO@*xLU{Oae54h=odZ5e?xRfSyu;^8lh{ zYrnegGsC8HyJNv( zym&i#yKuCb^Q0%XZ=9BQxW0B@WBFt@J`%X-7DqS~0mIAi)KAZ~RJY~YgGB@mNTf>a z8@vfsN+smk%py#&%9n(biY@#nDk<`pTr<)3^4VD~v|rW@F>}z~>or17IqBQTf8uGZ z=BH&EKkYTz1)YtAcD67Co$Xa9RgC6Mhpi*}h6j|+yrbvIYGhiSWP|TRS5WO zS|{K)sZ*BLgXX<_(5tjOT9yHl$QqL~5gJfzJ@b+f@}H=mB3AxtW96S_GGjFo=Wm@ZT>`DO27G5m zZ75(u3KsY~Cu+keSAEomb%2JbttM=3ickg7x*%$+4O?5IHm1tmk;2x+Q5)>q>F9L7ll$ltj6>;-4?&-uR z?VH8ehM}a1Wha%Y2Cc#_6}Su^hUherUiqF6TUYU^rM?WUg{Q)%)qFy1;LvYf7dGAH z(c>XzIR|ez&na6bPcw7L5!^s2>VU04O>yRib@E|m^yW2FD01%2=brz_|FjXuRcu5t zvi;)9VNQmZGu4&T>JW*_unk0_N{B@36kpJ^kp*c0wI!Nr&R|3Cik zzrTKa@1*Br_(L!7jQt(ai_y~@eb+O%!A+p~fjkUQz*+id)$x1soJ2Z%ONs34^=V}0 zXH@(isa92iWckxr?k2kUluBWmg6qNlby{A}P;*)`@vSxSdp5cZm$)PNCMBHUj^LY= zPS@qpYj5G}PdR`8v^;lHxhs(c5nRvZ!b)UPeTmc7%3^J?G$ksQrZl>Yk6!yB@$!O= zdNHSpMJRsq`W1@PdVctD_i<@|9y$UILIjhlIEo-zqs{AGz=o)aumsi`5|C)#uFdk8 zes<~yK?An{`z$0eCq*}mE*V{Fbj&$9v#i~k%SrE6 zTdvPnY@rlqwzlO~IjqFUpfDE@l$+w(=&h6|N~Y^;TzGCeF4RpkO&{eN@I+?+JRBF9 z{d1TP!24Za!^VojkCUWmJ`DmQ1)zU>YYF@gU#bGX7lglaxEe<{)QVvbwFGEqIbwM7 zQC$ZB_N^7|(lmf>iBIuBAS@NBDX>&jPu^a!2ukgMb`oTU=#xBI=8^-b8Ud3pG}EvV z*Le1eM4m^d&mT|v_!G)-IY~z; z^39kRjj;awGs*)##vgA*abHC?!N=%qTNzGSwr%viqq1|y49Qxk0+oNk^zp}oY}*(z zkIy0v@p$XFY};6zu~}py9&atrwvES`m~B&i6S8d=;arq$n}8F`woSx|XWJ@pF3z@1 z!kLt9yZGMvitOC@P#ey_AH4nM$D367=xpK%L&vdyRPU6f6|i!YXKeh*(foB9WQ7iXJK%TpO9?EqH^|@<4ksu zGZhIa;ud7tuK?xhnp>w(z#Z43vw*puZhT0H+qSUnWHK$LNdj1Ebg3wQbc-h@3H~#o2*u&dl<2Ds3r4-oT zEv_uv?gyf88xekP!^Wq^?RkCdhC5JoG)^zaPr)!Wj$fCo^y}7}qMUrnh5SYR6~*|X zyvo_~F=zYFmftu#p7PX27VzrZSScCAS-`G?(ct4|#QH|oZ08MHZHvInI@?&6nL&JW zXR1?o>q&wTAxbyY6=j#6H`yhUBMViOViFZijJFbMLdpRd%Ntgz*ZY}!zeA}Kt41b(kJ7#yT;i+-1y-@Z$ZxDK-J?2EX{!HyPp)G42d(9|$ zPH>BqCbq>^69w*r%WO$a*h*1Enk)9a4yP{I#d$SF6tOVh{?%VP2TC9TKIYpS=+*yZ zaG?RcAvZ1-vsj=B2RVMI#t#I&3b&1p!K5&PFK6zwvn05M~C3XS`>7 zHmqaSH&^I&J>I*_y#xb6JI#7zJzzU%(^3+R*sfQd>0!*OO~aT~bupR_rWX7wrN6n- z(}uZjPya2UU)C~QQT4ZuW%ZrqVfXJ!e67Jph!X*N`tO#1;+<%x`bL6NELB!zafOz6 zRNr2{@dfx+yCJ=ocvOGw!%pzXIOccy&oAkL+?=%%JzPF3zGlpF6t88i^jV9)HZHf| zYvaC7SR&f2u-t;%M=y_3zgs>lp_B=qGD`h&IjLi$j*U!RPU=&o)Elf^v6aqRuxdiC zVbz5AMyY^ViVUPz75~DY8N!Fu8@(#q^NVrlzgo4D?~2cjc?X^J+0n-noKNEfuA(il|A&jgHS~m=)ysvl3OCXlnK4R+?^{Mz*VuIloGp zMPfr1ibrp0GGFAwCHAj1vmX-l>dE>;$e@B;(O zod@GJM@TW37Ym3eCb%kVJx)NCyqSZmugB|zRQ2%sPXc(Q7p@VKs@L;A{reF#ZN1ya|PB6l`@^WZ>ZKlQ*@w&?qtc3hLgqKz`7u~u+_Ub z-V*2-Pf>j0wi9AMm|GjR0VWj5G$c^7r1sW5Gq-ZChnLKMS2E>zIciljt=^KcDC;r4s z@s5|`PdpRXTl*Ch)Fs+dS|fSIAihi;KCQ;i{k(A%DEeLJgL0&D=o58k&lebZdZYH!+ij_uYq&2@~;mZ@=_VyoRU-E~L{V6Hl3 zsL&0?z7)G*VoY5SMZxqFOIV>N7){i%)Z!KU&Plw#4(HCq%RMx83YYbnJiRo8oHuvQ zh345UuDK^o={3dSOt)IR6 z;px5r_grp=xNr2Z9=#6_E`pK3FvqVaWH8=)9Qf_9fiW)w$Kp?1LiJ3+Tl9i^%xD&r zmXh<8yG5?0Dgt35^_cDED_(ce<4oGD%O?DFqL=MtRBs7v)a-tgDHyRh{oe`yQl*-C zoH@GfF?~_guzF+E09ly+T!D^9wFJB`vxlWm+Mey?zk_9b1$$AbUZUqDA=;7Y9_maq zZ^PTHvQvB+byP!>-hb?dF>}+RDQq{I!ed5L*a=O6^gTPn)RPfp!_-5O;R4GZ3R4e5 zJB;eJ+?yYUvq80N!%7qzoZ^kU0;nB|u=%Xe?!H~vH=^58a(S3D;%5&llEA2FY1F+0>mB-*`cCW$`O8Dd}N>=Ti%6_%HhA zuXz@I(<|XB8J_8+{&8N<#BoyuACk_|2}_jf#Ex3?6n; zN?Um!MIREv+w--vF@e>5LTZwb+?~&3`(sf&Ozw}a4~V0~Z-=R!n2JK&R_c>reQG`+ z1ng~M4Ut^-SbWA2g&ZM7T0(-<&La8-qt|XbL7I2NLgm=dY?^Q56f$AlW6__oEz)hV zNQCXq`Q`k?`CCdsiPTO?yF`nKWtGs6U2D@R!EpW9U0e%3!8)q34gYXH`7r+GhYqol z5=*w_lMhkAE#>kCvj|$o{z||93Npd3<&)duHODMr&-PdLSPjq4gs+8=ViJ_ zmPWQ)a&P>e{dmOlq0fXtok%Qm)d+K>mj%#(i=hNr4VWGb&gf%mX_yI_ggFt z=v+vI0CwgF+(~dW;fL3LH9q4fYrkB)lZZ)@75-wBJn9NsFjfPykU`5dZ_A|~u+nsi z)w<0r*~TqpEnI`ot^H$N&B)4IPU%L|d|ZK(;xi^s0p(lcm2WwCF%Z9hH@FE06cFQT zRH$4X0X5)i9|kq($ZZev14}KSVEetvGi)MG#=e&TCA&w0l2aa(fOLbGoib4JHY^Z8 zmRVyBl#Knq<&>c36we@ZA$TG-U({7|YvCy;paFV!?G$w^0G;m~joZc;bS{D`^fin) zGS^#H0lRRwuEx!qe003#E!%dFI(-XD5Pf~hq5Wa^0qJOYLRaZm_Uuc_9p&^f9NIs$ zLrJ#{WrTq$=NTY`!u_bNoaaTdV5#QBD zFl$UiMX7)Xc)l zH3qBO04Lv9ssag8M|J7J14bJ(7sPst2gqnw_N1@#II%$M>`YC*t5YGc`rSpQ_7zDj28k{2GpOVc5+m$ z0eT)h#$_xJze*rpl%N?nQ6ejRl^O#NKGac(`K)U#;;y zCRwMrT{;(1o=6I(h@D^i*CJZEGf(2#v`!2Ih-Y&z+q?V|dtgM|6ro~*OQVpp>EGvm zib8LK?{hRRhGIG^GFZz+6i|S0VsnAm%>WdkXRvf6ACoqh)RSw~B{8f7lG_eI{Ac`J z&Ez}^zNs!Pqf1WxgF@(%aj+V13|;z_(n{0BH|f$F^1MMu)&~51RpqG&+Z&uKCHe0M z2xAzAOKyYKce96dYVd}xz*H_1LpB%%GbeuUh!QGc2g*|vCdB0vu zmd{H5Rs4w-_eSy}YPsn06BjR^)%*aV?u}yk?B@GCpR$i{L9IgbW)iq35m38Ms04YP;abKF>L)9+KqT1GChLueQ&yV)9vZuZ;`&vu^V6=^X$U5N&^6u8r!cMW5yz7qhSF>N@x)OLTm8Ym+wL6oVoDITVAJfCnB_qbB*^dqPnaSK|&KitLKNz4Xm9tY-@l7yT za-D$&)HPx}bfKv<3`G6E38QOyXvecQN<<=lpE@dzARb{sLMx(;CSsJN*DdtAiC*&( z`t020vsw+xaDmrLXsf-_%0qu!?Ufb}FQKhY@1MPd1{$O1q8HIPf#omLIB_Y$5m9|G zW)ISZ&9M&NJfANtH&c9wP*g}`#Oc|tVViDOZ=A4rkw1p^+k4{NhA36l%1ok_gf|zy zF!hm&wJutHQQ-k(Bh=r*=P`*{?6_Y6=f}S!Z~o$jJDWzecdT9&{g(|42rHE>5>-f# z9crl%F@g6_Ow$G0kL(w_8~Md(&L?FnKDskK#z~zZO#Yj#!%Fe} zD~?a9V)ZqS_Trwj^cu+78I$|mg&#zDE8|X^?uhPKX_sa_p}P81*}2bOwx6VvBi~rf z@`>e_253*sH%`kht;{#pi=&PRYX+`|*5ZWqxUSzOgF5G?s6il3z-~+WgY$ zeB;dg(rNj|y8P0beB+$_(&W<;u_ zHTW3%n#1|^?4aI%rx|O!l{hWnc@he!}DzVt}gcEWuf z4cpMut*`>Rvb!H-9N2+T3r)rJO52LLi{@rlJjMe;D~&>!ZWE0S=g-+_60W0GU*hFi z&+1o$`t>*Jmu0Z7#niRHG$s!)Rnd*HFa-lc{JwRhleBism(>tR^4WRfPc#Gw{>>dX zYUUKl+ zi9FJY(dy#GLtb?;81kx%47BQuKcOzF|IXJiTHXmmUiFc&v-UNNuFz=s@4z}=y>U@*i&6r7ha)wX#4+xmG zdJ#ngM%+INMhLwgkBIx8DNmFL`>Gcbg(vJ6$@yu?u^D$i8<6fD^e9#|sXFVl^R(A#vavbV_e5?Y*IwwIq@ z@gi#@&z;Hc4Mz9n`sb+k5KBb8-{Yyf!Wh9noqTEvmEqrVq}?Ul#9>H3$yAyuKA~|Y zC4{O=KgtXq=y!>XKd4evlu__Cy+Ok+`MKzF?lR9I^PF3-Yd}XFRv5-1jSVu3GQtx) z<48}~>cvh37@B?^*)yMSObHJB-Tu;!s>UxZR9<{QGwNv zdY)>S0ft#B?>7CPLWr2yug7+_r&6hR-cr`*_PqJ=G0){HKRNrU^%Ec#UHRgTv!A*P z)7d9qJT>N-5-w0gl?)@e5Im?GVp%bK>ZS@P)5c32?|n;K{j)SX6NEhj(VaFDkV}4o zY31lnql@$)jC)AeU~X2P_u$|#BEc;TZIB%F6zsitZsA6KP#F%&!;RXg-3(iB3peV$ ziXoZt9Ry!vNp=3n5eeqj`d8=97mN-71As$!cn4%ASR%g{nInZ|w zmOG;a6^x5`3%R0k2|yLoD7e8Lb2!MVHo|%HKk>oypHJW1I+y(e>!)k_C#J9ZC#Em_ z$cE#*?V&?al;32h$I#&RKjXfaKIbhvqaXhk({XDhU6aBckk`q0LX(1)g? zrQ9NhX1L#{5AEFb@$yR_czr~B*tugjjJr74H|hZR+P?bw%K(3zS=T(9ZE6ae(qU6; z*tGb+lx15xv#qPbrj=pStzpxO>}Kh>2(ugX+az(M>}IKq35^df%xM% zQu`5Rg)oL$UA)7ri2h;rF@D(@k{eId19TFS_X@@?!1^Z)f6jUTK8R5z8ZsIPm&Zp1mSnpj-sj&^Mv2EMt#>_B238* zCc?<6PATV~7juecQdE!gT_d1;*E;)Mb=KhqE~f@aIh%?(H&{*umxbSzC+u1mcA*<& zrL#u1N~u5MQ+KU2l`&l_!>(Jyt`$-sVkXNvmFTwve#xi9F8*%~yB14}NXaaozYSlQ zKP(N5iuUl_VoZ_TilYUet$-PY2kg{wG@xAMp8=1JMF$G^|F6TmcSLU}?_fks2ypl5 zzB5W?Z-=CMHG~}4b4a`{JqPc8cND8P{uk0n6YbQvMsBjvLi29C??(hc?ioJ@tMo^u zgvMSNqn@L)v0o?NMj|i!G781Vb`<{Mhsf88kX_)M_nIjRB%wqr7qj-yuCIe!x-WS` z!AN!Uyfr!6t_dE~eZncF!9;)z*(Xm(E&Ul3OaGKIBg9o~JtCBU`G%cZB3#Vc3J<7C zJ&`MglvZp|S^6iTN6P_8 zU8Q512bYJlj9eDp0m23iExZFIHFt86y90FYPHu2_prmHegs6w3unmj{$$a2E7w;h2 ztg7xlMG!)F^PWRMO6!R+dVTWiU%6?To4{m9#Ue$m2ZG&V^V*ikKrq zdPqa+fs(*c375ncY`9q=MP{<|@;^(S(1g(L(X52`CR0AiA43$N)u2EZg!C0;{s^A0_bP z*~TdZek|KEmB6Xl_Ky)bCENIM0zZ*$sV49bv+aLC;2&fg|B%4yY|AGI{Nrrh{A9M}Qv}v#+pi(;Gg&Swk1zamw&hv^ug$i9n!uUa z#?KHqJKIu6;AgY#vk07(ZTu{Ob=j8L1b#l-K8L{1WgD*}@Vacv=LnpWZU209kIo>G z+%@br180r>>n2(DTzH=)v?=@`Q+1!_`q=*#Ha0{HgRmutIx55V%4mL7*jN=UoDyO# ztD`z>ua4$V3md0J3v0rbny6!X*gid)UmG^oMhj<#Eiax1;*r|nztpm^5 zK_L}e_mjbvVlw8AFEm;yEF5-x#e2(GqgV*P?+IMUi%U)&E2^e+`aJ(aS-AT``_b3( zJx{Tt@cpF=Ul5FTinNyR*+Yc9KF8u%`S2|Amhy}nnP=Qko)Oh7jt#44!pJ-mT%Kq_ z46;s?-*YT*LWL85gy(14Z@jfm%)2HaVYugKv57wl7so14r-7opc;6p#A;qrln@`d! z4S;OOx&fHzb-|gvdf(>Cz;{M{_dQ%tR_uGYVD#u>?-q|P_B&i$Rz8%r*k{s?8%kU3 zFKLIBTkI=oC%ClH;?bV)NXk+ly6A({m1!=pN>;Wc<`k7_HJIM9UHeBYhvGW0wGn=2 z6RE=QZ07gPU0P9%D^Yxd;&`;$fpgsL@_Lna#g){!iaUqLQR2PgR_Oa_{Jv(gv*u7H zv2bPl@y^5;%F0dwo^>WhXFHLsq>`aX312M|Z#5g_>r0h(L9rGNY z2vcUD-e=Q@Q)VV+(}+`MCS=oyQ)Vv8rV*#i#Ik9`DKqhG8ga_Z#o09Cl$lA{^u_VV zZ>Y#N#eFM2q*ipI4vnHO$t_3(cmS@MGRYZbQOy4MW!W@M$&AmUo&E95xNMqcWX5LE z(M}WoM;b7HWz-N<15pOGG7Sn%d>(+0|7RZl57IiV%I76%LyMz^#IzE`QnrcDpPiZm z!)E}rC_~LzHC#ADjp^!Q-TxV+mN);3IoG`P_^^d%s_Z!Mbxn}>Ku)<(da-)`HV73S{Y?xxy7RcnLk1_$|YPpx|+8EmaTPi zLD@8ASulE<3Ryh5#>LfaH1*=~zHhi~X=5{v1%1u4v-hn2P+g1R%D?=+Th7m-SrKe` zloTws+#0s5C@LI`{_SJ*$ZZ0L-vJYM`dnV^U~wgz-a%lZAsxdm1M%L^v8uyVFw3BE z{JuZb;@<7)?|-4>4j$@j2p+m{gNH7n1P@(|!NZ{}1`msQfDOa*01u0KJjvZvWALz; z2N*Ft5AaZ42M--6G^ltEJXEUT@G$yImoLCt&{=TQ0a00n1&*GT524avE;rS%Ihg$y z8ua+4Bdn}zUV8I4c&>T2ICwvoZ}+kB9;Uk57{4I0TNr0aRWWQahldYbTm?K;jqxWI z2jxSdoFT=~aYLcN(_(0BD3r6K7&>7n6nLu8XmL>C0>i~s6{RrWD#H91Ky7hVWhrG< z>1&EQU+noGY0=t3`odXjZ_d*t0IX`=4HTd&PiRIT!tV&*T^fFPdzp~VA|vk5`zFHk z%~+7KJFaw}AbyDAsY=s*Qf0bNrkL&%bfuvC1YIfUK0#N?bkmhm8@AS>`y^~dR|+~$ z>P+X!9DnPV@z?+H`~HP)`u2KyA$r;hZa4XPP+;HAcU5=f=T&mS%y&)8&#TIJ)#T?* z$#+f9kIr`mdX4O=)N5o{m0lyersU^U=eu~uoR;sJnV(ma@2bnso1VuI_&o5dhK+a5 z92M{1lf}61opq>qm6lixi@qD*^kYUjexFkgy&|5|smG=0qtVMe{K|CjpYP#@!X??P z!gDV!DT>ug?E0CM#r=il&@3yv=hG2eQW}2(9kEsrK#a6g>_CmT7HWspP|_5ebedv0 zQrEAT{llJXnC+K&g}#!fq!Z$|ZC`ngK6UAeJ#FyvME9$@C9c1fp4=YIt6zT`4J5i{ zWga>E&Iv&12|>QTa^vn=lg%%fZ2oa%?=8u|nCJ4B*|~5$Uw_b23|fjqO0n*QK=WM_ zjzsHDy04?rI&2>+tkC^$)WtjJ_}5+3VxuGvUA!l4l!a5x`ksckbo;E*K%<3wQQ@-P zHqhMdhmW!=2%D~R(UC(hlE*)g{^MwbLhj?}Kc-oR4>9sXDv(|Hndiwu=i?C)|VlCTsa^`7*-;R+RxH~EJfYNyI<2Bc=gTk-X3}#-Po{x9p@@j zaYu#2Tf@5o7)@b}bKc1NAt9b>gS-1=~bply*DX>#4WO+9IkH9!_Nj&~oj>CsJ{$MjyI#?g&U z@%#RQ51XQ6!|{Da_F{jqhIftD6a_{OZH|g(GFdf8OudV|3nYr! zl!vH_NVDfX0mlgM>uTu65*eaI-n`&@gy84ldDR|%9-dd@;OF6awZPBA^Xh<~hXp?m z3vyN-o)-W=4-2?XIXtf(_<49<6Y%r!yjI}n;dyC;pEn!)ytM>BSNxOU=e#HXrQzp^ zzZt6GUj%-x8iJpP>#K|K^Kd=LX{+fjIbym?fTDt*hwHZw!Oz3>J1xa-OR@ih!q1ie z4EUM|dXEdj3V==PNsXUH^;W=e-+%WN}mc??7nD9vV>Z|5xzSy@wY5?&m}L z;{N>;hLWGHiPIt67s6MYB&?!`*#;!vV$pL~{t$~d8{b8~q7c2%J-MT`aQn+#0va1i zFg!MDQAeTY=`&~aMBLg@SoBl)ms_euac^vB!>$}=3<^K^Yc3A$4Y~C}$0Df5UH=X5 z^)a&ocng=%XaFoEc@hlOc#vS1I3=8E92`TOk@q$>A%l*9&zDNPA%6@*2Zz>h4v^X* z{t&VG1oP32glMJMfQ;D)#em`?F`a0^{1`C<#;(HqJDf^rhUaG)L5bubPwI`ZjBr!* zECpw=5*VY(iXywr;@IdhaO|@$i|Uwodo(7QgjCQ6AE$=F-`61VQJkkiyVaKc$EMO2 zOesV$aVjBI2)mqkSKw7OJ|zYftz^08-EA7!l?1@DOmSDC%a4K?!jw)TyI9nlZ%w|&yPa+ z7r)RH$w(P8`Od3jc87QE!AyyN--r2?xOQpk0pqJ47G8Y7yU_ZpGhUUx-*r#C2IW$& zxTiOuydvIPZKS&fR?5hS28aNVK=&L8CXRe*(2GI~j|=UIZ1PC_@kUgLy++8T+0^R< zemI-_8G)0t%|{8W%%*-$;77B`Ul90_Z1WogUY1Q22&~E`-z4zk+2*$h{8%=1jKHbc z(yGQ!XPaq2%V)DG8qhu~o1|5Zv$M@Kpyl(~ z6b)$qTsBFo8tbyi=RMvsC!6|vp763s2B-15u)Q|h{3~7+vv9d2VP#`5cn{z)fQytp zL|pjAA~JInJQztMUh&=H>^VQrEa_+;R%T%O#x^Sa$1q&s z88>E+pwB(uVb#Ou@~z!uZWpXNUHICs+yd|Di1+?hWj3E~%iWjdtMIMEMA>3Ih*vzT z2XJ*XoOC+@f=`(zm6a7k7MG1iUD0S4oEQo%C$#*WP_*onjXNh4Z8~MKb3!K&I$=a8 z0RV;*(M8a>-re)7;#~USYhltmXuWY zvCgErk98*1eJm-d?qi)vbsy`j71ez#DXH#bok?{c>rATqSW{Bn$2ybhzQ4zZ>OR)H z>47iUqCaY?`;L3neb$)q?El5%FUd8Grv(G#r8zHJb6&zjbACiYH0K~bn)7$~XwHxE z(VUm@(VRhiG$)Ub=KKU7&G{)lnsW#r%{h#Z=DdO^Q8x9egovQ64ALCw5atCIO@fUp zX#^+YiRipfbu4tJF%sVI6Yixn2Hu@(h);8Q20Wc}ziE;b`=%$Yb7-E}^O`A^d=YhB zmZ!$&A=#V7`G4{3JWT&E|-_wpVy7Xmr_vxCd z*OHt5Q;{_7c%)OOvliz`ea7A$+C8=oC9u7hvRXKDX4$@s-xtG+l!|2?xmQ#pM9>&} zB{sBIilOY4*w9`nhO$>;Lwltd%3g^L?UiCEdnGosR}@OXKWVR^;mR(vxdj3G4$im7 zJRSLsVB+vSM!x2gj)Z)2wi|~{?=jp`tL_iD3-hxyfsA4PZ{0JEO=Zz{HmTX=pAWl_ zhmbl@b?{VtQ$YiF-wu9fealgzq@n==#D@WSGJx{!M?s#?wVPPHN6i7l5*J{&dPJ)o0v{Y(ueFsQGBvwC(02{%*fU6vlF#j+&9PFsp9tS z{|9!WKKCC>)c*|(;6H9BYEmh~1?)tX7~wp^xUcLeyl+FH+;?PgfZefgoMKjA)Lxj7 zxUhn1(jy;;!d4W#R)B9$K!Gc~xRa#yAMYq|?dDkCG#4gM=Mi8{G*{%4rw{GVKmE#q zSDw!AdF8qMbCRfk1>O53tjHtR8Z1F-AJ*PX6D~}=88Mfev}_p#NDpA*2IP;VO7?68 zgS1D@F@I?{I>uh3CI1SSn@8r)Xr2UWZ=|c4+hIjaA9Cv6{}rkAeS~lXgELY^=(LxU zGW`oDjJ=3*Gq+QN?n!|pBH#daYX0ZJ(Wqo9{B|0hCLDE4nLAbPJ0?k=5u14iv5Z6y z2?7+}*1V?_6q8w`TK4hB9gWv(mz9b#U#3cnkhJ*<@}UMz)tpYJOcJRep(2s4C~FN}pP_WbRbQ2KbY@17#&aU@Xlx@K$d3>yIo*6CW@Nqt2QY6nw3wMs2)<3_ z8~?frDe+OOKPA5PMUqaK_3eO=)d?iCkiEh@HSY>9-u3>Or=xnbhZCiUU%+F

%J3n*6SRQZT8P-w5Zh zjP+6jt>$OL#WJsZB#GO%oL?R;^AW0JF<;*G3)OGF`mIpE&+}WCT)X}mp0cy*B0w=& zkFHZ8x$b%?9Gz<|5A6XDW1nwEXrkAoz2tiArM`)t{)j;Tal;VprJ96Igr^4^4cjax zlOBo4Y|%Ui=w+@O#+kli-1Q1I=e+WvK94j3zM(cM3CAY4%4=8~C(Jb^)p@yDUyE~8 zSvFd=&+{UiAjL!P`_E8MEb|IbV7U8P0f_xKLj^b&4FJ3lz`9Q$k|8ey&^Ir_>k%?} zACc-g$D`HNe#xZ4m657>j`AW?2jBa=WO{_EqIk(Oy_NfI9+8}^@AJz%_T(4LV|(m4 z9#`37wEJghXlxxBz9&h*4LAxXt!5&j8LIs>PlV{CCKWW#afVjEMy%T>ZGvavsXV+s z#k4-vSiYqyxGVxc`YA<$!u-#J+MO>+ns@i_BI6tHwx0_R}}fPNNsJI7AV3tB1>dgtaIKl8OQ3%fA#4rcRZXg2GiT{4YCTb2MRC8}MXl=lK!1HloL%3* z)B%-^{>I8`H5%=B7LwAog5)@bq-tsZg@QvybIp#wQI{)p)f(Ohb~Nzi|B4k+-6QD+ zqAuV5_lRD3d=D{15lN zr}%4muZvdpfJ$W#XvQA!0%gifC6sq5X_P&nGD7SDq4#z!=m1l# zrjFmRRp3WqtH57}K*gpMZ@u(NQ(T&q%MSHyPdiY|-mZCG7l^0s(C&YZ7k%3xM)o)e zYl)CKOYM`Y$^AoQV&M_;iJ8E;y1(@TKOPW{eLyt=X>`T1{q_ObWA_yKHP1|9&t_Ub zrcrb9zX>YI#7OqtxQ!UC<~o{8ECBmRVHPE-g?Vj6sLzia1WtQ7yEkx?s+^pT4av8w z23OJ<+Y&*f#oviEG&^l@qqJ0cr2jk8bNA{s|B#-my)4I0Ns3e_8g*9&s(j-L1N=nQ zb=>8xpGi=l^y{&irm&gXkIiLgVH4xc@E7)B;(rPY&p-@nRoUKpDcZ!t>v@cK{}%Fu zial2lq^MF^K?}Ef4Q$&FSGRCC_7sd8I5oT4q->vYLx4LHlySpbuLSvA88ytF;Fn#3 zAmb$Fi{;Z5zvmRwK%+SU0Xw){T?UTVF4X z)8cXPOY>S7>AjnIR=*~+_o`UG=)&i(ZAEWjhN^A5O7bL7m@*A!>l)d5F{r7n{u|)7 zK0>o0!hgiHocsbXCltS%mJ{jTA-+D-om1T~~IjwG4(9=N8tKAlB-~#g-0n5V&VCS zHc%EG!BJUwYI(tD)*uUyNsnaCnPTAyCm3Mi$y62|CEVywGrWqp1^!4l+9a(oZHwn- zK+{r|kd>z!1Dpsmnb0dM&oElqOm#V_Ob&CIoWr08ugJW;PJt0C&l>!G8?jGnU{%x!c85O>D9z%xl8If-Qp0kaU)=G+n3U$YHN=!P zKL&!?5(=P9iu(ZKeqL!fCO~?S#s5Em2m_4rvzf!9KVWJ(!qWcl5q+X5MXUKGF(;a` z9p5v{agG`+rnjP-9g@ubRk>(D$>uN@(x0%n`u{@O^Fu=5`+qeUQXBTQa2*^o!%$wf z1nv@ymWSY+D>H8yfXxE~U^plC(E(#VWrYY(G|xz4W@`4*np?@?ze2irSnYc}aKqP@ zb=iQjQU;Xec(^Zn`d&fz;>l;TI-|}n%24snp9cq ziV4zt`mA-*GfZopM*t~OEIlwDC?j9)xTf@f7&iLNFS+-(@v^gTadp+}u5At~M=>lgBj z(L7USKeU=nM>vZ@OnXbl#^uDWouxk_N`cR#W{Y{fvmKYrc3eE$kv!WmY_=nIwnLje zTl1VEi8sN$%^rx7hb{t5^a(i8?7igZPkKAUE? zOKh5-yce5r>HDz>xA6Bz{(f;JHsMMBZvFtCI>$Yi#YWeqpuSO4*R(pUw>XcHWq5O>#W=QwTp?$z&9bSh zZ=V+Xhcn}L7}s-i-q37!&!`ojs)Nb49Y+ZYrK&zE8>RXUZl<-Z(0E0ZiXP5xdFyS= zS;R!OoP>BnzKRu9?1vFrjkt6oV2lKSX1qIVp=<%tYCfgIRG(o(3az%p94R%=AA%yJSv;VFD4=ztfsEIqR<$|htHO?6nOg13A@X!drp=e1;B(34nn1N zNzwTHan_d|A#jVJ#YWRdKMSGcd^WgIs!W)>FYFg(2`Lelvd73!WLT&l*asG?-MIdH zAXWMCU{VDb%N=QsTn?f;rgMQ?+^pIMqU^vb%@{oke~DSLRJp>SBS`^Ffg~=!cF}2L z#6J2|H~?=U%LP_=KmO5m!c$QCui!4EcO>!gU-BWu36UM6nNZvq2Tb$ov5R?=yNu<; zZ1qt_wgK(TejLv9i$4c_U9wl5b-SboDnnJcqN{ZmdHDoZ-RWoreuYxazpyFIkrj2=X&;H0GuBmA$95NoAmU(RTXLBj)T`7u9b-;EOa-8JkLy}skoriI$(D2JQ z2Pmmc8V-zT%s0?L=x{iTX>^X%XW6`X_Q!RF&3lv0TWkvqpC|u}5?Z&wnDz^Di{GPb z)mgIP91qhd|e3^aatQ>e!IQsciM1qhrWxcwUx-XpjQg?Gx!ITU^hs9_3se^1I& z4P43&TJc1klPFY$q526c>|t(Uw`D8Z!UPFr6@ub0fMNUR)XriWMKpd8{-xAHh$&(9 z;f5-3HxJhPzG}3TGje?E66*lj6p|-lk z=3Ud*R)3;#ii}@Lerc;Z1;}Y1)LgjUxE=YgPX7-xHqBPe#ff*JMI;f_{$Zi1YA&3Y zRMwjS7 z4G~S<;z`ZoNUn>-S36)n;`j=g4t8&&bc^GeQk@8aznjozfxiRz^*AOA)b>e%gGvgh zPj0$2o)v_HlO^<=d;7|58zkI#D33JCQAxSQVC{>o=>v0^3^*`^hSoR|!UhfAqS%~l z`Y$QBm%SjAmW@otQz8$t*N|Y8P&?QbptEN;>C22TVgLI#$o;>Nw#itj60aq3WU$Tu zA}9c>O<>Wvxb~&95B+a|$B-9%z3gwNxwy@|mQZq?eLkqHtCvglg34$wdlFNSx{QTORHjwSf~ z_t|dKZ@w?lH}gKEe-~$mx$B^n%g)|1_V2Vy4i&6powDQn*^>xDYDPiauGLamUI#JGogcEA9`&xXLG`OrFO39Bg-Q z7m2N*)s{zmKbR!p?(O0$n-IeRKFEQL;yA~MGLtC#^qs~kGZ<&W-n_W8vHFssejrvK z$T!u@vs1yL!8Y*@^|XZmItSNc_DAW6Hs{405bonxxnnkzpoYBkvrvapGCMf=XkSVJYd68l zO7JLbYFz^&-SJXj@M$Sb;)mr2lWKwJj#tI|SIk7f*~_}5+azJlDJw>W4kkSyg(4?& z7w+WSU22yjhTh6VZ)HXEebn1BqxnAZZ7`!Hw@h5N;C!SaCnO_-tI7sDExCd8oY}iY z638J$dPSgRNib=W5NECp zlW~9pr6pbjTlxt~dWi5PW;<{%Ed{A^?Bxl@N`$>gLUDSN{1J3~gRP<rN-DeW4`&WydPz$ zCUM*(o9&&Khqzv!ta=Q(FFS)mji&gO{e7JETTG8TPgBZ)LpgNc$LvE`(+?&|2U^q= z949Rs9QQE4da!69pUZv5LyT8oH>S1RDKhyn(v&?6L5qTKmetC0!x8CIZ{k5sw=+f&XN>(dY95RNDpKP%aVi^EDBkY$f9m7OMP0Zwn1W^mX6pGB^hha;7UvJ zrOImxk`19wYkG+{#Y4X7RsIFYY(&vS!O4eSs^?q5whhuOA~0r#d(sY0YlnJL+Tfl(u5UjJ_1&VYYqZeeS8e`fQp z+n;%Q#ZAU~+AJf+@d+O{1h129Mjb&+pZA6DkJsD7ATkymbaMQy-7!qyEJ9ke%d}*_ zzNjtmhnAcpPeOz0bw{4(u@e7-M|3e=3n6lj*hGEr{G&?H`9df`XMciDl|XKA4;m>{ zM%?x|ztf^>Ey7aBA)-)NHk+YT-E?FpbeV)U8K=KwJX8EHP6{Q}pMk|RCd3?ss=ELl z4~L3x5tM7M`OO7z%Gtgap#>5mbPREEknI|R$eZ( z3~kQ-79Z)gdEdq5C+YFJ<;y=^-7Eky-(2xVK~ZVOoDa2{VUPs3f9y=DvC3<*c!SC5 zwKi>$lxUEd@2ri~-^myw#jX4CRvWMcLt~M`k5-xIj*_?(Tp~-3n9RnNSBG$N-)^T~wP>}nsMb|*dx*Mc2_pI0pgPTRH{TV}+XjBw03(P($ZLbB&gX_TdRyFa{CZ{e24#t);dlgt|_B{M$Va(e45wYj#k zr!;7o8%M5ZR4z?9KMHbdVor~37B_@to?iQN=G4aogd&?Lq4;rYEW1*D&seFM+8Hem!(!$Bm4}H!zZia7$O`=)5;@d5Ke?WCw=0d#p|y zul93ra$E^MfuWMDB^n;$+}mlZ_COhLh8z{+|I z-&4+=L4|^GcNKBt1RD!jw?qlX(yO`R={E32zEGesTcwg$i%!v?{uW5K<{N{`8!WZfYW$$VhO88aZR!wbbZ+3-+`#)PqKydA#XykLje=l8 zFoLt-Y;lgvo$k6}q2r`r`)&FPCSmag8;9RN*Obd}9DyY+y#Zq$x8yzCKppn@88%1E zR)t3{ypamEIzN}X(4dY53P(FA+%$EFV;18;76(4_Nj%orhOZMD=;G_5sh(N6Aa}up zYZ{0*ej7?+%?-v7wID9nUkE3YJ%-f^p_pVy@DC%9nk>EzaN;2?7!f+yD>oU+T|*gT zgd<_d@1X#jD+J?dzruprF;^ByuXq6le>(MtA{wP(agP&2-KHcvt++eZ7xemadh!bj zeCv)Ia()E4f=JJz)M+cfhf7d)RIR*G?`ixf(r44S{`J~?<63r^qDf)F|4t~o^-NKr z0BB{{FahLk=s{oePVVJb_H@H-Op^zoPS4vUJ+GZ}mhY%bAXk@Wq&twuk(`lm_Jp2Y zMdE20FN=kR6PgP5^knB36mk>U@!(q`f}0&vm@#aimrx_Vt`Gxz54Pjuq*9LbI2GP& z6kzg}D+UXKNiRy7aOaTFyE@%3Xccan)QOHEKSk%&F_d%C!c9{pmHd2ds&b6L2@I!b z9e=YRkXo@|Khn4%jmF?gso-RJ2_`iV< zQaa12C|o1l&I}n(%|}!KOH-l*!LJ4S;@iD*Z^wr0K55rHFA-XvTB27jbT7@Yv+f>O zeNswV`w7R2E0<>d++x9xQA_SpH-Ujl@hfkBSIhTZSx86oyGFhjXR(RG{PxKA+$>-C z`(F9JA?v~L_kHqxRTeuu%=C5geM#2F$j~<#ErDTKFUmJJ7cK=6u2fiBZV&D)@m4Oy zHyIV2Cm)M%zfJLCNWN_nI}P$o9Zt_TenStCi;qNAgdTW(z;2~96?f>Kudq+*f}*W+ zKg@phRmx!}wRBqT$t2G8|%*AbZ1X6>2+a+E^~e^&z0O!K&X_H?06CE0Ifiee=J?^WESAR3gxD^xGLv<`>76X4^k1d7$REq< zVHivC$RSroJk!7zi1`Y3#nQdNBFj&J)|G|FqvUt0*#Wo{R?OVwB@T zX&0kds5M7!w!=>z7r5dydbk|=H?}|tvBGn~o*f|=s-Nh(;l&1YJ6S0Z=HXaA<2RQ> zSH6Uwy!=9IH+N+IR7HoTXQq<8G{jO08#8Ffz#tf_fHoTq{g@ei!TJcgZHMzRnF-7m>%nEdZTfz3@+T4JZ++W%o*Kmo#K6f88ENk)``YmUH9EN8MK7 zn{#BIZ_fUC>MBs(tZ-ECA zjE`mBwLQ6SvG&3x=Fu;V4#GO7r;{)KDYSl}2AS zp8Ppe&~=IHtlo8r;)kl+Io5c8eVDyoTrKQnpbj&kl#Y!To{Pz!u`Xd%yYeFwiw_sm zz098^*JXj7tPSd@5yxpyeP^RTakFegEEu~BuRgmpkL<*gD}+T%L%1-}tazl6Y9z8M zCMPXZRR!2%z+p1Vrx2~?X{K{>@yrh@zVbi9yNjKWHXQln;vj|;X%O|y&eTrZn%dlm z@?~lvH2!J?80wZ*vw#eej%GR2>N?!%YOJWWH)mTd{!C)VfsHG@HFhVuBmnhA9I)*J z*L~pP1AqrA%NXwWJF5>{EL~MJWYgv8dt)1&sm-~OX+-HU)%ZuzEbeA2p;6uiM+11b zK6(av1!72ZH4%C<8gN|3f<(5YBm^|B8tRj&Lu%~%XIFz z_Q##JM!zjLI46d3IAdH**K?`skTZ=qOR=8G#zp|9!)ej-v_fN9h3b`7adSj(w>Tr&p+IZ= zWm1^R6(AV{5=J>irXLt5Ofz=hgq))VB^U2mqgB~5n^p`#G)$mf1Mx~mG)H!~kKEV& zwtj}z$=tU#^%VnBld^Heg$47zpKk;wMObWUwb8lW1zmchz7OV(_3T}HNmUf`WyJ-U zAeNGrlvIBml*+rPO8|ogmzE9a9_bCts0IuUgL~!O*kS3y zeZ3OtPPa^CYvO8~-hq^#Ys6D~rpjZDCBF{$!88_a28lEJ;43hN3|_0=a$%}*BxO3| zWR``rhMv~u(iU5Cnq`perZ=)na&)0@afI;^b`*xC0^<@uLpxdEJ{E`+!Hs~Qr_wZq z)0|o<19w-yw5Oq}Y$B|J_k6qiT`3>4)VjE}NRT*0Ctes&VZ$maPb)C)y6b$xc%ZgF zSpLdZ8f5R{!ojllHz=}U**oiTWN(^rv0&94Sk5|O0`y8SzH5eC&F}&gxy*J)yeW!x zIo&y-5!o(cSfA5w-911MTe(*1&9p)Cm)$hUO8yoW;7MmLV+Zw>n?IMNzp>c(ZOI_X ztG3>l8xAfvrB+`9pH(~LYf<^AB}Id4*MN*N_XCYIe)Jf%n_^_Gpmr_(M?^->Nnob= zA@yueZ(u1wjU70Vm*Xt9SZ1yhc7iG6MB=<6LmNU#LS$; zb>q>mhT;wcv%|^SS={!a#uKfq61~W#DK+VKUY$+CK7`TrTj}#B0 z#9(gkQDh5qy9^dR%N$GGJvokB-94o^B9#kL$r2v3%mI$ui`_jxa9rZ^~tnt;PUtDjOOm6cK4C4L!(oVX_dBA1Iv@8wW(AaF*B9r>=EO#7Ys|_IVN8{ zi$38!DUbIrw1l0d+RC;oOSQ^ve4Kp6l7hEpZM#2`uf|Ps8er_UiI&q=OV+E%NzMHx zkiXDMT4nfy+v4!pQ0FPVXx>-sr{70!aKJ^h7svPjTztD|H6H@b`Id7g~&SL83+@rkk&X z(@O^V#r4k`yM)gL#Ukv-f1nEHs&i+r=M&3%kiX<{?#)Vo0@+ z$`uExcbGI_RNO~0$oCO(NkWrSt$6f5LNtfAB`5e3D(r?xJpA%X-fgAYXA%YW#R2|y zjI)3`jo)Pjb0OCOA(vF_U^od+$W}z<2a+!8)l7>e%Q8h6MCR|MS|TJHKiQ(#EfE$Q zpOm29$B#=<&k~8BD6OO#u7o5dUHmmrva~0ST>J&!)UgB>C_WB0J3b{~H!oj;*1{HE z_Q(s>S=hzj$NZh)?=z{&VEiWbf}gv`rnzNH1^AzUv{|7}F|OFeEMBmsxZW_fz=EXX z$Cwyb^v z|KM*Qe>ChlE!2xfnPowZ*Y5pT(V+6Auy_g`|CIFQVAUoR)M*p8#Ul67o5g}ct=O4G zioGP=D@pf=vWl~$k0Yu%%Gp)rK55Z>TTPnMWJL{|yQ&lm?Sh3$8aH20l>6vt)fx%j z`KfXGe)l$cv6~N@H!;o9 zMAY-@IbY~YpDx~Zp8O+{gwMznRm<~h5*5)CikmlgAp`v1qmBawtm~)4qMXA^_4Nyh zaQ!{f_0gHXP7BydU!69x)VqE$fgOL6-&g+#2X%blePlTwPaLYhokz3UjB9zK&i4bp z+77p!Y-_A*Ox51PxLbYLW@E5^+uH7KN(heqe^5+Et65IR2chB#_>(I5gSM~QcGT^l z;JVHMShO8!J2G_pxwsgOv|LB)q4p-@W!?fO4z(X@@A$MyJ&dLU_5VR_4*W@ePk4UB zZ??m3RyC%{d`29MdIoYdb+n!@@sLnr<~WK#5$`mf7ih}Ao?TEoF!3SEbk#GM1}#-# z*h5>JhO%DW(HR;xWxU+p8NMv8OI;IpJTRm$_|T#rt~zC+sn1C?cYa~c@y+SXpZ9$u z6=ua$a44szl-ud5A|2L_JMFE5$K#HMj^2ZZJ6bzBJGwfIcN#msI@Hl5n;fg(LkF<9 zcU6_r1)@8ar$#sZ-oi4GBYG1v7faK@s0PsA#PDNj(r>ge!R)HST-{aWupWw(5ckoc zY-bKud1t0Z^QjNjp4l1OcsQ1Cn>Q}?z?G@)?Lz?xSKVl9VsD;(0E`F~&{SGIHC5q# zW$LX>rMfM(G>gwmO5L3)j-)5v?Nl#un?!{${3uShHg!l!G>k)useoj#G@%9(&kb{A znfNa01AK+RbABrfI9UO9pBm$cF?0ppxU;{ILs3Vi3$+TxpyJ_!V-E67-rvZ%YRk%& zgYmrt1hyVxEp%|g{o#aA*s|(32jh=R!vE}3oJ5&IaVpZA@terxUO8RR_qY#NHGUl9 zSQd!(JwZlf%iYoZ%|%|^U5LoLdvRK+Ru#kFEd*5+5mdE=pu0=6`ME2fpH;W>Q&~a8 z4+*Qfo3N_YgjH2l_+D}W)3;TAzvtY5Qa&4Jq95B>mbqYHc~vpKRSCYTgzq^&b05~& zDcIQPk7TprDDMTnRW@IhZ8n~!cDfH+9hgyTV{&KBc6Jvp#&PZD1Rzgno3Lx1zActY&A5%QuBctMMqX zu?r#w9>HQ5^l|C9N~IaV0>j1m@rHT12c!VTmK1WhoM4qh2NTA zA+$_9B8~R}E3cKC13S9Vh&QLp~ld?kOGlo*oF76iM0y7Z=A0@ zq>`xl%1NcCnDZ4Nqw*G0S_!kD;=Xyxm!gsIhr&HjAqve?W@zpU=`B(>G{w*?>@Bq7 zm0b~{-MSQBE89>-g=Qnh2`$Y^$vnoK!;teM-kAZMZ>BP6f+7SWDN6bHG5Kai`X(wM zIcS0sq6NykIYAK`g(oON1ZHTX{}CecZh)Q`=c1V!JMFyxvx!P*ej+NqsU&Zw6sEd` zUppsRS5^_W#~`ZR-*|h)G}`v)y2Vu<#uM;60iA7x(fFcr`P}K(J7n^sH%I)S3QCOg9X} zgaq=hY)j!~+yBQ&3`@hnD>~mK294&QPhzrDzIhTeBED16d_u^}gov7^me{hmy{+#e@YpX%ggMI1Ekl5PR@ zNih(xlD`Sqix|;Iryg3EYByNU7-%rcdf~qml1UBfIUj3HLZ^`WN}2XTs6u99J6^Y{ z4Obky8F$9y_NktfyHv(2)r++OKAGf6TQMwQ^JgG6VUOFN5WB1TB&E4D_W?>3qqJpn z6%EVuZda*xTD7SCnD!xgN5tEoQ~l#z$`*6Xjuw*5A1BzIUG!*A_D_|Bx>CJJ(Z!QI z`IUlB;W(*>Ns#%kn;f`u-=)B70^W!yvy_F0dCy9q#Bp-=TvZF2L%%CMx8J8=C#n~L zyrbgh0(rg|lMEmWUt{nQQCji_e=?VEq(QKZxH&bBVjG3~nW`_8A!Ljc zPvDyp$UHvbL((Dv76Nl2@-{Ju7J_|6el+(5Q5^DBZ9TDT8s?*Io1e-S6>wt3REs6H zts1w^Lfa##G-LJUZ@)kvg-;sI?o2(7ye-!oDQxwH>a;KS=t|I1^id`aAkW)p$ z&0O>)Pzl|BikcLw`dSoC?-Buu*@0gf89$#$&Q=8P_^d9IArNY6@I}&y$*QD_l$|_z zm!V6UDUn1FtQV?3L z(i6I>G`F~33mINUqe%+se6pP#&$Zj6_av5Tr-1btwxLgX z9s~9&Wb0eo^j%0pvQ?ild1eEkRDa0|QV<@v>3DtEL7rER_d%T>Zz8*L59JB;5o6L6 zDf^)-NUua-OS0~iWlbCSCahRYCyo`UW2ATTRI^w3y^P+T~+v5^2?rmz0DkE0PlR78GQmXdb*pGc6H<7__2-n zeCn!-ZKH+gVJo}91;e0859a?2zq>YHOb-9!)JtU$W1Ht0f-tU-I7B=dhvpxoIcb2m zS~0qjp!%fQc=fy^Enc@mdl$6A1g9>O?r&h{G9W2IgZ=Is8&_E8tcF!A;usr-fT&W6 z0C+lL4n+wsG%?SS$9a-yHM3|nvnpXp7tXHLOfci5yF{< z#=Rz;W4EcsR~AVZ^A8fnCo6Uw5tYxAc5mH08iM%qOfZVWq}HSyNK1@|ik6N2RMI@Z zQDfj@A9 zM`=PudhL$w){sb_#z=jEqSv6Xl~%E}MWqOtAs4Fw3ELF80(S^^_v5N8xBAXlxNSs~ zFxuveLd9G!2{(`7tG|z}9=9S2ep!8q($db7RZ%i@D`I|~#WoqMmCgOJU5vK*7erWC zIT4dBYj2jy@n}glh9_|nSE#ok@v9rf++6*!OAva`RkT%xtH8IjERnrI?dPAQ#A@^ubx08K#=h~EII8RJAhB=Hr04kTgHJk@>Gz}!IHWk@b;RQdch z0zq(tM;%F$c#7G_qq2GWmk}+2@&PIh{`o4P;xGex$%p;D>bwfky`P;321S#1tw^bE zHCgE{(KWj!o)1kWM|SKFY1P#q3-3}wqcwe7pFHzSWbpFe#LwXC5Fav4+GmD-;>^U9 z`8hjJlzHmRHJ@~R{7$Q(#2{wj6CSb5M>uTQh(ABEE!!E{@zu~z#7^zMm!0_r`$R0* zo)WM5gaZwZ$P@nn*x|sR{cDKy5Az3urp5bq$YB-`+0^j~X^w_&j8k zj}#MmIT z_7&T5u?i-HCZM(jyrHNK8gY*YB}hmD#LV|wd!I82Ve)?O|M{Np`96Kf?6cOstiASL zYv0#W28kVa9DqSVb?$@)vl%B#wd0E3hQCg%k;LDpKVRi!Rlwrr#plbNpT}!^l_faB zj|)A9YKW`UUN5$~@FyjWy)v5ak!^WcogD#PIs#g=z*+ujsGM$>&baq#LVY$U=ip$s z7sn1bR-lyK!zo&4Pqa8+?1KGtgP8u9SA~$Wl+Nrnd~!7NP-Z&@%GPjpL6UlX_I$T` z?)2e5O0oxTTlw-w2-!xjLrF~M2xBDG!nQc_a%J()-9zl~L# zfl4pg}*}aRg_5>F;OO&C}o4z9d{s?oZXT^#-LuPL3PZ z&YMH?!7FhAo1Os=XPdC@Hy>1%&Vgdx2X}vX=7xJI=Jo%>xEq2?Fqa<2DF@DuXk!=m zQl~fo#`P#C9`5Q7Y()*wTkUu=t3N=UJ@P@<1tTAIJs-iPo~8mP8SmJ5Unh5pJy5rs z5w-j9)+IsD#|<%_B^_ZFJ5^-;0piC|mM93Hq~!`DcB4fC)$bV|IPQn8z6 z;Xz}$&Tn1zM3~eQ;gv#)5Z+GtvEe=N1OzEz9pFef13BmDoA%;s5^jdTqEBcR`oSg= zO@4%wPPfiD=m0?UwGUt6?REHKyAZ@Z7JU=(f7%*cKvKqJKS}LC~Taa zFAuJdwCx7bwnOv*AR-Y&;NASMC{p&)E@db#0rr3w5Eu@vsi~Xru9!a0Yh##HQ)Jv= zLa&%ys%OX@%7ijpPV-@?hU02wOey;wL2bINT$MI<^M~_r-M11FU#>STppTg;&+;`w@Ugy8BgcDWiudcuWyia36M~2zM)Ka8Rt^ z18*=(gH!nnOtuW)a8+1NZVCsKFE!%0kHXpQeDmhMsG3NOeALi_vheBpq9oX;+d3jQV*BtV~LW|-ITaYpAIP+rmc6xD1*wv@0YQo1>o*w z=n0L^jooCeV(n)<=2HGze;~zFq1WH?)Q1oWVVtDdDDBJMx2`B;shmz*N$T9pg ze2FL0n;}bNOr+v@;`LZu`y=xv>ss)3aV)(hQwGXH_+e>lb@MSz=~0srK$al>|37XO=pzj(>O z-@E-chDMmj8^_L|#?W&A!H+pizlN>^c?juVGX_F{1S2Kd5#1k`hc({$~%b!M) z2Lzuj$ZPAto4huU?x#xWHn2_@k0eE&fM_}|S`SihvDXLu)OWJC5u2n3+c7q2yQu!y z75XQ{A(AI(=#^<>LEE77GA3hr+Ahi@MtX27KEi&=Kg@FvX~=0RR+Cd>)r+ManYhK? z4Nli<4Y3+0CN{8*>K6!7$!5fPVGd)1v71xAT;@!Y{IjSWo@uN}r*egF&;n0@P$SXP;G0_7^dn;E?`F2A^NtYo7a|CgX&1O208 zwo$A41}$73NY;6^?2R$oC=2m-dBAON+_;MX-Bh!k|ED$k1>~l0&2A-F@0!h|nvqQ_ zU8p}HH&5HG^))})}vi=0`>^p|W@Lq5K7gcSrs8qkz*#d>Oq7L`tODpCN z!o8_rs{w%w28)nwdOdgN;L+}3Q48O9B!c>Wom<;K;P zwjpJn1CwvOVXCr5^XKkq>{gzhcEi~(5Y$q+O$L|F2}$RwBJv2seK|eCfQidG8u8OH zxRd>bbKU9~Hc6yqLwR;Y4vk}vXMfXBRNH*J6xhkM!jH6oOQr z;ud;?h{Zyf(UgMbWPihqqyfa2FgNKXs#7!7sSI$lPQzGXBrB@R=JV?^lwBS{vySPg z!>9e$A)eRaUnR`ZyAJWF!__71fg})sIVHo0f$2t~*|ks^hjv>F-d?r9bP@aKG7whW z5^%Du2$@dPqg3q3e_Iv1j>_s;oA4!wauMe5qp_M`|B&IkV!5jW$5SxQ0oNW_X%srF zsSRdBFF@(SZ9M&XnEpJDKYV6wW8l+IP)sI_g4SVwl+4j(uXCzncT3-1Hn1$n?DN^Y34jmAG8cFM@q9j*lp2I-qG%|tMKjNf+d6FckFlBi=AZ2 zAdb*jH)3Kth3Y3vbT+V`zm7grx2%EP%mcSj&q$>jPeUQvZOt&mMifgP-$AX zU8+8YfG>8b{2!^h2{dcxhD+7Ed0+%QkMTe}Ox*K;Bvrq_16W9D=Nxe9el8JbdB}nN zk_3v+8J@uB+hmL`d;KEya=36sh1d*iQNkTDjI&A||TIPvL@LoR#iqTre?`K+Y55F+f z4Z&*C(Kcj8_V48XC~>HkI$fLgB)DZ$r}$zjE}a8oXTy)9q2aa>)_$FVL4-6$v2nQK zt+$#z8SUAO>^SEd(wmffH0*;7(bzy`j>h7Z32<|CKhV9~fov3aD8GVv4l>EcJD_oE zpU#5_bF2u|tBjoR6zZg+B}ERCtO$fK#`Wo&$iZbrpiXP`yTQ~x>zd)|7plU@LUV%^ zU;n`S-nkeh4JLn-aoV{-%4qGJv>tpJvfj-`p>@nzd^4Xj8QeOIH{eO6du{^%xO8nMI*s|^ z)zs99v^^56bn5YjSBJMb2_|d6(2I+dp{y3q2uyNr206wXyQ!CNra#6AE{mHv(-L+Q zYc|s#81|xO@>mRPrU&HBFvCHwx3UYG)d4VM{@cINoV|sNB<>}SU>;x4rJZvs3Fybv zM;z)HeeNxR;tv8dZvkALuFqW-C_d0MbJ>H$RzN1|gBGCU1@Itw&>{rv{4=}wvvs5J zNEsrkmn{m+7~%{Ud}e&kTEN5}l|br(@)#Fhy{sfWzq#BP1!n3nbw+BE+G*NJYA236 zpDZ+{MH3z=9|+7`mRZMMeVNoHDUO|~SOl@v6ML`b_$D8=O{Sedd*D%IdEi-wV0NJc zkJt+xz8DY8YM-TfFSpyg>o|~sCtC3rzJ%TIB^+LvfaXg)5c&X0Pz($dZ9xPQg%Ap9gn3ht%J%Y|nqaVt!r{r- zwrJ(!7z~#2SmA9%e|Q^}X_l}bWrAT~(O~Jp&!MiMt?vI2zdFwmZg&w^{zQ799w=FDqTr3Ns+LXvQm2 z_kbV`_7M)*8;5<&=ccgMTLsZ8bsDa!>l4)Yu%Bv@2TNzI`V&NGB4>^|PUnaamN~ZS zLuyartR<2wN(K&vI}lRAF6 zFD$up^-!4Pg()`dxOaBc+pxk^?E3m-t8v?BBs(0i5@vQAHTK|%D|jE)o~1^!x|14z z@Xu5)!42?{lRbnqC-a^XG{Rf&>WEVK4C+aY#8OhEqv^dA!odWInPFUY# zT*b}z-2i#zcB)~3Zw2oCV6U|~Fh8>0DrRiW`ur9aehI=#X}+g4LTtTWbIS#mn6QW} zgIT67HaQV%`hSPX3BeFn#f#>faZUQi#!sPAgT-B#jFWMkHa<(~M8siYw8EajKo47i zH08#>#TJYyv3yDz#LTlQ^+&RMH@}PC-}{m(s;I^nfi{>S;r0@nF?}Kf&M9mp^3hUl ztm|f~KBu8Ue_u=`>o6_mw)rqEJ^?(%`ydjJy*L?K`yk9(oWZm>$nD3qn$u!Q+CBSf zhjtXxVh?*gq0h7!PmX)NH~tv?6kRP)@L6DfGe+hWJtf>L3Kb+yA}Z+H+>aC?kBaYzw0LVqjSK zIZA^jQBq3TV-yDaJGrh<@k}`fA-xkj`LH#50y^+Q)|WSe_cm;Cy2{w0a_Tg&9Y;I= zn6zS&_B^%1ld!_Y2Ul8>{0@dY@3wtna$BLp*=uDHPHecEb7C><4b$n15>I9&Ru5tiMxq#3_s+kxBJpEbbA6;I zcbI!2Q60w0d6Mzq2fIS#YAuG&uL--GR|3WZ%%fH~hZxSrL<(du89=SGxPc5t@XcVd z8iAuBcQ#CC426ZDSEviN!_WPzkkk75UQqRgSF-iXj+8rz; znrE_?5K)8Cz;gC9LV+y9qbHY{5&tYAHqMw1gN2Aa3f#k#g57PVxs=^bX_Ac;G{+c| z4Q@6Mz$P1K!+24Vo`X7YlA5%ab~z&$8EMJ$ntW0-n*1_B#{b;pJZ2YkfA7vQ=|#*$ zaBzpk!bpt`Qd~uf9Z&Ig((BiTokRIw2kt93>>NU2GP9|-R*-4P%y6(U1?!@lucT2( z+Obcs53F|tFkM>jsz2&ZtPiet|LISEdV`Kcux>)+^nblg32T#Lu=h7t?Feofi_hIQ z4s!xFNA2JS88A`YcsvE$)jeDOC1pWBIb`#|vq*UOGU&0lDIG1ry3o*>bv4y-wE2)D zH?3%^^?#43jeY{8!hT~$@YxdpNZa>G+wJnc)g#jDb38QD0p6E7kNp;NEs$87AA=oP zSkm|i4Y10ENf8pEJP*pIs2*rW6QSUs2$*$)J7PSwthTAcuE24mepZ>lj!J3DH%i#}6> z-ey=YvNzgnx$?hPGz!Bg3)!Y5KHYFt)REfX{46G}FK>qK?-VD#Gb@#`+6J8Xj?`CI zn1{VXwm@^?u=fo_Zh&p~xMMVikn&z#5l~y?DelQQ{Js`~IU0*Xx@drz3r?GbnRkp? z=uPFDDN`Qyc}s2HKf~x#X^?tVN7?R7S#Dm0SOi`4^HYxCCkqsH^+UvSwe+EXe;Oi5pk<=C=y|yq0LUx6;eY&*0&H&4rYpargbk2~+ zBDa{Ca_6dVam56dp2&>t0W@5=pc&_(Sok`S!-|mJ1lpSNX;p>|(sPi?_&~lZnC}h- zQjpQj%k`_52QuIY2Bt!Ps-DzOv5yfc^fw4))olA%2WIiB+9~_>IJfabqlSa|7rjCE zG^(7=oLTA_+=XF7kOa8~N?^|_08b=BX9-nxAE_$ikJ(eGpGf|jkTFJ=$h>>qkceFz z+(^W#3|%ZU7Yr$A)7IdP_)xOvgnpvHoEz^{cnk|jq@U2mFw9|_NsMm(9r7-24zne{ zr@j$b_Zj;g`h>o;0;4O-gy_Rlz#5i zOWGyy7m9?dkdSUrG@}dnsd;tvvoN{0FlB6rURW_rNSTLC!WJ{^V^);tI5&fR`;XfT zD{($T)+gHI=r#vdx1Zw!@-+k3n1ul6J6?Dd+|l!$FKp$%t{47>U)q~!(K_SSxtHs+ z^WDn>u7c+Q&M_FrOe2-QZcy#))c>tXX`fvEVE&t|VmcuUXvLSa8OMOM46!*(;M^ z+b~$rQ9O4kHR} z+8X|f8{+KL2W`O}1rV8w*jNW(dIDp+PKG+txM@yIyxv|Kkjxba7I$VZPEivn2a=9J zL5e;l3H^x!}_Tk!>yQ9ubv#7af5Rjt*loM zV`Nlb--~NcDNV50iiRKJVf!yZs{u5y$@V$=EqqLK=V^ZrI&SdzY$a^_#hlPGDiY<9 z@p_a5rox_GGW<28I>VjNs94^Xuj}%jJ4>L%C>`gJ1fTa_7hrmr6*cuq)YtB5hpyMPg?yZzq-fZfcv?`>%a_O~V z+;2FE5jCa|yMx`$tB1iUoOPXWDp&LBHQKbvxc-6bR;5gS0@X(YAHv!km5R5rG6o52 zWARNfl(#k2CPQ;L0a0D-k?}~#=k+d>Hi+7ED6tZqqqxk-;1s}EWv|POE^tXFFCTPf$rv2@0PMx>6`uX#-2LS>X9u5z}l|%utE63iHPwfD>x5+8n?Ls z$90u=Om>m&Yx&((Jsfj`WA0wL8@b>Zy~sbAz1XRsVotX3BjlnVCfb=3twe^{v8YXw zzgX!`KUM0p+5c0*o*&111+E)LZbIVbfYYO&WY43$o8pt;u!(M}?s3?fV(25(0yrlN z?IZB)AVBQ}P8^n$vO0;16)8kCx?0Ii^(ixh9%t3{HsuC7+n58RhDs7#w8L!4Q;#AM zNw@=SPiv3IEtB>EPUQzOa$?&OLtimU3vRL@5UU*FSCmYV6Ov_{HZMQFSs6F2crJdD zr*VH&SF-NkqI%EM^3@i~eGGHA?+CDy1J)<9|KKZJLtork1}=kzFgm@3h*<4HO>NxZ zr~-0BytI2IMojqb!cfg#rc_mwiXT|0Ny^XQ+H-xJyxOrgj$KBn8}+$PeR4hK2++?Y z*V@{82kwzz%&njkj}qX+O1*>CxkWBy9`!g&N5+`Q-NyKAk&~dS%{0My%F^@n7D7gm zacmcOhWyw(6iCZ^Oexsl^QvcyRIfp|ZOr04$^ksd=!SbL*(kHfas4NvF#EU@qz z-8I<$j=i3Q=W0$-Hg7MUg>{{ZEG(kfyLCvba>vH$yrMtR)}x|V=!Hqscs&PCKA=v!$nfy<9&ECEWWyqq-=w2GjCnNIcw|9X&rx!9lZ72qihr-oM4 z=DNb{Hvo!`#B>DB9=3GJIv2Z-zC{;Ax9#b)AUHSDD%bH=xw`*W8Npk{%_dPnyzlYM zsN0&dh@;2#PF&BNK-~*FXOBWV&?ZIs+r*AHi3gIu8kJxn=q6FS>?~DfpGH185zE;A z2q1ewk}`q45&7P@CW$jKiM`Yt#yL8XJraS1E0Y=aKS?1gNw|=>8kjKW8KZ|c;P7pc zxS-)?RvP)_G|XZJ5rCj!O60qThKqZ{%vME(l|(iG7_&LLNF3)l12xo|?0f#@S{kUK z=3Izg<#R8feqBWUy5S+PjMiyzVldxnU@SP!p2Uoqi=@?!zd|HRQ9&slF;md!Ip0|> zlHAre$yPH-4JBD}UJ{Wavu}#^W{NG8VqzpkFJqJzeVtg}o=0CkCFOkr{7^tDRY{_J<6KHQTGn{?V`muC0HQVl}a8H3l zkAnm0&Yn7GCa&|#)7O4=4cwnJY>hF_SVG_`!+;&fHY0N#D{KURgP^GrF5)U7IieDV z36czY!5r3Q~yQ39z2&A65fEph=z8EqtW4; zSdscITI^oR=o2y=i=*nrE!u7G+4PxR*cykD2^Q?d)oSd0&`RYnTLc|ws+xii*c|@F zRZYPGmV+;$YS0xgk0N_ zt#$2r(&ktfC4sn&>m612pwVqEjcypSmblGTfn4ZXnS4iGl73UNLw;v{t~@*0xk1Lc zkv{u?(~HSN8T%){41)x?IzmDd@zdH=fI$sSnS?)d1?Ke!K>=2~#vcVobQ}@P1l@<( z_k~~sD+ggZj&X&E-pIcWh?<%Wm&Y4!YNX-L*X3#2y7z4AMf_kE7ub_oC1r~y!ZZk1 zO$*Nuuj%@55w*=1Xu!-vIQOBsMG-0|RWE?b%7yPS~;Cf3g<~8Bf$!ySRu{@Ege?S`eyB!<>93>XaOvYTUn9p=D z=i}4_;zMw>i#>JNj&?#@R*zGP*gx8=d}!Ym`7}lV^f))X+KgPvuE(kaVySINa)>Jp z{GRn%t;tztJd<6PtY)z_W?JP|WQ4_ySU}x`OemHpl~th$%ga4*t~{D&Xk@b-E9wBZj-vQd^VXp(tH72ot zDL%k?!Cq|bFLoITUg?tg=E>gx7sgrU4w9x4xQkptA z;b?3ae8wh(k!)f9+zaz#PJ}Ktk%+JeyB35AYx!UVb~B*gV)vWd=ox%A5r&_q4Ok*T z1@I}QR88&0Cz(=p9fIu*Ia2iw1Vk5={4EGmH3 z1|^~Gq+tnLP=CN*<{`Dghe@G@Hd2{Yfpu+q7L0QT;nR97u0gL#!R827wyhraOdD@D zQqOa>2GU0VF_yA{$i|$X!d|(u7z+UIcteHv237FWC=xT|Yq?-$M5sMqu#)*CX(-zX z0X8Y+tJ1;j4}e6bAy_(CJAmtwZ3;I3lgY&cRvn@+ZCxr>%TCo^YS^(S!IT@2_&Tu+ zEoGZf2MjkAv>gClmIFAhh*A6Bh&waL<&S&}nt+Zs09$#j)5F$z4n{YcGH#rX?R!Y- zaWJclxj3y=2-}pItQ*1}+(}<74jlk~=`zg*Rjw2(hD)$|1ySdn-LTQ-icCHZ0@nit{tDuUDqs5#tvU;8AfN@G^#P_^;x9XXW?pACv0o4b;$d$K=E&) ze4Tt+m_1$clW`sD2Ids;3Q~B5N!Ur~({*Vxh&qJik4K2T0DeZg?^no!Jwl?*yKex^ zrqNdw4Q0LSet5rHazd?|O;6>%}o#ObW9sUex2r3X&4Q zUv?Q~zLOwq41(w;*k9htzhg)NV2-c|uS3RR`T&m&k?F&uF=F~qNYjVw)_%n%A$#G* zFZC2@6k7TQi{Hbm!W42~{A_o3xS|?rD_HOzo};}->tFU(`uY5$YyLv;pYD%!_Y*>N zB5BiSpqh?2g-p^eT^Sn(k)X{d>O#$uhR3qD;2JlMP_dU9oY{(rLbg+S2u}@c^4vD* zp^XTb{wOf3VE#Hd$m8=C=m_%nd>oKVpeS@ax>4IO?_evii6ka+qr>nN-QdVBIKJWY za52*g+Tih#**5H^TM}uw-~@v7#cg5rGd|C=dL$$b&U|T;R>dtS?17_8!Zc~C)|3P? z<$`AEeh(S~wX9l~f|r+QboSm!;wd?omi+(CNxl?O#&L}(-Tw?hHl)y_OWOD^WiwCn zmJ=fy%dbJpPp3cG_+w6ku0l{-mxdX-ITb2Zd(RAZju!6a(~C3{_DHIK z>^^^wgwI3a0)bWY#A3EpfGu$$Z@a1fZ}PQDsy;;iSQ)+X>}Akic*f|y)HPB5wgNSEd7p>fN?2bM0;KVJ;keBFRuAxMN8 z_XbTyy=)|6EG}QTAO5E}|H#*yv|~XYHkhOxMKc1$U6{#p@0W*h@JKGH%CL$F7CJn> zt4pAZX^VH#eQ5knng#f-v0$SBO9ag2@nu@DI1}df_$FJhllKxQJrp&`m@lCX%t}}Mt{!{fWkg3am4al}}- z&;qvrOtqfj@y)bg2TWLz$5&**-ZEjcJ-%5MY)em;c(?RZ3;c)~^(K$+Mhmvige~y+ zW?Qgdn6O12U$F(7-;?Dn9^V`bJl%}C%;TGD!LBr66&_!f1sh?)?)3PwEm&+%mX#jg z^%mGzBPy~P36`(}teRlP!9v~vUec5OagGrp56lcR!3P{Hq#j_pE=BFs%E3bL0Ulw3 z+c;RrKEPo$LVYL42=NEzYhWm!E)EtN0N{U_VA9)`5&(FI33hO>&;tNJ-;+I^XIrWO zm`!E^y8mx!1Au>Jf@zLvDFlEQm|#3y0a)k+fOC7YPv#h*7J#|jOn{f`NHDiVJk?}w z6P(JyLOCGz8Aa4Jje~`L0QgIQ&8DTzR7*twv(HR0k%NVn0QhwioWa3DQ2_iq6FixN zg{}Zt>&ZTgV}z~%<`-rH+H-Fcf@g5B&=mlu^<-bfF+x`WlVB#8 z&A~!f0DNkt;DU#Pg{}bjGZRcN0!UZ*Vi%%=;~i@Q5GH$Q|E94S{C5JI%6}&^yoE(}8Ehi|oy^ksZwgD{zgaAa z|4wBt{Azo1!mDyv3lKmWaj)$`wF z><#`~!CvRTce0oHZzbEze--u=|6Ri#;lFDcOfqm1xS4i#A3?X%K*B0{kOmKS8xPWe z!EWZkhbcIh2l;|%1`pCmz_NLev_HFw2cM_lB|Jz9o+a}jsdP4o2j8M#EDzREup^FW zAPvlp^B`$mc9aK6&9aXWB(7~I?aKCX1Zh;Ziw8-IvcK>ksZI7r9&D!IGdxJ@kv+nL zq!QUC9wZ&e?&Cqyd#sWNN!PL4c#!lPyO{?`p|QCOS+T2lkhBxK z1VK(RDJ3?XBS;ak1Rf;i!@>@#H)$Kz#)G6`ScnHntFSM4kTeNVoa! zK~fRyFFZ(Uf&GyONe{4Rc#veDJ;H+|?`#tfl8m$ac#wpeRU*hqCRt`b;|P*pR>p%Q zvurjGl8CZg9wez`*YY5VB1=aQ_aL!(ZLV4%ciQ??TiKi3I!j)~GQ`5^&AqhX<%h>A zc^frm;dt}kY+f6^0#Mv#412kFGWWTi7JJ4QO%MDHTt`Zq5v!jQ=3cOw;4)3_@VhvH z-PDS-aSro4*Br_kqKqslt&N$QvDQ5`dxL9Y2CNQc4N<>?k%+0X+NRgnE{Lf~&dGAC z?Z!9ORzF?CE!X4aIBpIH&x95d1voB{;~pdfIe^R(5L|zM*o0gsAQ^JpP7`t|hpd_` zPkD<&O4%@iSX{yGpm>~vCsk@I_TsYFr>MiT*C(r3o_7V-b-qc(hi)TQ;c_G^z7otj zjO*wB!JY(M4F4ApI=92)ODfmT`5dQSVl7u-Ulb?&*ve^A2bQq^iba9Af{(+3EMzZL ze**6K9PBN`L#!^u8V&PRCF~wV#B&06HzmL!dx-i)AWPb@YI=!pFb?ipX(=@RmwYLd zkL8hM!3F~cm79;{k=ufuy-UEb&Jby#^^XB2lzTqb86r8r|K{MN#3n^5VfSHUs$Bb5 zICKZ7!Br?@D7})&SUVIx6E_U13T4zdbI2cJ$e3PrMuumIR=8J%YhAkdcG~!QGchb+ z-%O1Z$aid}rU~RbH&cTI@-sG5GZ1U@v8~k!o`8#i7;Jvw#z-sz+>DVvOk6_6}BuFiza6_BZN++Gs`?@B~ZjvTkogrp0|3_0#S6EaFbisZNhCS(AI zteP!P`G7-8+39e1bm>5onF5kb0+MpRfE>V5p9{#u%g+}O38Osv`hm+pUPK}urb3Kn z5{G1|dNQSTus@3^Bo0GL*jkFUgnb^bzB99so~X%O`mz|4wcw?Oa>;D4G?b1c3A_J#?=R)OFf zU@w_4Y!wIw0rp5wme?&2JOcP$4ko3NR>EGxAX37f#C{qz>cjlEi`C#4dt9OSu~(<( zr}VFBQln`aXx!bTZ0L?C_N4S05_WxHAkhrXa*)cs(P4ceY(<)t5x6nK^beXtiU{fm ziU@;1O!m4ioLj+>HgplqZ1B)%0B$3bLNZOVH7S>v0HKYjShkC<&kGZ4e?idHMw9_| z_@ihe+_&K)9kvcj%b@$rM;yc0s7tYD%^hWbi|uJR49(T&*|W2IAwC0Vm=%LB!{j#DV8Khd_<|J`a8Zf4O0OpE^u$1sw@B zt`T|HzpIP~9XQDPA-%OWX>iSu-0R<0oO6{p`0!P~r%YmNM&b6OPH9y*6L$GLHH)0# z1tIKRglm%7=w77X&pDJ?2byz0>DoeYs!o}XjYxX~%yHm#X@T=#cW`QJ_&S_9hBMef zjI8_&A^{tbaDW-eHWKfNpA(=hWO$L47rWN)Ub+Nstm|N!W}#lcxMAZQrvrm*c(|MH zMi!<73X?^d@ z-7s5L7prw9tuWA0=|TdvX=!G=ks8c>E^lhw>bR=K4kxP-o+n*-^nS!+e zZ<~wC*hR>TSUF7lHLXh=f!!o1f!#E<)OlKXkLrLKXPOvr2ew~)*&W`GONzWlc;T&H zuu^Zllzf9%pb1k4*&Lw4;yypgyh2vHl#A&yDDJMoF%S-aKVlsa)Ka`7eEd2PA$KlS zJ_18656I_n02}?pf+v4PEC_CWhN@A6)cRPeXoMMs+*;jvoOmCa0cPu*bdMwSb5O?_ zfyM4mJ{DY%{B$T{aLj1jCv2Flw!(Y5Rx-pzB)xv00Ix`Fk{#=Q1IXRJCG za}vayh|?x8`dW4um@LkLMuo4{<~U;m>r>dDuBD0=y3~s)VG7NSgDY#0t(}of**#`?()zAIkktkIsqb1F?Ljm zUgKyYTj(^3vX{7&sUUQuMbl48eKE#Z#8M{{?k8qu1FSk<1b@a&EDl`f!Y5JA$SiwF zvT8?lP(tsnvLy4L3)x?qd-av`8dS9BKFoF_?YWcAysT~=r6H_79e;75GsjqDl2`Ge z*(3?sg>Lm~s>3)O657-ZZ9T52U1>(kwW5tG^`I6v@XK*vcmkRR@5qvg~$;km^y8FvMq;5#J!|K+%JVmUt@qif`xGO0}~uOAEtb9N1XBnO)nFZmBg2+6&1&p z0X@Ym?#VL;S-E?%`n||1@My2BdXdOQm(I?L?xa(Dk|yYH5|oW|xe^XF02COY)#U`2 zIs$gQ41;YpcZqLAMGo{IJ8_0)E-rn5cDoa=;*^DiF69^l<2KENvEwax(I>? zV*CNr+dM2DL;V$DX-8wP$55LA!HM>2*hfg=%2ulWH9$~qsME0J`rPE@0VjS^mIsPr zmj@~x+}Sv+$xb1a^+u8thQza-YHCFi#xNXAu-g}Nt|IkVvPOshSUgO>098S%h)SYY z1QUHZ!TcOg1!;O3;|P}QOYpfhqY8(sPWCFaDW-DJcj7HIR{n^R$j92}>Vq=|h8v{q zczHDt#!1eDF4yCi%P?RhZ6AWSRq1;aDf1A<=jZkWXAH#-ggTnuS4rDv#USC}-r3O% zDjq3s?=PiCr1ZvInCTRtVx{fg*b7lqo*m`cqm@T*LpwurFwp2Ra;&mS+TJQ{k4$#UZP>DCpS&9S?MT6!av49su-!ehG1MLY#7*(;~zf zC95yvIY$N}tASC_4Fa72bV3yLivpbpbYj1RY(OME*CpqeA?_$y9Y8Gf5p?^!AyLq;3-nN+hekm^D$v7# z9@Z}*cX2|J z02)i?3;6k}K%WBoRKJ8gfJl07o1E8X5z-bVt6N1@r;*j^DCj(aZU?$O3i=X(?f|-@ zUqa|PvYy*1=XF|ybVkVvYZT(h8Dw=P3Yu=m0eu$evr*8$6X50@h?)viH&ERdqAr%U=i=q~Phk!e8PYa_nA(UTS>;A# z8uZ++5WE(DYlHv(-*Z*)TL=7gM(^G;ZVkSO;hj68FB8FpTS< z#1i?w==}n1iG6fJUqmE5_n4e_jLSC^!QIhD@^vCFYb5Vm)DnU2PyBBXXiNN~Q!<)U z@~xcrt;LdPBYE3dUT140?@P#s0^OhZZx?7w{G$`{2qNjZ-^+R5TZBX#$?p(ZStEI0 zLS_qef8sw$pe^x_PKc8e@`IfBgGETRk^D<6_|a#$n#DCS!IbunCbV7C`lAimWocEnYNVK{8lOii?^zTc^ zJp$dI#NRB?mc&OVWC|zbq?~usA|%?}T@qPYqkmsQT2KFv5`R#jEs2j#$g7B?=bo1H zPFsXTo4aokSy`ihUqWsd=>8v(Wf-G=*5&^FW|w>YOD?w;->dLF9N(v3bh-C!ak;;H#^rwSd6)Z6eBXfY zW%#z^`>PjR?(KhcxsN>Ua^Lfu%UzD|N%;2S+lKE0DB}f`As#2vX@pyE(imQV>_~!t zH=MYLpF73R#p36wVOUkcffllm)j=$4ZK&4B*+r{aC_26<>l$;*p+X|J~dMqCl1+54) zwjRp|M?udQXly-}Oa0Js4X48`-|Z$k%H3!i7=yW>e1W9* zeQkO^!scD{`b#>UW-BuJhX+1o;!CK%DYE$Kf%^be6<*%MT76w|JL zjFPZS@qT?F>Mfu!Me!cH5VaX7Oi#Su^g)^Q-LK=WyO$}dD3&4Cu=9$Q{EPU$LnYQF z-cS_uG=av%#QSX&^hE-ViHX;UN`mA+hh@C(IWBvTTV&v7%>`tTMPAees}k>bQPA53 z8q*T*_fgQl6=+OLyeIl0L*Zn!%HCFs3|y?afQ&gJBdkfhC!?T$BGA-?KSV*3$7NIk z%MkCWe#rQawh26KvbT+d#)}C`Wm}Y-$m23lSdw^8M?wElpxc3NkAkidXv|5x9sSTj z9+!1br|j*t*wGm!BRW(E8CZ~b&qP6|3N&UU-m_8AF#?SliT7MTWPCw;0G=+{+ojwk z`e;}0m^VQ*4yo6I1WZZ1KVFD}UZ})$#M|Awc%BL_>-HEx=b!?!`rlJr7<$@8()pF_ z{mSCRSG_ai(kc0;@NI}n>@#rT?JGqi1lkhdzG(8`0!pI^u{G*{KudqmH?sE|iwu(K zz8U?u$OuagZ!}?kNTB-@=1PIKgt@l{vdAdrWEis7u*e{}?n}nyA|os~ywQZ&DbW21 zbBHheD=lG;Mn)qo`#mRQ?+J?xlIy-?JTEfBa>E-8W}fmGET|f zQx+K{*L}$tB{G66d!xznS-z^T>`#`D2(%^3(a6||D7vRZ_I7Zk0lBUux$aBHBO)iP zHN4S;d4)jtC(I=RZ3%NUIx;vN=Vb3Wiyb7_eaT1=8DXX2C1LK1Hu&m3BG7%&?+LUe z)X~UznMaugiOPXsOVwErzTO*)6Sz|OQucmnk@;m5I({w+!Y+e1n!wKx=>7!$Qh~Mv zJ{p;^h@yLr$=+iY88m?Rt?y@iX&)KH`=WOVbbkW>dx5qDJ{lSKA&Tz#R`z~tkr8bm zpD!}92J*gSTr1H13H(Tbwgf&J8Ev%c_k1sVzqiPUHjw{|ulg&kfxIsnuL*R20{^f; zTLK@AjJr4)Kgix6EHa`EtJnct(}hK;D-fqXfD? zfj`T44I%;`jf{^GWx4LbR-zYMiOaccA%w9#{RgSqP4aC{migE&`Hx^q9qQy`)lu1d zlrs^#jg?2ENZ7rieC#%Qqv?T#0^Oe;m@3eg9*9Q$C`8dcU(4RFEhf?=zAu+fojPw0 z&=>uIKwFdfzUXZNZRvq%WIT!}x+f%iLlzm)CiHiTjI238-*Sotx<5T|l|Wm1AQ~Bo zoQ&^e?{^j%(I)idsSE98%>nw7(J0XU>485Aw5124kx`8(y62?qJ;^%%Uov(Gv^Amci+)m| zt${xp87e2^jO;yQkr8b|KUZXA%>nwBlOfRk>46~vZRvq%WPFcn*r=yDo0qDGUm)7m zu{3(`3H_Xesu-48vEPHewh_2YNaXZfUX=Vt`QAV-u%&bju`ha$Kw~Gal&&K7MZX}> z*om8i>xfb{*^TKvi_q}hhXATk4*}{joNii$_d(493I)%JHD3Im^GFqW%!%P|H0Y&G zF)ECmnSr6(v9`9}m(2aE%RO)hHb4LFa_`;ka)0m_m;3(hF859No`&yYe4pKh&CfTm zNxIAB{>SSs_ey*h;QK0kPsjK1zq#DIce>nve9PtDvDM|i4d2({I~Cs<`2GTAY(*JQ zz3Fnl@tVtBhVLuzJp|w4X#km+BZn#Ql>*;4A=-E;g)_MoYH%gY%qYJU8>zMMEbq|j z9o2`FdDnZp)Z$vtj|fk_-ut6^U9G1Z;mfY~cB>cHdcp|1uZNqbM6IC9k*#*EfWs+k z&1MuLgh60dw=oqV^qE;d8rL9%4mPXHm>^2i)h=UDts5B)xqgTW$H2MrsTC(O>*z_m zbt%^P!Lqso*C3_lRb1T0CChoUY_+iobWMLxjPxf*8a%e+E^)Xv9x#{I7$d#lNLzU_ zScv<#sd&e*vlXT^8+PWp z>aEJesVkK6Q&%e^*%Pg>dt@5{-m%->=_Y3mh1fBCGX^7?kqGmiF~-0*;cEyUQM+Pc zM7Xdvz6`FmtM>pDyl2DTZ8>mT0$&UCo?6;nr*)P{4^h`uHfStWs%}S6V~NVVA|+pA z$;u=RwhJzXe-_DKixk-fosyp(n>Q77;?t(R>*hDZ6aN#Umsaht%;*0zhv$U;#G|!R zw9r!=2^;?pBR~d7zUtPRyR>&>6Pr-wP!*usx=x#_MDk5FeyOpWl$$j+SSct{GKi?* z@O&uwW07M=gRKr$j0zGzHD+osv8c)$Z|e%bO*RMHs6gXt4!v~X{4NVJ&P+1|ru|T! zA?>u`zO?@^>|XCTViOO8QRI>w?-O9=sukP*M5@&WK6`1!E!%NPeKD?mi$@&mwMM&z zu9`kg{iSFvUtGm9L@Z_heBFwfs5QosJwb=fxB=E{@8szJ?u~X5I@V^S`UX_2;pm4V z=n-1u07B!1nb{%e<l+q1ULy*UaMhbkdyfyt zRl=k`96Hehx*}KRes~BCI0Nt=T9g7=2o$-w9SFGDsv~QWS7$+ezz{4TW@N!JSFvZ7~~JW0}gFYfvZdmZZ;4OP)W^{*3F}h+# zgzRxzR>_2x%sfN}0l1c&-xiqP8JOQ{*x`6(xduhqA?=9QI&X!l=v)EgZJnenE8q#i zRF+x);L7sWh_W1%d8CiJL{7*9O0#Uayaz5(!Li;dlk84NJN{2pCb`-9|AWdL1v>s; zP?>^X%jvfcg+x8*6f@F!huiJJ!S0YI! zW5pxcr{7qWXI~-+nxgqk{PAj1ujID9oU*m%Nv;xl@CjcW=in&uVV)D81lSzU{J{cE2|&Sd|_ODAWt&Wq=_ghMxvc$UL z3RuA{XtNh|aqo|?=xsC5&s-U_#y~ExNR9Vk!&%4IR)ty9+q_|n3G@W*&-UB-rH9oA8Nj(thxy=0aF*+8_4v{L;JumbowrK7r=lq4HMOJSl}qY zY>Ie3--JH_%R^v2D-hphtewwj;U|=gB!rpO7OO!<0~?1}G_gVvzvD#+X$%&)VWN~f zVX8!LMz=DJ81zCYeGsq?!W(~4A9y6ETlyfZ1V!h;^+82sRsv2;_s5{;4cZB3U((}s z9iNUlbjiWMaQa-_z#qzM`(iTpXUB;M^>CwNEx7m`BJ&8{@PRwkA#lDq2(5_;Wo#fB z6XpBFhh1}qH-aPK4XcK+ROk<3bKxXjqvD+(c$!R3m|lG!p9QV(lp)qO%P}^*5k_ZN z4xez>!o{kD>=VnNfnMJMR_=AN5Aj;Q$#x~8uy+vDZNj6UaMHwQ;7D~DEIp*MSAh48 zUlPb*_n}mZ=O>fYaGw>r2ZYtY#W|4;hf7diln-p?18&8EG=+o3E-ze?zzk|}lD8zV z7*jnsu(V;QLBb>$Zjk&iPC`2+^1I5eK#cK*7<;tNdnLaI82OY`y^?;{ zhU0WpCA5%Ik|!8yS@LXU_KC;1eu{WcJx(ZcJP|BD&hEp+K3H&^x({3=gtxv5%3+}a zT@+TNL^>!E_?_*->H?NIs8=dthf&$5~%Tip=aiO zp|0LgVcz--mU5AZ0(9&=P%LHl{)M2r^FR)Y_2&ygzv~UEKt;HeRY!&XvNseyy^!O= z3qeKafzYTsN$mW2Q$Lh0K6^1>UQlHk-_%no!yX?R_^ohxNxu+ zY6c}RqiC)T0GKr_=u!1eBp z&%ulepsVd#C{?T9FBd1R91Xgi*#%uICa7_8ap$Uw@$KM_r!Ub9lIL#08+`gw>@c}Q z>@pU@5xn%;{3QE9xFDhG*Y1W+*s#T+j@c5UPScuWm4O>!BL-Sz^+41#9b1vBE@R(* z)(zim$uYv$G8|V!Zy6FGn8%S14|^jvztF0@NwWy9*gF90;B!!^JMP4J_8C!y5>>(%SHiWhV)9=p2mTt@Nx_l&rzwcih!9bODC2Se}d+QC{8!p+K+3;b+;oipMYp@^CV^;^e(@C^u z%dDf78Ja&B+=^-iwr!!m&mN|~{^$AMR{r-r{+C{n1^fs2-xvAcefY~BdbgXd=B2Zv zpGE}r^}j(-cfAEPEcNSt^2tTwif$cB!2VKup({sSQp#S#s9gScCw!q|-KdPgR1=F1 z`28HGx7K&Z)gO(wySC8^0frvmY0pjDpgv9UMo~P>aK$VW(@VR9Pc#v0PkT;UmE^x0 z%*kw*aY{9&>b^PzE_*?VD3H+@-Bvmhjw9X9d+KbO@9$9+u8i0a% z%~KbwmZaT#r0rEtV!5p~OSE9sgOte~bpxgC)y|!O`(Omdu_WLpu-qtBlh=3OfO5HB zdSE{$o8dJ7n_s$Mn?ef`7s1Ad@e}Y1HVXC%#tMcLV?z%kAxg%MwNeC|D$3;aMkTGN z2;T1)oWQIAcHlOO9=aVtEl5E}XkjEA6Pkrn(1vm%sMyeCgsaY8sZNBa2GQdB z!mhxxL!5Pj20sb9gP;1&tR4-fTGlVJM%g)c2 zkd*H(iYizTdL3ciPnh00(s$@LJeOI@{Hn7R%8aVBcPhzMXID%9^?-{C*$@i-ilW_c zr#fP$34~e|Z5X-@-iW^%%T?UyK;`)7EGm-x4`3IyYENnt?~NEq zY(gLe<8riJ)EKtlt~UJeg>+z73xWavGx#e73s6E6Z$R344Ly&~4nJ`!0}Pb>*w*OX zSOI+Ypw)z)cGP)?fd3fyYalkG>xhVM)LRX+5-5wDHB%ca@V6e0|MO2Uau=KVq5TrUN zNw6n(u(MYsTCgi^_Lk6Lgg3WFD(qq9*zzq8)l-ztO6ehfU%vV< zeGM%ZI)9+?cOtMhIq4Qo$B$px+jvP zDz4?apOooxxRrH^U?9GSE1|cEyIkkw2)58PNaq0Tyi`$F{5{CDf1&4h>Lp#FckrPO zsXBYR`g75jh>b>BHt$#K;{*J6BkzeX0JDCYzKwe2#>T~nSjx^~p;gPZczM82Dji%l zdqrRybyL05NDvuQa^0>^$-uHuYj6hqHH6OZ3iuyHz!b>vwukWnNec5~MDO#2c~(K5 zqRbPXatlVR@JHbT`m@yC;i_iiSE!i{&UuB%s7YpaW#K7znsMQ$W)}(g?P@Z*Z?6ay zJrQo0qz*9~1GR(DQ_m<14Mr;l1UoMt(-p=nb93(DJVJef~ML_#g7#9!qL>l8iv|;30IBQ)w1e;5TY#pEWRLt!D52HOk z)jMId;E2h$b*^50V6?{7Zim>yJFh?JO^FR^{KcH;AngBgWSFZFf87T7HBM|$Ch1%% zpPJ_>DPzAoj*8T01fCULLjP1dlf6FP&be7Lc#g3vSa2%6vCt$8dt@&c80Pb%%xT5e;Z>ma*Ui=%Qe{kg3+*8|9Y23|H%4_dygJlQ|Gd4{x9<02R_Q`O8lNAlQ1EFX80ck zG)O=!XwiTsCP*R-1~d{x5+VlNR@3~ZwW2;F+7d`QNzLSG)OP!K*Y4KZZt1SwwcA#) zT1*H{f?5P*+aSS4P4$istnD?n4#BY&d2aNFSQ zVx7#9vR}w&2b9VYb(khMyNvr4jt+tS!-ufPfHFP3fI=l97v5hrY3+Sc=NAU zg&&1i7>Uc66#IodsM~*peEmb6R%>bjpnY7MUaSdv>NT~zTzI^Ff8<$>$WC22C-f_M z80T#rqW*Kp!Zz=e=#n~Q;Pyudx!3*3qp#vx(x2^nk-v)%T^S;r^#{eX%L|5?QYwIjn{4NSC?f z+i=2$&+%!kgthjzoD%X`ynxiI*l!zdl@E_gC9&Vhdxd`QOuR49?|b6!kH5r|aNmZ> zc8!#IypOcUN$PqZ#E&dTwGf77+Aqo&aN<*HJmJCLq?V zHG~|GNi@og!+$ethKNJfN>W^Bgj}ONC56NI?T#KH>m9IK5Ex~Q4s1JStU6?(3AGp%T ze8$^HR>h;{Z|g(cL7oxXow_ zPORL2){SEL)jAq;B`A@1eZ}iWqvUeIRw7jI=M%sz)xcOsDWTy=wf3WA#-U2J4w<_{ zA7IWUEG~Cm2{5@1Fc$-ib&-kR9~OoTg7?A6>XA}1RvT&82Q4a0FWgHq_o%gsK^7&3 ziWpG>=hlapuR60nay!ZO;Z*heml8dRXoIVBfp)#=R0_`{U~aU+F*bb!x&I&)=%~MU z{Zj(DVmb(>3u=FF{&xO;@6(?8aCWK#WdfCl1eF^{0kEF4QCaC}-FyE9p_8fGvO_1+ zf|DOxkJ$j@x%`B4>=!kca?TF=@gFkV`{GwXGP@qXRTpq(UiO%oC%zzVafetU5qArZ zS7XUyKZ{M!Y{JI(g)2u&_GvG$FEh2a`2&N62iKo?FBDC^rWo!~aEFFJ!vpS63GY z^MdZ`YAeIO$vz8{vl4^W+n| zzkfJk?nVR_Nnp?9v(3j{p?sG;Pe2s$s{PsFa@=sj<-TyaCtU9MHgL^aXdAcFPq(~8 zi#54(C0lObtacMF-b2h!hWG7_-Db`(v$lOg%fVn8krkUxE!;Y(=~VIdT&)9tvOKK6 z6G7O@$B*r3)2Z^UpJ+N&wSA6v*TJ;T@{Z-ZpSQELBTkkDkF-y4ituKLmECD!ZMAj} zt0_F*RJB-|8v7#ru#9R%wB^^>RezD~>p|cz30QqStyK{v)i@+cLnv z!ZP>rE7reOxe=r3ft0FXN_ldDRonBtEuWW6a>jatepq45sHm`}hN3rYeIOLA+?E@P zuJ%5(QrEO5Snm{%+{&bqMpgMQMQ$$7a^OS+@HFpphkGdPVE|#&|w*gJ0GOD|Pmt#j28L_(LZ>IE|bnAns09I9KGKC`49I^IJ=4%_^nH z+x#vfEi3fa^UeEc*k#QjnX?Nt3g@+^$;almwo@Rqugs#X-ArgOLwB-@I?=g4!QYgM zir_c-j-X0mXgyNMM-ooz1bQ5ipl(z+fkdQ53QEAqKmx` z{R`lPqUGM!C8SVz+jzz9xdCGyug~Zed3v-V6 znnnJJyt_0eiYd>oqsz52av6`*ivAjrm}4Qe%-b@V2Sjbz^Wx_n0Yl|@TiGItCEEHK z_ltPHA|8UJi@guCpNB@CqQ@@l{g7Jt(Q%RC`$z0gM1kdrs%RBe+REk)5P3sCKgV!u zL>o&$FoXn|QZi`}@1e8|VqokbZXkJd5G%(H;x&Aznm~7C9k~VP-mfg+skF%ZZ~?#IzC6^qpMu*FM)fQXMLpgJ zAE1vI#`=$C`!tpSN$`*$15MuG$4;lL&|Bm^@JcSArEMrZGDyN-EuSFf2wOK&K7oqJ z_x=mz;}c{HB?Y&4@%ww`*8A2SJiV!5xea{O@b*r)mXB7b-C7v}F< z{QZ!>$6WZ?>cHjQ`tg8Ddg)RV#=O}P&h3m{#CVo?=kYgp(pzZow!*!ow|T!I8_c@e zE;TOcdzbQzdCN?Cn%3p( z`hsZ)0dY}6{ANPE<3evo#gcBmXJpjGrq)`wBW0xpJe#;EWo=3Cz5Ztf9QK`y2sup@ zeAhGkg(HaFiB&Y6r{B7&&p)|@y_oh?6fRpH~O%H zy2TezocLrl>?w1*ns0N*xYop37`w!`eL~o0u5{yzT8B4bVQ13=?iBAobh4Ngr?9Wz z?cG)G?l!xcE8O&+6}8+AN@Xx4gE1cGM&XM_N?CWP7+te@rG8KP3U8IK@_{*FSCAzY z=&G{iNxsQY=#-pzij0U($&IJTz~~fjJf%reGLa?IB;s*OEZ=^)LciSRw&g7OK8~@` zm`JIM-G+lWo91R$)BWx=BNrsJuW>h>O4*jyR;97vd_D(uLdBVa6!7T*iCw*}CwApJ zp5)xrRO#t;?IfWsj?A*|cEO$_YnJ%4@6Bjm>E@20glDBGW68?)A(W&Ef>Zjb&aow8!maD&cN>iyWPF6{Xh|}B9k=f=?)1ge53PBUqQZIE1sDxIi<|vU|pvE-6bmZolFgY<8fBr1>=@WV)bDi-2-p=)-?T)R?P{D(xPCJQpS5l7 z7^)?%WiQ8kY5kkR8?!esKM?A;=-@q_DOTm?((Fw0Mz@)=F?O312 zl}E;pT|4F$v!*I`lSj|3!p@=;V_aEzQ?V;}QrG%EN?F$YzV^u}ZN9P_gD0H&Wa}%) zMoV3HdkX4#*43%Oapl*tXBOm`6uWcd=X2+VJ$mc1HHCFlEC^-B!c#_I1mP(nAfSGs zWKBXl@r4(k*2z)+-Du^8=E#F9G)Gch;T*+}S(z8k5w2@iPJVodgm8|Vx1Mwl|GMgS zPfmFE7Vh22R#0q~|MV>X$eIp+w5p+KrriBKTt)A>Lp_Y|#_DTe_YBRR$)j!^awBo* zj$v0)ak=t$Y-L>Hi zGzB+xr6pIg7N4~&Ta(;tl(G{!9Xj%L+@DfiV?#NBw5hu|m%Hj4v&}3xl^gG-EHkC! z=4)a%RYP?>EV<*rLRwFTYZJ9^NX%i0k;E-FsPUK4aT}p3OqFzevY9JWBtALZ_{ItX zM&Xo&!MQR-rpy>b2tJLuqsUxr*Xba0Il{r%x{~`0fl!}=IKdKzkx5nRISScW$`mZ& zTQnEZVJr;SWExlMwQ{M8nEHvKe468=n*DjXvuCxeRQ6CBo>+#N8(#j9ThNl|4Vqpk znz`k2b3*7;%$QcPd;28whGw@p0m$M@ot4jvvliMbW2%+Icn8wd8ALxmjzKbPpf^Ja zi;fu_Yymm*11@I>&jY&f3U_#$xMpypJIiyWCqDVPiOBMV>fJ86hr))E#TeGgAK5)D zyzR?VV%>4J9lP5TyBmHZm{#vVSXdhqkO(^tP7AmjMu5c0(_`m>dXhkXOgKeTQ5ku} z7~+YgYu)02OoH)J^QTve(~A_b`HiH#&j67Ucc@|vo3^=AwqF{$M&`V6A(jE-e7LeP z1;D3qWiT{RTV?K3cY`o113g?2moyIe2)tK#r1%2E>kxyCd-wutlMFFuJ#J#4@PWI2 zez#Fxu~yFqZcWHmeMt1A&k;u3SEII|LRjYqJnBh1J*VB9SY&M~H3(M8@Y2h|gOa~Gk$-B*ZsWYdy+uA_QhCwjl;Ek7 z7w*ekBb`3~tim$M3lG+qC);Y=&7aZSBs`597&yi7Fw0tz&)((~x8B_Ll&k&O#`UF} zjMCBqqo}mhSX6qAv9R<~4w?PUR(I(awiTf6yGw7`HiKIs-KE9bCYLtd<1RgOU-pS~ z;pUFJW5-#_$Y6VqhM!TnFuQ8!4oN;w!HVz3FyLzr;zWJKAb8=-o z&ncFhe^X8ISC1nqbS%kiyH=yEW)yE7jnQyMn?K5wgnY7B3%R`c3Z`SWE&QIW-Ql{y zHg6a^;A=4c`Mp&&An2q}14c_QEZ5GId_k^lNt`B`mu#wn<-b?Bx9!F_+G{!*Plj{b zDo?gmzFE@6R!w?wr3Yv4b)FNiPU`CMznOBPKa3fY(cil!FkTj@)oN`5o;EJd@|e4` z`oeVs!J`|BD&I8Rt9!;>+TlOxz*d)lO>UI=-UCUXnJF@obTXWQcxCQmW~*b&8%-z< z_<*aY#$`~9i3u-=8K}%HRIyuT*feHLaC|ry^Bbit&l)~dvKl4R8ic}lq-?)QT7$Q* z3@ybCzFOT$KC85S1@egDEln}9P*AJXXQkRCcKlE1YD~iM)vRn!qw&f#sABxzW=d{k zTobJDFVla#Th1zQF8bHBpJCI!ru&P|Dzs*eW;X3E+5dkWN(W%fLN><tiStH> zgw91~Z=RJWPG(E6w|g+eEE2lnP{u{V5HB8G9_8>j)R5(H#r1l16l&}TtPSU)y4A2| z*wqpIoUbjqz__OPLZQ7Av~OLpHjr(LCgoHM8=h8pkSrP37R|UKcnWguQ*VK|IK>5N z0*h@D$>B0-AEouQZH!N7T-#9{UEvFgHgb%WYoA;-4!%pv*em88(`JiUr;2g0PUn;( zBfNkfj0i(pB8yl1xU0ME6QP>GIAWH$WtN#EGA-A84P93i*4fl40Fu71l_^u(4BlKZ2A3jXb3D zNyEn49n7%j5S2P4x`t!cKFAz)2Y*r$Ynagp3)LjZ_^dp#eM%l3E9~R)xa@k$$JBE! z`uxmU^m(p4-#9yuZBjdy2Te2;uL)+CKbs9E;#O_6u8H;(YQtPgHzIOynnmvh45Jm= z!u-kal;31$uVMoZm;b0*1;h;cFqf;(X9#eFw{X!_>I&(J$UET)!h2!gT@kr2p&EO7 zVa&P&p1P8S^@`G}?UzAC-XEOcKCtrdbiL(H>zQ<_oC~LHGk;2HS@4zi6vyUOT87)x z*-XUxlnwR)I58iswM`gyYN`$VCyem|%Vc1&rV6wYP01>-VOQ!Q2*rW@E=JpdJ*w^_ z{VdoKWSo%?9m{j(i{_*_#6r}MZN3Q*wQzT?ZESF`!ob!sOlwPTIS1$7Yg_E>u68Nxb~{pbZlzJQLKEl{FTw@{@}*A4c(?trucm%tAI-VgKaubM-6UmK1(XZI@`jDMb>7871y9O&$iUj;WCw$QaUE2%Dq(^{!Zlv3q~$ zOv-)b0tC~TA8AU2^V6*6BR&4p3xr`m$rR(#UR>~RqSxSIK)$N4%7pPS&qejYI&my; z4X0beel`i^iw3Ic>H?0qK(S%}2qp~prUt>9fiWYnQD;A<9+B*RN{(=hi2YHGfucN= zOFQ#;Il42V-WD>}1P}Q)?{$^2o(Vlz_t|9Li`5_8TAzXO_qx_U$DF$!On6)0XZWyR zIK7Ew&lZcUdPV5efyGEdU9BBA#}yzO~HkRbq0Bv&=#DwPY=rej>>-KZD501ZZ~t6EG&KM zE{Id~8?|JS2DY{~KI*Iq^h7Sa{#)|sVTe`*ZWHd1;#9$O30!P*2Q^nyd0g7H4?*2x zr;Q$s;#`79N!&cB?X1+x&PvtdP3Ty@BS~r@XBp}aQrg3dge&N!6qBgUBn0W|5jLE_ zh1_&5$jEn);RQ}E{O?U`J-WEA5qlGE!E}bZfhp`#v|2e5a zO`9z;m9!?P7*xk)R`#jeH4Vf^;bN!ZZ;~o(70Igr%8EA<%qY`)u?VZ%ucT2UyWtUG z<@>bf7OC4><%}!p3vp%Cny6}l!$b%BY=@x4*1ViNQ2U6U1<%m;Rqoll94f zt8{7b!Z?HozHbO04_qq5ChHUCWZNqB0x#Msqk`48V!Aq5*5PleQma{OI$H1N-zv3? zf14UxQfh0x4<96J)33IYlHz^%*SrL7@9>8q$|kjteqi36R#UH-RX`?!DM@Q>z(prbVt*WD=j7RmAKkD)Ss*q)$>S4Y{ zB7m$rD6;=FyNeA^*mPG%_DCNYI$DdQCbfH}ka=~(?ZT)#HcOSoziTpiz zV|oT^?PPw0v^K#>%`)ZYmAam>?UvPc_asDNAM%e<+~h- z(P+z)WlSeSuC0P52nwQ&ETlS}<2dq@W1lJmJmV7qpQurSz;~6*G<2)4*XdiRWfMZ* zSe#K6nL$-j-foknwyNoa<-v;^zD?iNr*V!2LqC9vHhi6g>W&V9Ons4=6Coyqt09|I z>vw@KN;4jEH+@jCbxzXKijOPvHACGDjCk&cCO^(7P*)1*?tB)y9g| z#_ZL>>WaJ3k(h-;l*>VyE0u<BFxI$gHBO*OGK^jHnS`H{9{$3;aP}gPx8+%SS6t~cu4*b;AG{(y5wp3UR#YY} zU8?2qk;wPSSkX{90Rsy{MeOGR?b*BO2W@&1YV&_h{(bz zY$=(R?N#P>s2erM<|x7__#%M*0^ya!#5yzPE^1^Mnct%(gzyDD>6uVXO0Rzqf^%t0 z!moI$d0vDVhsRz0qPOKoB*NruU;g5{L9r(cTDjVu@B_xv&WeQl0_PM%Co{K9w{g41 z%sGZxpu_)_D)n<5DI%3rG^>#X{kd@6P}8aP!8|*oM!m?RJ&;>OPB}K6D&V77i8Z3- zert6#YD(l)8HZC#y{!@jU!)s#0GTOyl6)pUN&3_ae{d$rUAH7n5=<~@Rn{X?xWoVR zDm^VaDu19>GN&1sb|}pB|2*vPH~l;H6hV>*`v+7OT{ZnY4w?P|tIjU}M7;c?D$_2% zN|%2k?B8$tpLELa5Bm?P7xf1oDSybCIjeC`hrbgLzK#{8Q{DYZsJK?k-{Hz3gdvU9 z`Tpk|i;7^v1?fcKiMQi)3dQ{ri`puiKpD2m<-Lw9sy^% zTJJ82&W0w>{$%CpS(PKzg~!Y65N#UYp;P7FheM15XX@}uDnSpC6BwaWxM!_m<2iKd z@@*SJr;5D~m-2?KHWpjd`iPcsqbh|dR9qJ|=GpYzU)8Wxf7!3zh6up$JYhkCNQirY zRVc3Y0z2Zrtk^(==v-8nZETG%ET6Dhgb0tNvfzK-u4{nCM4}WK_@^=emBXC+pCp^h z?$T?5<3%PKR%gg1+RU&T;R)SAXaKE2Bcmu!wseqX^^!cIqYm11xz-%+Xx$74LNtF1 zJm}G3=E>$Q67ga&dePearfspygLCZeBDrsaVAMWFh`yFedzFKT)pnucx?yiiE>(); zBGSld7^C!89VvjhhAoR44uMfQD5eIY7?`76z?vN z4Q}Y0!{#m087#|CrWb7W23Qk_R4G&q|wVHHcJ$Csx*wD1{ z1GUxa45%bVB(O=+yP!T_x>h-a%GLx<*3mgwRHR&BJ-O5<4#6rOD$p%m>wUwHkZaL7&Y-k5r#yX3PmqBriD89v76`pvZ#u-m=L*w zYR{!TWzi-poPQ@%zpqM71P!$i%I_c)2tp|f?8^5Yd~d5#?~4e>_ow6EUv$2Io9~@f z>Y(%eTk-GzBHu6R@IT4-??Jdde1Ng^sbD-uSaR5Ej8dCOjXVP8m__9NdrYXzO#-io z);A#2N9&r;fdJFbBcqLGKctBrT3A9|(1f-6ER3HfeQk&T5vtrSHZHL%=$V7cFakZY z=D|6PDbS9yX)IQaa9-p?ax*kCJ!DNgEhF?E%YW$Px~&I8C#$^=?bZ}tvu#i4WUcq1 zALzGD!KV_`6FOPGbq7;m+xXDQ72BqSPOjSi9S6Dwq3FqUHtAh(ek01grjMqgr81Eb zYJWP(mT{E2g;m7)SQfV}S(8xM*|CKwh+Lx7Vlp-KJ4^iQ!kah)VRMvYk-79K2|tmq z(VygZ3GH8ae1(%_IDGUOs+3SAPx2Lh>TNm1Lv!V6aSn_0@jPnXYd*FEGyptzF+qV% z_ov{bJWFV~^2u1JvLzPk5J4-p)|@HpNVU0G%={gdPlmOn$2rE{33oIL&H}8DCXE4F~I0oS(p>UEq0_4@fEWjWKx$bClhmOZ`hfcG>-4BkWeGuPA$LR#GAsy4cyWzo=p*VK$h?>oln2HUbGe(@n zuw{MFPvNlc*QT)UvFKdUjbV)hmKxFg6Z99-?tH0^riArpR80bHddGyl!!8k`@b3)= z`((ppx~aW_s+LuUbr$u~Yk=Gn=@i`GG33Zn$UIFamj*HN91;y+Ih-NB)H~}uOMbh< z#kHg*@kA;L_iFqjx_+ape2vIQe>}=;DB;^qPbUm}*nAP;Z6XL4M|#|!rsDpzB33yl z?oXUnpE~~q!pL?{vSG6p?uIkCw_=lO$mQD#oqcDs`N7EMih2DGehuu7*g_21oy)Dx~Ra4?3(V zT&eJGC~J@*lNSC}S`hwY%T+=wn{ejQ12-yB?WHFyN2b3|h(tjhh$DKV{opML5eh{@ zn`deYkHFlbffU4PZO)yw&0Q6~Ky|@d99xSpJ2Bn_mug7cS$J?tC^Jg1Szvdb!T4zr zKx`~On;;rYACbl=-lsf5_6duPQye2j+NLUS9Q(bv>W)5h!=?ll6TpJs zhAR14A+{#-q}s%67N)>XNrhmD73SZed64pFVGnV$AJUgKy`fA>W;YbcPkO^bojUSAci4_g^IJMBYoXw;>j{XMA&%v|=WokX-C(BV({S$1+I?vIo zqN++JjyAhT)nx>TEjM;`iQe`?kQ)vGp8vDvUWJIuvdkR6l-6a%Y?U z#l?otaZ~Q~%W-s9FfH78teGD2c=n=R6xo!Gar^`EZ4XZ6gr8vJAE7}T8yXUy3qrwmqU@FBVB=gyMW;%J6xR{4T^I%b5;)1JsTZVWf$Y>dazDdsm zv+qFU#DU{oDaKXWg`V>omuWFixJeff84vFSN@lgJG_tJH_i zKn<#2AB@FxuSC`FmeYPo(0g0tWJPh~h__YDaTJy1<3AZQmXAs-4kAN#R$;0UN%9*q zmMFapJv1<=zOQ*Rt=&n=F2P)F>tb;d30{*ZiRlJYgbnLg$!7XN<4)7Rr(tuKPzaDe zrzX+~U4lw0skGs9`rDv7NeTI&mN7>(e&Avbg1jxCBua&_47Wy@x8*|KTYJ2%)A&sQ zppo_c!y0m5G5~@`-TJjUmB&sKyMZ62d64dPs&44mtR!(yxRRZbp-3;oJ1e+nR`5Bd z{;c4@tl%LD_1qb5+!<~h4mb9P8~2;tCBI8TaY?VY<(p71iCcsSfNY5z$`K6piEI;w znaGkLjlZND*U9(9lR*{9&pEj_2c(tV0hn$gB^jtt^^0(eibpeASfYqeiwX zLY!?c4>bYD*1R|1B-aY*K8PYfiO8{fk}IF@UglHU?D}WHZ{ZQI4J+d50k^jMc<_miPSz z8M@c-hKrJpYDfsl9W-;1ZpHzH5qXJtrOZK&|JrjfU7u!=gMV@$jGGBt#QKyN3ZauP z*d`p{yj9PM9V*R+bGcBb=t~gJF)?T|ulA_>bSjVN27C058Z#K};Y7ca8+y{YHRV_6 z+{u(LCWB73m)zE6zzya^&Tq3iQTJ5VonR2dUgrpzFnVK*5d^r+oDr1b!U?UdlG(+*TV0ibSR!VO`CatZFl%e=Ywy%t4+e{A*v$3(&DRvvhR_h6&u8~RQAC$SxC=ZE! zU1Q`^!u({3fKqJ!CKIJoy~*G;2&ZMWl=oM`edIS$y>Bv8sxLVpThKN{he2!Pf5qgZ zk!5zz?!8nNMQfa(78p+SPwy;i?bM4v^6J+^_5ylrzJikktK|IO%Gs4`C41{`V|+tC z%}2C`O(Zx9X2vUKYK&HUc8znAYHWT-7S-fh$2A-ifo+XM7XB|+dFj|HS)7xr^fo-F zg}*`d86o5;I%x3}(9pF+4%~TL-uM8ls**8(E^-qTV`pRD#6Y;Hfi|Cw6!Q%ZrsbT% zgW7$->@Hq++Q^vQJHN+D!nn9V-wV|`w9ffEp>;8?e{2E5Fi_JZXQo{$CIn3r7Kgmb zaLLA2%c{y!-tcpc$sMZ127ars5DmONU2sxDNVy;}Wogvg^3O~FQawU=fO;0yjbe6` zp?;4NTHN_a=R|Q#)DBxSs{RZ4V8zC~kIi>wqCVup;AmHOI9;tepvUQ}!ncyT>Y$gS zkAhygqQfECbHTjG`Ib}AS$Gg1%&+g}u1%)P{2piS850vIew212-vR^w<@%cJ`m$yE zvBt)$WXH%EwMVE@t_{&s>Rj+4M+o@`xX+|>J;Jh;DT5WSXcQ+c4o*sCiSJX5hsNUM z<6HRXC>x7L7w06b-|w{W>dU%?>1VZoxXoU(ff3H>+UK17|7_>CkZYJq3>2~hijZjQ zL+EXz-aB|r^->xAAye+H|GZ zceHm2Z0HyP%fv^>v6W!6K~U*+7l^Gw@J4CBKv1eQ&)Ek`L`fBjP&>aJKYYmFE!nQ_X zkW6RDSk+~_U%(V4_5EQ z)n!HI(ivw!#oaoeR~gVeev4+x*csVHdb5rlHqtoTgJ&M3ZzIumM0irv?mqRN;0!F{ z+cJb{si-c?xR|_bh;8WTXn-2kEq2B}ov~js4v-Nr;=V(W^JXZ>@x#^Lzy;oxl_cR0 zdXj#G)cF`{csZtDJk_|7*?aUItrau@VIyhOMy{a<+Y43rU$N1EE$oqU)TOX?kkC7l zqT2VVR3RGVe*o(7XQf1PrC2$>c$YTlWKJB z_QL&Y8>RSw2}(AdIx5@LC+&JR>v~3=ixL*Z<$5#V!9HY1($u&pwgQcljJX7BB8I(g;R^d>^57ftOwxxw$ zj7hMesQ@tcAYo(<*~F$7cd*3W!ptB5RuoqY^MlaasW4Sm&`fwfQ&*eN?j)e5vM5YBD+z_ z>LKv{TDN#@yew2lS-VC1RFAmXzy(^r&99a+9CK+xqrY3d+YLYtCzdU<YP>+H$I^wm;6hEdW>OMQOom$t% zZ2`M~rH|kE@b`7dE1XAo=G4ODFi~80CNXt106owr#0k6%DGlydDRzT*owLEmNVQE8 zQFR$~qx-F`Glq{tnJBdOPXEOCw}e8A?Ph{1V#?XweNPyOS6!S)*s)7?0FCKl!-kok zhMAj{{fvwVJp*E_c23wnx?9FGX;lgxyV+qQA_}p}%?kEm%#|HHIpy%52*RZW!OL{y zC)vpJ@pq_7T`AMQ@=$1xujFOJ=QPwWQc)SAxV?(Bry$^#dmr6BD>&@kg;8y2Rp^cR zq1{vGzgl>3R#1i49W%S-cD2s^kAI>RN{^kvE&3a?}Hr(fO7 z;0H3*iDzX9Fp6Vat2`8Wf|>%EkzbCbe{YKbS^KOqixYQZRJvt3RQaPnLf&%>P&15lJb>m5#15WHZ_XuMRVcF6ir`4XeS z4(jZ)un+1JcsJJ_i?kuOI0LpnBCbGq3LE2`l|J;rh296XH6w__U*(9ZW9ni-7h$e1 zNtnBfDG+KrhKMY;r=7v)j5_>vkuCHzvVq$q7hk9YgLEk_vHP-C8p5aAzL4StTGBo_wWucb-< zo;b6ALVXz~24m=z=_&>`YNUJi0R*IT$!}cP;eP_U`gxTqJ#Mq1Z%cNY4UJd`_j@!W ze?qN*{ZQKjXVqrSOe`K`exgc^hzkZ3Hlj}aGO>Jf5Q{Zc5@TIV>s))Vh3n5}69JcF!C*UuM@%*yXU`*nyK?S?Alu;F-fLo~_!h_k{Y4u!%mkLyy%cdX9IeBxN?7%val)zj#*W!h_GEpSg9>*k_dJW`$;+P(AdXHW$UKfo}Tj3D0b=Clx71rsp9wffws4qS0cx zA~H*c3}2V@b3wJ`u`9kPsp32BijP1&(n-PelQR63(X1E6&A`aOuP9&CB~BWR;Gnel z1NOAQaXOYqDJ-PKIyWO2dcGQQcVfoR5P#+zZCM?sVbaS(VY*yG`mSWk*9I*;v?D;HRqUOHJ zzKocd;CZE~!Sf!Is3%(DmZeSY8y33KAJc*a^4r^8nrd8_q_k(n5B=bgZ@aHS$BU&B zS)I=V+mA_v6zrYzKs#<4M0<((${v?^DWT4Bp+SU&Df@Y;ecaAxTocnFRwxdO1S`6{ zx!;=BbTU7fTh=!IS}TQZRj!D&+`N=&hx#|$yV_E9PRBVT;TSJYt)f3QLTtdwFrh8$0BzM01EVQ#0XH zh`z`=9+iNPRqF3B!V=Z#nonRyfoQZuq`rcx!-crk3-*TlczC@7#VbOUkX*c>@ifE< zAs+{Irje*!6mdl(SB=17$%u~Gk{wkx8x|CZJ(&Ns!>`;s0FW#Y&W^HawXPS;%ngchBCRS%gbefX5(bJPC+Qbx+jpX4vvRRFv|_|+y--!JGsJ3WLUXb z2e_LsQiiw?gYR5et@WrJ-|K^{hOc2{kErpWb~S@ z{kzY^x@l{^wA&42w+UoHQqP90C<(GZ$@6~!vRE)b7Mw8#vLjF1ko`?EWQFH|>`Di+ zuVxBlqwZJjE^TwtmlA&tRmBnKISuB0D(Z+|QL`8QMg*`>^v3Nsd0Q@J6(ibUC_3Kz zU^dU(IdS6wA~L>xA&8ayNmaSgG0a114KCq0Xwn-CT~ z{~45p3W_zjqylYKjaF>EKNMZL%^Qkdzy0$KQQ35eNE^fQ^m}<4k|#Ol4)VM$o%&(5 zQ4@;ZM5u#kjj=S;xdYg?EJPl$o2ydS!2HZA!j8hbrBkU<^>R zwXNZh{A_9Hlb_9jGaKyFSd8GTpd5v?En}e%&UjnJTINJ@I>zjmC3Z+#>obGHRq9q2 z84=imDwyM(`WsRQgb#^tkrv!lM2ss9ozhggjJ^8a2;XG&rA>!l{`Tq|-ynbQ@HfKW8U7BH=3qZ}rKF^$ zrlqCZ!R&DA(lY`VrX;8M`Q6ppN_sED9a05GAf#r(#f{4<44I-X?=42a3!L^FFsd-OvT>ZCbr*5D=2$*gsW)-Jgi;G~4sDEQL@uGi7xfwuyGE zpX{GVbYz1#^ne$NuGzlXzK(N#xAAEX-vDFWWjbiH-8RkV`D?*rS)=p#zpbf7=;wE@l<4P?XK9&HC;q&*RaW4J+hi@(ar`l3=`x>7lv!ez5#wLO4dg@6 zmH__}`7St5EhnG3xJ(iVw$CAg&l3$iJWh?Hl)S$o@74`#t7B8-0sxahKg0pAX%INg z{@4^Bi6z||j7+&`cS=DDUc(ZyQ3CICE81I>?sa9;v^v70siUYS{3AzWi+_5PzL=7u zt0V2}Cb!(2GBbFAzJ!kBc1c%N&h~TVmkz@!o6%!kHtKg)Av~bnf0`>o^NnK5#(qi-hzuu9$?2aY zFzgQ$m^e1woxq+1DN8C~f1rSURdWK}oj^lnPKKl?-8dQ*zVIUy^1Pj7PSVMd2K^PTCgUBp41Y!iIyR$#63 z?2<*kT6Q0lYdfdtVC~1ggp`BtMP1$yNxX#nn=wZ7_RRPl+9BQ!v>!3Js@qZ&ND02fzp24}$=`(2I~TX~ zFd>qtM{D6L^yH6U7yTE(U&B#?1o(lJQTXq>ISKxsRQ?}>KjR$m`~U0kf3-FV{v`B2 zy*3H_?_T$R4E*aZ`D^Hhz&PEsoOSHS5LL{WsEQgAw|#i7<;L>#$K@-BWVseZo@`z~ zIN!Bqt%S;D=9dh!KD#Jqug?cK+Q|AGVwM#9v+w(Zxz5*?QL>*j41u*FR8(;r}ea5c^0z!`eRY0UDIN=|VFM7OIH|G;_A$F}oRPM;*=y)&h2Fe__+mJY1c^?DZfFnP?wUDkzZ z2eV#D+uMx&liU)$s`aao>6uuZ*UN8B^}51X;UCTA1(UNn8>d|7 zH*YTRo#;RDYN+28CNaz_A7}7!@^yU68~rweZ+Tf_{k)+dPb%OV-}eZNy_^c(L(=O$ zaXaaKW{>=)9k8zBo8-K?V6x;nU>!Jo;GF~gSx15+lAl~N$i18<-by=gPi_hMws`LG zmXP)n|AwLfmHSK zGe~<5nG^Q8G8<|*5u4p7#D1b{@bvI$qd+{Az_?3I`mA^*a=VV}js*Q*BlOQ5H7sl5 zqNCmoFw7ZNx_XO;+S1`FeTd!a4TjXb;v>Q>l(sMKg6k#(y zS~qx<-GKh1b!yGgjH7kKvHjwA)q0S&)El1`u5u9tBfEL0bkScbO(Sf%y6j4Htfb*J6-w_xtr%uMkTHFxEm27r^<-Uoj~j#xoQRaK5J4)oviAicG|#)IyEn1^gh zY2v{xwHS8er^QStLOkgs@_eiPJj}Bt6&{m^aXcs?T?;m;Mg&122S+Ne2+<1NE5&iZsOyIU1CVI6qIxT(TeCMFtV zPKB|8BVLm*qx_I!y*ea}SgWk!UZHQ7Hp|$_a^};z$bQDZ+GS0(pM}We3e2q_$;0!S zj%|50v0uomYTYdBvfRJ4(p<)E06v3@n5S~}Pjn{Nm)+5M#zHUaJi5Xcdi~9#bx(%& zriCjzYdUW9tl>&bslf@wppQqtf0tC2$H0KhU)(SC|4WdhYY0EWoyAKwUj+vUW>u?q zJ_n1SO;7Af_4eI73vZSn!C#yOSNTJo`Q`?jHLY25)bBZb`eeY!H&jP4v}t(@IgU(y%+lwJB=&@H0%wo8#Gx6LG!cW^cZ zYV_B@9Cg7xQoAqG!bnutsCMeZoA5r#GLW8sfSXB=`HtRN;2{BE0(0;YcuW_WVcmTA zPp?LPKX`o4@-NIdn(n&N>#HI2;lVN4-PX$#mFRvpJii7W+(eG{SFrvckvjs9)_K$< zn#B}P&LkZ@`|zLERv)b!8Pnm&y_CmTv(5u{*}}=Kt$jv!x~ra>{3ehxb#|9|xW1&@ z+bWiadb2KfXB4cM!}YUYGvBK(c`cZI0x4h|N*OoV&8#=^=8D$OK2cxtUU10?|787% z+s`?{i}MMC;u-LRg*>{&pd=$>F38xZkx>x&PpH4Xq~F^jdmr^B!`!jVg9O==(W2bp zPHN(xcYZ&3^swM;TJ_O>slQ)Mp-+6o0Qhf|Iox^pPs#aa9e$zLKf(uB{{g{vd61b9 z<={lGKT2ZL;TP=m^e^Nj9=|v>{#YKIJNkIu(N{aJjUC0aVT6Agqx66EYJRPi@jOYD zlp9jXVY|s$uQ?Ahtc;_t9_>Hc-*IgU)kr0s!SUBw&qx|dubdG))%C8g^<=R61RE?~ zS5T~6tB%@7TWuofghk^p#7s|@qihao?4XY%fs>EH_QXhsubG2b-Ssj94qTIUN zM7;;nDd`cRD|D~Tn zNa-4MH^1(sy{xYKu>Z^`|C71Ok-j-1{a-3MzwOi_AE<=OA>*GrT0E(z6NC0uT7@hU zZR*y@Pt$eLk8KW&gT!ZY+#6VrFIss^rgGJtd2W5Q6WyP<4($nX{)Omt#(}3TN&*)EaVdB6b!XhcHt`%g{_8WCTwi`mK!-d zEm55^sN{H8s<-t+mX2cLEiG%fL-3L^-(pS=TPg>ZIQPHQatYO*jua(HC zM}ilqztbfkCYSoYew0Cy?tLDB#Jp=;i2865FumdBx${_0!#C$HFuiH}j+eVD@SgfY z`%2uD#~b(PW#x0RO00ipTy0sVw__Q>%`|uKS)Q9do})zG_8W5NNM^szytK_@ddut* zl9g%gBI}&-!8tnXT*-RA&N?}sm29FfkZtaG;`&u82_jx)j*BhIh)o&Ba4yEN`-KoH z6GM8#yQGavsT}E8p5Z?3YVx)WG2MYRl>?@c$@M+ zFzeVX7)k36*ZEitSpPx?XqdUot$XKtTmFrg(i|hZEp1sjIF5W(KKe&0O8V~2(@yr| z3I5TQ%BP;R@m3K0TX)9%gdDI};0KW%2TpqC09CsX0iP(TY)p+BX zp0pfuTzh4#?S_nWYfeesa4`oRTQl1ynclXWbLZfSkrs@<&0RLX-VK-Xa@~RcdG*dUvSunC#MV(Umc-N@ZM165C3lD4ql+!w-HU zA^X>yR=5|*>Kk2YXv8Do8>TL_dZ3A{`p{n{3LP>U96#*|orEmx0AM-Bg!(YCUs8$X z!^lU(7ngmQP}iA~u6r-&s7S336^{?*ggV`P4UE^Sg%D8-`9gjlyB?ZK@H-=#Ycg#o zQOO2PCF|l;GOwPq>f?=n59F+`52p#GM1@l1;Uo`p%yB)-GvlEV@XeN znkv3I&SF(LkKu)pfbjF^o!(ZAvHJ4t2xfY=^EPcj{R{XSz9;TR8UFyosq^6R0lW54 zP}ub!*T(A-4kcdvoL%sg%+$R4eDyk?C0kD-oBAf>Z5=ebVbgII{b$%UT-qGx-L-t7 z8$-X*gJ)E1dx_u2AzkjA{yV3d?Yf$&v|>HIRyD&tYW1-(CgnnRiZL0sBSfgardZ(tECu$9+Q_4 zcO#4gJeLd9!0b*Fy`!U2G{PzK*cC+hd;{r=o$Nq%L#NP} zLx+N@hvL3o;{k+%L+VZPcT?b+p@fYJJx9#`+4?q(DaCc@sYMqMqSNmoYM*^OH5MmK zirT+!Se?eI%M2@CKRI}TohifnR4#W|7jg9kR{yA2{Y4UZQML-aPjRu*FE=}TB8?O@ z+nxX#t{&Sqhv=YN^9v6a*Aa4Qw_E|VraF+xR){^lTRX|kRs`#rE^<$P(Z&?Ex0|Fq zjd@$@>8Z2n6uE_G_3WBB4@w3`sHcR_-1jIgbqa%_YJ4rmwB~KSnoQO&6Pr~s-h$Bo z5W-D3{n6*@n%2(yYfI9F5bs?>(2bMn#F`hephJrVUI;E!ni~r)4lR)ju2V3)X0sU! zllRFj1;I&c10$AaZQwtxw6%fb80{Ebb6j{vMq%d$YpNQliW9y%%_Brk7ZP{HVRR8k z%$kxHw{9rO(J-tvyC8WkK^rTY*pz!^sB~e>Sm=NfoH_qR!?)7#to#@#LfIK$ip{Fe zk3w-DkKsHG#0E!r1r2c3V4k|ghEOd{WG7!;HyHBR7`o?tb)}PMdLoaq*jupv!^T~< zMq6m`!mh!L7fHDS%E`?6F)(x&9svv*i47*E4|w4IXF!Jr?0(P*u0xix^C(L%@)}&n=*30U zAlyO1Zwv&oR75Ia0rLob{tdk#?OU%4^o^$ppAw3dc{?Qdn7Qaz0#7SPG6^Bdw!)z4 z)+y=Yx{Z1s`XOPcBg?>qPMiKwR1j8nz)Axd!sgUG>IgLs z5OJM65AW@En@9Hjmae=iu&1>PbsR!S&0Hs6M`>r4j7986Ce z=16R)aVQpi;JU~HW>%mqkgu#8C`U|&L3r8UbY$y(gi}I#D&?CKns@6y?Y?)K1Q6kh z(w?&P@uAy1E;Kg^Sx39xPUkp@O$jfDUca#G_4s)82;tNy+29teqIZlkViEae5Qn}c z>@Q0#)zCv;^JHMcq{^TSU2){NKigxZHFf8s=eWvnj8AKNv5CYcd4L~aVBY~QN_vA+ z<6_61mGp=qR1Zq!veTK~^deVxi+DpJVk!a=SF_+P#=<*UO(TD=L|4McfyZ!>wVMH_ z>T2~U{$RDWO)sWsMWZ9zRx*0*iD6D?yETo z*y3S7AHSh>qDeFq)#G3b1N{P~(7L#j&kY4`Vp(A-aqX0uYh1w&%w)6TbRj`Gv(Mmv z(%>_z=G|mg72KpIlOeRnQ%4q$>=T7kcd~%Gx@mzV-?}Osm&$15_Sj;Q9n&v|6P6da zZC@pF&BF`I+$_j9t`2Q2SP(NZN{oU9_g&ht=sq+1XW<(PvcnbGp{+mTcDZQ{TDn{Q zGmlzHeWpBY;=y{FjU>0@u7n+yzF^n5-PG<&%@4Q*23D#y+2&2z#cMoV%pzUuUc2Vm zg&1b7Ea!nrvcuPV*hLF}HhbNgs`a&XwHtQ3F^@ajMeFpAXbp=d+x9DD-=15T^SM~H zP|c%R*N}CL2Zx=P+YX0A_fHlp^IBC2RbfuLeEw?y>n^0>HQWdF8l)B# z;PvaRys~VrU=!3`5;cA|AD`?^WBiR04hyWz>G!ix1}`r5dypdkjN_C8c=zlxF%3Xn zH=Do4P+jBz%P~v1UYgJ7r8#a;37{bgy)C#?T8^iZHU8lm{UL5HsEcp}#t!RAt?|cr z-p1R7_Rdks!}>$K62vD_SfNPFq+r^Sl73qpABKz7oYCI==SAnjZRBAJ>X9%GJ`Wt* ziyM9S%~F4?NMPYtXO}a3t-APths@0KOAQw}aR$-d9L3L{_bxK2wmxiMw( zWIG+)D%&GHk=LYsV#H4eHV@}lsFo;Y!DCn<_)^Ao>o*eLr`}bA+i3|8l{(K=- z7W`rHx)B{2@0fJ~f%=41SyOmOm4|F6bRTup;k4-crG!75z55xLTgQNhVQ^Jsp)n7s zyMP#4)0T+U)3|wwi)w;36XUI~FId~5X3F_jMqU=WtUS(csFNC(FXA6`asCg`d@n!v+ewK9=;9t|lZdl|F zupSEz+Uq~^#qz&ehdErWs-zZOEQ;6wr~QeVMpCa!Ru1cXA}wSG zclXslgNqDvzpVYI(#^@=5D~Bz(XtkcP}AO&wi}QI&i<|?&(wmZ{HC_b51a1zu%fLp z7Tk8c3)|O^pdJvEYTOk_T}%Dn+aRm}{#Nn<_QY=zBe%II1Wd+iJ{G-mCKib?W6?g@ z?TTH=RaP$bop&@>rSIp#W6lpwGJC&+vF1v(>Nd?OAzYMJ5Pzk*mfEH6ZsVuN{V|%K zNAu6i41LB`29-7X@Cf%0%Gv`qXY{Jz<}ENjb`2yGdcEOC|ba}jT;_8UL) z=EfzDZ!Fdq;8mAm;dSWSnZiuaL@=JTRJp$7jLVt3L?wfU3S+=ghKNVNU0$(rXucp3 ztC-dVedb3Vn~b{&UD^78&ZCG|_+>ds%`n}Uiy`bj+}qN)cmBstv(AcH->avau*G{1Fe$As@5Z3mSCX(DMQPawSvXGuJvCBpi%JHqo&q(#hcT znm{Uf*4HLr?nZpn=)Xqy^BVh$^>?5}AP=N%!1UOQaJB>wqAS(F5dn=O6aKuxiyUCM3hLLR3jO6~rtc|mhK$>t8NJsC9^PG*>44ev1l(0eGRt~@TxOQMJ zSE}M+B&W#tk@R68`$n}!n+b*8xP(O`)odGx$K_hJ{(Ri00+SmmL{S-R2W5^1Imoiy zDAo*gktu3-Swd9!8YNIitb4Rp;R}@6qLTP{L1$`fR0fRw{DHW}AkGaqdX=0oO4S#r zC%n*lUetv_V}5bt0IIQnNR$Td4oFtF9IYW198x3FCA{~CR0{xh18svqdj&6At-_%* zm{)SbhINvfN*nf)^!9nOBw4j6kE6XYUMp^#fiJFq@|^9ErH+|pyr4Vd*x-;SWX701 z>fcT{of(XGM${k9wX=YxQLFlM7=Q{!ngRMJPI@QzDlSMSP1-jnAjQR1zv1w^HO z@ky|A1an((Qc>MV!p*p_Gx9uh44ZZFdQWifr~q}hETem}$?I*=EX?-#jeM2dn6-j) z)h_(T9HIH4JdyTCYt=GZq@JvDR~{^Xu6krN_ig9QeHFQ@u{9B@904)L*633QjxSH0 zNzAIKafbkYp7$xt`zfRl->OwM@axS#M}8|Ku+Gyy@#8ng&0Vik_F#S13sT9` zd+W{jsG`1Pv+tg4{++lbqdsl(Wb1OCr`|K4fAhA?A?1AQLU#P39oebJ%V+w;=Q%tR zkgQbm1u0JOP?`s(>l~m?6Lb%t^yRzj+ou8Z)vp-uC)%dr>+nmdFR9SYR_JCcbh8z@ z*$Ulkh28Awb2odMEmo@77o1g3ppZ=3^R^ylgp+ZNv2+vGQrU}6t&k7H9KR6vY>ubv z(-nvjK6_58x8ZxN1PSWUs)T(uyv9)^IxuOQjU%D$7kgne&|1hk9UNz^eDDu4TuU7tM5Y zOcli^hkcPODe3%%ahFp2PX>~{QrvX zvFhr04=?4(?%|n9(!>Afb&qBdSrfX)lj!8O?(w%gIl4z1kIA~nqI2jTFTwx+_jQk# z{@c38`M^}G{!}E8iSAMV-_kt}QT{C5$=C3xBsVfkL_Cb*b&z~Hru*K!Rln) z<9uLpbPo{=pF8U49-%w>@ko#(C#24NO|v_6kMr5F>`ziYe#2P^Th-X@C?CJRQRhYZ zc=G@6%7;tK*h$LAyRRlG9}mG76Uv8e3_@k|w%jND@_#`2_!5n2)hAi`xYJHdQa;Af z+((s}mcY*{`QSX$+8Xf>YI#W{MlB?sul_Ig-UT44>)IPX%nUFvgL6#Kn1sYhz{CW#B$!4V z)Il7?L=;6(lc=CVgeVZ0iJFKE8BxdMB(zOh^JqeEV{034N^jF@9#tL&0WFS?8i|jL z#wG`cCZn-{N}TVv_BjUzG><;Md;9(Ge_)-n&pvCv*Is+=efC~!J+_X?&6kA_Fw$J% zO+qNsV^#QLY$1huAD^hkO1!eC7of+|mgb>F8sYZ8c_;RMHA6h)s*7x0}RQ@9Qn; za-CdK?-HE-fLwVd_B=|2Ri1t&a-&3U!;YY9N>s;6gnS*Me7UXIor-+b$JRmhT!4U& zuTI1Qd%x3=z$_j3&mlF2#>9krv|jAF-tsA zdlf2ScRvgw!5X-AjaU!43RR3vjwCu8#Q$Q?W;|!(d!0YH7~fR7QuKOAv9=jFK$uvW zjx7t0N!)v+*V<;x5QkzKilgcnSZ{QtL>8!=DOzX9JZH)kvEKk#bHODmYa_- z*z(gvJ@m$>ulmIxWY|?~#Ft`fEdtg2FHRb(}cd4 zx)1fdD4jaJ_T^{~Lf$hVq@GAXE5#ad{dX+V^AdH)ZHzgg6pqE&f78h<^h!*x=>rT} zx*1!d(WLZPij8PkGLNv{OIn~bN#5INxq{>-Ew26{T8t5>QuQ6|gaJ+&r0JvU{t51#>{cO9yXtgwdD*JMXx1!wCtV_|d6MMNhT9ueUa z+Bd|7jTf;k@rNcOI{k+D1E`G)YWGe;dJ7oa$-0-ge?#B5xGo1(=|2dCC8cJt|%2;bytE%NCilBGbdws zu?JWo*1Hl1LrI6mbh#AYSg4C)4zG76uJ_&sb`&M(*HcOSeI@Zvbw;ii4y3L|PRNKW z#@WN30`>emG_NI_+RDOW_qh^Zi?99iO{_<;sTONl(yRnq=ITdco3eyB=hz_ZL43&e zH>Clkv-fYl`Q{?IZs_#2mbHt1sZ zdX|KPv2Iu7PaZWjtqB$GcaD7?^&Rs(=QyXCw%QwT#3nJ|;1_xXsxl26IW=53C>9Jkt>oocSwH8WM4SEjthvri)a^w~xhgIPLBEmh&iGF7C2OV6 zCRN78_7^Y}i?4N+`A}u;@%ZfE$aQlro6l8tcI#H5eyi{d7|{`hsMPhSAJ^O0Nq4@sY2!n=U`a) zTYb=(<|bvR3XO$8iKy9BR~GI73|7Zal&o zmrpnljq_zk@b(M%=wN1wJj;$iL+|0wkQo?Af^D>8Yuv)o0#)4^+EF_{w%*jbMk`<^ zyNl{{smli)Qy`yqwAR-()e*U=sQ*iR$C!LAZt12?#j zn87Kifu)8bMoY{9IULYV!ozy(PiH)CtO`8kf4I)$J8@X0eFn(Qq(jyWo411S6q=Df z8ry|B$}Tv}!CwB-%U)yxQFu!)fq=Cr1k(E52*d{h`BVkPR*%HCn%p3e`KuROk@!){$APha&JQcb4Lf|h~vWN_V!>~~gCanhM@&J+XX8~mVA*2yu3VL8u z^Z_ZCB9SpP^iBa8Q&XNe>`}dxR#xIh@E>z)@C;W9 z<22I1m(@G)xv$}-E>HtHK;?~wbYKEiLlS0D<1gcgDRIDBD zb0rQnT~%Z%#!8(D(Bb}__$U;y(`a{xTCli@O3Na^Py2>DA!0fTD05PEJu%V&;%@KY zb)8=SudeO_Rs8XJ?HQmlDPN}B%*dT#^RNCPX8PZdVd1tp$e@~KlNjn2WK_-k1~AkB zIvz;I|2;e(Gh5=Ntho<=0u~v-OWTBqbQD9kz~{KcIO=}lsPiR`3Z2T}sQaBaOK0vM z6+_SbyD`*kWd45}LwyWoCWcC7|9^_1QilIO%}|%3fd7sRmGWh}Z62=w)(mwyGN@+R z>|?0p_wUM3samhYP?y2`J2KQioCsj3#()VwwyErx6;3lpp?(AtM9*+`+Q0H7>>6|k z^};O7`NQf?^R(pSv_bNKe(KdryPzB1Uv7QO^-vIWu(x1tb&2Lyoe(TU4ic=;iuu<# z(ifO}7Lpz$bee`KlQ7KI*x@5TEa5|V8C^I>#&hq9BGkk(-X zzK6s@9deY`<6No9b56xUG>`KprlFpmT>YlSLkL0k9PaOk~ihjuV$ zIy6KpAqHp%)5bT`F*U=EjPNW7W>_d85k9S(t)X0qKVR`F)hJFM?V}h3qd^0lMev164{;{pY zc3TQ!s9dGBMaDNLq-t!21g%X6U1s6!?*lEqIS=MnZ(umjXl-E$4{L0i1f6xKGYxAB z7v=rTr1LvdR5wFK6Li6)I~uguTJDO1J&F^C9XA+u=p4BnoZ&Yu!rUHY9j&M_ou{Rb z4y1syPaT|dWsg;z=MQzPIHAJ#2Y=czd~4>)ccWSao0~pIV23A&0jIUge^}9|s$~U0 zp(;Dqj_ZtNyqCaJNdFfa$eLFg)1aw}D_8mJNer?Z=KZJQwLZ;rZt_hQz zvO!%7^zV2Q*emt|+q;mMQ_E1c0)^Ia)ASC@i8*yxGIX2{h03yZYw_ZVX1y9yt~R+k zbR~m?FWDO1R)5;3t_5d_s-2ll&P*Q}7Rm&bXM_s@RPOGh^2Bh%4xA1P7U3+`QcsU-|0Uvc+V;C_pmz`L--oOzWO|6+kAU7YTp3z5 z3RFuAo%qkJWket5$bFv?eO8YvF&r~gjp-5*J#{OP!TX$ZEhG96hh>k-v7#1Ke}u@X z{5_oYb2NhF_xsamRgEiuzG5$td|su0tx2Kw(PVWq%_ot%a8%t1Ce=&qvW7yNMDYId z76dI%lYHS^Jy|$FM1Nj=RiXH%Z=`rqqXE8TJD6Z=`gU@MjaZO7);wi$k0Nq^SNe{U zDbxJauc7%%GR>E{uc7%>eKhaB9?iq3CzMzdg;wTZX8~5?(F5ZDBwqe=0Qs-!NB;56 zS@E4t%SJ4&r$Covm8%TP(3_pMEzY#JooU;gX>T~ws-0=CI@4;LX)h$C#o$|}8z0!@ zPh{1fe}(uu1M7Qw8G ztpV)_t=<}}&hNEsCWhmE!;!lgTi)k;UqOsc+cre{)SCmNBZV`+NsP7LnK;^d`}hXy zdx^=0=e~!?5-n@PM=ec+v5PzKR1o5IK2)Idu9GrnSicB;XFyzrbvf`Pb^WFEGIM)7 zsX9c$-dhqA%ArH#?VC$66u*6M!^Y65#La3@`0)*8_!!l%h=z60spdzXoe-mH#Tn}A! zrfruiITSY4ehg2!K8>hP92>&^{s|O7+x`Rk9GJGnUl2OKeHPpWq3zhx;CI@py=s!nfmC2F7A`H=T6Daxn*b9{q$x1-F+S*J>|x*qZ)LF*pG{kCw>QML`AKdYSh4o)fq z3x>^0u)O^g^o4WeAMAbkEqj!WT&$^#%??=B>cotrnK z2vs;*;dOHD#%wN0(I3`@v-!={e4L`|Ty z4A0+z+)b$QqV_hAhEo|aoR;lmpPLRxl|^(NW1|K9FqR}_M%r+m9-5o>BxG(ktor~c zUd+$e#UBzZ+w01<<1FPIhbPpTwvJ+W6|bMD)w`mCB%MSFYH7&WxpgasjCWlNb_$uT zq&yQ9MoW*_KyTQQ94`3cZLMsej6N7$C&@@mAadz>r>%;%JCihIMC^Gut>L%{j@{C* zalo*Fmh|Ldqje=#`kZ7xZBKgyylLb(Y8#%^1^$bX!!`sw4~CU&doXTLp<3I|f#CC? zbmNXBdEBtwM1#f+7&Hc9oN(HjoVGpfdsw&F;wpnW%WZ7buxt@Bw?XMi=BpSrX1k^Y z?{Qk*a3$lrRkeWs%s1G$fwfo=#c%4Fu%u$bl8?rXw-F!44RjGjShBoud2sUuL!_FxL=$lZfYp!*$V+p+C=0y?E&!9qL+cw3{8 z*-|I+-tXNMAp(5h z(AsWWmWpRrvZi2Yx@d%=0x)T8B-z=RtPb|xy3E}7Qr-LV6}$w?ZGQqMTMau}gtq)< zJ`Fs+u}&OT2fNIgx>1|dV_@#mu=|+8&%2IeJj!!so^#qhb(WoR zrfrc}bLH{XhMjCGAZ8QJmdfLXbqzObW&^Lmv8#6yP(@G5B!GuR2kWe`3UBkYlMZ8M{JL$&vd8=hB3m>LL2q5et8VsL@gO2IovsME@$V@E5?sYaC@YB>G$4XQKah ze_E}htd=WxU03vnicBW@3vP8SX1Y2sJ)DkaQ{7rHb4fqB1#{i&iT--d714hu ziT;>OVBRcEc0Z@7gfiJph1DiW^tbGkMSppmw$MyB%}3g2G1I-oX1doE{oj#9|1>80 zTP|NC`pcZW+wkl!Q9m*_-v%v8jGKD~aB~q%=9Hnbj2Lp7h(m|((w9#;juykSuY(wb z1s_3B#H2Q}rgx$#WPTLi7Tpx>uK0wA3=0v?1sHKL+BX>-(-~p84US|uc1nchGi-Q( zo+QHZt$diogtOS~)}BDvg5`p)Y#T-t4^AdJEoV^wPRplx&a!h%B@&9!nU`SOhH)ie zOo2JxcAA`k*Py@OmZH$)jR}^swm}INk1Y%_o@@VzQKZ{83>e24)#Bw5kfp_qZQ}5N zsKwS_B(k(@hel&a7HK;xo7X-J70{TxK)&z@Y1<4t(A|6IqF8{53O9WOf^6$Q7hR+* zz-%K;YW}pHjybVi_c}|rzZi^bVS$+=*m@1`vRp;%U zjIpP0G@7hxoE*%xmy#Yd|B$unf+33pn^^ZbU-9`>bO8!%Ifn%X3{nv9J;H*sSeQGQ z>w&a~wJ?Z&=qg42iS5N|^##B;x=6f@Bg&bn+K#_zWxU7^fFsYArnbGjC+5DLD! z&&h_uQK8N?HVoNv>2DA+_tbqcT-StjbX~4Qm8l!Vc^R4=Q(%AF)QFYgZdw^WG#MtJ z4q1a>wWY}w8RyJ=A7-^QCUdYspllF2kavpRlwb>MRL4DRT2Nbh0aGNpYm)Q)+*S3x z$FQ<&*tKutJar=ut)hlE_pY%0olrLcZbQR}MkE^~R0qszyi_X0+(fu%VsFaI>HcSC1u$iZbnRs7tktC%tqJywX-i`$u~! zPEQ!laqmuGysE`^Tfze;u&LPkfZ?qL-RL?Mr)zOOyyBAmc8EMioR#ik#_ph+{52Bs zAVgThv*;(TIr)_zIsm0@ifJ$e0yU~ubpb!GvrdEKyG`28Iw)guG(o*8Pj^*kossTA zP@!rKh6V*Kmtoj(FiU9JMdq=hTj%a3Wf|{r$eHyH3!*mT^ zgau=Z{?Mv39k%HaQ4nT^xT&BtKubHAAp)`lG#u$KhJ|cvkh!x#&P;=xIap0ZW_Yr( zh`X_?filGMDeQZfG7{YaWAK!hXygS6T8Elikz|YQ#t6!<7bzoWlrl0Z?4#L&8m5>@ zD#L1Rh_2DA($z1dc%k$FSjd=ES(~KFnqv?PNP6VL!U6#qtdaYHZYv`=kQ0l0z z&U!`y$Yp1JkX$(|f54iRje0^$&^8rHdekw2dLU;_qAmzMpwyEl zS->kJ2`uUhi4HZ%}UC1KTbLbq| zMsBY;hQ`;04dN@nh;K&LM@w0s&$8YuWxZL-db5=EX3Ba9Ook&Op-!sPS;&45c4pTa z-pcIRghf+UjDLW}Wu7u?Rz-q>^GKbCxzpj_?nMJY6 zR)z2n?q-DlAqf96XJmwbStk4s`xAcSE~1#|R!6(pdTT{HBYQm_hy#Gmhy#G~81La! z*g%i1*U@SEu0zpo-9gOn*8Kvn#SYRR4hsR|BUlKVPjw`~h`G{9KC7obd%esDY&g%C%kp#A?6RG_(JqEqy zUqGlBZJ8EU81ZBySVATiY_q^@X~>1uhaNV@fE9GIv8P2VjYTaQ8kvY2bT&xrfGqij zRJ2fS7Zd)k?xr3Bg4AaiB#QK(ZQq+qITPgZe+e2CKRCO3mh7Z>`~(0 zYR5h;F>~BC#O85Vb=Fhv_XQD852@HE<-SFzi*B^WOknwjGAt?EjkX(fA0ylQ}G&&B#U8;U&nD{-$EE=Fm%o;VC@d|uyF*9sp7KBaQf+YcV0ad#0XbXq2h$g)PxZ!dLOa8+hmp(K`d`5krXQi-KiLK z$A|-SQ_di)%)`h`+#+ngGxwh5>wt0Fxk?|1KlBgvf_*}r)C(F}*Sao6UE2`?De9Yh z0@fnapqaMI9bF%NfL1{C^flov6iqlL*8Xi^e^}7{kCy}eVWiX_Q2EqadaV9awMw+q zuPZ~Cgl|B<=vNBJl+?s>Z+2qfsqgCwF3>TKEm2=6>j3p&Te|O`>kIF3*k(N0u7HY$EatFA15ifYy7L|>Y8x^{Q?JeAl1}U z*YE{&jjdSlXG29#+ln6Q;~3`t@pAm%>KZ~l7Vi4G#)bi12q_;eQEwVfiqR$e`@Xg8%n zjs0Bc8(XD;XcB+ixeANm+GOV{J*^%@z#TSEv)95y)hebHDi{oc0`pXj3SK{g*SdX* z4k%cUl7*XsKGqY5qQJ(jzZ*v+Sc)d=9qD zJ{rZRu3asAsrtNvnOF2a_=s=dQ_8vqI_Iu+Ow;q2P{~f3RLM@7SjkSBT!pZ*!sgvO zY*nPr`hYZOKzpJzXw=#6p~7p`-aBv?kmsw8W|dP5gLKm4SGs*(aDA=A*h8^kAc?NC z8YP9KdMIMLlfC=Idy994V(nDHg-Ragfb(& zgNnDi9NEUxf@N461T$^hJbWg^`)pht+1aPtU`YU;>E5wIsu33QD1)g8IqW21pA5hj zQnQu!*(WK~$$9Bw8TP`^5$Fu*F#eFopnVfn*qNjyk@K#raF7go*A+NOD7~u-4qDH- z>x*^s*_)4rAn1>U@y~{L{m{i}{a&i|_Ef7b)i$zX5L>-53Lzu>(4@f@abTX4r)R97 zvE>Qx=XVShg6c#SHW|vRj%I=J}m7NQ*y5B1=muZ8nL)PAo@4xEuVd7iZ7ADzDUCpxh9t=A?P@(Ra!r`y(i&Q5u2K3D@^84ELba1>2Ju=ggAi)5D#^r3-mwh*lG1jD*F z472@#bls_Id%v0+afn=rTyI^fM2@c*So~c`(=aUXlTArz(;Nn|K(Iv71_Ac<(1;O9 zu*6_j7U|m^iG_4p`q+iBi0v5)ms$p2qC>O95G;cCG(}? zXvB`dy~+}xQb39po+m7kFz05w2b~g`>oouvIWU1%#s3)YDaVSZBX!+8ePf5(*31cF^u(n`d%OHfThuN@^>Zkc&B$I8-alh81{6r786CV z2nqqcJ#cE|LfB%_P^q*h#Sswk4B^ilqvXV?f2b6Xm+%H&aDLWo!Ewu>~^;+oW1X^#wM)nr$Gp3q{ z73=}VDSHbx3Izk+$dk!NrD2k({k13iO7?vgB!)`%1}d_*WFy4~QQ-)wl=itacmtf}z#$(JTf$&w7Usk*2V&x7_x`>C=!tueU<#^DL=~UFWC>5v!E5H zP)GDk>hp>a?~}aR@l9Wj2cN6t4^elDH^Nt1N`+N$Os3os<#xY6x=~k`E)K!F`OF+W z`|c!Zl7Vn_dJ?_u0#VPG`Pi(ixx0u9$>wRHt`U6Xdgf_YN&`sxa|jE)JZr~6dD_|$ zq|kfJaSJmfEA@P4b@pa)KO^a3?G|(sXsr1J)CO~@V_ARivsBBN5`qT?#c2C3>)A2w zTA=q1X3y+I#`^5ijz&hnWYPOr&siP8_JhT`addpOSBD+Y{&IRK4^u<^HDv#__mQz% zmJl<2OO|>&HlVcV7LzCiVVj5au}c)_=tzW8Uac!_k7&dZ1!=X4uQozlii7d;UhA=SUqRce8}hbgzhvkNRx)Q|Fi2PsiO9<$RcqY1#+0{g zBJDYQuqh0uG<;CR?phV(*tH$G3tBsjcEK9{V8N!}bnz!!@TJAJ2TkQ2On$<1O+X@D zJ`N9=;L>gi-DvSo_1446-)PvV#s03eVHke#7z_J(p;O@p1ehd17HlKn9cK;OYl%d| za^43wg(j}DMkLN-UJ(B|Z;Uf7G9fL}7Dbr0p$tKjIM1fvG&KnOED#kU#OVCeY@vHD z85+cK8u1St7UCCpgx4rd$DbTEnjMj?d$~H;qt(Jgw=j zq}LN~Htec{*%Hy{DsDE}B1=u!N`V4sO=)7utd*D)beJq>OQzzReS)RKb|buPgP?E7 z;wgz=8Emqhcxn`|HB3FM3DSZ;t$Ul+mIUr#;W5)8wFKDj(g7iY8PR}oNGR$&HdoC2 zBeuCUs~c}TNN+R22D&$Kehbx9y9K0%p6xGx4~4OB)7b9b7G%R#V3jo@VS#Vu%_Z1D z*TJnEjNx-)g3VWw06k`99sZ~86CeG{S5PEBEJzZotl?yB5QlF3UI@&?R95V7!6718 z>I|h9IA(@kxM(3p9Be!8c4`^Ujl??Bd|k(2U9ziJ zqCO;w=tJctv+;vrZf;fvI*G2?ng*c_V)c%s-#zjNBQ}g~x-0zbrcmqM;kDLiM%(;sZxD9elJ`Na5av{jyFepknLc6^LURnj99q8MAg+M)IGWo>VoB6q{C=7 zf4m?lgZd`w6h}YA`!+&BphaZb^PQQn>gc!5he}bx5QpF$lxnn)>bOgOYZBXG$;bL` zy=>Unh;}a5n!Yrwr;`m#_gO{(OgSZjKY$e^f)!w7Lv!CGt_C~aOY=KUEQC{_tpLpp z%Q(9vOb$a&C-YU;jj9fw3y!QV>jRsh6yj_fz-icb=3&IvUDT=J%ME;Y68p05HZ)&JN!NHF4ZvReCf;PVatJk2v3Bo6dp046Acl^_65)=0fYz& zfTK}B2CVH^lD8U{VDL7dwgVW5RM;uvHB z;n{l>f;mM|Nd&W##7L~bkitI0x(z_hfKpApE3HF(3!Ci)Se2L&NfL)`2&xdKzw*fR zmnKirp6PvH_;}&$&TH!@fNZ)IIi$)GD3@cM1WnzF7hrx^4nM9wMVaBLG%sEfv zoTr7^YQuVxU129#(9*tY5l3W%gRaCdw#V*P+GEE%a(PbpQfPxLp=`FZb*r0ckKF;` z5(aIy=T202)_I5P_NkAe_pPGi%-BvBY|0dAS129m!Zu}|bMn@^q3)lebgd17eiub& zSs%tKtVtvxII5DNMLlTx82j>0^^a$sjHQMM0yTBQ6?L<4fn|yEcpO^-PazI=?3q>z zojgix*mWN?AYv=GtO5}k>y|Rs%{jH!7}vwJL2#dPbQ~K5+i>U=OFQAHWik|v82wOs z$4n>?zoJ@9)fzTHmnRv3fR%@Pg>;Os+34RgNs!E?f9eKQ42&6b)-W@4mBP+)$@v8h z`~4$0Ao@pW*j3A}dUhoS;U7Wfn*Ae&v1=5&8rhYsYx+l!c}f3>QS2JWu4CDC9J|u+ z>>ojfnB$->#OWq&8}~->o#`X&aO4=`Zl}nWY_oEwU%A?uxk^$?q^pw zyB=ZJW9)i@T|Ml2mR&`5?O<0QyIy41Zg%ZqR}RBG>YrUn>8F2$o?Ur%jbzth>>9_NJ?U&pCbQ(M>#7_fM6EXYdFY*jylbP_DHCP zB?LT!LWBw)ixGKQ-e-_Y!0&qa_=My3Dq*T94hZ_!MCoKvvW&ozx7+Y) z?SgvjY)aDfi+|uVrI$qC>|L5BCI1~z5rBb-4&mB*AlxNP)*7CxM6wxk$sW1;+~eH9 z3NeeP-T6~4XpkHtPI12O0TR;m^3K-@WA6s?0AqlyDoeL%vUbh0Fq19(g)%DF8P#_O zbftoY&UGz~AtlYwWQqBOOEdhXSSY&CAM6%1&qAJcy@I%~AG$|)mNf=;wLhtsnglDY zuEH zgm4)D)d)UsK)4!K0RyQbpb1UBvM{9fF!XwQcR|b@j;%w&WW6wUi=f}>9J>>iHJ$pG zr#@`DVt8&ODhP6ese$%OcW0!7Ak)Q9P;I6%zD(;Z)3?LI6+3y2n$s&aCsiP|KXlCs zZ9?jFVX!bcCTp(Wjb);t!epb+3MWK+>kN8~q(_`}Pr}7t5*}27o`YA^aE9?_r!Nvw* zhE$esFG*EkQDT*%+(~LCm7)ykUlb&f>Xb`TA6SyU`b^Sbcl_FjRBDIO`%)LZNvcvil?K6VbltoeC0A<~#Q{{`w79^}RmQBJ+#HqDIO^x_~ zJTpd!tZ+i2$IonPSaVL88iN-%*vm&^FiaQY<&;*&G)^5EFCcsA?gC89ZY*om&%5^ zG?!g(#85m5!))5AIPGP+wB}-?#UBLiDlS@-Ar1*fbO0*~t~nPpv8^(`0ArIXLF0fP zk%TQoOSWNx{VeWu)DnjB{BYom8FSRbDENVKX?|RQw0ohqD?_zCi#$_sPa@EKlDuCYH5eoXkL%!{_$9J?rKl`XPB zEwqYp2yK{K3#wi(!aLdY@tRtp%T(qwZ2lWMFwXYSP!c4h8~umCTA(EvSQt+X&*h`4 zWd6o5u!G4QGaKYz!+6;i;#H`LcOU?B^g%7*IZD?SNv8(Mid2F?&xUpMsX|y!U084* zJVzT9hfAo|oAB|r(~WOjg9@-WU;O=#F8Wz)f+G<77AYd@4GMM`uv^6qz_yp0kw6Va zEfoSQ8vaA;YH^;C{AL=3sZsbiTIL#y64`=xAt#)vwQL2ld$UXPdpvOke7d;*KB981 zfOE{y_=$ZD*pD?g3ZMQCt-V#)C-~8GwZftpA%ex`+r=m>DI^jd5uMoe?H z>>%#k4B@=E6GEtctr@(FNZA%I+$c;QDeOaY+z8$YzDiD<10F2Q))e^DLGf9*VCm%$ z_z8_}P#2vN<(h$Sff$@Yi#noCVkr2+lc}36hT^Od9eV=_^iVw|V3X!^@n~GZFU;VD z$U31LI$RJAS~x`$Ub;GfCD9}JXF5=;g_@n!BLKw4kGGoGbG6>qrFB*uRnDNl5D^+nqgTjQSrgt8Fe{C?&Gy7;~1|U$9OGHlR4MM5mjs^off?$ z*E@b;gb`l^h2dLQkTonzeD4J;FznILN1{HtOTw1qTiPXq-TKINxGZ%lZ};zyuWd}? zgPcivXOhO5q-{{feFX!D9TX?KGkHWyyAw=%%SK@4oZ*VB&%*rCK+;-Nqw^NQ+Ng1s zXi(u=)7~|IS@`IpK%quJ8i5f?@!`wmXjVnnlt6m#(lhzAfS)Bk!4S2IfWrE>mPF)C zY!?x%ZVlv9T1<`d%@q>e&M3F=m`~$;%&ifoVH0?dGx7W)=almpPN^f)VUGCbMl)(4 z6=v0GrrR%f`}d==g%mmjHq^crM^-R!ULM7Ax);VH@asSNEA9 z%Nj@%14_v9kMUe+!>5-X@CRfa)cB0vr5ObI7{YPnUSY~!(5_G756Sw3NS`awR~EsZ z9ecGY2{*0jHbpkM5}TmJ{tN9i6Fr|VA44S<60sEBEkrdrqkO_+O{fX@A)#cb?r*|~ zmV3Ndx#%J9 zxx!l5kbq!&C#3f(@T(hyEHrOD^p?)4M_IJaSs=DLBDOQg$)dovnjz&xZYwlD<-?hR zOlc(o=$)r1G)eS!jeLkfMQOI{l~=s?8_VnHz^An*HMH5!){R5J(=)tbinQYw=A)&* zqH+o!ni7X$dNvD_2o*_FV=p+PRFLeS1O^QRoGCJnrf1!tc4uVwRMXWpb&n#mi%1w+ z7%&3)XNz0GIY8hI`>&VZao?I>xrKxoWIfZ=X4w2&BtO^i+*;hO*K9}cp&IDdY)Y%$ zfmZw0Mhk4SmswmGYgqq6n_)ccYc|uqvM4%vl!8iWLa=aQymOrJG**r@Nv3^kK3fEO zQ~Gv*Q)%Ao*2hRGMMkUx>Gj+AgT#NXsjq5Ol;JtMTwTu$pz>diyr#N(sr)u=yZQw6gci+vVSr%Ms$sbwH!JvhR+QRt~oIpaU6VRztWZ%BTR|WTW94} zB*v8EOr+?8@xWgJ(QX^wcG)o@8zz7_L*)`+zZ(p1SA=oGq8N+wo$*rMr_VD z!m%a>CPr`f?iW8m4Bk5>?+cQ*cLc>~tvPw@_~@$4L9n<@Sy3Vn=^wdhKm?D-eE;1l zOl{k9U|m#loxnbdUgCj+?WtMc?!8kSgDBgeYpU&Zo4Bd{{${VCk(v3t3Xf(6UxqhZ zHErHgr^EkhRke8<;8}}5@f0D5r|M2Mx1D|EJ7wU39H zLCXEh$bG}fV~9z2@>rW!jtS1s;p{@BCy%xB40qF?yqwsKNZN13vyx|6!fJnI6&CI1 z#AsbupLnPvzDc|t2CR2(K8=EY-1L>7P*x2@xdJnDJ9iSw`!Y)AK$K&Ia+XkD0t%*p z?d5x}(A`lttbI;9oPlZnny`nR0rnGegqnd7ZXlE?gpx`T28Lcvue#guYBXLcg~PoU zOGLR+2x~t+)wG8ySpDALMek!i$NSIr%CSIoNV<5F=K_8EUmwl!20r@pT-bToa`XN(!EZX*EQ3LP` z3I7P;fA4zu8*d$eKaudcFM#iF>{nY9|NeCG?kRow-!DAWge>VO$S4Z3zh4l_&#{5= zpQ;4%JVJiGAF`bP?pp@r=PkltNBBk8;G=%J2bOm&;nxuUq-*f8-?}y~U7Yfl3;5EB zZ4@TT95U$$Oq7Trfg~zuSmgT$hf#%*iYkc z-x~R)-}QU+TYvk@L*R7r)R8}azQ-?AiWtk&#UD*(Z6-f$7QP~im7mtKCzop&g+CWT z;Zw=^F>`9jd4xH4lCv33CFE@<17kHvPn7j%-zN!T(@tDbfWJC0#i#}M=*EKw4?BHS znxF0It5~@}LZuZsPZ!T7v4}0Nri<^K=%u!2m*b_d37bcy7O()#pfw+_fkOFJGm`|vEl|XI;x`zU-p+M&v`m#-H56M!H z^QQxYP^aj87D2XAkR3`8VsqLD2efZ1;T<8o=M_A;I*^}^bn)g5thLBFsL!eHbN=F# zR9irJLUJlOr{We-^u`N_J`T~p0p}DB|{qdi={45JIAqTq=CqTgp(B zaTx8z!Ibcm^gfEcU-k^SdFI~#v$v-h_`Sh3labmmvJHn>^@N@9V{@^>;Y{*w3?`qT zy}^Wx?#%5C-rnn~kzMal9PN+Uu`ot8*uJkA8PHHxR_(gte@9O)cPT#G2R{~L;LR}@ zclQP_?Q1_f^*S^6{Q6|D^N0jf7qkWL$ zD|`k0|9}4tHBj^AAg&Q`3eX9Nyf}!v7hndY0+s^S0A2uW2fPp14>$w32;lxUh~oiK zfN_8-W0B-|o0s8^R03zTjfbT>&z`cNRfXRR?Kpx;}z-GY9fbD=IfHQ!L zfbgzCTnu0$U^?ItfEBO~@G{_SfE#cE&;jTH4C)@l-3b^ENCC_PcRZm2iRgi@ue!=$Tf>ivc4K z4dO<=%knlBkM{mxABksV_g>&uA}sE{6!-zh14u3^%%$jm{p(*(z|HLb4(U_@=kHCY`o3?^e>QOb z-gy7Ce?R4$tDcN90Rro%dYn{0Ra1ia;t6<%^42^UbWQ!($D&>ykm{!fcd8#d@T&mn zCN*y#4>!PVLOe+TdQL!I6A=dP$wjNUH&K7#A4X3|&p=o}U_NT_OzkvfrH%p12c8GE zQ`|#A>^-#;;Rd$TVrz2I^5yxZ`9*~p%W{fyr{q|3sNBU1(e{h_r{{YNw6FmA#l89w zsoZwlivihyD$oScMfPHp0ne$HX;V|@q)f9+pF&aYwsZCetIHA2-alTuO)7`40&zWs zy1}!%M5<3ao~r;9Z*3{uPXia_hTQVPsp2P*4U?MpNz$82e zKrP&XV8?SH^b3!m{eE^l5d{1w?7-jG`$3ZJ{ZS}9l=p{nfAu5?(G_JS< zX^!9YkB3#_Q~HPUO!u31931zAZsAG$7Abz^9siQ_?0FfqD!chtKpS`_oNT!1ZvH9g z36Kpa25@zNpP_F7d_RXjpcqiw47{yy1L8iwJ3PDLHgA*SDOT?90cVbyN1hhUS(a1y zMClkh5x}J{&&?^#HRcyCS#DdJYs^_(^b|ei6_u>Wv7&R2GOjMNl^D~P=UB;al<}$D z66*BEqCDfw3@HwZXIdU18kgi08cTAEONy4-mgE|(%koP{8H=SPa#!T!FE=i>Jzi>F zQdDToFSHfeN{g4RF3n$3I%Y}Hig0cw;?7%Mv=ZqXlaeM+9%U?AT#&oOiWpZQTT6|3 zB}FTYrTHst$PMy9>25u;ETg&>^|3_F*Svahj90(Up=;GGj|c@KM_+0ln1pc#eYbl0_7CoUka+Qm;v!q&(mtdCe#y)7moHU#G}T-6 zqe$1>Kc6*eDn5N4@|ui%;!gR@#@!C6rhB@SPjeRPV3G=BwUjEw$FETFzG9RecQd(5 zfNw)MxINh_p5A9adF}g}BH-sp@41!Nz7H5@W-?+(SzeT5l}eHheAbRgKYJbW8=~n~ z@5Sp;9*u@C2E?uEU+-o-bI;244!pPs4bQ_b+kx@}CTFG2nLRCO#?KX200MP-ByT(nxtprVNMqGjNQBHBGZMj4ZRPU&t0H3}VHvZ_F(@#g<{)zP2 zP(|sde@i;G|6DrKP{kROIL=tdzlin(ZzEo3hP&o?xDD@cPsiPZd-Yq0Z#!rJPzCV3 z0YBW$zr8jON?r!u<7C|0cM#73q=mcZAoBA&RjO_I^1fa| z{#oGcMJ4e8{TV}hd0ZFDdxyGTzsWu)^&9ta@bbT*4bfj}0Mu{Xcs755Izzv)123*a z>Nn|s?cZ-mZm#(p?F?`Oj0jWhMcV^v0sML5XOvTpb6=9$)PDK9x9z~TPWK}(0A8c! zjaSgNDm7nx6=@^RT09p6?0D}EQu9852T%*B0T{mmK7jatN&eFO!pDinOe?fvc+6QM ziQ0>;dPBJ;xD`kYc^TfAJsL6~WfY*$Hq0r4#>DuIvW2?mCtxi@_BvYZlz+|S=V zNR97BfWDs{&OrFLroaBOqQBe@8ma=_1oGu7@K@r?L|@d7f%FwPZzaCWN2(cLt{IAU z3*gH{Uju0%@Y#F^(gg&jYsB;a?Q~l%Ogq1FSNSB|pQI*qXUJs?7?%R`sgz%lPXc9S zddEYCqaf?tlIe3RURr=|`daOcN8sF0AN%hDrR`Wc7>d}`)@v3<< zfCJP%23$Zo#k)YwD>$KgyT80AV2Wk=wWeVE`c4ZMDxxejYpe*BZnR zFcVZD>}EB|e2K>m(;!z(r#VsYZy^1|eSkddL7a`q7udtCDSk@#9|3a{%7}Q= zPaxip)VzBi(l`J(h`8N&2gpWxJf7XH;1hUu{|V24T5#qXau4*-99o`mOJ z7r+17E`-^Qu)E=&h;X}sk2peh=K~Kw@l8ZLyOGwU-vS45>_Ymx;ogO`Cn6ua$}gRr zc<#lJNp}M0-Dh1BZ@IK|Qt;)oyUxNN?{;NROxO*--SA`U0>Ie?{N3=UFuQ@b>zT;6 zrk;L!-K5%IgFb$y;oZMNo&6s79e`h=>~Eq?ccT6Oq~U7-_F4^J-Gci^s4qYu>=zJLmKkf{cK!AD)m@D>D4%_NZzt!pX16(*9Ur4LQ2Nn^2E~mz)(*u@ z{(n3LV4`xxF08jekTETqj+z-4zECh~#8_IhCR6ptZP`(Pl z${Y$m@>k&*u-KYjgo57Bu6_CIm!G`6Jd$B*{r)d@U-g$0Osc&jl(&zT;`f+Bd0(y+ zf7Q@Xp1UcOuNfZ7*8&z>Qz4(_UN>gG5V!&L7*`<2q0}RuJpii58sPS;%Z%a@MBEpz z%+${A8)~kuM3_HId8^$Y$`=pS@>Rz}d3SjzpKV9J9B>1S6%;3;$ept?zmy#Po4qzh z%hsLDH)%n;O_Q-+{KuDHnm@_8abqG@2$EiY>E=msw`#9h{~&t&y7dowSAC0?uK_4@ z`)&7H;CcY=fG|F|Ii$@4jDRWt#bX}@+}pK$I$*JN%Ido82s}A` z18^NwHuQFK4_L=>=OTW29mjkd;snq-j&TXxBelGF72ZFN`)Vz3$Gz$vJOjsl7vfxr zy0ZcDfIA%T0KOdLbur!ns!9=N1OWI|fNDSu!2Hn*b1L0 zELvG;%w4r47Xva|&?}bTXX8$JlID|Nl!J279gNnZBIEL+!pFJ%!l!b;%#5;saZzbL ziy^pr-)bz)wNgBN{&`r2?0eDYzc^=UpEu_@8OGcH9L84xJl$b@ zbsOp#ZXfQ|N5gnGp!&lwjA>!K@dR+_9e{&B=MCe{3&Q#IM*;K0dG|cr0q(iD1Bx@l zS=o>x3bQ08TQR06Cz|QXnV&a<7vuNNBuhz2QHilM=P7EX z5v2?>Bj8DD|9i~QMl3X!VkTQ^oM1GLSZ*_h8I$20kr&Rvm5N8nbHwsI=7xXo&z@yw z?%H|g5%FX0&ohn~Va%ey0ns2(n2~ly(r><*-8kRwSR<`g+OX{WxG^ukFlV`O37hSY zUWSDUWH;70CAW|ky-{GoA$ChC#~L&8R}?SL&&yxJ)|`!F#;`^2fchcy^pgCdl6>oG z<1AaQEf)a?yr(KODs_mwl;k2dgj2kLKjWIz!;C9(R*f+(1@F!&T!Q8FBFc0jt%xTl zml};m92W~SDvRxTC`i9T0YSmZS&GGTEFJX4RZOEI#X&z758)K%=0Cn{F&6wwufgoa z$z286;hNWd@su)xX3y8Vuy~^BGXhq&b4!iIxg~+Q?u+NCqUE*~xyF^ql6Bd&F|l~? z*`PRQ3Dx^`(HIWukP=t|Vg%kbKM4n49HQxyMlW&p!vjvN5o;~zq(r$yQ(?ddOJXa) zN;M7)LpZ(V?DtZBVDZvA4%W~2hx5jkaK7fW)EA|G)i&wHqj!nAF@txFo@lh(Zyd?d z{XY2~hkIWh*t-<@T?)MeF8fxWyYz8X^hj=$k>h&vgE#k}9HYPKhxcVv)&+pDGC&wP z5zpxRMv*^$ONxq0ma=6d+nwVYw@JX$puvBiG8JBYY|b3&+Ys zSpyLlmKNufEj2RSu-p~-rKR~#LAqNG<|g@-7UwR(_a_L^ zeSSGB7U$>Es+u*o5aUp9d`t85@@%C_a1P0^IPhf+5;GV=n&Nnd{Bw$nb3tR&)06s~ zsF1KQwsI=f%@X>OgIuF3rMZ7!Vu6bO?C&R7z^C13@%lg(! zV)C(<1uzcqxHPNarUR!F&l>@c73latoZ^#%c=z4dH-hvqj$`_tvjWj_TM%X#aDR?4 z@G11O1I4EJKV7jthT z+)f$Z$GsNNB!e6GV*n381o!~m0Pe2qq{Sf-{8q~M9Qj_tAwlLZ-E-t;@~03hO<)8g6zf} z8^oJ;hVhZOR~-!F)7@cw9H1It2Y3K(fD!L_z*68D4@tE9Hr(71$*pZe_yAe38ZurD zfMh{8AW#-8K89yNpe#r--jbYBCWE5LTsq>b&4Ud174iW{p9vZ7suUmZhYXnjxlfiY zi)V%N*|P!guL5qBF`R!D;oay<)qom6EkJ}{_UHZmJby(Uz|ROU1IB$ch#&Qo)K4?7 zBX>{7`&IJ$EpWdtyU)VSVctc3Z0zwt{6V;>OgYO*a#g;Hcg`-=8YzyZtHQuPy!G>+@|rfKrOYpOPXy^|^WOI~G_*8;Jki zO52CUbA4{f6~x%1z2aHNy2|w%Haa&6n_bU6U-g3@{^*4lfBcgzFTMQAt3Q41_3EGf z$Isu``ioy~`_-@C-2R_${if!(|Mk1KfB%OaJKuTtz4!mPYjh zr`7}RgNF_u`SZ~akF|Yt{KUyqAA3%p`Q+?hKK)E=Kj-cE>*rti&R_WQ;@>*Ex-VV6 z((~0-Ec>X`n&6O7ZCJQYA2Gy#{DLQzuP7`kezK(0YI|zss?|?F z`q+Yni?YA|{WELI)|UTIm;Zk{{eQatlO`u)Ha0bN+CvXdPn$6_eb(%ZIhk2==goga zs{gNl|8G(M(~}p?nU-eZaG#NxW|=)L8S7uki_&J!u*^xCJ)hjkbLOYBr808QoIX8i z4xZpnnm!$i|8bIReB*mM{%yFeqYV6AE?8KR-RawP{HwCN(W&RFWcRl9dVZtq{$Zt_ z|7W|w*H`m*>IU4RF{8LK7!?))On?alaAf6|(h^j1EEb<;&l!)^CaVz(Ube@V8CPI; zC1%cSbqPydSmJ=hinSzo&;*DkKVvvM&ea?PjN}SA8@C*Pw0eL!VF)(ES6yUJrKv%CApEtaq06ywcLYoadXpQ#l~pVQh06lvTXb~ZdLIr{ItN} zLOHD#H!c3?5pyFOQnhmV+zPCB8UZEvD*_ntmg6GWjDzFmaMQV&Tn7FEaiq5y+%#?m zH}yJiIBp6;rEy6N+d}SfJQV_iO8yj!p)W3a zpU>XpAY4B8w2W5-|8UfV694~U@6E%h>fZnHt;kShN~4SsMM#kj_X!zNQK3{alu98g zQ7MO{k|Yf%q(Sq52BqWdL$j1IbLKI_F(qSuYY*P<=RI8C@8|kl*Y~gAvpqep^IG@X z_qx};?!ETfd#!U8k#7f*Zztt$beZy~G+$CbA(uzx?juVj?D0l_ghuKNcO>yb5hagm zL)4P8+7qQ7AUTn9FS3oY{U9orXrcY+{|Asp4>Hd_Y$r-1^bv70UNaZ65lTaA)DpVC}t5#^+4Lx5Zj6Lc_B`CgRqH6A)i_>d z)0DJn4bni^rTLFo8gFl%f3&w3(m;5?4do-ewF}i%9+lvY&fQMddM90m2STd0gf9ra zl*AL|9JhWvX2;9X{zp0gJ+25X|JvS!?s0#cp!l>!oM;!q_mqV4FySl0KF$9paq;i< z(E3L`#%&t++kdOUj(_vF4yON)^NH3b-bN77Fn)FYHy!e1%n@-rZVPe6QhwFJ^!}l5 z+>ZZQ`}vVkMz!9*`u#uK@UP`9{a=(vTuH<=O7tK9S;PNxc}xCLo;Qj~s_hRUE&Gv9 zU*tazr1L+oobhY%pB@|cm+n9G{m=d7ge><$t>ll&J&3MZ!s2m8wA^?r{X`}8jQf((qxF~e`JcxFp_Ph3A})w~HgWf+G>_~3 zXZ!rm^}3*1>_Yw?*LdI`){e)M7JA<}Uhkc#UG|Z_A+9OnUh0iF@tNqwiMtbVr=vVW z+&!q?ojQkzu7CQ1xV|T#f2R$^#EJ2U7DD13dFe7SapGNPBtqgPu0BEuB3)lhoOll! ziI8~bsgH05!cTf);>3$9e}uCT3L%`0FlH%whmOz!;anmep&UZy67)_IVX3Z|xB|i! z9rTV4;X8zf5#B?11YtNr;-{QK5RM_-fp86ZZ4mcI;+ok>*4ZAV5%s2e1rzlCbpi6k zdX)0l(bZ~{%N2Q=@HSBj@jjQRC5G-G#2#esj(TMYcJ_dKlFcU zDdoSHuo9g|jo%bV-8TQC%>e1cFe@~xy&jF&(1;$5xpYvursxwIsH4NoPH}& zCx9LS zhMM3#*g}(p#}z~-6+CVOor*Izd}crzh_`? zpQ`+DO@`QobNB8V>11FEGggh=Oon&CZnoN;wG1p?`>%)n&DUzsVT zfc-g{>sc=C3{3fxh{yI65WRJ=$Un7)ffac9M%_w*?cWbAX`6|7|m*qt&KLbZQS$UZHiGk^#wZnHjgMeeldT+sZ2DYSKl6U(V9A9uv zsImv?n=xHfzVR8TX&F5=e*A@j-Bq<%Kkqp_oXHKcRC&h0wi$dn>Gm9q6tW{Evs)P0 z0gnsmNzcLU?RMAWKDi96qT+5><8#=5@j+!aD~o}d-JLpL3V@ zKbMwuRX(X^V2`nA4YgMwxb;E&8~hyuD}Ep2=l%+6ZnQQ^81fj{`j&Zb@4tf4opPnj zu_Okje$iO`=PPh|{JG4J{gZ*2_w;O2e+^GP=U-&}Zen0TI?JE#c@2q^dTkO{Rxz-c zAUVE=uYpm#=)}EBl;1$?wr$&MFgtjjGyU#;26|-sdVfJWtPOT|Jz|5}Kk9k{=8+D` zBWRV!CZx}0lB~m{bZ}(0r#l^sV_^MWtDm)}!!SGUs<|+mfn`t8o~rQ%Tq;`wZ_G#b z+DYenc)kI5Tj374fZ|Kyje5@GH(+0%t6nnnl7ac@S!i^=fyXlEHf=F`&cG&RE{N1* z!=?!NX?wj;{*{qseO_#+u)RP3+ydn94^?(sp0c6%%8H-N?Bzge(nT*-Wz=3ZZ*<-+#^K1_o021XWejY~xjRM&aS*WASm9oT;_GeW zZ1!^;1f5e&ZC#Olb48<<|H5H|#P!pfXHdM^PFg>v&IPrU_3xTu3mKSB%|OUrE-ZXB zue9tes_*_k`C^Z_Aicpv8bp3DFpGrFxOOgV_TQDWv$3Cnbxm?!q>%|izXwkDDxvnR zI-~c&GZQ}gO$vG>isGv&IMn4yCO9e9m~j7~@;x?foSB>U4(bK= z;4k`n8Q937`9r_o!PA%{oid83J>5H(R_MP6>$^~V zhqgXWe-Cf{-ON7yM*8bpQmzlbha&UAG23z!AMw4~(TopZ-y~elnu+|;*?RQSu@8`R zwNdNDBhjP+)|N%( z*DdaU7xWQA?rFNrvP1e6QdZVveS~<+%lA(Aq5dhq`YRv*Cm1aLD4bTG&%n5QZm6yN z1lH1joc>}UA3T())7ZB(CCj&+GSpTXY1&7i2e2g%oc z?-$Jm-bT-RmCmR?bzS~&gLO9O+U}UHdIzzfp85IQl`o*p7L$tRp!QB# zdum$g7pV7hIXV3m^3UL%Ri5p@e{_=WaVK7 z)_nBy^;=(I!KR=0?aa~r$+J|{t??@u*-fel6GZXYxX)2X^&3pO5rJ8jA^mxYIfCxr zzcL^I}bEiyo$=4b7C>rF8$^Lh!C}5Np$% zY>3@CoG9_M5KihpQ;i8i*PoT|Ig_43*fSz^iE$L|5u}sI2wzeJ(GyGFX6!)a%`BSQ z?q38giZ0X7R1kXz4Y_Y;7s1_m=eP7NLpvGONj&WtD}uf2#S6{Oq5F5uqC<}Qestu8p}}pEmshTuP6q=-67X6en9#HKQV^n zO5n`3UIVE(B!8STW}#EUOdJw1(W899{0vjeCNJ? zmc&;E-RG}pOuSuz?swGObow?!2&YP>W~!3^2!tL68?w*pSDiEXQ_{ebmkx+~7W z!mQ+6N-ANti`fLd*T}#2qI=HG{Q;M3pU$~@81)D1tAlIYe!v<>jrKe@G#*gCH(|@; zAF#MS;pgMus6VTH6EfQO1LFGDZQj>_{O{F%=M$p}v^7L5`~52!*v_lM86j05VEOo^ z<6UI$$q&{QIaSc{rQCn+X;lAbN^;86s^P3x;E^+>=z6=PCLiNi4R?cMn|8lP@s6wh z(YsR(haGowXHDfp*N4L{eqJ?9uG)~lT@3YquE!>g&8`8#*cZy>50QNNll&-V4Op8- zyuP3|+Yq}x+GXfg0|x7o9Zk2Q{-{1X6AP;WGcSv;D;&`HVUFUhiYGO2P0d)``{hhS zOl^J3x~3Xv?|OEW)7_26x2y3uty<`gO0&z8MfXRk9kpwZ*23#I#oLRtQGIrfUf=tn z7Tm3$hw`aO8)8Y%Jo_c;;M`o1&t22a)sxjD-3eyV77L{4}zau^vbjz1SI9tXR62=Vri%3Z${OF`yUhS$&SdLmF|-y zYwKat9Yg6|g^1f+IzC0K0UQLEw{|xo|I5ldUI}b~xnZ~Ndyb;^n-!n@JhuTZu4vRh zdl>b*7 zd$&vz1ePC8d)JKeyFXr+<=zB`-s{Dten9>b_<5};qX~AHuCIEvToSdvN7^09W^kRU zeo(0c`NRLr3g+%+_;qsjo4hJ?y>Irkyw7O{f$n`aZA>IjE_e7j4Q*)`n39W6ME#E- zU&s`%7V!O=7X#0bKjua4o%yi^Hm_Wxp}Gy(BW`LtTcH&UHfLpf8O}of`K2Wr&R{S(Y|J*FP^KHwu0v|OTmJpNPpmfo6VA+uwdxeX3MFF*R6Yg?cz`PwQb*w?aR^i zI6+lncJEIR@qNB%e=YLoNG=MkHaO{?o%cf<^?w3ie15%XgGHeaUzSMBHN=jx4;+}; z4)*pczorKwf0rxe?LFQOTVjP0g=eAb^;#W&Uqd?_GL{t`zWbelE$u(8w59`kE=qJM zl%xJbWUjb$S_k+z4fshYq4Ct4=<-^{PAEu;y}7;tT~EO&Qja1#Ved@Cr*bB!JjpcU z%NP&DPuDobWuyMc_kF?J{XFP7Tq1q`4ywPYUz%$(4@TxM^q25L@yt-1y>3w#aK|!z zMtsx^vDWZ?TRpm#G<7mbP`lX!6PUH#{CFoj2XQ z4B`9Ya;X=cJcv2DC|eVu(NeWzcRF~mQ2xonIS4OJzg>F1od;{qb+PymzKrmn=hMc6 z>+j`1RQ=?^BLAR3%7b^SI@jGR z;X%}q#?=+YJmAMAm{SlQJ#4jL`F9??y|qVdcM%VGxn`|Vg*?!hpZ4ZW0S|6^MV+k6 z=K*u{iJ2h6>+kFSsO9mX&}QP3Rk=Jc!5cTYf8#;mr)^y)zw%((8{-p+IXrL=aGUb_ z3lBn1UpqpMa!W5Yz_~Wo($7X zW}|W@GT&ZH=fUkle%ZiRJkVRZgt_@84;rqjEYNt)1HWk1%HJs{4r&9}r9b6CrMdT+ zgO7R8{Xi#JtieJl0rd$9IGGE(BEk4YH@HueI z=nxOCBv=HjIDq2)-6S!FH`3=cSxeKC2M0rHs;BQk?H(*NdvGUe$M2(6`P)(a&Rwi_ z)0qc3GE%?Y9eMC91T$0H%!9K!_EI(WJh)T4b8Ex~R9`qYSJ#FIb)^;d`KwV}h-XaL zX~_d$@eOi4D^Wb+AMg7b^FaGK<9e4758~&48F5{X+PC8bo}TbbN}J)NNDX#T;+xf3R@KDGV5T_-$Q5vWw-;Z9854FUIz>s z@P|~^bpSIj4nI@e0oOZM2T#rJ0HtoXtDYGhAg5vW{Z?`Z^qSg^rQGU(ZySf|qpx&; zy6BBHc4s;u=g0$rpGP}jYJ*3SomU5ltkVdJaqa-4k(mW4>pQ^yvT67Y(+=3TG48;w zr5#|`-Mnhz{0^95gcly1*#SB^A>ZDJbU=`{(@@E9J1DE`O#9r9?iZ}+iijWWu=Y)f zklMF)xEfVwAI)h84`uV?Wl!3ntZl(kUP3!`A2ib~jc5n)oPvdy(0%4bQdohaS368& z#1)@#Y=?mA#Fm$<(RoWRzI?O19hR!UZ@-}44jBR8mn=i~sqBeK1}TE=U=eC{bMRLi z9MGF0DcR5l-CryOmTiq_xzz>{#YO>|$J;=lPUFIZjcpLQZR-IC ztu`=N?|n;8s0}=CxE@(i{S(ZGbX5<$_z9K;haY_k`w8djCR@Mh{De=| z_b&Mg{Dfl9%yi9?R*?C#=0o4zRtP)Q?3Lx$3RYJ4RHMvVA*k15);4K$KYy5{C|}zG z-O=|y=09qI(DGwTJ^WfgR(8ywh1mj_-e!Fx;TBLnnIXS3uNmA_)jT#{X@(=FOflgt z&2Z`Nz#gBu%}{KEtqQGa0<+imKQV4JLC!kWRcD-=z`v}>&1r5EsLQV?Oet@KYl?9j z_FrxUf4|u=7uGhy-iAEoF@Z)9Ik92U{WlGeaqF;uz`h2sNmwesL!kjAHXq(^nO_et zhi^WV3ap3oqlKw=)$2jTw)|srVI6c=_9{F(QU|4Sc}E-P)2d`uROB?TJ5dg8i(oMni0oeGo?U_@M2SR@)=R2f-1qpBQuGO4Q9{mw% zT9UO};U@YT>=>=13*CFrx*GHki_roit+h|@p*{x`G)_u^ArE$ zc?oh3f`#Vm$@&CS$TFpnD@doK`B_5Fyg=hd3NbWaN~CieJ19L*DCGLlxPrzh6f#TO zsr1+%LhcG0Whi8|w^HS2 zQOJs=5c8qaEohF`1CjL_MDv8iQAi;d%_EZh6oo7o8X0u@3>y2I$$U&SuSn!)KBh5@ zLhddaSJLUq6k-!;tZO3AV`WmvMDvb>9##;Am@9=`W11_|asdjN&5cz4&lF;hDC9=a zd_RRuJDM+}QI<~Uqj^&UsfU|Q<5LQmmnp=K(&^4LH>Z%LL33#uhwDi_%sL8LpJ|># zAr?#XAX@HDr?01RIgQFRic-kxucP$VP>5yI_?%8pppbi-<_BopMx!N-x)gHdXg--j z>~}4xAFHP^mqO+n3b}V_euGW4L zzeCF}(-=bI0UBK?WUZqRTS0RzS}sqgi_?gWBN1KyeiXR$jfTSS{xY+X1O{5?bwqn! z(-o+Yw%;KJD-n;1cAa$tikB9ZY>irjxLhLNn>*lLvFzZsSd^c&b^Eimk3hIMJ0mJ+ z72=CoXMZL`3+ZRgWxq3n_|jqcz_4s$#eC(&;R$oiR4_rFFt{C3!GEkf!hT2n{7o}QP49g;qiz!=cqx$6smim-H zg;$~W)3AAln77*Avsft{D$5RkB_Mw=l4h0pq{ zU`SdGr5IPo?tb6AU_}Pn5T)gpZ83g#|Eh58BPyR+cwwyiJAOGmvT&^}Aboy| zWA}c?_cy0&b7hxhVAu)A>-|Nz*%PY*&t83WzKMeKi6UJ4u+CM7xEeOb6b*YVUWBXj zx6EhknTGODYB&*Jh}-jLn#cqq{oXGIrY$VQwWDNf>byiVu&xV2bJ7cNojZmef|vDB z{SqJdSQg;cS6=)P_E$&eM|v>J@^KyRU852+RKH~F!X@tc`0bE$x*yglpz^^$csLJF zG7-ieJz9YDmxpf(&%qMK{=g|0&s_gr{?If- zoL*k^pgSG!%? z=OF(lS_nVB{|UF-+_NM7%xYwx;%)iwA92GZp*+()s6JR==1cyMcxSP_C(8h}5A$W5 z^WqQqoXq3X=XEbe^1Wk^x4*~VDqa%Q6Iz4fV=Or7@;kgQ!CUHL28xeFwRtZ;yv4WT zSAvrZjFEi>d;ABpaHXXdS*O=6&%m4yPUzLh!VMj5vg$3gkbTQ-!(B4*{Mj#W6~99E zVk$QquW<2x7Y(JDDk~(n-#R%C$L(E{(oX22@|c}bmxMTYXyEseGj^yx*rvvGe>NVS zd1|8U5)^OD{-Lw&>A0!b&xc>62>-fUrfz$Uo0r6<4ymH{WhJh-Sd@m_J^0NY=v<4= zzxaT$_9dQeGciZe>&s5T zqfaDAr5;1}uqp@c86@NLZJ)L(Xrp*zo$rhAdW^Fj53OUUq4>mhmvHtyz?Dk^Dt68x z+A}zF)c-E7v*Bq^n4T_@+bL=W-p0RrzQ1T@jN*;8G|4jf25z;X+DUXaidRg&bII&@ zd`-`DjWjtTo{nfAGmgeDCQr2L#>^2H7cJO+0f&0)ov&Na^^A3`mRooVKhQXJlddHy z5A*kYC+vqSw|=!x-%j}Vza7+iC*~OS9KifT<0y?IG!D}^MB^Zh12q1ov7g3YH1^Th zOJfg>-86R5h}Mr2)-ln#aT3vbaT42UMC-&!{*%U58qvCNGQF9`CK}N?a5BAtMzsE$ z(RXw46pJjTntw z0a{KYlSYh2Ex2>KI$!4i=q;bJk^LEshcM?` zdPyrG^r@-~HV=)bFz%N%d##{YJ%KIfipFo4mdxiE8&DT|ys-TU8qZ?lJ0=8d0FKpt zTh(ebentbz^-lKCF6|n9rVEXa(2myW#tslxH1wx$r3&I)(Pb)IA+WFd)R(o(2zkvn zVJE15ywX+2aRuT7Z5jQ}Fh?@&`%WcG#A}AG?{)=ioyh4H&WjOGj6YYt9o%%H(_H>& zBi>aMy4nqNIKlXY^=SNw#oRc{*$FH97tcE9gvP@dlRr*tH*8^_&~bOOMm%{m?WQ|a z&C%YUC_vbo7*Q>~7eZLKLcZP7K%AQhCHMg#Ns0714vRP^If?a3xWGa>~IE z2cgj||M}VoCXs)`r$ArW<_-IFqlo7dq4x^Q4ne2H#+Lg)*uxME>Nx}pLX{%T9SD0Y zW;H$Z10O*X)|t1&`KSNbxcP&Y?SbLEta9;DBBh;a`2FeP@Fp)2)yfzmwoy0qa|Wz)Iji)vGfEx6RUD84PiT z;?l2&(DNh~*{g6f7{Y$32}M_%BVH^nHWmy@W@4qMM2UE@pEbJW1O(Pb7nw+*?>%D% z;=ETUVDF$kukSZu&&!_rX(2GwGkxH@1L41c%!>UXAnk)U>#7pZ51Pk|b3@=p+g!uQ z4QTw2omMSccoOu|2kvc3R6<;rQ*`Dem}FP(ZOO&p>nR^WppCH|m7z#PBHridAMa0j)j~8+-WIsNHTcv&2l6V7+pRhRd+;xX=H|4V) zL$itTMomP)FJCYbdAc&Xm&X$SD|D@z4#`L0BK{?(_i_v5~YH0Rc?T!{F=bGIG$ z<4vp2zC9FVjW}B(hHpQ9>(kYe$=A9I^Dl_ca&(Xci*! z7fsyL;f?d(n>u{2hu{HUHeNtn^wSgWB4Yelqhz|o8@C>Nn7e2(!MkQ|OWlV{2T!r& zJW)X0SyX$-3+I=YvCxn?8WatJAHR;vIOG#{!RDx;Hg{0_(e6) zco)-OpVZ}!9}atR{J~tpzn@mS=)2=v&X+wpVn&Py^LEW*?Z)>qdJCEQM0;4=%KNwr zKc;u0JgR*j;zDC5rtZQ$cNJ-?JS4{Z*iqe$JMlm9FD<1;iSg`=>2(Qi__5d4x6|*f zKwQ5tylDsC>$H^}9=rx|o2R;(J8+q|&+g4yO0*aAVVUoCJhqDew@xb&4+9^LX1L<@ zFKn|mW}xvj<~(7xfGb|3d1-&|F&V@+Ii{_4!M`qD^FCoedS1fvN^CAU*l%G6lM)6iW_g2~=tj_Sr;iKCub+{8OrX(voz9-YgT~*`Vmi7|u!P>d0bTxkSNO-BS9?`yuVzT{~xVNt2yt9Ku{5vlymNLf+ z4StEd-AL$n{&Ygy6kqgOK+Ab0*D=C}bK^$X!lz1Dfko$kL+_TS{|XnlGlg zHib+r3b~pz*P!_V3R&tDVrn#3rMU`)TxANGN;FrXxjfD1Qpl2}5Sv5uSu~$XA$K~3 zOc|O>(_D(?5)`t;Da5AIT$JWhDCCMz$P}izAk8OHhzU^0nn?2rG{5db_%gJ3R$f*Z=rb;h0I0@x%D)!qj?R5 ztZE9eDw@kI`hctgc z^LrFB?^4K3qWNu_-=z5s3b8~ASqU_cr};GsnO7;~#?U;P=9ejCU7`@XNb?IckEHo6 zD(<*9DP+>fxagKS&`)Bl7^oS^H_Z4=tyW=}o8aqtj_*dQqI^Ny|NGIgQM{ zw0sYpP9xKuPTx%-cNZl^AW-GjrLTWz3(bWJkJn&5tSIle%xlcdKU4#f+h1~Ka6gDo$nr^LOlOq zyC<@0jdnHtxNf6Lp6|_%H5ipQsjKA=n-cO;0j$|*;;2gMoI&Dw74sLwei~^me-|Gf zO+5c$7Q$GE(YnK}Z+xE+&-+-H2#aSV5V!rt>mUt+hfiX58?6udapSfD@%)3m66E$6 z2@g+};2d2;a7>ulXA~;_^6Hmi;&};k7UBLfa*h05eMy#he#K%YW511V3LV_h`ihh* zi?RlcibiXfeJiyl^ zik_I6e~bj2JudElPWn$shC61Y5W#ElI6%Cgz@(=nV(>6WKQnjwB!d5$!sLT6art5i zPqO|hV%P*Y_sZ^I@H%BezG*6V0@%14M;zTomLDU|nh55HO7sS==n(P-2_`@IJn^XQ z_gp~m2~suupm1ArkKW)Sg71({6o3Np)$$n;=SW?NFqz+Gy1XDPv6kC0 zFIJeye`N+&5b96w@9G;yzaxSkz-F<8Kt0=HmrEj9-iO&tVR&eg9yd3KY~TH|mDro8iHIAeAF``LSBd+%42p8`#$-l@($QbhjM$~9A9 zr04kO{u0t(x+;mHkShA7R-lQ@U!=+ugWR^F=^^oC{1mBSQ$f|HOCT^C{Z12xRW0C7 zg`K~2vr@K`^&6I9ii6L_iTPefiRWvqdU~Wd@OGKVC%6;uPq6+OUE(l9VpEw_6scc& zmW>3MFHaF`S+7XQ^=2naK%)M)pv8>U1b3MuE(xOd_nQ@*wj%g8S!YS$ISJT(mLuEi z(p;`2WGxT04Xa&B$n)gnrJ(4l#D|_Q=y#YfOhSPr1wJtw)s-fa@u;YnCj~e5ycP-G zN^)-{Eolg_)Z47oPqx=vD+8vyXHzBr zknz1-Q+zro=euSGW0HiNv&efo>{6S*C18^g!7a4&ro$tnUG{?mvIH;D(V78L)>Xz| z2gvi)by+jO)#d{Kih&bEecvysnE^p!kKOKup!szS+o{K#2?1M<1|L@^{VBFAaVGpp zn%PwLnrzR523<43+_AWB^bFa4stlW1kaN`Yjjsaf?^?s;S-=uGv^AGY>bn8rvmvRq zu&LaOpVYs?dp3+5)qAS&owTRPm^&LrXFqJZ{%Z;$PcxOD11}7HbL790*XMaN)*Q%j zSGnJ#LA>9>JS=MFK*_ItoY8QyJ;yAyWWmx_S?}dKlDAq#%0gaz`PZ!{iT7(*>6$KC z&^nkG%~wgr|2J!!xgb)fn(M|U3c3E6)g+JMP{`eP9vzacWdkJ~xR zft9>e?eh%s`a8RkD+gu;HeSom5%IvvT_`ROt&TS)jT92~XFb)>mxs?EA}nuQB;~l~ zZh7dsyJXPw3)#K}i(=%VJXbzesFsYU7OhY6@F?YuWb#SUz5?wod5Ba@e_bm@@=hIj z1z>lu4ksz2`FxBuMHjsf?sbwBZk|iT7fW*qO933u_W3rhB;J>@6qnvp0FN-Gxj*=p zAdaolD^-An5AMumUm&kJSYh29yG?X}A? zQW%!4;UQX1+NME}E@VB6&T-t}4SXDms3=WaTU8H)ZhKjx~f_w|HzCzAGz5s$?edz6twToI4HINtX2~kJBc2Ldo@MxR1-Mk2d#B z2)?d#-!%L+_N{Z?@?`{{PzKX*$!4dsS`SINUzyxATzA*JrOioXc`D^RX?#so-15Dr znMD4=a$FkMf1o|KBE*>BffeVaaqV}$GR9w|39eS@B#obY^JKEa@Bka}{K`erc<;rC zTMOk32)X}{F)2J>^ZSWySsDbFt;&_cdxH{rFTWG-o3OB|r&72e-cuA`YDsX7>Od*{ zxs-vUp*8V72K!cRErl!jIqfMsPnPdhBPWF?MTX8_uu`6oPpRce;u=5gj|Vu(68v5* zE{V6v8XtS-w1VI!b>}5>{^x6aDuJii$MSx7k^Z^Z7$||SvlHLee~>Is zxyf1rmyk-E^fsi2@JDKsoCN-s(^2i7L!QrU=85B05A-_&mg^AubDD8+oS8X)lz#>3 zPy3eh;`rq|#Y<;RC*vWdWuG|yb<)pAiMC{WEo%jFT*bF(C?!RcI3I7FDvnP)ZZ>kj zfXp9Sgipn@#e_Qq?ye%_;oqI6;t^G=MZa2-{*5di6T=NNm?yMk$oPvaxh{s^Q4ZA{ zH6JGI4KD>T{8;GX_1Wji_nV<*)uOmUfTpf=?jRvoFTXB|*Sril7JQnN&#eGa++1;w zr-%_+zkntTDyyg9q3YF{Pok^|K2jMt1=kXlziDut0L*~z`!YAYF zoZl6c3Xt|cuXdV@PrEB$GF%f*~{hFnGm&o{4uf;|1H=$2^?j)NL@=0}0 zBDj$4;_RD!Rs^r98xzJauF*^>zDc&{%lhlWICCJ)Y4iixUSSO&jLT@pZQX6xNYrOj zW3>?ew)tkY_XycODouex_{oo}UPb$p_3LVq6T(Xh9(|Z~cMhTdWiyIa{~FmvyQk<8 zT)!ny5MLet@sQ(;l>{GZkrTvY?PGpSc|)H6suiDv^F1@3#63jXzyGJxB)ob1jbBYE zWO?dsV*>bz^c%{Ry+9njf!>xUM`^ zfOwycwRZ&a<6>4(KPKd>5`1eX`t`J+yaz`wyOQ;vz^k5!C-x|>e0+$EpF6z3iMVB} zpZfYYATg=)n4bzS%b{KxH((74d{U`N0@!=AZABHBFlkI(_cMQYpmFBwRIZA|n`M&EI zzC5gITWK13eckH=3|~6mdDY-0GG4d-#K$-287BQ<+KG>F*5sz{HLxSE?*{ZMTO8?W2@6%9O(67r z;sp+KUhTeI^3{%vpL<>S5XUa|=7&!)^%spiL%@`c(=B#V*9}}EAojCvAAary3Gfu0EixBeznC?(u7w29> z>sjHUg#`Eh70BZpujJS{$dmq>(I42!DSDg#4KRu_lgHYo~)2m+arNa=r{ptTYS|A@s3E#>zOp)lzFtvjhpw933m>#6DTpaEnXE zf7j?(A&2kV(3@qMOUe9y#_~9Or!z#F{K@vf{)~O$^qes9v0YF0N31_%A31LZ>Q2_~ zApIdfHkQe`BWYgB-9^?9{r*rI$7y$-=sW?^9@f~{UCtD-hz%QFko^lX;h%@ht7oo7 zf$yMmiDofQ<^1RRf+dTG?;FZ=LE6_>SJhmuFb=4Qb*`KcY&RBV>3&uiHtvI1Bj1#>dvW*hDV2O+N41bFYjHzb^JvP1Q0uh(W zRhoBV7{AT;FNn(N0w3!-naa75jGOs|I_>pcu>ResS zWv|;Jna3y94a=^XE)#!$g`K1A!Oc+bhL1@e&!k_hW7lZhe{$8h8xodwU&R+)WUq@z z`V_Od8-yQrzTU$OW*l92jPYuJH~5B?yj~F*#Qu3MTz@dE8}?3(&Q}t@!1n#JuWie% zZfMwTd35!b2u9IX{*anC-5}Vnxg>N$Ec?Oh-P)&$x`DCdCg#PLz>qB)QMBpkhO=o$ zC&W}=U>AqGo?bqw2QG!I3iADUj&Y^(*ab_u9_UEAbm{p0V1~Y{uh$X%9@sDN@#XR4 zSO(llwf?rQ2X6QEaqKd~7(W8)+*a=GfwER+?Ba}IM$q zicjbPJITjSTO>mnk!9t1fvpq7@ak<`W_r_KS?}KW*=U z6H4D#PR;gVOiR-i`7@~(8iKV8_Vk4^v`bfycq{Zm0RN1L=D0A%n9Pn;Ax1AGIV=rP zP`$|Lz8>ZD-o6*+o2}j2x%Djj%Y6gAyFR^WJZ+5sG`PavSt{W9B)k{c{IQI4mlN2( zuQ%#e-0g+BUoOQ<8+_S~_oUuxyzPazYL0pXV+m|UUCGsVs(Rr~LYIVac^G33>k#N;i)O29{#Fo?=>z{LtvT+!+t|_fY|4IV_d(2Ir}`fsgV-8pT%3j0_CYR- z$A9FS1!E&7+p%I#9~=tXk#BV=j*-X6d~q|h54v0%>-8KTw$b{IfSWv{slKf-GgRZ zF&GvKkEiWx{soRrEUm#i*BHW;hhEnT^n-bo{Wasdi|mtj2Fb^j`{BxS=JCuE7uoV} zb9Bv2`a$PRyf4EdoIOv`E?D2S9~RGXI3@h@8vDTg)kSte{jkI6x9JmoXU5m%fjbit z`@wEUs+8`JYizuHvack!AErzm+hu=dKYP(EH%4l8KRh_i*lobs%MQ7;Zt&PxKlF=* z&YQ|O%|7VV-r_6w8)A=~4mI}*Vjm1jvbYMrVflAAkFtA_?9aQ5?c1DwL!qbkMhE3c zHaJ#UyR&}7LYWC7uXe^TPVu!JkiPL77RB;+`b`UA>tt&2b!7gA?`vf8S4&=JxUS7z z+FSPkRL)7php`Kjw@43y(!5QYhl}Ic zjqkrE>*)=`o8vEnK057UPhJ1%i|OV;Sk{$vCBW(s+h%Rm_26TJkau!+z?K>D>~z6w z@9xAw&}sU1A>+UWc4XJB;0x~tfz#uYY!(o}K+oJWx3>&}*Xkz!Y-N9jTchLAol}ND z=ldK-ONWc>#Tu*HuW1dz!=;tSwrX8rIL4uZj|(@)UT+zKpd3ftuQ#F@ zJ71nt6BHW;`4KSP%CcZT@?p3~@+#%iMxes7HUG;*U&hc|N4v?@Bd|X{c*FA2bL@aK zf~zzHMr#q$;$8dhm*Q-!C3RyF{{5O_IF`TX)T^v0!3R|pZ zSbt5rz*f8QOWI834_uKx5~TVvn%xg)n>B6z!1qmLbiFdpvq{41||V%9DH(&n}Hj2+F)W z26Hba^=_~@#pw1qzaXn{44MzST{+n1%AoH5|NMPH>h}q!V&nL);{$Xbr;fmJ)ZP`= z99U`xcp`s^(B4Pjw>Ev>!_*TH@jiGg1OEop?=q6#ha3MLNn*YN=OY!5|3>0JaEvN% z{QI0WG_MJy(#L;mabP?jogHKS;}XsGgT$?KCleNsf3lz41Rl=*w9$&{Fx{g+39 zN~?j1ZvNimr(=B5*o9?N#2pottwNuP-E7BGD0|0k#L%v=gk2cg$(68&&`sDdUN(hZh!f=y zmQZDnmpfi2k&h^c&_nEuNbJ%`?Ab@zZiblqU$`x@mdJ77FKHW5`8$vXVo%laGi_0> zzw$N6f5c9&<9atB`M+|aY{Dj@JYuh0q8tpPbQ7G=N7zp2A#5eiM=@ey;OS{V?3{*T z104(H*P!G7WADx5qo}sE;SGe*0IeblqP7T%N}zhKs;=tJ2nmJ|lQ4)N=_DP}(CKcv zJ3tUHAbLg)>!+@t4)t?3A1}lTL6+vF%MbVip<}sDYC^?q~ zV@bm?t8Qvt0tp*c7v?fX)kSzkX2s$mUWb8rC9l6=MZ7K$*LVUEZbUo`@)_ikOa@sb z2Z*rIfqDiRpm8=LSiwdGBCIr!tczE{1J8(fh?~l4>f#CAZG5mQ64Whab((2(G{SvL z>vS4OQdov?4Ezjy3_J|}?3qxyW$vzCmVZo5w=^eDuv7%9tijop$ZZH5jMYU#$zWAR zA{j^qsX%r6^iX7KRkRixW${o1sj9M`i{DVDnuZf1a+f*bVX|0Hdl7SHAB^a5?-7Dt=dHoo|430o;kSDV)5ny%5Y#q{) zm=)rRl$aH)3MFbXszVVjBM}Qk$UZX|3C^hthADxDTU{Iqf+{oYT7IlNKP_&-IH__g zPaq!Pf!er50<{oqgo&UGIhTp2Hd+;oBXb5%ZE!ZY@T;SG`cN)ab@2di3x*rwfi9!m zOmc!@Ya$Sy8K{b)9qVC(m33|GsBly^q+8>`SY1UpgkLK(cq^k&5z3+|y_L~$w6-Ed z(X{f_gu>KVjBpjvaA+o240|9xH5f^16&{PmlPJkxBA8JT56lULL&zAONHjhxI2D$R zNIlF6RFlcAB03#;Yr_W%bbx|q81)&7Q|*yPfWm)jFkWl&HtgZL*=Y6Ax;W{T&Vzi{ zB!iJeG6=2G-2&(k;*`AVcra2~(-MtgZzD(NK|uzpOkpa5Vak-Ir<6L?nj*v(}b)eO1*ZQ6w$dW7O`bQj>AypZXb8m*JK`#}Yurla92y;V0fh)gg3U zEpF7__2zE45d~^kOTj{h5i74Ct^#=3tf$RX{Vi@*Ju?W};znkAhOF;p$txPm`skGD z^uQzJp2hZo78Pc*W^T`6yFn-B=d&H07O?H0Ir*h*qwc;&w=dWI8@RoU#Xw2UBv6X4 znA0pA(1Ixg*c#B{vTU{-G_SOjE#fqrB{?l*<(!UTg`DOvfzw=O<#Yr~=QNKUTub3cvOS#U zv-de2&0glTh^^1m*2OS6~-bYyl3>js*WU&I*EiAdi=n)_azHiH%w=Cb!e^Di37wvv5f zA$wV;8$c=iTAen57MB#Tm7t~LE@F#8CuWUcb3rdcxrTLnxo$56Eh`(t(@W`5U?(~h zl+sD*Cz_t#KfSBPjhoan(mJK3TP)qq=r$&&m}LP)Eq!WHOP^X04hN?O!q$rVWYC%j z&ZtAzM(r{eodl*u@p@|{nzROMW663pqApTNO_@7|&|ZVJ=*OwWGRU^#mu;sftbv(< zP&iOQ1HcFj0~py;6n$63 z8jQy=XyM6@N2doP)>tSOWMvSG4!zzQ4n?M;?<|4HjA$fWZ>>hm)J zWx+VlJHqp{V_uL*CQuG7Wz6$6HZnaDoz+^1*_g@&s;c7j!^6cG6EXXtTCEC3c=yZW z4o0GNQ){$xqV%=MM3JKF#;Ac}u8Wvzo0luP=Wvx3NuysirpYA5#cQJ~n5c}0Vo)iT zH#=Bqt&U*L0@bcW3hr6f!{mle7f1j-1x<4&d$cNB38h1u%^r9E?pWLu5MV+fa8Ci zu5P$`VMCwJo%8bYuDa@~&Nv|`G!!>1X5HA3^Tguu4Xi8k2tsl3Vnk~l(y+Jzp;&oM zc}-1Cw{Bf$$6~W$G5n|k_`9*LpbS4mU98p2_)w%OI;#{Dep&&*eQY8aA5$?6O}-$a z)eJnwYhelliDVWU{>)%;G**WNC-^nHCWehhT?eKHv!k=eBR{3cN68Vtjcv_|Qip+d zq(x&bj8+Dq4Cf&ZYfdP%Vjc%UAxX17*$D>P~d zvzFDzf+<4E(bu4xqcu*b(weNnvZ2a~NpigEJXVfQ1nIA`&g5MlT6wIVnmVR?b<_u1 zu{=U^=ZP)Xd1h8nnvTVZy7*7zRn?=5M3T_O;P=4M2wP%BJy?1E%MBjP%#$H>WpmK( zb>H$}jgjo*CdtHm%!H?whbc{_l0g2BD7SWtjFrd2=xwQ|L-t4qE}d+x3`Y}n@t~H! zRDY9id9*eXvQi^Xgr-ux8xlw_usFPQGSZ>d?ab;VjXP~>t|gkbJgD=Mva$lmD9_7h z<#km_EO)R7k6>mBzavVgMGP~Qp;6xLX4=HpZtz zAD|m>Xk#zSeqax<1K1911vUcf0r8ApmNlSF1Y0l5D$td{a-b1d0xSmR0<(dkgL+w# zpfMl})BsgLIWP&B0F(j6Kp~J1SO;QW4w?xlfCxAN0T>Dl0R{n9U;uC$&00kHd3<0cw1=#lt$_Cg0Yz5W= zOMogs0r~)YH}tY>1J(eGfC<1*zyeVD9eNt^0o#DJKqHVl>x343_d~knN8sOpdw;s6 z5U2zm03HTBzoc8re@wSr2iy;&!QKt%4_NW5xKyW?SBC@1c|xt*-jFQ#ugS5e`Po%%dt>|RG!)zw+CpzS9rV- z!Onw_@&GbaUmLAUSZR5|=ki=O{IRn{8+=-JVLvju3?|EvQD;nwD-(4Hj6F|Gfssx= z=35arO-X3qqplW&RYnaLGNW{Y@oHK^z>5WE!xpg)%o%89mnLfj`ScW|0z3Nn(zDQPG(3Md-&+4>mhZ ztu?d5u2J4!lAjy)S#GCQ_XlTnIEtNeP0uMDxHKJynZ%PDdQVx7)P=*)Lqx|)`9jiI z{R$2@c(O2GABVrin7uogoehJ%5dYdfsqfc}_i5I)Z1|Q5|M9M^0q@r~gKh@pPs8g! z70dvBEYH5XC$RLApAWutR^+}8I$e;&&K>q;QGOAIed1~gg*@`Xr!vT2RWN7|-=o8e zH=6+e00kgQf$)T*QwiLUUe3j$vzYCii;J?b-C;YYplAemh~{!y%H5|#p!I=j-h)g* z;0pLnsgD3QK%hNQ{+h2EiGmlKQd1v`(s))GsG!KhRl2`sWr-PasurGBJ1CM|Lv|#U zQzVsBS}7F)w5}Qi|Lvdmv}Eq;Y1zJ`r==p96OGIa&Mu(ViP5yQCJ+neBKP1cEzZv? z$s>vXy{7{`2-*=Ks@Y-E-OYYob-pxEdhkk2mG^WgoE!ynDyj*KRku@8mN?sXQ`IB2$8!HJ-4OL}FtLkNIs7f7NH`Iy=raE}` zP-`rjP?f>eL-~K-P;51Xuv%$#f(JSv3=Qa+(7poU1UJB(A~#T(q*ZlL9gG)|uwc=w z4o(c!J92Ks#`M%+q975DAPw+B510feHdtbseMC4gm50X`S!J>;R0|Q+JQGL8S6NwA z7EjcG0fH*4N@oRwkU|znrNutC(3`m=H`RoaBjS+CEi}svX-0H|Xfjwg!y`3Mc*TRA z84QouV*=O6c(e{|6NX~%EioMip0t#5;PPU{DaER}mPAuZb|`koAUIYyE*Netb5w)o zoYmG@D~Xi&!fkm_vm=npXuOTMKq^z<2qtSHj1nxv_JCfff!shXFO(z8J{e7ph+-?V zO&oA6i^j2yYI3C%r=)jkJTTM5G1QtfW^`(4!Rg4N3Pom)#>|A*AIhH$yg*XqgRKA) zp=53dx@6XD!y^EkrAYYOuc_gn;gE>c z1mn7`G7!V&ZHre6FZPCN4E{=N0Y&k?+OX?GdrSIr3`OG%vX!Q+yh)_A)>>?wh9W$r zp~UFuOsp3*wuILI!@|ouRi2gFJB9^-g0vP3Qb6U0(3t*VyE!RaqZQL^yla5L&JEbg z#>|A;EG0OV-<>;Vglr8&rWQtLnIxkVh)f+DGrM3)tB+xV0Gg3`jQqTDH^g?V|!)RDJ@C(rR&$mSHhm!j}lWd&o341e;?EhwelzFmJU z@dig}!RWDtZNeK#$7dk%FscL!Q5*=xThfnq!rvKSw`F)Dm{iJqExUlsdp@Eo=#vs5i6KKFp zn0R^tR`OP-TLT&IxM}s!$K@ zwpP>H7_B@%D<@aBV*do2C0KqXYWVwrvXX+VqLEO$(Rro$Q%du*ifLM@@ftm3D|fUS zA52WG!FvsKW-T%!{iQaMIsCVAl3UI8U&?yYqdvm31|kk z_vmEV_6hc*-qONa@91cW0mZPF12J+38p(WnM@s{+9WcT!LfHN9FpCJ zF#s*2rHSGS2$&l-A#3DT{>RzfGd22~&cpA=C-=>odq480CzJ5moJACV3^^E2^rS20 z9v_{fyt%DV?eWn)UQjNpGc7`CVMk9#3k!eK)3O)10bhN-0&2jpFNHhdX`K?U!T$@~ z@-<2St-tx_^|q`>-B=O2q%^m*X6~Llr@edcMa%x(toD9q=09l-k|R&5te-k76q!^L ztqo4XS1f_rgl!VN(M^PEGs2;YL`EV}GYM;g$n;5d5&c!jq--d6Fj*R^om7Hdb8NG= zx(tg|RkTU(y#sq&*1ik9(H~j|ZG?R*(1h`XoQa3}FM8o z;%iTyIV{N=0=~T_c@*X#>}l_}mL2i5S9a!bBr6|$PJ|;fJ(Sm6*vkO3JmPCFZRW5P zJ_f!;CV7;G<*={P-N>K#=rPxUISlzVf^VBi4#mA2_Pr)q#M53L&EZJa6W}}TgVsD! zm?5wWx*PcuA3f$YnZuCZ4)B$mI<+Gl!$FtH8HB zMb^EruLrj2?!?ocEDE;)ek6zHH|Dz8tA`~&3VV?3A8Bbe!jNBkVMrD|??bkrhar0! zkO_Mk5CgUXdjRqyUMg35%rYswWpHb`l0V5Q2A1ey$&JFUf_*Ju&bv9RSuTY&mkY_@ z=>ttI7qg$aT*#f|o6|vV`EVPuqqTl1Z6fUXfVqx|uRVP+hb4Jc;G3NyZ!zpE0dpTh zeC^3Ihb4K7z_-;TkMg|(_I>ThB;NMw#T=ewt^wbGf41hCbY>{*PTh_CiH{z0J($CA z&vScQs!VbyKS|gdOtKhw+S6xqIFhA+Z+(ia&9H9+jCMvm?a4BSBUzKccgQ4*$|QX! z-V*|5dBoRV9hk$Cyeg%)Wurb0lbxPDD5G-7(`;J55QBX#V2s(s(_VVb;kYcMV{MA8 zjj(Su%Q{Nl&EZH^8s;ywg>wFpP?&||JY#T1V6UI@F9c1lxwx?@f!$;(H( zS!9w!VV1-GK48>2wJqYM#~i=8y;1zzOkqfFd-Y+Ci{zz)FHP46vQwBoup9MC{^U-N zIc{?p@@oKJp-B$qV-oB&z!F;T={)VF$sCUO?uD$CCRr3_4eaZ6H}WSwddz7uhata> z;A=L?p)~D>{Sc6=r-^vlOOrVq$=VCPp?VmyQy2wyLk{_qJ3Zz!nZuBuMTP#S$XNut zQAXrXeDs**n8Pf6;skcflP9qKhk#eoS!Q)l*6rJ#>_VUjmQlRR`;})GUq8BmdnbuFtlz<^}Fga&F@cL?iyY0h7`H9rb-R_t{(P0o$k@Y9!Lq> zO`pdbZPh3zcY2e?R6T@`;bTJZU!~#i{=?- znN-(z&eU}b5s}|Eo#K`3Usd%dSe63hPIjX2oBYZCO^W~a6#p@E+J@he;$LuiTYt0s z2j-q&c^05}ju!u`i;wNUzwy}qdu}|oKfCGJ{)dj^UvP8V@aFQRd|k6r^C$CIn2(mv zT{_><{CEDnZ9L|3B-t6aAKO3g&SU$RH?{Rotw-hA*0krF%&F=4#pKuVxz;eL<#PU3 zEj~*7m#?;(59;Qll|#krZPS?=|NJ+O?f?0I zE$3Co@ZbH%36^Gn%FHMqbNHc$POu2T(frAM*I%u5@{P%CmP34ZbVzT})dx-H%_;4B zMHf1u-z^wB(#p2}sq)Kk7M$v@FQ^r0Pp=Na zz4NrTa?JYjKseps(<72@fhzJb%bt?NM!j-u{|8=g>u*js#dXiN zWBU((tF8asl=Asd(&kb|xup8X(bX{hYkZvB$q6T!A}Qrp>=Gmh>5c>LJ@mGh77FI>^qKee2mTiDj$T+Us# zV$KclJnH#R4gd4@WBU(!PxGfWH_0*UXl$$Y4T7gG7i%O#&o|?{2$C@u zZU(PGGs#1LVtqPrKC&8ZdJV$R`hjGwgqhqofD(`KkeeaTpqYjz==zh6l*_(vmAhY; zYk`|77*7Mqv+iqM!{n#%82m;WicP%9XQ z;Q*!ZnYuk6lyr{1IkoIRcA3OS zQ2O?@!w)?z^xh^Nl)iyI6_or3gHn2F{AfH*8e-t3JkQs|Q2c#%5fa{NwQM zH}NKq!)x8&s(Vex;Vm}tZaWTd19&Nq*XeYHPVds`T2Lx4`o8$!F=T~*q7RR>XZCMo zZnYckMA!e^i){fu0e%7c9O%U^05XAmpbD4^EC<#C8-Z=W=fDAg{ks>l00RIK7za!P z<^#)sdx0l_&A>Kb2e23T6R`Y(aKHtC2xJ4}fNEe4umrdjSPQ%ld=4A{di>gp4FY^X zAuth00*ioKfhU0Nz;0k4a0uviuooK$$iN6-0#FUafVsf+KohVIcp3N**asW}(ouf> zfeQc`$Opy&)c`#wb{>yg0CI2wqbxcaX9`2vxR8MT(6NBN48~`h)A7Y*q%zJ=kuvtJ z*&Wv{8SY=3-RZ<}ARf${jdL9lx=mo8*|)`cYNwX?%A!Uh*kfs>^d&I+9AS(@w>h|F z2=}OjOL35g&P~6A8>Q$tYpVPAERRy1>dd50g~7ngBiPwD;IG6TKm21Z9Lai}ZmKfb zU+I7o*Ew}@TsxDbPm=>x1yzh)&0V9l`0}Kx5SOiFu{S#A2LrLJFp7(H?1(UQayw5u zVE;Dv8xaqp9NtSC8$pp&;S)HNf_4p*_H8#~_obEMH26`yI~62ygB5jCaV1VMzD`4e z*ygk{Iw2pySMU1ia{6koHN}SGqn%0up#)Wjaq(0}r^1}kfmkU%+KNoYVe>c-RZJ() z*=cDd$?(_+KESDBgFBB$$p?`{+&4z~Dos}9^Yo+dBKzoQ6}~+t{~~<32T5Ic?&)LS zAW4JED`{ongjO`{RWc*kxIh>m^lv2_GOgLRkc}79X|0mA$RH&_rDH19mreQ9Y6d?` zOW`QfS^5x412r z&ZI``+5Md^#@$`@#W(;*S2*Ch5S}{d?M)PaS*UWlp05{8rK9H_id)aq&5u;k*@mnH z&k;;44ceviMdKq7H$H$gwCsJJhEjePk+s7Jd`%Y4ip8j0v)HXX9(=qV4F_|I@okjJ z4%I*o#&;NjZy{?artG@vYJ3xk9W<&3`sABJCF5bOO5$vDC%Q+3SaH&Wc*tlpP26md zgOlq)UHxh(Cc0XMc_>$7@89PC+i->5^+?KP^qh}pf1^B_TGV2u)jMT7i``(djc29An-{4XQ>~THC`zGTWz_dDc2QnQ zQC^|LKCFr#QRaQS@#p9Lvo0oc{|>$R-1>Q#2^%m5tUZLW<1dUk^D&-IL=SQ^-oN26 zwiSq74P1x!#@FMUl4~$e11{BFScucg=%hrMwMz5zlw3$T4M-ih6U@N-}WPXo}j z60{n!Yr$6o8i73k_X^0SaL|Zi&?dCU?UhIrc*~KW{g=QFl-Gf82Esrqitj<(QYWC(EI4;c+hzy}DBxqlh>pg-0I#EUrTQ2ct>_k&s?ivjB? zJtPl#D~EkE_yp9&cDTiWOt>|{tr^HfImUoy#I+guv!aY*pp8Hi%Ay%E8j*%#m>Kx2 zKsn+nNBXxfha3Fqddfzm%K~}jsNV+U)q=EcM!3dn5eHBVbBywduxvKc18fFw3}F~# zHIO^vu+9W8^4>tYgfN>IK_*~D8CpRzQHBEe8ObC$h_?ax$%O0}%C#9fX+`)L^4kPn z%R=};7MumHFoxEFXD*aGYXz5@OLPUzg54Fqh!D4-lj0)GSk4m<*E z1Db(@K<6&KS%1I=i~=fwdBEMklfYKs3*ax{#IC*BFkm!L0n7pZ2HXVP1H26E0n)nl zW&?m>fFHONhyYgr*8)wz^T3 z@Hy~rplf<>b~@kyih&?77q}T%2W$m)0Y3qqd!lRs1t*bHm~b_0ij zGkW!A9$-8W1r`9yfO~+afLDQcfo5Poz~uDOox#q;iB~H-n+;^=;0*9!oJl>8;g$h*0dC_MirYAbvEfW$Hk`_KFeh{2wj~el zUy^W}u$L(ecYxq?_e`AK&c?k>27qeZzFULaYp1d4xZk%H_tWA|Q#OO&mP_~Q z&SbOD4b`(b>~eHF^U#f6!5Y{Cwvb)P7O|_?)$DKRmaaiJa~)fPj{ACc18ZbAvSsWh zwj7<_3Ust9+27f%>^8QF-Olb{cd{mS7rUF?!|rAGvDNH;wuU{x9%K)(wd`TGjy=NG zvq#xu>~Z!4dy+lHo@N`^GwfOR9NUPK82T6Le2C7XGpd;7*%_v*Idpp$Kblb%rPWi* zVLi>a<#=34usX#BE2n5YT*Y0rgJNbEl4hJC#o^Wj*l-B0J_nQ0cp%IVQMPr`569__ zbOaMC1l?w4AE}+l(0$s5(vD&3UYezaK|2LvIJ(q zv%!&~I9ehgJy?cocKL)`J7d|(PiOX9%GPimiK00&O(Sb?2kjAwq9e(Xs^V}U(oze& ztn!gBtWJzmordTm;%YghYH(;}#Qz={yc9EgtnQBxi)An!9@UGBFO%uSSuk!l_6dwe z(89u3RAx!~B1>yR2v=7d(_1KtnO1qaTR7ZmX=z)ASOjV#6ZT^5q#IQfDgkHSG&#n> z&3dxpSSqe$LwV^q%i>5odxV6aa z%C}umZ`v8`DD^3=LU4dPT8SfAx?3m_#ga4@s>IFLd`pJAlZw(ROS8nW6pGd0Ada?V z13isnx)5&X`(&qQs9^ui_ur;5Su9UR3 zO`E`Jzu;_*$tY?%@~=l_IO(NNvFklC@14i+n+Z^hQJkhq^=zBA76s+qHRt6w4P>Kj z(B0lCnb)o%&=+$@c<0hx1T<10Vp_KI$hm5ajp2G4TE}JuDk`*0w5g7kJ{l>=*RLNi zQqOnyV*I+8Xhc_(Hg(*Ffp)n9@uU0mA*gLN6zJ#RLjgJo&%0!va!r(3O*I#jmTC^| zt7AJU;j;Ck4YiKca2I9mQZ=c7Xzc7PaoK!kpk;3b}$&D-_Kfsl>jiZp2_=GNF4Y zNEAf$*#{YMdpkA@$@wDa3+@;VRT|xCL88r24Qn)_-5)^g#o=guO|S|x60(&>tCQ4| za5KL(vQ-N2Mk~j}kBFe9n+?Y7tQb2^TH~;@l0dySh?fMhDXC2mj4bln|B->t}ui< zThYu~nG8+V-P%!4@ETpCK2bFFklfSAjxm(LvAt%dIkz*EkcVj~$*xDM#uXntNoEgI zA4WlpAweG{%uf6=77R<%H5mq{K6r3Wqu<)j;^5L zh1X_O05?~H7Uj{zP=h#PNYlh|rK5}6s8YA%rb&O#8DoVSt#&G0{Nmm#e4yBm1V9T2Kp-4Pet1;Ep zi69!-_yDd+Ffwk(?!*6k{&$x^2kkz+EB^bGcBp74x8eUn#<9cWegvfhs^S0dc>d?h zKYAIV_%c)TN1AQ8k-6=o-G7nlpK3?F`_19Z^<;&I!JkSuTe@4e1C1wkw={#M`Wtq9 zo^r+h^R(}7hWWF`;R?l2MZUbXZb$!t#3lmcuVVc}bQ6y`R(+6&gB zaN;{J+-p1mSP3i!HtP2Eprj`zSz{}c*oolxEw`VCP&CoT*A#Y~e-Nd*A~RnP+SU(u zI74IfTxI5t^$*gnN5vjgGLf09g=vYjy|9S;zZj0^#*l5!=Q!=hE!t{^InHX_xP;gF zna9qrmM5*ED(b3_lRC5fia-?y47(sW@p~)Q;$fqPS+QwZhb=-}Scn^-up+J^ zpIrQ&NnPzMtm21S3zJpDtXYMH){=se`DLZnlDyKql5u&t!`h~k@=vN3sxnHTt^brM zxHEZ5JedqliSplh(Amu8#8owfO-A%Sb$G{_0E)iMeD2YHn{tgiWA$vc+#PG!HQMZ_ zyJP7=@;DlBPP``n__#uU7J)wu{-a^Be(u~lUi&VYXa{O^#$z!It+XSIW|meT%|!l$ z|M&dA68KL`AXQ%)&hKSec%jz+_k|Ecr#k88G|NBWT*av z{OC?!!;j=rSfca>mEs_EC$|%UlXQ^#*#Nn_0J3N4_8i?_3`${2boWx7jt3?A`1FFu z9oF4zL9IXx7y!fpN-O1q_!|I9^8%eN1SNlbp27X`IR#G-K10EqTAVl2?RSAv`tAiN zulEC#?xz7t-&+9re*}>KX($vbAEV3`q0C`r%#J(HY^~oh)11V38jQ}EGyd29oHe?q z;v`yyMgzCZNNrg4X_Gm1c-kT z=xm?{C>b{k4kNrljkFndgC5QOX#W3IenXByjqv|_spY7TmS?&}w*BXS zN0|gF@QX3kQ)@!griW`I(b$Z5B3U`gPqb@2OT{Ncn;*!#` zvE#;1xCCDp{%?7Xssh@$!`iQZa{b>@0z4o8!&QO$(+(Zey4{NjS^2{Hg=g!Pv-(rb zaMyhrU2tdfAu?soZ{Tzd_+Pwd=RH=<`-r~_{?wc@b^7Fg$e*S2KL!4`@7{BFfA+=Q zd+)|aqA5>{{P}nB5Eo+tdGXH6c3Rn#ozr$6xs$!%y&a!4|Ayb|m~VvC1e(`*v{non z$1wDDAoc0AW;6QsvZwHyMt~Kt00*ALoB?<&k*a2(>o&bnvrA`-t#(**)6Bq)J|Ai^#rso>_ zxAYv;r-DD|cD?Cp(^ie;f9PNSJ0!>W(=p}gq(7LFZ1w9>906hzIJLt)vTS2KEZU*fI zItY~J{sTa1eUlDKHMIeh)=ld{&jj87QZEawb@qW;LHB~vdLRw{l-5JAp8~oDc3P_( zfSoee1Us!MR)SLc8$oI9vKaJK&<4 z+w|J$*E`94*LU0AI{l^hiZZSH?|3qH;hKkExjmG*ee&VKU-Z9zOuuih%$%{}qnmSj zbzXY@BX?x}wy7!iy9FoRuwdacnVXakR=@pM!CA9kdM`7&rFqHM4X=%S_NFyi8?Tx^ z_B?6nud(L2*&9E;{*&Ks|Kr>F4_=&e*OxQ@we6?#hc8-iZtf7vsSh4pXZhDFFaMtV z?fvin{Oh-$jhb`w+j+rluin^e^}42h|GH;H&)}))bqlVpoB3M)=n2_-?!QU$x;8xa zUFqoiznHAPGTD3G9m^*EHF@>^yR4hb?pxvsymR=viP!BIJo%d6c1?Zo=7T?4h5;^J0fBXOO z-q2vb&nKMv^gBQLxA#70(a6(Bu6^bH@BNvpp9+N^>HNIs>TmrHbz}CNFFshl>BF!5 zFC9MoR$$SIv$j37$3MLA($7Y}{KE2^zkJQV?B!B#UGAHGUbyuU|0h$QT`uK4eCDHX zFZT~RXUpdspY_@T*Uj+<#$NK`{6|aYp7h)l|3}{sU)tq`Wmlc@N3MU6|GuxK`_B93 ziZvJb=dHbK&&H~`Bes7$)>nCXmu=^i&ARfh8yBcI{x0{MQ#m-twt1~`_BHQ_3rF6v zfgS$HyL<73Zl^!G_g`19?&RIFC@;I~Q-81X>>4IJW(;`uoL%;DS9FZs(ZNb#F}{e8VMa_ip~=rFnns-E!s~UoDMbRQY-jH@bv$hoF3CSzrQesp*TLb6K;?8^aAuyCud5@3d%ybcacQ>y zxqmT?;pxEl-q7baa4Y^8?j2!9w|#^=?TzHaZ$hj$n}|u#9xVV<=+_`>EAZpjvuneV zggOvE8Y?F%Yw)32A_KqU8;>UN;aEmxv{nuzYKP6V4a6@mL_*d0Vh?pkpUkNPZNr3t zzMeg-Rt2}mC-^Te|A*2XS~!GE1S{)sD7K!$r%@QVD$dHy#ZSOYn4lZ}Z|t-&nv43|D_n28-}Z!Ui|sud zlRR>kJW?JbkCiW#1M*b4R!+$E@)hz`@^x~f{Gj})oaTMR`-yjn@~dJ|X>vm{zOk1( zBhJ4$8=Zf5-tBzI`GoU%=N9MN&K=Iroco;zogG|VUFoh9U8<|XGuQK|=Oxb$&qCip z-$nj^`ANA3LB`oajj&AEYpZp9=b9ug6qkuR#h=Bl(#aA$Gx6JLAaB|}v_0$m!1=lJ z8|SaiiLN@=Lf5sfTU=AUqZHY9t?wP*pS~6THU3S0{BA?BmbQllm#fV6qN~}p#l6G* zgL{zYE>DIyM|xJ?>AhC@UHM*3^OgGQd{6l{`3Cr{7>|bdv;6t~GXDfWE7$g4o;{wuo_(JEo@@Q^Xwc?)7YMSgzx@K| zDEU?y2Ud7{In{Q(bA@xX^p(^>?k;D_qfo}1F|u3Bn|yT&NQ65grs?5JuTN5Y`l!on$|B~Yp;R#`jutWGkIM*)Px7a_l zpX+cru6Eq)_|18OYmn=AS7&!$_Yn71_j~Sr?qA(!c>a(cke`$P@a|H2t0${V)p{Rm z*6Oyq<8&VCuIpuJFX=-F45++MVs_zY+nM&C z?T1{O-F-YYp1*q@@^pjl+$Q}~`bN6e`vf%ML;n{@aR*W}wn(@^SS>s$)YuZXt8FW6 zPSG#!5q}ULl%A9-y%kDd|JnW({=5A2>pfI1{cVGxaeHmu?H4;%IMz5`bDZe<#dV#y zLX1fD(r(nD*EhmqBJVYm6ggWrAfJ0 z>8Q?7?^YjH-&KEC&+_?v`M$6(hO(UPo9k=vO~YZY4mh7h?RcLv!!^lW;a=b#>3Krj zEN_=rdGGgb@qX+*Ns*OOC7?`IqRK4g3gvIg4azOboyz^nBg)gtCgoM-ZRI27Q{^k= z2lNepDP7gx>Z$5k>UrvL)vu27RZ;mAcfk2F#&!zd3lkjM-J?7cJ-a+vq9h%bO60rb zCEj)3eiRVgVq{pBsd2(F-LO#hm%{x&U zrLOVs@UsT3%)1Grgvs{l_JsXe`*uf_bD49kGwxdE+TyyxeWS-KW{VTVNn*M9ptwPN zP27R}3_y>Ol-J3n-lfWy%3w9B-k`pzjzpcV_BHqy`4>Z{8vV=tEB&kdzxY|BR&HGc zzi=_qf2FWb*e@IqGHerUQ*0}3>ugWj_Sk;4U1{HJU*LGe`G#}0Yp$!owaB&DwZzrr zy4SVF^^xmqS5LRcv($5^r;nH+#>FM#Phv-DfRrsQlkSzCm8y`UW%53GfVadO_Ad9{ z?X|1;Rq-aR-90UQB0Oe0&ynYN*Kx=((CKt$JEu99q3)KrUqY|23+2^E{LRPKci{be ze|v^wp4;kqO*&70Kz>uM^3L_{@&4+)QOWU*N6Sh0uJkSQ-Gx?H?WZ5WqPE&mNJoAi zv6b1w_J`~uVhA{=JFj&vcc!~WyS7P(q#?3L_Q|4}<;&oGm^SB0IOaHRag=%2cvt$H z$i2A(@23k;TBq3t*sQiewjs8mHo@k!J#Bjjy{g~{JDzZKb6(K-9n1iuxLQnMK)s7g(x%=gjUS{pc`>$#C+4fcT(;aU*tkMNiu2dw|NO8&MWxgAI zZ}?8~U*ON-y&V%e@^Ty@+=;&A4BIf%u>UosMrDm!SOCInQwY;9l!JU8zwnhK{jfP3{GDw|$iJR@W`ihjpIs zJU5EZiGPS0(mL-C-p`a*)P(P6-zNV%{!d75c}JW%#eGz^0~p%_u1fbi?vp*6#h;~^ zceZztcPH|H*r)i%6K_nD|Cr{8TC)+d0^MJ=DUL(FKW_!!LHQw93 zC;5i@CQzT!&=F@(QKG1;?`&V#6~`Ed+Zl!yuX62k^>=5vA9o*c_xGGF=13*db<)?; z4^obNt^AbyocDY0F!eo@ejoJv7yB#x*ZA-EzvzG0|2w6nu_K?`9JI;yiS`QnNA@9( z363Kx-->b%qWopXR|va26@l^3A(4(|)zcf6k}+3Lr>&i-nQk{kU8{H&=X&byLs z4izpD-W8VH?zL^Ob+b>fudyGri;l&P9cT@9=xT|x!WnliMC-rXIn=!yc~(4O&nnM0 z&mqrHwEsbpEG4ABNpDKuNj>EZc{KWfrSik_JMxe6>E36Qo6&YU^1Y>jwnAH#{RR79 z_HRVCy(6EeG}(JQMmwHE=`EN3k_O9ya)&ZmMZ%gp;#@H9q!1F0zdJg*ZxEMAo#caZ z7q8D-?47RMuGFf#Rh066tqy+_{t!;JU0@q&8|iq_wOo1$JzG*fNg1Tn`Fi>%X>$Xd zS*G@QhD`=v7kf|pIP`5d*f-ewIjzX!?amXG>o7jttakO~p*1h^-QjzH>Jc}L#Xvq5 zz7U4kdN>|L+5FJz3md=)@NmoiYO1DUNO7}~TNKZ?fq*tZ4 zrH`afF(UsU{fazxm3zyl%4f;v$-~j={d`V4UY;UH+K=;i|)7GYsDVYiSlFKZQh@}X-c|sH^!=!>KgS`w8nkv zuj&SjBU&0x7cLWCM&EwEZK=&~e@2|AU~b*miMPF8!Uw`{LVw$Nwixs;Vo%y%wSR5D z#&NCldH1XC2R$EC`8Rdq-%3mqeiu%+!M?r|*Z)nn&+K746aTeS(qVkgKozO!v9&ga=o}@bUgJ$Ge!b4|FN6rI-`_ z3G!D%{|IJ$^EW71DbZHceFZL zeH*jTuYC-+m{ULfhx04JIF842_xP>&{-*$KW#kGE4Vf;GOJM{4AEH?JWzFg=>WuoJ+-_KGu-Nx?utTo^7&y zn*B!m0sCo=YaKr!mL;C2J+FASV;$Cvb<2LwLC>F_E@CgSuXv_-t~g9|i#~CLSR{@U zFB7Z9h&WT6FJ3KPk2%d9;%af7_>}m9xJ7(R{80Qv{8Id0{6+jz>>~A&`buX?=SssQ zx8##XV0;=UT_#ma5zK+-OIJ(RODm*1q}9?o=_%<2X^Zrh^r7^L^riH@^o#VT)J5(k z_m$6-&y|PCZj96;Te3@L0S=&r`KA%CaknfOJWA5`5dfhGZTk?nUC-Rr__wq0D zpXjxEdHZ5+e6Dwx*Nq-*gty2$&U=}++8goC^v=h8`FigP?;YONm}NeN{(6h|E$@d| z%YKP*;1}zUrCkx#}?0t@_jvYLPlly-clEBkD|bzIwHKy}ClZ zLtU+|Q=d{_P`9XWsUNDJs9&nztG}p!s$G1&e0_aq`p!k~S%D0QZm%p$7O#iw5VScyY=O5v} z$UnpX1L<0$rmuy>(hAu zb{Mn2YcW&X@4nabPtTX0c{2PqYx;J(ut%ttnR*G$54LMMZIesnC6LkG`yOUIL)C2G z4Bs=pL%zwp-KNoUo^=qi?GgKH_H;+BW3{7)r_hrpRY(s>uSp%T|B&=9^e)5dX16!4 z+^0URZc@Kcdthyjfcw*UzdA`+B77t)vA=15Q{E}xfZ7w3EM+We<$L9zGDE#hU8`7rD*@1d7qRaypCDZ z8SWjPN1)%+F+OPN_{8pUEON}ioNkkAk9a#)$md{Zr=Ri@*AI=ShcF+j&Qt7p_F{X5 ze75&PTg&z+%w!ay6<*x zbpPfV=q<$j>@5Eu7z?y=P=yYz>(Ga+#rXb__iRN}hWZp=uCD~GV2|%7-|r-^xii;~ zhlQ7IZ#i!h?-CoOH_*TIkv~I@3cUT5GOUuPqOM+3@9^#Nb@Ctbv;CdFgjcZk zj@ZWAm)mDM9(R1`*yvn>Lpv+dP)n`=Y?JJD_HmAT9a-*Q z+%=w4#WTcw@mA?M=|$;nd6##t(#fCUcleoAu`BA@<{Iq2*jP1qrP$@aSKb6W>{Z~H*|g;-6D zwHG;Wcd_Cw+C0j0zUN_av-quef^>znLrO>M?M&luc^5vfxX*cm=Y07t%yGJKo!;pf z?##jX_`7?B=Sk1Yp7+Jw;#XpUyhr&7>!;CbobBaU^Da zA4s1|AIaazqtL_O?|sJmy7wb*o|2~aQ->mV(^S^jh3`@Awe_^0f>qlH$Bm9V9gjJl zbG+jC#Ieuun5zX-d%3!ls6+F!Eow)e-5&#R6P93ML##wz3A&cn_ps*ay*HZs|%~C)!unjE}32V zzE)DWS$InL!P(yx!G7`IU7xsjW6p9|>?QTXx@v+{E=4gz`I~f;G)rD6-zcw=*T|1! zpK=Svu&?Al$bQnua}4H-`l%5dO5E4 zoG4$Xtn{(+uDo9JFpj+E{K5IB^A>ksX|~)Te}ncmz*p|8@x4j95$npw#CP1^qaWz% zxxjNkK7^UwP;V|~FxB3}Sb@vfS(~R`g;~Rg>M1?}EB;gcHh-@FQ>v2&tz2`3e7-9@ zS4gvUv!&ZCwtB3im!S{-&Goam0J(Znep~*R{4M57r+VwL^Yt+H?RF{u!k9S<<7*#( zGu8}Ud4G1Na5DA{{%z}FUx;>dKSs9~MUE{8E&viGtA8;3Y%01U$CvG+NC$@S%^?Z%7K3g0kE*AeT_LeS^ZsrjlCi|ZAvF(~p^%pJ_{;*%@80}i>=_Sf&4Y!JWvGRP_d$!7&yYhBC z2y6PM9J4)z*pqWYhZ>~UCAPmS@9P5MP4Wz71yb9M&mBG!+^+qur29PYb}z#X)#XV4 zdBPyaH0LVk6Yh`P-?}TrBz7wo;jPD==;gMHAB*2$q)d}elxne#nTPf6ZPFU7{y)X~ z_AjY7R<^^j-zH*~w91=?)!;7GiT4+F|2Jr3-MG$ebB=O<<9=K`AYO*O;w8$P7Os-f#QBw#z0t4muXQ z=Bejk&Htru9M@f~jCu<>!aa`1lzrIiXN{WM4D3Vyim|?s+DMa@u4&G>D6t#m7nRP? zqu(&^(0HrRlYQ!3<(}?q!h4NwydH{#%|e-Dn~QDN!X9w0cFCUKq`$E5{w>AbtflRA zVY%SK`+%j6CRacAP3|K9GQ4qt|Nd^gk9l9X$#xs|7C&>n=B`uk#BBX^-#3`g&c{lX zzT&R}Yai^pylu0(ZkC@$`}z?h{%78Qd%G#8V|QejvJ7qQarD(&Fb@BuoTgrc`Eppj zLcKZj`0>Yu)|{7!$if2{v9e~rK1f0h3||1K?^xXqk&K3zCR$Q34HgjtC_ z+^)70ZKq&nd7kY;TOMMcgpv9#+efy~Y+qm}>{r_#wtUBI%)%DCUUv0xpNN+E52+k0 z#>=5KJ=Ol|*(!EVu@BOn>u6uh!{<1ja#hp&HlaITcXpwdCb8~ZPaeg3F@(9-OOhf# z!Rw~Gok*nh%Ko#-fXOu`7?(KQ<@*ELwNx}^8yf6DiG zAMpN+aXVirQF^I;Fo&L|?p2HM4(EQ~^Vmb3?Ptwez9!i+?28;boqN%4N8;Vhi`Z-X z34QYOc#9JD?emHLa?BgMbKTx2jKo~R;acl{7`ycEi+9V*lrL!f!HJJz{H+x3vY+HU z&6(kJIlazI=Q!ss=PFlk?CQ+5=SyhW3BG=KOY=?C2i(|!0@upxV_F^BCHlVloK-wiW zWA1yo+y`%hI%0pK2jAQN3gJJqH#lx^WMhq!a4y6cu?9O-6R;~|5$B2Di8WHKcOF(? zP1tuj=g4qpq@(6XU@is z!4=XCct7wM_T;Wsmf~&EtI92;bD2G~a&!Fb_{cfabFX~B`zltmJJF_k(N1u&mQKm; zxA$?JfxV~I&NA05*LY~iN7zXj57{4y2gQlfXV@J(L%vuZFHgk#fli7CGlY>?bHAW| zNb!{G@z|C-E^y24G49FkdiOTW@ABmxau(*IJ@{P4Blz$ZW-4|7w_=y<0-N798k$va zd*1eq{W9m}&MTeQJ4-QQG9SG?i;xCl%++L{ZBsF$nqzyx_9)(e{$UR}Zo`b~GB0cF z!RPo{_GOr79kM^?-0A!R^UE==0QSHRVsELhdyqR*xmW#3{kN|x#!#(H_Xt{hy~4mE_{=L4*re0T@4#`iHwE7$*;_Fi~>58hYx69yqK zV}+aWHu!6yFLdo{=Q6BZ%J>|#2XBWxU8Sy*@g&<^LD^@vcDg8 z5Gt{6umEqL_o40nN_pMhgI*Q05tv<+2;=dlc|GP+8}TM=Al{7(!Q zT>EtU+4ghoSK6<^uJ3aDr}i)Kc6ql_X7{jPd#FuSc09WFGUOXmL_4x zphns*?UyXFAQ#IUP^+VrVVF}~=KDYGoqbeQWxmIcN=9-}N=!6NDlAgU{l53vZ-*wE z&`9Y@MTuQ1a%e`23X_@0XdN^H1A#WXZAMMZ^16LqPud?BTx(xSw= zpS=Mm(^}Kant$%9e_E?Q;+*|F&-eFzd-f@F?}1P3AnRe)b@s2tTHMbQxnF8a(@9bm zlOx_MP0>Uxtevert8dY}LaAnC7*DllgpY~8mZvL8OwW24nAh_3Jip!IVyQ|xRZf*% zv^4*LMy2=PUlMbaseIxuti$aXcfA`(i1PP%7nJ%c?KJ&vCh?h8lbs2FeZYIBLX>}& zWvM~^So=mFZ%i~!2mNOHXtfQM^C?sI>QEsb_amI3IGnLW+?lVOcU|8HN$LN)w+m|& zSG`{^GoG=EoGb7Y`v1R6lvK4t-L5ZzL$87FO}6eQL;oD@aR%A%bs*lK-DXtti0~MA zT$Y~(#0#wzb zXsWka)g!?-+y$B$L)Fpj-ILJ&HY@uC`C%7LfJvH010$f%&@=Tsy$EjBroYQ+PBub( z^HhAsN@E=guMoC#JMR7(knvBUCqs>}o^7FBp-)2*c!R0*P)sY+y4bqO`i*ld4Dvna zI(I4DCyUm^mElFb{*-tmE6Y zNn2qx)%fVSCSWskjwB=K8V&JiT(qtUe!oa3D&)UAZOXgewRi!+=| zu-OadxZ~OQZ74Tl35d;sW2(n-ZwvZxRP6Of*S{wD8-bOs{W?b)9 zFmEvBypw5Ok2-7;o8fXFiDNk_=~4!XRhE=33W_1wEmnfGowy3(v53C@9WW*yXZzt2&thQ@|@o? z178EpbIsp@FH&Q7Pt*^R5wN1ngHEph(<9+bNk?D-w;xPg{Wa#(`J3}@2r>GjS#N%)#ZIm%uDHHEJyPmB`pFwDy4PONZCt|E93{zMJdW zLN#B#Ni9Xk){t-iRee?cU-et{NNu8~XfC_vZtWp$1AUmGJd=0PB|&EP3eNiKP%Zpq zm)U5)?)v#-Wt7kPN~PyeA~JY0TW*w(;Q3yyZli_rjhctn`BdAd<+1NR(f8`t7&m}1 z0dm3{n76~s>&;utVb&-z)>7*N+j6?xM`;ng>LnkwQM^Yt5Ev#zp%~MJEBKCOG#j3x z&GU}%fiMj17$?r6A&|p$P#ZMGq@Sznt&BN&mb45R>m^V=_L=bnx+9^E5n(xuoG&;!OO(T4mi& z!yt+bBOWLGOgoLX&SF?!hqIM)FqRbICOUGf=uOm+A2fjeuexu-AwI)F9u^+O9GDP3 zAv`s#qCq^~p2bACEBvINhVy&VU`arjObb|p1>}%1MW8%H@pADhc+xAh2j4??>_K%5 zkz#1ZoG2wrie!Qw=Sp*>OUa+|I5*czH#2qbknWMzN{>o^A{BlHcGQfE|GLzU8?}$V zT9h0k$HDq0$&+PKPN6k&mYhKz_dAegBR)hU?%0Pg-{)b`A1GhIJcp}s>ha8*(`kBK zipNu;-leWm*Q*;je|wocQ^*_BwAtEX?RY&^pQ#ta&rFi)g`F*1?vfxrbE=LEd>y|Ca3cNO2sj zwNotd_$JPEi}DVfbB{7yjZw#_3f}rQ?RWaE`bthyJx}1T#!Do43R$V=GL*B|kH)vV z*t`)BY&ALCIBSx13W?czT(8fqsWq4G0G)%$? z&t~Ub7OwRYiH@LAbBE|kv!olOrKBDo!|@HyeHwbCK|V&A zgDd>5vK>$I0`*0hvW`yrfDCn*o~ZvyU(ILU%xZlcn#d_>?Fn=?IZ9@w7WDNS~NE=|TT~e-fY15YC|0 z`Fo)RuYVP-&OZuIFsqw{muT{M-H2UeMc)V)qVykPrMHRQ;-_HqJ~4{PyimFhj&`}6 z%F3Uq?P10o12U5)Ez!!^ApzrXW0Wx#7JUX8K@aV; zSdwTXGy|1z1@4^JQ+phxuz<{Vhy6HR;rHF4;VCo=XVVfTV=4{$zGo4tW~*?g^qMqR zo`;|O46n#PLYb}HO_zD6GE6-ebiNx8>}=2Xsto$`XZ!@N@n6j6%oog; zaavzD-!%W1&h!Vk$h*u>%+Jj)aYesnDj#k|F_}kOW36MXiPodu^Q{dAg8Zx3%cC0m z_bVL%AyNnmF+!{mC&UX0aJeLWN8XP!z~=@X-+%Ogb8(0AgnZ#fajCdMwB^f4{T`6N zkxwL-o2}eJL;E=#(=Bk?US)(jiVpRSwD6wR3TPOqiK$qXH zITj5u1r>i0t3DD0Ylrnj8RHGfIEU8nvv82Z%wx=}NRQW$@jnF?bl9D=Yr1e1dti;d z_C7n(8RMMbh;&lVhB+?4iM|Eacpphkz4IL2`c~%@cT@O%G+_T4#0hJJ=h*M9WG5as z>Y}^4Ti6FjiWZN8B^@tHsNZu%4OMxzG#j4m&AMwii8q37>(RbJIY%C$%%%IZQJJbf zt-hk3q0Q44;r*OR_jQ$VL@0%w@*!#YJ(}_R1sj4tWasw?nS@pz@6Jn!1zj=NTxR3VogaI2>$; z{+X`P5IGY^{Q{7sgjUE#l+Ip54mtEbjx|rF!!gsGPv$zDeu`zCV@)8zm`;OdkzI`k zdMrFJlMG-bYWOcs6H{(D4C^%74mYA>-h!nc4LeVV&-q%JTaQhAR|oImI*@QUe&>a- z@eMex+sTnmhGBTm{4VKsm|HS@YPvEHyk3SLeFjbyP^YSz8dj&{zRZOoT#ef`LK~wU zM>k#3E@U-|v|H#~wP^2G!gEQcTm4`9MKqij>vxgCt{~96$YlyBdQST|ghZNLVYZ6zf64H;u^9ZRY7DjYI4h znr>zGuH`+0p(So+q@?c%Dcv`CPFS_w8xYl~d_jUMVeuRd2(u zITluE(me=(-y=y~taOPO^LvrV7 zFym!lSBaY-nW>JS zJyk~gerE~p&o22B+~zOky>Pl=%Hc|sG79xImYv?CJVr-lr+T=SiK346=2dO9@Bgh7 zi%WsVKv*gExFn!=WX$cO}Ug;Io;$-Dsn&8LqDLU0T+LKzozCrBbz-S_YVr4TjCfy)QuH6w}oz)mCa1^k}NJN3}Yi zZ#@sXZN`6l7hiQJO|CDrKKNmzK2nd>$Lk6DiJ+duK5$W-Gw^KAqjP-;D0l^)`|oh; zmf&pN$@f~LSJC^aWezlOkE>aKl@s$eNVT2YPoL@M(FVHK4s=HQIYTXJ(NHrQ91u;* zY&6GPj+O1-yzu(0N8!~k0Ha0goNw`KZgn%6Z@!1s6YYBgql8}wzZPB; zUS|S-DC}VmM5A;ju@9$-7tmb$6{=^kSR$5*Yq&F0M;`Tp_%eCp4)J5S@lZNnW2A{p z358oqVLXf(IDi?_MYvg4ONC77Wl||uTlsCh2<~W+q@}aJGs)Ak@jr4w$2>7#EFx(t z!T%}cuT;|G?iKs^0i_5jQVODXW2HDLUP_P>r6hE(;Qu9CN|B~XsZ!d0=PZxrZ2^C= zNGg^}(7~nnO3nnp27EcsPD4emrGi3uyv1vZ$oDLDE4 zeXm^lzZ<;-gk`uccP0Q|grlr4jbo&AHnR2km1zN2rn9 z4~$V`(ed$W0y$EWnyd<{LVwIwQ2WokK&Tcv+n zrC2Kg2}-pxbU}qysa0_bYRI852q~x=JCuT_oEl1F<~HDzH-(x*EhHjsG_X3z{HD;|@N6ON`S#AQsTDTw9hWhQs$=+?oSg}@|6>lY2iB=N555ZC_!?LXuS}dt_ z`O>+$k!fXt06BP8^R05a3zaZ{>H}`EHQ|S}(35Dh+VMm>x%bm$b@Q`i5q2c~_ZT~t z#44UFGSN;V853-U7NBjX*wgG(9ItfRee-dN^J&`^9`Y_+HEqROyAF=gfG62RpT5Oz zwc8G%75d;$5d$uBEX}!iC&5Yd9e4q5ZulPjG$++bbJCp**lZTPvm7Vg&EQ^27D_*d zcF+9p+_Ni&J1oa9Df6A^O1Fvxug0x)>)d+WxJFu*&2F39PA{yJT&9bRxraoqmrOJe zj)3h=m8>W4Tx%)`G+JG-ewIvV_*}^g_CDEqb^Q)cX@K zH2r%iMBYIdTKzo)?fD*B#>lb%CSsPMMk{_2Zua6HL@1F;Q28+g&7_Bz&5eo{r4?=7 zu5>7!Fq;E0au6STxnB{`BD6>?sKscpTAUWICBO-iv}9O;q8adl6m1&JAWch$8)R~W zA)DMG7k-e3ZZ3f16u}V=g^|tN{EgJ(zk_>&cy}nYI~3Nnp~*YGa~pa$`l*jrhhf-8 ziZP9pFO38!W8h9>j*$yD$|Dade;Nt1%^WiqC7Z_$#{wS-e+UQL%?|G8ZrzWEy>$2jR)iI41xfk- zd2B2qu_+-BEae_$KSuVWoQG|hKN+V6aVeL+QJ#~J)&?6AMtavz2e?kdLDRmMTbhU7 zw}O8M=pR!*!{4{L-<$mhPW^htoA^yiv%kLw_wk|Te8CUS^cwYt_Uykh*W=jZ|L#n$ zh5NSP|9*eRM;Zx6qLIY?U%~Iv9=z{){=!H<4`{|!`f)V&kg}WE+Q)krg6yGqW@|EY z)n=xqF;BCYrSq+P+LguZm@?m!9O?U}57T^n43<8)_X4&2el%-tprgFZFB9J=7pJfQ z1zLh{R4!DKkk^t7H0KvRa}Fc1}WS%NOjZPe{aUOxUC1xc~4)C z^fTu!pK<5?OAMy-0I&0>^Or#D$Uyf{ulteDz9?j86tg#$vpdSz9~JD7D)vYXyQJ=W z4T2xv#W7;6ubHZ8p8pUph-ZIPu>6i2_-sP*;5R$os9^f+HZ*!#6ZhOZY& z_UnTha=2!HV)oGmi1E>5a9*a;=F7uh9h{8iXog1ALfd|&&`qx-7G2;e0tFS2MVCE~ z+d&0P!*UwKH3L(y1tqZiI~pJ!-tRGfkL&l(wnCVG6&%0bXZOuavL0|}aC*hT=2O6& z3>bVi_qM(HRN=Gt8XRknt9QZDd*}+r_}sjol~1$1H(um@hliKLy*<|5HZaG!;Mg(r zFyr9XhR>xlm{Z+{m7H7e#I|ut`}kpE?_|bv8ohJa&$4rQgN4Vbt6PS1(+);ZF9-9TU(X?BMH7L^C9Uj` z0Nlv%)nJyHJ)i6^{jiZ6^9l}p|ey($GMTb#lxKh`bnuM zq8vO#50#g*nsuyX8>kxuSCd)8G*++xC%1}qYh~5?+(=eSLBnLTPRnU<)RUlhhP%83 zoi6rBKlh6z2lKl33Vo(jCRo1TT=7<{&d*6(n5Z3Sn{L+2n_0;Ui<7gPPIA9~&E(W%+c~^*Vg7$e z#r|jJso|f=T-LPWd&)K-jN*5-k)%AY6m=TUEbS+HIyGvoS~q~jP3->`wN-6X+i5s< z^5naC(?cQN%W`~|3Ott@`Z5hX=@!^o2XAKTre)a2UBw`88HtCTCBw{Y)JPgiB+E#H oL6zb`w18njT!b8Uaux1Cm-mLNpa1#!3H~C+*MZHx20g`qmY5r82wyu$leY={L z87WX|!lX4%FH=)REbn5syW-03`uS~rMJ>n=r!+P#L@aF8d8DmX*`E(K%T~b>w3_$( zJ@AH6v^u<_%6wedrL9sKZzKK8MX$Af?Qk>JL}$ATaI zSg>h%NARyd-uIDrU3~G}kX`hZfB&}c_8w^2?fk9y)-$`;!2e|G$nI76F8ulrc7IU5 zKiwUb@6p{o`1T&SZudvT|Fhkf%lGG!Z{gSf)z0^Q`L=)b!~HDVX-PEVa)MNWAn)y_$5rYI zBNCaIFZIn2?IEA zy$#>ybJ8u&&-W+yACG}fHJN;NDmm#fBOdc7=Bkj}Yzn#6zx5uNkbn8{*3PHrxQdeG z$D0wa^_f$JNs8lF6cv#lZ!Is%3#HH@_5GV zWwWh^pDKj;xM^BUb=*X{+kb>~Nc6d)L>fH>@JaiBQCL=1LDH6v$pv`Mg7 zey7)5>_IK8VlgW6oOq6)c`|d&lFaq=0YF4tWR@Zcy-w3Su3~(o8r=m5N;P^4{HTv3 zGQ=-pD$*gYdX{i@h!@N^cV+rJMDszrqL@y!R=ys z94zIPCFSpMIaH7vAjSRpO7!$v)2AA}#q>yM^XLT~oFy3$4@gG$q=vD$KDMWL{Idk0 zR9FC*K|u+BY<|`UcxU1VVQ=O-Z!up$`W!z%FOVG^I5A;9e&PUF!ntfAnLZakucf~( zgb&pg(DV}Znx(3w**leK z@tD0YN=&B3E7QvqpL`ZuUNl2@Kg!;{%_i%dpEj>6C?BW(1|2c+9l$ z`!i&`(u|yD=p_8V003a<5yWKxk+5|j|n?r$2b0q{C0?-ul zIg(jpV%@PakCt_OjIexIvq?I5vfuRaG8YmZJB-Gz3RRx?rNlIcGB*X73D_o0tO`|_ z&7RCnL3Hq{P|$4lWo~kxpgZ*3zX2nV0cV@d{u8E_vMz_)Y_7Du@de)|)=pi)G)`b* z&bPVLIaOLSomyIQ??YTE4zJ?J!n^{3-uuGNF5l+7mvR zYmaIQ`3ZT-ncR zf3zUOzt|^@SHT+rm z#wYR(rZ37snA0G3V@7MlPvL;h>@>EM74rpOjzI7IRwlULf|=gMQr$qFn?6r@~~*%CCvq0DswWC>Sh zuB*fse3?Oj*^OvBBE$$wA}G3M!x`T@X@(&L2h1=;X6Xvkc-O)8c1Ify{MM z(u;(@#m^kFgh9BJG$oWEiwzdUrHz1eR>B1&CtS)Anvpo4ot$$StM+1b2xLL#2Wlc` zvz>yu1f7D(2%UmyM2i@hO3*2oM9?XiF{D%Ux@ac(?Ddle*wyomXthW#wIaFHsq4H( zO9O`tX`b}(y3`k|GNM%~RxP#F8d20%XGBq3gVg2_;D2>}9qJg=2+3zpC&%2K>VLY8 z_0X0~CMSJr2?r2It<4octJ1NcDLwdodN5;TOE^dhd;r5Bm>cA3i9}7Ajnx@+Ht4E0 z!>&iFx(q2nj}COT`q_%Z+ z)hg;XVznyjF=BNp>@^zE$qw2b_Qv$R&;8Yz>1jLU)yW&NDllFME3s-bR%^!U%#{r` z)B@k>s};*aPB{G0FEjo@E9!tNl=Xh5acUPjG0fcLd>j?9x<_I9>=i9%$k|N zSL@xXPdtv)wGv);g7E+ae=NP$XbGY|i~)j!K~Sl@A)}=lVOT#@Aq3U*j`W2}UL_A#Pg?v`N^&cRtn9pA%KC|<*+4-W`IR*LHI{s@=jK{dx zhh(IW1%5hO5E76Oud$#)LVU)>K?(623qleSFfOi?kP2f#m4pP1i>oChWGtwakV@m? zIti&#k<+T}MPoq&y`58E4D%Z~Yh2s}hq0gqzh7*{&pv@f#)DAM%7Fhig~16&TC1fmg1&)L~Z(6ls#WRA@)H|j+$IQquhf?n$)piKu z$n2a@ofBqcFGh~pdqlMzF(Y?zNcA37okz{cBN$3%Z$`CcIE*@vsm^0&WFJP6*?Us8 zoirm4Vl-glSDnwAk*{D#aIWu#Bx0$~Ne+#+Q4Wo^T^t#mcXMEL-p6s#cDClb92UF3 zA;ZGqqFHt}Fb`+QI)kGa7*&*6wjA4^5fM%9z! zk+YyV*Ytuhq#mC=h0N-po}_ctp*}`Tzx5CjF&O!!E(u-0P*#8bT9F=-QE! ~DO z>wHmb?tMsKKl0*$n6qWaEf#0tGUdyR}kgojtnLasH8lRV(DvQq+&U&CG?WAzv zKkeinqRWYu{+IS~FHg6Ru3Wqtnkw+88Vi=wdGojM=a^sS?|_PWjaY?>VqOobsNaZ% zR5W13DpjltU*PqjaXPkYmHcwiq;vi78PzV8e8+aL=7D( zfQS7T@fZ@yWU0nz4tUTUC5xy?7Apj@9)WC?K(l4WO1+w)5*?@`yvbc%@vP}Zn zWeLF`b+HLH-TO1x^=PpU-HHPkU(%Hj-@a^0q z$&mhjaUlHzbU*|e`U)Hc?MV%>^juJCXitIP9O8y?F5aOZh7v!T{&MlgNTw*id#3!m z7%KEJ=qZkmnL`XacIZ6&B|=|JA@vWEOXb&J=m)$t>Es=rIpz1I$z>eB-}a|kP9NxEuV}xD^kq?3;xjSd z+Uo^&v<)&Q--0StxX_RMkmna<=ZEnUca6vQ+;>1PQUd$4M1@;>r%#H12L40(T3Kz^ z34y4fz8oji0NKk%8@k;5$6BrBs8y!Fi@L0T^>Y$CXU^SMTR^^=-n?DViFl&j>X+78 z3lkJnP-`mo?Yb5kNhpujK)nZj7PLrJ=9)_At)SbYK5q?T1Lhhi_A1OZP%~AVYpT{@ zhSv*Q8^U$Iby4dt1QtP@;EVoTeAv38D4u>h9_cZ^Tb;)M7Vy*+9G~5Xrkbm($D7@x ziXx!`>9PL-5e>4@xx1ee6Z;-StMV|6^ac2TrTiM6@!5j!AJ5L=`@@&bh;N%7zK)dA zBEiSoC2bBL^ylyqwdNGX)1Ql9YyJGvtbhV|dY_{|KYqL@o_;%Cg$wBm@T|2yUzDEy z{PJ!oil^TxuV_BSE=s14P|Kv-?4H3pj0hB{-QMQvy>23plZV<4(FqWBeRBZ}`) z8&Ui!wGqXyRU1)!pW2Ax`_)Dizg}%b@dIijijS*}D1MXLh~hV^jn&59L6YLeT4S%w ztsCo%y(0{1Z!ktVw?YdvD$^>qutsHC#V*#UOsm+)8s)Ugy{yqa;_w@zGL>RSYxDsL zsW3)4m2zKebgwu<#^{6Ms5D0RiKEIGeMB79#%Nj`wZ`a}IO>eiLv%nHG%^9l_}~%z z@?Wt;4*Ww|Gc?I+9hJgxh|ma+P`^wyh1Nz>%c9nu|AFOO*qvF{1P#uzFjhv*Em~Ex zDik%F!_*5xZMdunG0+PxYr%I_XoVRLF-tg@%fnUEs!)#^uFfQm5!k{)D6!h;u26SN9%DK6EDc6?CE%zvmR6zuI-?t| zI%9bQ@{^d?tj6|8iVM+hj81}*mp0ToP#xlWfD zuG&YKnn?nANt!r6NDNg3*-|R=lC)4S(G&^;SJ0A?7MtLNmVva`0%xVrBJ^dTC=hPH zR(nG!-?B--fF=^k%07u-fsk-Ka~(;7O1W?pIt+QxOIba_y__L<=oS~U;IG|9m# zCaN(jMG-I^Asbx)J&Hb!f-;~nBT=nD->!hSfyxJrU&QuM{jegm3Nfn?14Um2VjLQ- zqY@%V#L&M{@z{@i{m?-O5rdGCj|w`(3?PO??1OFs-Z)An5kosz3-2cUZAOYs2;Qu9 zctN8jj1cViHy{xD#U^~JjFuKq97%{OzE zQDI?a)e=UgVwO!HqbZZHDKoISi_@ln5S;ivtqLCOWV|HL@8FbC3x#BWvmIJ*}YF z9%M=<w`(jDK#L~6)a z;Mem3;5DUTkPR~)ZVLE;|%tT8+zj@bC9rdBWxEPX5_`p;20gd#J=&-xN!LOJIm1af;m>&4m9St=$@e8f{2vIEf9VCK?-(7SFeg_4!yX1E;i{D*z zVZZuSlnExr9-(ascA(H@;(X%7Pf#N!HxKrdB&0-)TWPsN0j&hvKVqNCjV+BHZb=b( zK@fJD1bmGm?og3FG?ECvAW1L;dT`rV0vs8^R2EG+Wq|HKZe~sfUp+mpCBD3z>OjQe zX+>>pfa4?K2F#%_6Vk@!Ub@VoChhl|_tFKl1TsS{bluBeO}PdAH{@py3)L68oH4pn9o$WW?vQdDPC)a9kX_!2sF+G8WyZjaC+HrRA~qRc1lq01av zq5WtNUAjFzbluC}!uC|srfD0o)ong+>%+^{XTYeTspz&6zn`8?Z6hR@h(2j<8(;wu zZ>Btj{l=JjQqa3Cj?m0Y((nx`NX~GPLGq4$j*bmG4CbOrB%YiT^<`#_;v@fQHo(0`tkD+|{ z%i?gC5A%SNu1wdi>oSLgFBcijg58KNiT3r{gstqhcHIiyDBWUn=rmw2wz!=s^8$0o zLj5#ol#LNh`sVZNKoX3wy1r9lj|a6=VE>If{(-^wP5za~=SsZCEb~I}f`z0rFm8Wy z@;9aNE0VvN6~9y$?HJz8-CKf9Bl%gFCPviYAykl~7d7-EfX?%SayYDn5;(Yp5;(49 zK3fk*E^%%?8)Y7Ilo`xjOpS4{i;448sU&aAuS}UMq9(ma_CCt7x5RkTrnFrAhs9j_WI3i7MQV39vh*hcx#UqxY> zX!T9cmOVn02mLQR1RnoJMy^G4t_Y+bEkpX+{Pcg3OV5#dn$4yB@S->{Qm5$P$U6&1 zt}v9(^gMtl>D}T$dJi2;?=7et=>Z?@S77Ys>FTaIliHU?3e)|y|@TE8E-Q)Cdb3(nsoB_vo`+&Y#uc+6BqnrV|4yEzn&j0~YWa(?r9EI(}BKSKd8=?8{oIKcqJGMJaabUSyX2b;H8+heByYjU!s zTuIVg$zQ?}Xu4cVLrI>BYD^5kK)WRKm_EegjLsX$wSHnQb~^#r!_BawbTwpZNau0V zy#;QBi9xd*3vwdnalVm3hppnYP2(Ce{aXw_#%WLf2>=*=U&-kaHrJR2SDw>8HpAJT zezS(w@m{Kda=bI`{!C2d=MNR;Ur?C;oULGK0N{0YYAiy|{+a>bpW}Vh!eh8%$=nVyI_Zv*e}uU1j!gDW+zzCorUHH8L8#z zl@%S1U35-C%R^%;IRkkvd_KSjjVX=w!#L3Zy%dfxKuHQKdI(}Lzz^nH2l^-)UuX#} zZ7~?J4!~f9eGcqxWrOksJtQY#&^$DCcW+UTfK2Wq~1 zqxG53=IM{rLCer3cZJkJv#ZivU1iQO-()Uzo2%U>bhE2H=2Glec}=KZSNqJR0duwA zTpBc22h61*b9IHe6nTQ?(rR;c$Xr@$uC6qf)}a)QmGi9!ZF@f&XF-)nyDZuxRhqr9 zd#W;f_o1nsj{upyY0x@sia@nAE9yOj#uA;qM~KeeqeN$K1|~yL9l}_M=!AW6V-V&@ zv_-OLe?&thoO+r7K@cS*fecOfI*&s02U8=E1B{GF0&Ny(0Y*kNy0Z<7_KIQAJ~1qM zPz;M6$u}&Dd`6H-!y?&pow>c{^V9%zxl>R?>Ix7t zz_E!E^X+`jwK2DtUwFj)!Yk$%Fc^Vh2n2-L1co6&F~5Mp2+S{FFapC6 z7>vL$q*@F^YQ-=F1|tnL7zu}sLV-VjERf-Wo`)v6(U~LZV{x)li!t?V=egQY}ct-=qGhC6_|(# zq*jFL+WTEqX!Q$5Ybazr#c_x&3^nI?MM!yKNYK>YUx@_PHxLEOfH6NH;T{QgH>|}r z7@R1c!D{e0mU9ZDHtd<^F%B*hWsQjS2esBgv&^F6kgIU}taD>xZsz4<7oSA%DL0N^ zJkRWhkHt|AbeKNuc~r(gw^)+4suxf^*PhiVpc?SCM`Qh~2-;Nu4HdQ~$3no(7oHas z{R2Kg(eQejNOE}!81yNS#>7>}&Hhno_3?~#{CARopoXy9uVHkKXIx_L&-^6wD;vU6 zu!P3}&WWD@F)bP(0Mfq)WwrP3B`lvtX*Fk(V-<_yq0p9hp#fFN^ekYjstd5anXuuw zORQTxVYvY|!rBnlT#wY7k3+}afF9MSUOu)%^T@)*nx<#yn;-doPT$;RQ}!z>oPCbM zdFj6clG8XUbOxhfovRBvX&`Fa`Z(avGdIj>pmQeMpw}2WD&pxdyFEC4V|IJHV9AiW zWvzN*e9jSCQQZ1lG%eyuee-@!1W#BWrGFN8yKZoEr>n!meXS*4Zfo7(<5pLPAA4JB zNdVhh>V^vLaCHQY?y$n-v9w8H{)ix1i|VK{x?9x^)yDFu>ZmokSEw7X9oC~duu-;Z zHMZu|_>C*`h0QF!MsoPV?B3DUeGbml%f=n2bpujM*V;;YwZYhP3tdC1*yg@=(vDK?X-kI7l#*{jl zM*M<9o<3W9s;74 zhKy~8B&yQbF(FY^#yv!zT3T&vJ1SAN#*U0c)fx8?KDD&L*mhE)!p4qg>A;!FA6kDE z!L~-7y|yi6X`n~&vqRHae)b2B&lRnxtYCaT){Ki}V9Tt>XnbyW&W?KgoDrLykCW*_ z`yLgp%Nxz2zZgWjtoy;Zldy3H{Jqb886FB7_;)b;k%Aq|efNK;c)w82!}O<7N_Jlm zO4v7Jt<04Nr#Y;hsqn)pxFQs_uH1;G+1xsW)5@wd5L0ax?W!|qY$Gr#;*WM&3*&If z;hKDa^?1j5Hq2ZP`#{&*OWs?Wcmqi5((R2U?_FGyxFos1H2GNRa60j9P5M;XRq14A zdTTmyt#EnOQfK-@*jjd{pk72h0F1z4`%ASqO+UT2gv*wNX}x$s`%xdy`gGS!FWOQU zZ&Awv_1}xn155I)E17=U*b-37e1#zpy8MM9ezmNkFr-4wW&t>A_%tl!irS|lIHv5! zVR>wSvhFB^28s$mpce?V?!^^L*-RQA$;nlYum#%~Oo9v3XJpHLX^H-=r~pMJgsCDI;M2+#hJ^1=Om zb};;+Z|={vx6ib?(4!SVmHB)(Tp@Lb7ax6sX$3?qNQU@pz+V`DP55ht8Wxr)z-kM1 zI^2o@%E`F74-*MM#-Vis_lp7$CTZFbAmZS!Kr(btMT|)i1~Llb2=XdP3S%fUqJ_ms zB|?IhN>5LBW2OkTt0%2xyP(H~VUC!(d~j>O4 z2PkMdw6)@cE}vK|CVt^`-?e(@U66zMFb1>+)FI*t@+A>)+uw7zKSyp3D}d9_{S6#K z0`Jb2@pIF%NgO$4BcF8s)xQ-!@iD2;olX3(UEvCd;P`9DPo-4oY{9GqR|kK25Aubr ziRUO9g0q4C-+~^f_}`dzbQR~?j#7d|x(^V;v0617S{oIv#kX8q{*94U zKz>_auUA}eFA)PZncbawaIv04y zKYSJq$)Nk5QCocK6M?PM=&sCAd0#RjSJmUp2Tse6^~p0=_y0p%UCgb%o#yt7Vl@cw1Cg6>!7ktx@#^ zLRMI7G3Js7qsz;;-h$Q0skaK%*JnYWuRc+|u4Em~Mjf)=iXB0KpH>-lVCysS*_hyr z8+eLxm0mx+YslsmHvJ4vp>@Qg?r*3BFhCs?4u?Q4F4O9evDgb68uG^=`6F6L4q1i2 zS7eK%aOwFhTda6xwiwJ~iy>ekk1diPf^k;i6SU&rnx5`IjYj0l3=5J6*LDf7>`+tI zhaeMbIRR65n^!0g93~3b17tHnwn~Non9qRq*fZ19*j-6Oy>I>C-yHh51jVgw2Zq1a zQKm6`SI7(OtTCG1tBqY}0ReAic=D6^`1NAi_1k3`%~9)9+2Y>7&gw@|AWzJQ22OR2 zvJ5a*9a59d56krr>&*3!NBT#41xy2-!j|CgRrL!eA@s|C&UAI`A;IEFw8&hJAqDM^3CehFz5Fn0Ndr*D>!ykz~f{c%{=ZZ)gWL z+hAfOcvN?Vh#YiD{TAIN2qE$?;eoxtVo&gGz0rV&4;nRY@R{Rm-75G~vazPU?+ye}2L{o@zOl{puo zblT1GRC1lsOa)SdvCNNuB@~bGsWtoogL7PRWvBfQitTS7Ojwjq%i(NXuX~nf@Epz50*vw z4JFZP5Fd7t(b9*|3L^>w)Ai=IBdn`uz}$9J9C33SXBb)!ZX;_lcMbA)o7ezo1Hx^x zrN3(g;bP?>>!l+5MTj!c$Up`?^C@Y3{8II4wkm%TRG(&x3qn3cG-F#f1#viqO_YeY zJ+)-OI@O3!oVaeAf^IkAWl)A)afMp!#tPzl(?<$)G#C$<`TGY^mUZN0Zv4I<#EZ+K z*17OsX?9&kq+nSQf*28nd|>?^qYAP?)cQ3XID>1oeG64-TD%+ttQKdbAq@P3%^xW- zmU#-qN@qMm`s0{`w8$=4w-#!7uzmc6tVMZXL3s9BBd81`#0#w}nvO|S1g@wx`qAt_ zK;^!|%5}xX;zo8pq^`qZ3B#ei@abb+7_-~kTFvJ%Ol|Pj45xFaL%j`P1n2^h+nM zI&;lSgFk`$<(GbZ)$}#Ltw}@djBTBcw>cIOZ#m&GL1Lt`ofS}Lzp1^OlkSJ#hG{}m z;*-|fg782Mjpe+Gz%W>~*G9W2hGMdK1eI!`>E&C_Wl)a1REq(bqYv&+U=E@VvD4SF z{aymzHTbhq;$VBEq5RPoMw&u>HEC(bFSl=Je0N@RezWyV`S2AmB!J*KT#heD0iX{6 zB~c(NkiuaTnNn!}zRK!;1idFVXDh_2jHg`Eu)aIMjVB&%q4k-*)*{SfC5O$&^nE%9 z^gTPPLQVQ2AGzZqTqz$eWpP9y6K4O~CuZ0+wPx5krI0%@V?K`yy>JNTFzP*!!LBRS zAmK{ftPu|3RzTkC1lmK269;6|nzW3QG05XXnsnx|4EAn$0s_Zio&Jx;J*pOf}Ow-i%gr5sREhELNOQz@c||9gNQ0*6@geEWl?%?BO-)CC^sVq8M+$Q zXH|wp0&&K^J}H3IeiTDN1gXgiP>gT{%t$E4rz6N5P>k>hFf%~pCuMP!s96M{+C`}D z3z31K41Q2>fXP7cD5`=^PWZ#2pw8TY1R*#PS;^0Es0ts9kAYZfxzD=jVcGKmDbduE zEe17#fO!8&9zE&nP=ub)%YL~w1-++(o7nH@$e@!iM^kbND-68<0W^jE)FPVV(W@*? zff~-GDUgk(sdXSEF_yB)5rpiJ7O3nT(n7u&NXyTjbBG4+Adt9^J;1{vIl>{+K&K4i zV-pT=o`%on@HwKXLIGTmB6S|opyXQg%OM>hlSYw1?l@^sk=iDBDy_wmyE!eaOdxkA#3i;dG-1_)fvWu zKU~}11!0nGIky-1mmBlgvgXI&z2)E7IfY`AlP!ZkE$u3Q^j8kI#ZuUg>qboZqrZ^} z|+r%EqSQXVq{i?cIKzA0QTJc^Z2}!V1R^A`mKdl%IDAHv@TOC2}OjbPqWk z8Gx-mE{ilHLr4Jm_pn;Q6Vinc7__$jvn{U#U;BEOC>+FNpI|sn8*$J;A62xcFpWbKu8$;A z8UD48=aDPrOQOwktlH*64&GZr`_Ti~rCgXESqxXUH)TCU5%)_sqmVX_(HJO*b=FIW zN>2LFySK7;?-cw|*RR|VkbQ>OECOaXPylh=uPs9n-~ke#^frcQAc{`+Qc=fGMabAm zTTx2=C<9rIA)1MzTciwRHFmlYzygdR+KVC)@5%BSJ83kkH$oivs87t2Ujk&WwK3=y zxN$8H@&c{w4oB;I_xI7^G8Sm>fbJ80c>qam!qCd$e@C*tC3#vyjo%K_TKJ5EV)x#X zQ>ECFp;FeJ{DnL2Oa7uXarwv)EjRa;+}L|yWQaz#;@o}!{&VoZ@abHSxxdQd4nx*U zZgS5~fJA*GBWsC(T?70@Gj9v4=AhAch}WLt@ppIwU)UeCEh8|?c9nnL;Uu}6)ZKIetV~PBql+*#Bebll|{T~ zL`7PPYSsMs+^AZ-Vj7V39#}WMt^~fI_UZDni3LnGKN`PCE$-1N)MBXqb6uFNA1Wrj z?m%5pZXD`1=i=wH-R*UGaRe0c+4B-=WG_a#l5_h84$!zmYJ^i6SeIMrv-=U49)q!Q zRp3Uj*dTMr_jv4;Z?-%WwRLiXHHDHQWR&C-`0&a@3~g)U*P%`=(#~h`IhaPhxWFmb zaW+;_K1&81`pT&Mv1PZ1k%JV8nAvXsASIRal*d(;#LKNxMK$XDXC!_Wk>SEgfV?#M zN?|-ibtd zq2I#&W-81*oN1%hjo|3G;cyvv8H{lJyxVXYtRcNdSA}ZvQDF%yNhm5?0>&~}Lsl4F zSj7j8Ww3?}8C|uirII>I*al(mgZfEWN_K^5DcRLTOUW*|LaVuzGxL@G`7^U7*qPw# zB8FQ45O-&A+afn}V5UiBZh&=j69|=a69~0)69`6{+yrW&paEmeTKHhYR|g-=H5=fo zQ!Qcm0A3SXj)1UgX+Z#BY*h#0r>E800zEl~ADWBSz#NtKq*WNpJ{Sa3e1RD%`U}~aZg-S&=%uu;*gdHl@7PM)_l?gNq<~0Pc4CWOCFb(GQ1FNfujd_1{E;e%f zCjUNAYC2+J+du%+3Q((25N@Syf)kclEw}{=#~Qe%!L1E|y%pyh5LJhux|#`aAL|P@ z>Dn~DWrGGnf@5?5oS2uxK9?_89+5Eq3#W|OjUJ*0h1#1DMv{w)?m#+_*ywyP`0s*loK^TIoO}zAmONJ z=Jgpp94y$|GMek)>*F8+pvE$GSHIDWtpV%@wA%Xj_|<2&kt5GaT&{N5x@a=VFgI_i z!6lJtGl}S6Ul9?Ze}!KkvYCVU$SmY^$O|ajz!ZMV_Z<3nOyQSCX$!wXY~f)w z2V3~Hr2D0PVhi6dw(tXD3m+F-_)R4FPYt4W68)!!#ZhT~Y6Olor2Ha|HS_qlnG;m& z*0+wJ7oM~p+=~y8$woi*;asDg{2&~e^&o@Gd}@qvRfK-b%HaCO0LtK;pPx5IcIgCK zh}Kv5oHO0#=Kpib-&0)v{W>69{sRT&bAfp7bbf*IUmg5~YY1rt*AQ~N*KLgKAq;tI z2z@UR5MYeV3?Au46sx9O^arjoB-$~a<`jhCI8{Ck{UPZoe^9~4f{@?F>@FurPYm`i z%Xj1tJH4KN4Y}yD02XGk)3QpY0uA2&s2S!HGm+6Ri@rV!WaRNlstLt2yEEe6@UW79twn)=GGtz(zf7W_xn*EdYBjG>)4H6@V-KmrD=oJBktuv)r4XrLcS~YQ0Y4p(BI^ zyNw?$O0Vt5?ezxELB%gJFPua9Go>d~uO+=~NzQbk-FeDCeSz}x@aaW+x%{&} z2Q7%u2Fg39zuyV0XZzbvJrMFy?Zp>HM9(62ich^E*X>!FM0pqJ{{e!DPKxu({@)~y z!u}^bSiFP&^b#-r7SbO$6potDwD7qIrX~*gW%*;i{K^T-nJ5>Cpu&mbGmqog3}%Xd zdOMn7&lF$o&CL|LNg6-N$@Mw;qd%{_^5HL$V^Uc712kVVspnqNFfpr`12!rkysSc( z&;SAQ4ON#}mE5mB56u_AqSEUiH`iddz}fJ?s{A(E{a=9ZPWvJl3}N1_!OqIyT_68)Rm!@_hIXZAcuw_f39_HZ!- z4<-SUk8`tz2FtlwL&L?1nwlyN7^TBP$e{U=lASwC0;b$qk|iE=`Gx6MU2WH5B&iH8ret}s>(2+v&1sy3ZPtcJ< z9c|(0S5QR`Luelcr_*znvSj;i8zB2MlJ*wPP8nrNHhsyBA`X2K zT|!@ix8j>y^!+x}mN*yOX!*ctf0n)$gL_3uP7wK_G^xO-&;%?4hO-zo&uEG8HUg4W zc2c)}7?wO)1la9)P4nYE*jzaU{2}>0*Z`a%f6$qq(Z9Ha)1IHRt;1#q`2l%0+bhjO zyVyMN2qJ*dA?voh(j9ro>EDIOJSs##DkMKD^gaq>MqK_NoiZwv#KQ0hWKt`4#tk8(qtaMXr6P|wUF}b8@}&|J)>Q|lryueHvFf)>i-XYm6!cwP`P0U$obQQ! z@;hQ)_#M%%8)laPe4P&;kQdo+%+EfP{Oqve{LXmK@|PlhCqU%$JAo#T-w9Azen)C> zMw^U>S1MnC%r9S{%`0Dk&X!L)D=r@>XoDbHb&h_e%MLLfyzGvL>&kcmPonPJ`**HMzFZm)BwwBtzs$VT zLPqBz2y7Si$^|_9rL8~9&&`)_BpW{e-8qHx*ZJsKuHMO~H%_3SgsdV#?&HbQ8*k;w zb>xM|n-q=lCM8lnk)n#^8}xtn_)&ar@}Wa$e^!gn_U8ECMOb@Bt$mQN!OG;{Oz~6K z6vxA`bV&Dy*SQ_Y%iDp3AENwR{A_(ekW;+99)#*E>UGY$AU)8|AlL}ZaGpZSvv0M} zmg5k-?ms!tG2@l`x;@!j?$4j!zV#mZ{VQKVzYlt0ed!sW1jwt;c@n?@H4nepTk&Uv zhxkK#D{$%PO!44%SbUj48Mezxv`DUSp$7#hoUM!aX{$@A2@DCE#X=pJz$eHKV1tu; z;(7PW;Od=~d9p46MtWTz6$a4dLT6clTH2+jjsO+B3XrO5xq_!)Yb@Jd!I#|7}NCqtFXq%CiBH_9B0PO?w_PiW>>3QWU939@nBK+sl*JD3@1o zK(CFn*Sv-UsMh(Va_3^4f*#QL9GavFkF#Xe_OTEfWE-a&lSy?nb%W2^qa51^cs@%H z%r4p)vyC1sDJ9vh@Sp=4l%hr{Ia$Y>6OsqHrLfj`P%LQ?QfcbDq2Ua|Nns7nfVrdv zAD_9T2_KKSBz(Lg)Di1H!s`co|1ad)WCkhKk^R3YJ%d{(njZ518J_fctmNSee6c%G zk>JLJY~?&KCr)?ZjASW6q+cTaN@PDG?UyQgDYSr$F$35Cfz#LVrU+fjF{z5gr|T!93*_(I*VLUr`0B~dgbu5MV1#%)p^ zecXvqOIE>5YY;`K8`i`6W|*!HS~71|OZpLYmqgJ_lH#)J*lcL)Mzti4SYlr(W$tsFb)Mc%X!@AW$V)9LR;IuBvFt z8wY4r)-nU6l6(Mh$;p0TYpt2QA0D`R%;X;JikeBAI9kjk<$J`2%_N%vR~=}_&QcAh zorlE5W*|WUacNs7g!+(xFv979lhCm{s^0}{|05PS$+7h~=!j6<3HT7F%j8k?SaNaz z8T!%Tx+4KDCp?4!T+V@T0bI_CZ~y`{|NlTy-@u9!a3e@9q2Sn}Zt$xP+HMiIYDon;pS`4RpuME@Xb1atuf)<4k^Dp5 zKx4>#bal{d@ezrwMQmDP>kvC8u?>hlB(Y({PDpGMVvk5{3u2E-EN(qvb^?J|4MA9S zGzpR2(24<&rLZL}=aScdY!gm|t2(n+A>QW3!I0Squul9kIk^^?>cL+$M=vdK+VmEN z(?mj$2q;fq3d9#RJdmy7{%j3<>>Bp6hM#?tHPotn1Nfnzi= zy{!-N4&qiKX>dZf7RCis0>@xo0G@stRikBASZenbY*{+)|aiTy~)vhHzY>Hu-JNvVEVpwmp2+qbaz;9nF5Z!&9Q~411aYs z>=N9{lN_U!pbxqeZGkS@v>-pxAsZ2-`c!t6n=)PaSWdTvur% z&RR>FL8(*5Ul_Nzjaxj%EnedmpK*)dxFulRQeoT@G;ZOYW+>A6 zoSVH?TXQ1C10LnaJS$wrIuG)3juKM@}xDZ zSBeCU_XP?RxF{bF8A4=E>C0JXipSka?!o;h-Lo3K#n<;t%#=RAJfRodaHLN2d~Kt9 zaMJ=;w=xq~skx_SrLJ==YVqD&20MmRZg2z_W8;(ctKIQAsS*QgL_E6z@*jHyarf zXF*uw5f70H4M96DN4vvgz41-(wM}6JcHy+%StOnZIt97wDzewIIP62#3%tSU^=e-Q z0I=J)^ABy`Cfs;m|hBE5y4BF;4z8A8o;PWq3ZseNO+V*P0&0 zG1tglgUj;820>_SUjKvO_0tQYJ`e9A%XEWx8+i8l2lLSQc|tg4>UPhmD=?W!2Df_o z)LfbpZ}QY1NZda7Nf7(Z?!+w2gJ3E+;=8EFohUOlxlheo^d_uk-+yYBu|>iIiEEfT z?p9Bp@~b&4O?@vhH#Z)T8CSVp?*{W*w9=EZb$H34VLFm$j0VgJZ^>h{BlMq*xM$Wuph*_($|Nu|SoK^!VEk zHX_HDvpKdm`*Aj7WVIioR~Ko`MdYbXQj0&Y797w33{EAN*`>ZoS9S%91=#BM>JtL{ z)+XNQu~5e@K;mrO|IEpO<>acq$gbIkDn`}-C1-IxC!f3_CL1Z$V0OS<>~10c+I&!o zqUXQ?am_SXA|xgE;Oz@=0O*Cp)j&iH_bmRJ-10pg*(<;Xx1jq^#D9Y1M%c|?GK;E$^f9K(0tNT!~h`oy7&ks<=>QZ>CO zmSwg39aih`N@ge=-4hd0je_ihfn?sVeSF>n9Q}72pY0^b{~N~Vt)#`*G(N|(IdX#} zJ3ilu$Qj1xXPM`97@tpYZku_0R-=m7X?!kVEq@xHGmOuo`Bq)Rv2S=1VZ7-@&XRb8 z%~8rlz6UE21_Sx-@&lN5gi~CqF0YSy67!5p?2!2L#wD6d*z$q>dIaQ!|Kj@cL<8Y( zuOxrG*7PV(G&dfZpH1}=ri5k>Zk)1yiWy10CwD8PObNe_{{(YOp40<=o6kAKwPTim1k&!VzuukjV%j$#Cy4xn$ojcJerYzuSnSEVrPa9~)R#=> z#H9)G=Yhcbu@mFrt=hW(XR7~V){m1UIRLKrBoIiU#0~^FS$D2_%om04?I3NCi?R(#PcbB%Y3p#@emhk%fC}IeYZH@=g?#2Q9%Xmv`;O1?3fv_KAtt z>fb)UV|!ra$`PL}u)k7%^3MycZ~xf*C|~Pqlbbv)V9GZh@nN;*!a<@IcgMPt?x?jA zhox~-xn8hiI>$#M=xSQX@C3OP((G=OSR$Ag+)aboAXpg^+_&vxjxgSIK735yj z?7ew~otqcLb9%epy@)F=+)fE#QBEx;Q!b@D;#c5e$;SoYTMG(8E84A_3W5kQE>WMd zK1p2|);i_c=AG03uL7S}Qqq0(`1}P#&0IS#gwJ$A$|8JzO_m!Mz~`OCx%2Va$vg31 zB|aAw6eRe3TR~73pC2G)T^OHG{T7gT7QfIic66&vTnn>9zgjZVHU(MfgWmccubL|V z%zpTX4?}%|cO+1VXk(sLKZdpjp^E?l=(~PV;?3>2A-U7r9>t=2fW(QLBWE9Mc9*Dw zLh&9~DstAE%|$<6z`>=wH+b9YulL^kR=k4)ns1jkIgq!Od3|e)%M0>m*Y9hLHz5KF zGP_WR{Zl?8e}d+vd+0aV&AECbEJOVEsu!Ar{v)DrYNa;e4*y~U8k*&9=wKG6>mP@N zbeo%x6f8o(sW;X|eDRCL4e{+#a;0hI8>O<2H}##s*N`V(4RV6EpSzcu_Cs zYPHc_e|h=V{q%jpQ`hWIR2*FFFM)2Rgt6^z+?^_b|1k1B7leN&k^ngk z0Dr#P=rP`npYwxXPVkp`*cQ*JQsZ*tde8YAu+gL5Pb9y8#s-w30YC>pzXIs^3!nqg zUydFn#$s;v%J2qs|7x{_Y8PsV>efIhh1)L*-y8n#F&=K>!2Q#B_|tg!|9w1+V?^h_ z?}tOlD-oa$+Ox(C@&A|~54(-&tMg`wK0~|X zzsA0pE&eCwD_{M5u&nHQ;6LoeIM&#Cm19=;udWaHdIuE?4!+=r4Oh;O`N@_)x6dD$ zVSnY7;*0TF?0=>FuM}U1J#9}q>01}jx6szWq|jae7~V)&#?2D^fVcA3lWKv1XVi}! z$hqnYC?~q*$uBG#Fx_Jx__Wb^c6z}WX5Ie&dYECfosNx1PET>8{|8Ivs?+Uet}&)HS<JjP*MW&*e=&XhvGk|mecwO*(?89<<%*O$JS)xr zupW63yihm)9jUgnF>plN(KGKmB0MF>Qle1Gz9_LwYqC)pk67WIw(;4F ziOVJ5*+;+itLVA}jgU({uNR6udh)S8KjeHHoG zaqLHP7H4YzcsKyBP z7%f#sOUP)c#1nf?k03u^D8u$Xo+Vro>gulFU%vITs2GIrN!7YwoOx<4Mlu%mv`$#(Da?=J3NXCdLLQxHC7Xa8M_TQ#*dVk&7NCRtu`1NHz8q1n^p0 zq_W&niTys?9C+;hRztL$&t*aSGy<%ngL&iOhw#|xYYr#(v6tNCpWA=kHTw^!!Di9aMF*ZHl_%Du9(lBV zhVlE__|g7xS3XArym;^LL$CHd&If?pnltuzT67Cvlv^ICe=_eyxmyCT^)3wYspbB{ z5WiYpQ5aH@`rG@_tw!(U!N}QCYKgGhkxE#wKz-9zWlKE{k}7CH7Bx0Dgb>yS(=l`MQRE;63+7EFaFeoy}p* zXd9YSQqY_)5=b^jEU!|x>-Kn%gWR?tY0vlV_DIs1+LYr88oqrw_+V{)E^v3S9zvF_ z=QQvZq%ke=He)^9PjS0+`6GW&`J+cK)QWUrnwv(h|-wANXf}?;9krC- z&W!5hE9tna(p&=b_oH|~9gpgmOJKZr49}?JNgZd#qg%{J?CfieONApGK$kFI{3{X@`s{ekk~9cVQk3xfS1et-}Ddhq)-npdi2 zwIeGRXtV~5<`&6?*}H}p4kAaBk)pio=0n~`z z$thDhPhypDQkrNioP!OPNoZV-g0kL)!f4;qnuMr6#095U`1MU1hf)@XagXiJYr zc<+ZR)4l%3C(n$P-8Ro$y59Qo6?ot#@`$-KZtk~lwZ45Wo|3hNWneY4a==O7IAGl( z>C?J{)~yvR8<9zyhXCIfr`q=N&CDln($B^r21>c<{=59_yo$UG1cn*#b{$_qdmoMl z@|`3n!x&+2_UC0FH$;Yc@~+;AL_zX(ZoER}^!Z#ZDyS)xH;wo5XvTxb2=%#_Ivf{r zM3L(@i728Qz}W`HUj$g*JfUA?TZcVm4L&6uqd8z*k)MOh3FNp6kEp6HycE04*N$iT zVr!j+4%GpSEgz|zkakKh89HQ|Fx8+7{Ng7oOny5tB;3$jCZj=-h+6!%Tg zW9?ahCGWC2qj3|f>pY_7kd-XP#k5T+;S{j!1&^41+CKd=-W)_WSNv^gmN`fsX7!>7 zToDR<%#Ze;#OvfdS7v=upYocKi#P+r{=VK1*{&(z-g^Cx``MP&;S62}9&p@xxP15k zXiA^B593({ymgPuJoNc}oJaF2Y0kBHe+i+U=zXC$kvOZOUQao6;40G86Qf%=l8MSKutZ^ttv?*H*qBpG!?xK4;y>5*Tbnk56m5wdZMXiYpr)@ z5Z8)pIX)1}N&rr(_>WX`pD@!t@>MuJQ@f7Nvo7=l=@4kaI=mcSLi9_k0JQ7})YV%59>qTOs

|TKV4jR3prXrc>;a-G z%#*zQK(2|Xw7$-o zp9jNG_Ylnbdn8*c5twKMibb9NMw~H{qY;UBIry;y;%81>1=&kd`RX@$67!HWtWOG^ zR#Wv`;)%PJMnIe2C_#sM*T}@>%T6^iqGpy?Fu}kOpi5O7iL@GU8;LPB0E>CA5t-QM z0*~%C5{I_Rg`)}Y);lwRzkGNSzqqV~zuWl>(FI30>i2&F*A=CqlQmCbk!|*#RBb0s z%sg#rAh7pY)%mQ6NvLy-KD>KqVe*O`g2j{BJE__rKupkv{B}>xnRy3!c5-swQ>loB z`SitD>YF5!E~C@3W_wYqum1BCc@j6J?y#&l77fuZEFi3PS0PRwpfe&zI6JxwiaQy7 zn?=~2NzUjRse%%%FiJ>mt3V}K5T0xB%o2boGPy`ZEc>oSK;fgYkB zGwQW<7|}qUZiZ=Q#y8}{x}3;!p835deK7J!iA>yj20sfE7&XH6udGl($fs4ayXLTw z0IH7|ZO?+K?07%tq_)XZ7uowUP*~jVQJ-fE>OW7KD^Fd;5+eJcSNGMmJrbW6wQkxV zDuJ5AYH6KR+P2SV+iSEvs9IVvPkzFSsd83g60;@DB7Ca7I;Jkce2&#?x_tN>D3sP# zao%9hSZ}KBD-uh=%UTrKTfXBnNW)8^_9^#}8s*?qBOGIDAJus^I1G1m>(g6$Q!O|I zO}R7Ed5m*WWBlUgq5WxOuK(5Me>?R)Tq6d{5{%WxYV>FncZ+qKC-D$F`u7-a8%srm z$kHZd+A%Yd!8KuK?}RnycQ~<*`%SDc;Hd{ZcWnfpJDyRIXOodhUl=HiJX;?*QhwKW zvCebmX2H-U#1PaxO#@nKGQ*rza6|<1Ebf0n|6ZG0qCou5inL=<@RGW|FQQ4XIPe1F z2OcJ$c)iE5_Tt;N~+TFO@l4q?)&z-*j{4Vh6&_!}Yvxtr7^sy$#>=B0P>T zPJUIOjw*=3Y#LfP$dA6O1z4g;m|FEspY@KvcDgETw-Wrs1rhHu6j`y(8|wf@#=by| zptGMr*}*()u=6f+I?rwcFxCNzXo*_Kfyik-a{ypd-s)7m(OtfEA^RM+0O0V}0o)fs zCg$|?>KhTj{=-GE56_kznw+kK{I@1uDDb@!zkb#6J$jQMn?C1Pxp@%1FHT_{r z^C2Hr`D%K*@4fRAZy0_$e(CmT$$MiZiA$2{(&YZq;inVN)}&9BU6oE|rm-jYXL2PH z-f%E+MHSvxN#WWk*bSqTHPal<#}FXc@aFW*_!vf7$yB5D?t2d4Itxb$bf;7u@IWFL*Dozu?7K!v2C!_7|S` zs5EAL3E46a*d1Jg-n_wUVCjFmTlN>ch5HNk{S|r~NPDo~J>Km3KkU5^d{pOo=PSWT zj2+n;J8~i?N}_C(EsbeQ14(glS_v~kI0$Ai64*h2TieT~>8|aF*s;M26_aps7#H`V zl@~d+jdm+KmL*LLji>knTCL(8>wv4i#l_%O;9L!~OoA_snQUNEjz= z_wK!y4?1(s`@ZKr@1N&=-sk_T_lxwNogd{$aS`h=quaU4pv2AwL&d&hk;EVq_9JYH z?)?d|N0AN5?|e8f(cpV*e&Z9ui+3Pj+||oEe|!0&*$4dN_UBN2X@8FXMEx1PhWQhcQ9Yy`A}*9_WVTsO8`=DH(z3Bi`1zT{=v7F_y>Or8mQF&@YZYh z561R6auL4OO#V0hOuIji&@?f{+xTM5qg9Vo-2QN)BqGACWu4!Hpg$@zgto-OZ*-#=Y@hu>j)|L5St z*WOpueo&Uh8xV%t6yNkQl)t}17zM{q-~O<}CtsWbpM1t6R#qwbY5HM{XCz4$Rts12 zf2F|N`oEe#YS9w1=OPzS)>A3Gz`tD$UWg`C0x~5J!f*IKrecrY_(-LH-NN|iD-9oT zkXydoWP9Jq@lPB+N#$bq85Wv6JQ|<0h8cko1`!M2+%i`u;mKO#?wLckog{Qp<~7zg zxer&(yx>eS-uk;gz!-xi{^?EMS^pXJZbBwA86Uduv;J?63_yNkgJBdkWS(KTqQ%bb zmH6{6dm+|8HU6aj>F~kNTnl|gGa}Qwt5aBWYaN8<$35Tbf2!{vK*ghPPX4|Y%cS@f zQ^!l1Ic~(=6!DS(`+U5lTMO}$xGRd6#CBa2FNwU#@scP|6ffyNNx(IAJRsH!D0Ax< zsiUDCFP)XK)wic7lL!{c!;uJ+`x}k-RP@=j^Ic4zUF&=|zQ>JsSMRMQO(=O_KHkp% z`|Z~EtwKO3DbNQ)Ujb&XKzi1MA9Gv`Y@9T`Dt&cGQ)5Rx} z|DEEWP^2h65l>gdCjz+ML3|>sA&O7Ln}zs9q<{a)_(Y@@#V1-Mapu+HpHSgX5ud2` zD=oDc1Kg~j&-#bpGZ~azCv}1f5KOYPgMBIH}w@4|3vH6 z)xOE%6V+}aK2hy-@rm?l#V3lseOtbsiBA)s$iDm#Sv%G7o-Qt7rTO1!m@RW}aLe`}CAmmOPA7pZSCqHZb6vfA& z@~Pre{N(X5RDV(Yi)rfr$>U#GbP5egUjOCdRWKwI@hZrU;#FuIOEK8w@sRyIaFb8*dA_&2MzpT@uWD(#=z zzj?amU&X)q$_@Y`dH?1qI`Fps&9zhdH`iY2-~3AMc7a^p9})OLcCYx3Q~Og*_9t}s zrnAUD6a3`-Gr1}FX9_>&{WFE#O8hg)H2+L8uJF%%r|p?yJrwPiSMyIy;GbCg6Zj{7 z5*?N}5BKlw+B>;@lb?CNxV_H0a%%sw+DQC;b^o&8B|c-mas0w*miRYK@%^jL|K#`8 z-#*|aEDz$G8`2mHuG7I$jdA_PuVANzkL5DL8^JGzWx;@ZXMlHt7F1GKyg|(Iusm$C z!-~Q~Bgeymkwe2rNBY9IT+8u3X^e3aQ{xmR6r(=+MsLuf15MmFiutFrD5oATE+uUss5%_N{zF%;(;Z>aJ;-zjRf&Dsv$m2-kk%Lbx`2;jBj! z{H9f5?6=6~%JH~;BgZtDO`I4C)-}|+b-e@+&XR%&@MTr4GBeLs}e7xn$rPe?Al_lsx>lxY2)pSSe&(u z%Z<485*BBu2`66FAldlaIV+eiy^3{&xd~11dsB~J*SR(A{^t{`GvBX%Y8Epfqm2p; zCbC%CV%@#%5K1z+>&&1_*mXRd3XU_li(*`6Fwi!@3{a>ciU!Jt`QvVw=V%m<(XSy5 z>M_P-h>G;M1;dUZf72rmoy>!s?< z2Ozdi2dBf+L6Hd&bE38%aBbJL9I)0O@V6ZFlL!1w&s+U3s`dZA0Ik#66EUpVFEZru z)liT>%>8o=UKAvsBML4L70#BZ_WxKQh!K$#CSL1>FnIxbJM0Yxyta#B@*;;!g_v%Q z>-o~ip%D8m@yhvJd9Mn_ECkMh02F)>!TMwG! zocOjLa4Udv0Uj}oOog#s?Lp_$3IE|`tNkyoN(D<>!h2f6*ukLl&k|wmK+ySIVx-S4 z;b7_FFufQc3;Xz11WO}74DV^oox{3qeUa1_c$a_NACRmg+}aoSSI(v9=-&IAV?;$R z3)@@zn+?^3ef`a{04@uT^*0;Jq35r-6;0*q8mV?-p+s|da0j&&AxMYcSQ7~j-376m z-GES6RDSC9we8#vzUwSjbc$k_YY*EF_-}%FD#Nzt(S22gZ3n})7qhLqI5#mJwjIs3 zZ4KK7S2FQf@?yt}MK=Bb_nwedGW$?gO&1Q19e=>Mrdzjo4 zJg~#9;^E_(%e~=%W^y?n+*?w$ ze%^_7xua(|10av++<>|H>#x)O*0c-xkt?` zop0?dHs6}~YnX59H%kt8xD`_@yO5&!jrO}XT526AY5%Vlwg2$atF`}s+%#4DFBIDE zS_{0=>URXMv`4#kj(r8@^6ket?r*1#=M{w(Z9fbnH+HX^p z?saoB8iwGWm_3-vp|j$khCVufQ1&5xHi{Zw{G&O07dsYw=;mrBhY}*-CdpZQGcZdG z?-2=lPaL}79u`lrN%M5$HOCY=^?v(oe|dwK9QB*$U^wi*2P;{OinTN1(5W8d-WlCr zD%=N241&*K5gmY9levI>@O+SVeSV@6R5c3fOFQV~`54c~f}3necs$PIaZzf=!`gKe z2CLhupMYcwN8jOJFTjJSN-EIBZM{BZAT2fYyc_C=GHWzgQlDMP~?U#z**9_l#k+Pjo9>3XFx9OydKxFWnaP(8!p z%F*h~c@ewGOA8Ng+2C){G0!u5v#a{6Zrc*St>aR*^Oe{;?JZsHdL^MRB|aUl{iO@~ zltyjSpRNZVndw0l_AA3EKXioTkyxAm8!_*54KcB-cL<(^-#~6at?uM1tc9At51Q-k z9@joFQgefVx8qdUJX`SF5wP)w>vUXIrC!RvK2cTho2-XXU!Z>q+z>p#OK1F6Y~cgp zhX<%E=R+kp{J^eo4*ZZO&fbPzxu*6*m#7Ogv$-Kfv{tny! zX}M`PK87$6qjTX8@CwcCfRJXSE(n>5j?QuHC3Dm&Tq<%)JQ+#~TzSA^qxcc>Mmoe3 z0YYqarQSLTf32$TVC8qv&`Wk66GnbSnYKVqaAw3O#37@pz@Irc1fA_uEF?aNO;*XX ztXzud&FSDP;WrVJ6ij)+Z<_5doda(MqX1ig^rLIMqWa6nsONG+CNZ-dCa#De0Qaz) zjDpb%_)YnaIf#gcY04CcUgclBvJU0DdK?O!t_-?cw&)*<_=ky!>1D~?<{tyCTUhll`cm42Pk3Mv{Asa9X^9UGFKK5(+nKAYmBT{si zHabOFOOzEGYmBmDW1oz&VqxL-OU)g>7e}8$0}@M|6&&R+E3r> zuiVI~bFZ>|u}S0bxs6y&@Fdoysth}~yJ%6Gzcu{*8h@YT@ALd^;qS}*J;mQ1{{DCV z{*b?X|K1)R#3VM&hR=gO=4%a`QMjf?ms(xwb!pILcDQB^Lk=4{ z9ek{vU_Mc`Nb(R?qJ0sN++91|7dFQNE^+^%xg&?XO$Wlp+5SWG!#BK5&xef-{zD6N zd(ix_)O>cQM)iPGnRyrr8`TN+E~Jhmsvl)Sj&H~*xnnRG43mSwtH>5!Hg5ZHfH!LL zRFrZYDZ=rfbvQf`v<4$Di~B@^kvssreJ8)cJOi-t@{QmGm;gVPY5Uh`dOXAw; z*K=f>o3i}Uw%_UI@291Yh2xc4)7nC0fn{5dU!=>@uY)jxM zw2d+$-?Rjfh`kY3Dj@>?lK{UHH39t0z^TBcM8JO}9ApKI@B{E8$IAnMm=xrH&p|%J z9rhC1A^09+>p@{f!-*uHrGWQw>p2$B5k$g>Q^VuyqbXuW5b3xXVLE7ty~qm_v(S2J z-ih4DzL{SE6Xto-_(OJ``)T13U<6u#$919v*iF=nkpU-YH^I{&y9IK|Lx`qo z!^6lV52Nmo@Cn?A$Pz&nw20W#!iJLvr0Cb}ClO8sOT3{s4c$gB3TYO-qw*Luav1!g zM1WG3C0ia`&!8=;50=LHjr1XlYRFY`gG;7X(~m5wjnZdF=_TB=*(>I9AA=UoIhj}3 zj>M|Qe94#a%w+-LwVxVIg_YUlF39c_lg0{3p5a$QW|mMg$GybP9S5}g9Fyo{P+1Ko zjP7_Io_%>74qOyjI zf_ecTWS6SyP=LsVeXDuCiLg6P1+05EH9TG~B4y1k{uFG=c(C|WLt$OH`eyq--K$ma z=+j=;r)+X7-%Cx?uf0?DYu^<8+8=g2OTW+-E{}zkf#9q;j&e@FHB!};1YX2lgDz5e*s7QsK1_M>CUuE{ZA5$>cagA+pEW-Bd=!V|A6Q{E2iJ zvkU+}6fB9cH3v)BpyPgjVG!b&l)sr<@H1MktW=h1drjD`mD)bBQu`|dE!lyvy%GIG zxUMB^Ul!4&?WwSRRk&_VxNhw$%^S*C(~xqzrypzkbgDZHik*J!{kr*!>5&hC%c^{8D3?K))Fz^=n~9kuHjyPnh4QMtEVu#_0z z2|rM)>n#^8W!$cp>{@2&al2O8HDT8pyVh})W@DG1Y6;tRhi$m{g>BE6c4IF(jC~ue z#cliHq}!fF6@n7PDL~MENU70w6fENuAH+gpNrGbvS`T4>xva_{xoh5ua2?C9SK`oh zjU)%jec`%Q+F+Hk*isH^2H0hk(y}hMXfVIeLFr8g-z^j4k8)WI)QsT6{+%@;s^#E5 zP8nbyh_4Rr!>Lb@fM#u&y$fV?XJYusFuN4n23pZrz@CE~74_(`0A)RY$IzTL&KBlA z0%@&Z=w_oR1|3l?;<|*cR`3IrI~rm7Uc?eSzG%99aE1^n1_nZ&L`G<=0hcx=Y&A62 zT54m1zF8mXW*cXZi0nv?290yWMq$9l1yI~a0zy6{Uo@(m`=hQl?uKyB&!+~YLZ_b! zPX?`nBd>^XKLX9J+J?`HxHf@QpI2!_i=qw$!m@#4=xLUZUBpFy_5!M1IjR|>UO*TCURST~(F{94d@MPc%%Ykxg& zHO6(^w1k@`+JtO|zS6pdw9?O863SXrC%!seeG91XZXLP`t5nB^j{a&Tx)&<0gidGL zMRXxehP^@HNnojqFtlOY1-MzsEXu+=Ye0Tb3S-of~JbcZ#X>+xV#(=0tRC@ zogU%AR=U&(AVdn9QHKhKjsZ5V(Z`&Wh z5F7^>JNEjmd%YWhP44&kZTmPE$P6I-st+w-9t zHN!dW=~LS0Wt$X5V&dO)>&^5gch+i4!V2cjI*-tA_bh8ch=M2u@b=P+?+WipVb`PQ zyBoZwwO_e&c6iUK9ZmPM*8Dr`!+X|v=?!7dYueb~6stv8I`6dyz4QZN`k^r08Q$|4 zRBC#&-?R}EyvCN!kF;!B7p4;->>&HuJ+)!FPN945sb`-H?`a5|mW?e?zA|gVbaDQ& zv06*Em!q6z_9pN1ChyM<({qq)2JoFt4FQ79rr7~9&WJE_yF7C};HBrv2E_z&P4hJo z5@L91xGzx!Ueg^BZf%H`NPl??5PtR#{;x=?dT^3{gvcZ%;|=`FE<|c)<8cq}Q`b~|egX;g7teV-hmbF#=r?G^Wta?3J6)auTA*;N! z<+oMM*n(Bxr6YE|NUQ{SdLM5!{Rv?Lv48zvWmI(cw@*qjQG?b0(#ENN(8|5eJfToO z2qhf3tVT5pJ${PvB(g9(Jb zCcK3stnpj0w3)p+c(7I$cg+vM7BzQ{AYytfkE)D^YhD&Kw}711b|3Il^WoMI2#0Sd zxKCq)355OB9q?0L>p?_qZKyaRb%?^$8RrHkXX0Quwh4`hc($S7M3@|M+?44;VAzld zqaZnI5@GI#MfjYdW$;$P$RU-I2|%u30x&!wWr1`lSQE$);7kA|BY7%xjAY2@OpGBT zp6o35D7idZ6toxaIg%l-&a-95M_!Re16t>V6ClHzIhvM3WK)HF=)AM~RZE z%Kf(&3!7(zD5l&0>O@sGC0OXtkZ3GZu>eH`7OgIfA!aA-r$aOoA|We4N!nOM#7&rq zSt=V1sosUXY}dZ6rq+y{&D}~Oghig%t127m%T-KH%cF#; z$7kC)1+DmR{{8oRK7|y34kq!l4=;UWG_`+lSlp*JAjE$TW z%9fT4hW$D)45>}=k>iMN!xQPSXETOpD-nnNDMi@GNWu_KAzF}tJj+y&MCjW@#|6+m2Wxovs;4lExDiFGF-mp z+`NI?`fvL$Uh598^%;NpVy|uYZLj)IZtVK+1VP-@Ie$gxM^|jRH`Dv%OfR_ut=+;y zTPi#(T9IA`F7+9N0%N~0QDThn{81J!TkzxW_mx*~-)2b<;N1c~7>jcV%`-IW_<%z2q~w|M3G#CHMHP+Z72cmRgMn zIVIzOJw9SF?u~hURi%pR`ntWf({Fu7)uqy7o3gEY)AreYR+P^iDE`b-lU|@bPgv4a zU$)Px$!m(2X0O1dQbtcrYMnp(Cn9eWmT*goxf)(PGoZu zJ%2>ml~NGdruvfod+PO&x2ODzzrtGb3VX`EY#IH{je@q^FfrqSTKj*cJtbC`Iy&H| zH>=DqU+s^a|MG#t`4`i*|9^FRFxj)BKdr)+1sXQXK}*I@#zBjs({98s0nQ3RhCd7D z7`9m36$UT=uX1K!)?pM%;8pY-^ejYwqq83|k``@2-43Hxe+qbbNA`86zAL!@hqtdg zc!|YT)Z>G4$Sr!N{}{7<=!}W9g<&EBo`LdFVJTW4D$@f^cw&tcAPiJra@4xeQ z_~FHGTmydSudJu-BWFM=^tMdw>xG}t2HWt%f4a4R9}aG&=A!Y7`d4vn{rihI-mZVc zL)X;59r^yrNdKql-@!9e_U99@={8>uK1^6gWM{Y!h!BVor{Xv9O)o@BQ7rwBq91-! z3=!70==k$|m~5=y=O03;9^M=8IE<|DnDPf?#vKlbCW}f~b^$#b|BVqBMtLSNbl)$3 zyU&;N_4o1dJkzJT?~P6R{QE>yLitU6UPZ3<{kZiXe)O8ZA8~;8{kXm#qa9KA?fVoc z`aVT0zoh?2WbC{3l9Rv9=>N>Mj6Q4g>f@hZyfh=i513=8y87zV?YS|ig03S~#?`UL zugL#&Y5sKUoda6@ClWqJ2$wRKtDpKBV9ZtoDn?n?damo07^kiyDn_w}jt$Xu^QDq8 zKaxKtqhaJNlrYN?D$6H8=I_Vyz=Z8G+IWP~b?_fZsu*Z1P=Ge<(J*&Nj(RhkeVNo% zi{dCoFdN5cd_{HUi_pa-ZiLhFuW#3%_(Xp|p_jtu@vHU+VfmH)!Qz^Jo|(QsIoWXh z)Apx9Q&g!Q;c@1)7O*fS)6;y(M21XH`*_Lxj0QJqdOqN$=M2+h1t#VP;qp73AK(#7 zT|(^E#;&JTp`T* z;2K<^`r?mSp_wYwbts0P*urmC@7%y9OqcM257wOy7JhR}Gwp{^D#t684xkR0d9V06_J{9VAB7uEr0zzf9Z%=}@J3+@X=M?0 zaNcU!B}R~=xIV(j^*74jr-*@KygSbR?hOV0pub$DCm00sh2ae6DMtsuZlt(QnkXD9 zoS!^?C(Bc^O+}TFA5J>2cQQZAGXd_~zxEe$c=^GP*M*CpE)md>A1>ZDk>qdNTGAhX z+xF6D&YLand4Ji0TPF37{#Q-g|LNAh;#J_nAT4uH?U!SWfWrYslCp+CYBkcg63YZE zSA*p;kz|oO$J$wtbUb5YxMtxS>B<2Kd{-qG(7&`yXA6d33z#ti3WVcIds z(I^}~c>o!qK5jaUn18oyL5mu4?Y5!T5Lvs*D0G%=x^PEmJw;u3BcNvJLkTqtG_@oa z8Hp{yh=bIdottdM49(ncN^FEL3puoo>G7_;??YXO&Fv@y7^nYe)@I)6${wW zacS&>)N}gVM4UYxq)rE`5#Fa)=31_2qr5u-j=?Tb-?+1CoyL~waU+X*5}TMljV)@) z^C%+SG=Wx#`PMal!=txW|LNB4zg(Wczv?l5q`F_4I4tCVe0tHG%cH+Z>Vy0}%wJjA z48#O8W|Ys+O%bx;cB=CwiqC_X4jxME^EQ}$UW&ekP4{D;x617EHU{^tDN)N|V&qIK zn^FNf{HFT@Ob?ni22E?&o&Uj&D;diF58CH_^!Exxn5}7l+s;yaJ_)~1wLcxLoPK|r z8Xg3d=Qg&l1~jX`{18r-faks@1JCXJfE1y~CeCRo;2`tSyZfCx<5OE?_FxVqYpRNB z6rokt)t6!|NqY-DbqrfxnHr&z7fx)d0oa-n!3o{h3N3_9@-1kRmjMnqVbg4osFII4 z6K3f-tP(zZcx1EwyT`l%n zQSP4dFj9j+M9~`&g2io*oPtO30QIn5Q&&8AdW>`ArALgxMwzpr6Kv$fHuTnh-)*mi z=grwmeh-hAcfO~kv#N!Nnm?HLTK37yW7qNX2so3{i@QfdsM~PyOGDfbO)Y=;w8>MJ zbq}FDmjq(f3q!>```Q;qgJrzG_9gSgW+y~_d&c?`IbIg^#`tZ1}kfRukJ*Nl55iu-)3O0?1vD(hrR$6E@)|bS*a_v@} zppLz+`TJzga1jkN6}_sqaTjp+z+QFuMDV~q_4?J3H`Mb1wP_G4;{;xAeXJ0R6&7@$ z>}LnB&iu8aU;*@asmMiz77Z)>2lipfb7xN|(}+!}oc>HyW;UHziD`FLd+tl9CrzkW z1U(LOO;PxssS4u<%c&G`e`RB;9XD-g>jHnZtNv5viAVVRWl+%#{8e!Op(3ID}vT1t4ae*p{4>Nd{G%35cH1k;{ER20KTwaKhj|Il7M3 z4gPcs9PA09JDGXi%C{clFT>wdbjH-Hz{}kd71mrF?1DqlYkfxI+iL?5*&gS?Mz855X9Az9I{TMwZnTjWU34wV zxfTU05@R*tlDT8miWXq1Qw1ka9`RS#dn_f4hWh-~4eSP5(1-lhc&4%&1o)}0o&(>a zUEvMC9mO|ghI9+Vaa6@+2POAyig)z-?Krj8T4Cm0TDUi)kfXzlfjOBBN?LBtt1Rs! z#ss`9Uk@ByS^q=fFmjbpm%>{DQS|JB^j{SVY#9ZcbPc-}=w zlV9S|{^U6DQ8qSnm(c$KWw$+_U0i|D@j(Sv%dNzd@6|B*yfa-1dsAZRa3?%~p|Dnr z`I*DYnK}AamJv)Ko`YiexNqR6V>{c@WdFn+VaLwgt>Bl?Ok(KwjbTP6Gu**=9ttxu zo6&=8s;E%Tt@&DOsdW(kkzJE7L@tlO(p(KEzf9;E^XU`2Bqy5jaB&47dlVQ}->|b@ zDR;$JBYaSs%}De?FH`#B^!?j4W&d_h(Z4-m$1}OxSvIn3H@8Ib#c*--r%%l91YfK~ z^@v3DzYP)NTH>lGb9}FL)bYmJgxXyH0=6QI@cp(UqG9Bkeoj3N9?F=I&=sBC4#(&(k<{XAe`Z39ABKy6w_dxV{hV??fxiO5#c$UKN?e=8`8&&9?r;8< zzOXD_&3)JIj&c`rr~NF64HIrQM|C5kYg~R9Htuqj1##Pp1H9)R z^`7A?y~P`2Kg)m|eN+4LIMS^nW|>JOlUxIXG)9~g44m+YGh2|zJL|i<3mimM*{)M} z5+@^Bp4bMOyPNxezN9?y@3}84uc(+YqoQJFMXZ8D)-V4JdEevjZ~2>QAIR^j@Eceg zcbwhuH77FCvvXT?Hpn8eZ$Uem%ah*m(_zQik5l?|&~f%-(Odc2Og~Bd-DDz|D+6uw z_Li%;>7R*@i@G`5u_6OueGloO3c?Ru;dEjYAnbzb=#{p%w2o3=bG3r{iv0^%+l)$dl84fh^C9pr}HUEy2#{va1k#wA5YNL;fzd&gJ9*q;%SD_hqU zRDCU5^Sfc}>1^yvLDe?AO-9akl*EVAc%ZLm{0+Szg%#UnPS5aNmbO%$&@f&uQJ4iJ zvs7ZYCZQL;ty?E~s|}jwYYv(g6y~u=IZcBn$FpOP>I)Od@^g+UTBBn)kwTyhP{f2a zbvsfCuchX}{>Iv}@*S!75rAb!OTE`JI}+LY+hjU8A;0Y}-!oa>S&-){NAlb=PfyP; zYdi?8Cl@mX4Wo%;)4m;x#i1Sl@`f8a2QiI1y)O+|-I@jSUb{pI_oZJ%<~zQtE* z-{klGre!7HckP!ccu5y-@=^&MHJGaL({zRS_W&WoSXT-SYVU!P_6eAync*aDLX=l^ z9lP9Lra}w~&o_E4#S(9Wr}%@A+7D*ZH+Ci-^cyQ5%rsSXRvswtU^#xgS64x<`)_K0P527d>gPo)C+Rp3L=H=5*M3#%iw%nzANlEq#%eKIocyJtNg$u7rsp zD{1fbh4#j;(%u3*X%IADyMOzt@Kh+TFRT3hS1Et+!sW8E2R~e%Z()D=(aG&C)TbQP zS96v6qz@^sVT$@Rs%>_}U=`7Jh@Pb=&-f112LH?tA*e_}P;wgq@)De88T>OzO~Ts( zau&y9!LOOX{>~bA6MWO8F4g`Yu__^GTmb3n?RxY(YL$KZbrT=MHN796_uF}Sa_lsx zAtO^>A_*%s$8S3t{F(?Ip&q*l-t8nhs|xjYu5oEtk6UFyeiMqF4vn8q)(7u5W_MFf z&_P&3eHq!4iG)6bSq~AX=k@C5+o!)R>AGTP6&}O-Dt(@$AInbCzhx)cleTXhWfW-LuU~*H zTD}a|%Jy2nu?~G7UcT$jer~L!d4uh%H`cz#Uii3Pz#Akc)u5b6xsTh;leHT!QE4tg z2{JR6J3r_**Q)ZH?)E8ipexi1DdDI>&q{UKjMA*l)9A<8G1Rk$fJUIe|^OEHmTNo<-p^>-FHTnPpHd#IOzkK_FUxJ6v zdkvz-&XIr6d#H-N)Y#`ZDl?vM>@T|mkbp{IV->x)2DvYfR~~lBd}=)`tuY7Qj?1I= zOgvLFKB|?W5wZ9liw7cjoDkG@zEKkw{qkC!}8rU$M9k2OTL6FhDk z1=$0Mk`3RP5K2BBf_NXl$A9{ab_K*fo-1-73$vMg9^sYzk`T%vpF5IwNg71RL3{H2r>Z1^o(rBDyz^#3$#gzu3FWpEquvGROHoVBKH2N`uv2Z&n!-J}#5@C%un& zsfO1f9UToEGs+Pc0(cFqR~eTpsbixy6Uu2cYICu@(_++SN}T?(FtrM{CAG$Dc_>V+ z-Oie`ttmX4lPOam));{vZG7PLZ$?qoINlus*BtGxrlQYJ{qgu{-0=~ z|D~h45(MTZNgOT@ZSSq#t{_N|fc}m_-K>t^jk>^pN)OUuY#Qe<&3Uh*Jn)b&c3i-8 z62n|hF!ArIHGsWaZY(Z*hJRt<-*u+_^&BC7!NLV+TVKpw_ySW)j7QwIV?3w34%My9 z=M%?0`JzpE$1n)+J^qZ(mYHMs3g)p^O7sQnsGnJXpxjVmfyI~%Pt)Ot1+s5u>m+dT zoBnTG{}g0Fa`)`&gm$)1S7nnIQVQF+d|8$VYyHU!HnLkT(sQAs+H7)&#a^Tmar4LE zg4PvmJjVbbGT$MaE=5@<;`lBbJ;DelHF_G)6}56Ryue~!*`FMxWEo%5kBwn|_&5c; zH9s~QyXQACc7$gk-~=MO;cW87RCYyOT1}P_Lt*%gPD$9u>Fl9xo?p!JRx%Pf!~@QJqR9E z{)M9QpSW82$^5q9Uy(@T+1L)D h^?Iotp&&IaGh0JU!5(MSPV-kO__vMXLm5udg z6@{PxdG3YD=fPhgFZe0Xm%qHURNgST{-}S(-_73ze^=$#9rWsUF-YG0gBE_IghR0M zv3alI%lQF%C9^~Slehn3=MC86#TE{2zKE+(tjs@TKXh?2e{e;AC-v_g=T9--#q&G4 zzmwbJ=0_1<7QV8jj*TN*^INMkuXV=#FIM??eA)ZuMhq7hzOwn8ixfRrLQK)Wf0*}) zCsR4om_cJbi=brk_pSoZM<>CvJx}icwbYl4UweC8|FcyYVW7_@(z#Ec5mOV-Z$vIX zm0N7dSDt5l#ryA?`u*5d-xu62?BDWH=I`!o&7pQCki!fCylCFs{y=LgYa$IvkhLa`IZ z$_cm8NuwMiO89X_3CFB}FyY4(I(*F>R6T?XU(-OSaBZs-n*#k$OW=+&z_qPKO1s9{ z`VhZ-5G4dHQL4o2QhiGH&S=CP>rSF%@2bEWU8q1s^=ge(BQ-9)a1|iQ7fazPAkl>P ziJ*l5YA8PR9t}_;T~`&RRy18ey{n-E!zMS}Ev5}1xtVtQw0wCrtRj*XZ z7@`VDjVdsZyUSpJL<5-@_4(_pB}!KtEPanUNG;4+)J3bL;~5%4aoS^`($~ z#$)=9po$KYd!${(Mw|HECy-(g&&)^Mlzb6cMx>7=IpI{Qh&5zghZ6R<2UI&HYzJl| zG81!lCIemzT51`LNOvF&a?WC^03v%mEI|5%EU@@wUh0nAwnwL& zdc$W^&;R7}=a_uf6)Z&1GP^x&Y{1@@#RtbB^hRj5M%EK+kwjs?Mph^*kThlYNiuhw zrF+=WDnGfAztbwEN?s85Q?OB$;zeOFG?e@)lOna2bV4hfCyJ@WNle8!lpk#; zO9rg;bxOvpEtJAOE_uK#aK`H3b@;_^q zecOdhP~|6g3ZEn~H8~R|w*oatKXYw=@?#7=2%N{xqo{eD0*QH!Vq)823eVV$;=G`s z#uw>t8E@*|<&3~~>weG3K#AhFIwPN2?9jv9MNrLfFEI}JK*s^U^#GsnaJ_uqC(<9w zqMc=N(8pxP;^`3&iJ*vBI^flLrGmUd=TbWR!=_D`w9_1_Y&$@R#kS}Dq@n=(gjMQz z(QkW^xH>MD1T3t@=G)Pk2Ygf|k)ozLb1#C))4|y`{e;4=ZxJQdh=58uW+4~{6oL`e z&ARxy+?^AZuwu~8ISp6-y!iZcq(u~exw(kHWWOxPm-70q25OwnAIH%%hy-BaX7>7v z`x=XP-#k5^=kJiWOl-}zp2Ldmd>yK1&jF^Rzf3xk@O=N)=S8paFWhsG>jX*TH+4=t zP=ouvF^`*7xt_O4<^xL_I+oPFlfO)jKXg8G!m2Kr!+-^&uR$DD6+tw4(UzwyA;(7qqiy5W+dqL- zPuJIF#WkZkBS*CFe>vb`W_9NMviC8G2#yjro&GighSLF}j?>=;oRDwgDUn^0lH*Jg zKto-;>_mbOta)#AKI{{Ze6|9Ep1c1Y5syAq#vGP) zKHl|Ys;slqU!Fq7ld9j?K-pI+EDw22K6MnpEPC8i`nba$3&N*Z+3DBKZO*yQq0>T~ zRfY7;N#Ok7m3R-aR?!OvlE7W;X^&RWIukO^{D>xJ;w0@)Zxwh@Z{H?TYo&QK}TQT2z%G$Pif%jEw z+mbuHFBO^=N$So%v7tobcJ?<)pJ~73X>FwDMde?3oAL$xoVSPA zew7z!&xFA(0TbJ*Fmtm2gs>B7%*#*a{;-Q7mQ6x!f8|yU?o*@8hxb*1nfDhto12b9 zQB2=!sPCSl`gXoueP?f5Upk)eFMW>2vx3z8cy|Bh+mGkB)=iXu;A-Wy|LviX`8C4# z3gat1i3F$(SE*pS{iMFXd@t1!`BsMTs2b)!j~mOe$_e0q=F%<-On z#%|_%Pw%yx`QFp}>}G-Y^nTqm-hnRM+x9H?;n$8T0W`|c07sfOUbjk4g6toWTpf1Zq!!^k22patau2QergfQfKAkK@QL0%9x% z-TrqRZ5ZB@U~3EeqWC9;{0j0nu|1C~`Y&qrLIF(vqADuj`Znbh8hD_v8_hELp?sGD zwERs`%*Bb+kuaC>V};&HEOHm&e=$h28o=giLh9-U#PbK*+lV_yMp&wqA$0G}G( zww>Du)iAM-F!HE^!`}AbdjIoP;iA~;;F$lza8Vp{AAdz`sY-Cl;X-SG#<7f$HtQ$HYlnwG`1nBkap{7ASqsI8@$f0* zP;l!p{$;n{x*I~q%gWjujswZTaeQrT!>73&dk8h?WzG7$?HK|Jo)y>z8`QK7YTAYt z7&Q2?uyHQzmzqZ09Gy&IO>4BKHCodeuV~s{4gjY;p4q$uh4?CnS;{wUA4h}Jo~UVL zkJ3KH>5ihk?1~A{s;fe*t(lhqkU1V2C;h9Iyfl{6AsX%F@c6`*|pNS4iLsNmCf$O5Iy%{f{*!Gi#A}; z;b#OEUUrB~KP%Aix=-o;JzgfzFYHZrST_XB>+aP}dzF__AVn{GlAyasy}mv3D3c@J3?{5YbXwij_Kk$Dti7zI^IBuiJnW^$Jt+MK!Y_v!Z`KbG7b{vJboFhaCvxovXKpVZQNplatR?^@g}{+!E0e^}OtGnpBW!dxmp*^5Sd_-qOulW?lAnW^Ek zDC1!@Ns4&Q?}^v^Zt$A{JGDH@Pas4aK#(QeB*qLek0G4zlBay_h=&ci=Xd#I$T4)7 z0jC4fBKSvC2grxUwVxl;K`_W^`8^YwWR~3_4?R$PIN@k{ygE&XGb<>>OfGOQQ>8UAJ)%fk_xUXxEBUP7GT&?4D;lvCxx@1zK12I) z{t|JDWEI%$g*FXt1UUVcJN(8*&RgYRNuajZd4=I$mf$d^NnT+MOtAdXqD32pWxha9 zfF%WHzR0b-!+OS*W}-jhksv0dTKTah_v>!9VTYa?`tIa}Dzn{TgycR~dCxhkVVB~W zAYjbDzgM{;eR{NiSPRXA4QQpcLJvLKdjMda?oFM-C#uu3a``&JDi+o!Oj4n;PRO_J zI006)EOmmPo_YVq=yjn&_G}U>M+=W#Q>}_fTAI<9?9e*|s;9z?+T>*$ zxcY=y=Kz=N9J|LCdpL}?!g{43sbcwO)OjyE-^vUN6J#`4UiWM(GXy^HvUgZ6%k7-k zJ=fBycCjUrvcOUx#g^$NtBLWt8!eG{S6QNgV0Vk|yETbkcCDq`9^Rd@bWLR1>t3S= z&XcZ9Wix81J5|~MyX%hy#Hk3(GriT`&*3F~_D?u#y=%O#`pZ@3`?{l%3Lt?TjWZrz$&^ZP%( z?7Jo3(z(>js*}|_hRF@j_q`Dddu;Twn%S{R-+ujWhaomk#7{V2!4sZ`(IX^r>p|l5 z=bK=QG9#MDK#dg%u(3kk(GoPr%${plfYIWLmfyqzGBJJ0@^cH+N(gO9+|J_DB8=+T zTU5dp)XTQkcDS|1gDo{%zH(MAtU?|v$CkZYpH!*!$%ArE+Cc2EHOG34vrn=X3(FAH z!@A@|qt>lp8;hV6C9(c`(UW;uL8eobdEHuUk)rIpm({|VSe@1ctxIbqT#r_v^+k)% zx~(-=W>whMi<%9>qT5z2KB8G$aCj+p&!>#M6tlMAIH#c7)+PG1tgSW^V_)$d zO^&6kK-ujnU4JYP)h?t$TAnMkJm1a!i79)wX@-|ohx2Z$5y-z9F5Xq|ciT`nM6GA7 zpRh+Tt2LzGi8a(hGgQ1ijp?>d(O^Lt)^f}0_c@dvw*|i`<-a#{$i0a+Ssjb0R8!*6 zfZe$*mZgb#t97cRTAMaot;?tuZxG0Dn>K&Z8g^x?ESpU+p3!*a{^(h%cioBYD(Z*B z2Bke&cJ-cwmFh|3+f=ejD8CwCAyT)f#XMYK_$bmlddcAHeHcVJhRQP#W>&YqB#p$*(YMmstUkuBvWuT=R@XG22Aq3#b8qC$9&W;(XSiznfW8egTD`sjyl)fgRzoxu zi{{R><=5mE+rkzCian+}+zi-kylzwJ&w{8-B}U%#Up2j`8+GU%O&FB78|>HkEHvi? zH=u(gc+qV`>A9if9DR);F_5|Fq)h|l0 zl59_QjU4WCo3R5OJ*lAUH%~9)`34218H08bxzuw&`L+eT;_y5f!t;a|Xb`fmN8<(} zpADYRsH}nv^gN`OmrukMj ztD2eIz5I+ENr%V$qx$w?QLcPf$yDj0o8|N+?-iF3#>EiLV zv+WY7b+uOFO^7NMZMZzd(tK*M5*%v)w{w5idJ7?SftpjUPH76=nq;$$MPT750)s5= zxuQ31I2p&HZPSLcm@ao68dBpORj#?U4%v(`jKj*yJg=+!+yQqh0wi-VN`7abvypb6 z6S&#uCZMfAc}?sWappoV@uQ|1evAeQnr*{n8|uri-EO}`{8XzLc%!f|ZZd(g3nbLl zJJvNGum19X0Kxeg5wBpg_qrtzcB?fQ>$AmJa|YT&6J`l-*)Vk5K$%R;!T#kTWN90y zC0B8tnJ>)S9odZ9%e?6gepH5e%VzIa;o9c%vgp0PPnHw1)WSD8#kt!D&WFdkz8FJ! z{PaI`L)ZRIp`qSZRSuZNCO22DoM`U9q>WK?mvcKt3_uGf8j06_|L!93+O8E(mM8w4 zzYF~Rmu=;VCBH@d1paR7Do?cXS2oN2E1xmb!Vg7{_M=cl)A8GJ76#HNISD2>CsNzk z@2mNGHmAWnmfIh%-Yz||zudeU>cx6vgEJ2bZ=aXhOwTa;*m5pxlgI%p?F~`EhSls# zJ8IccGXqclYWA@WaJf2fhj_gDz)(0#E4xky+W0FLrzKX+uE6(V#gM_{w*Fo|db#z* zO(*;<=a%q=g)ix2oA(x)s5V!`rfl=f>g{8EnH@fP?)K(*^;4&L8k?!_FHiV)Y;#KT z`s&OJq!*fGgCK;FZ({+~@8|p+qW4}fXdq}l<7Le>a2G&^7u;&h?WOP0I`+7GQ_N4SKYubK| z@KxL?$7|YS*t6E&TBEBmr?lC%cI>n2Z$cS%bwr;8DI#1#BlUEuS5F;}bir-Mjze=@ z7@JKEm}zlv%GR)LyPAz`rFA=ISO}kyJmYLav~wJGBG(tZNbqL4kV!ClI+1$3&?a_(nc$#%FL3+gbiV4 zv7Tj5V_Z;}Y0vW`1c^eBn16RIZ6 ze2y9?%vCUW8I-|G&_>8VdA5@PdF{y$$sy%~*#2ktHL$kIZvSBC z^^F`qvGCC5i(@nV@r7?~d9NIR*6zCG&&Sc_pd4K~cU{6y=8Cg+)r7bE{!g2)_T&8J z&-MS(Kja+$hq%G<{}3KS{*WC15ApMzcm3eCnYwu^F>lCkn%nY`p@r$#Bk``II$j%( z8~PO6)ir*(^ANjqkci^L1tYzx)pCf8SN>zp_1l z{hWDjOw*n#%m4X1EdQ0OmM`@87LBJH(H46H7nmtVv{1e<{_nd=>AbyQ@qGRDfve5e z)bvHyxj7gYhfQW>zG1Ef1DM|)Y?vQ5EeJL&AfV9a6VhKODn1xx)Za839`ZguVxbAo z_?yn)Km*5xDhY#w^WGzI>0maU^B$?npLm{5up+t^&P%Qx9v~cYnB-Ur%;+#D zz>sdDcAa5~Y9%tZj%v=w^K&8PkP=QhDc!-O3-}pXiH3U`!Qc;mcn8)8nQB*y}(buF?TYM?YA9M1Do4xrk?E zozPvQLK$5ZY0hovCncG!hjDM7&Ek4hZvI%gg=Xmq_m>z3b3e*IV$H7lGcc zmjqF_a7=fmgVB|OGWV~1nY~MZzkIIg+Jfc8xQ`_H3m6;Z3zz_d5{{_Md{Q80mMe$O zoT5Hyz7|}> zte6kgbu3t+uM8-Oi`9KpY1BQWM84=!gD4d>x*8>raCe$_&i*y14SQ^gvsz%R%Wj)i zH^nb$Who8^2ZLs_dOh2%W@ld*Wew$S(}9>vVlKy#>PSabk1Mv)e)8CWib+a@swZ`ZIn@8L*f<<@&4idP$Ew2ax}yz-e!n*M1#4@hIn^0#CxLDy+uQ8 zYL@&E+ptDMY(snAPsd?k7)<{q!y0}@!@Lst#1$j_FHcb_j|qH(V87d-#EI`S>Yo3C z^&_LV{g(tToOhfZ;uoVJU0(=veQB7hFigecm0`ECBj__MP(U!sG@#7rE%YYTT9WCK zRat1Oz9iF<2fge8vV+&X?&taSUO1@hOD}SD-@jhas3Oqe-wv#hBiR`ky%k@Gt7+ntm>xYCF>hce2?+un)hvOI`w^&`gv4O>m*#8zv&h|=N1-ni?Vui128MC_ z32`_56l?%tqDMW5hAp|wYt`F-)o}0J;68%k&JXUJFN2!<79hL4jz1SX(9Vo;YYkNm zgBSLB))aXw9u9Eflxl2g13x`QNO-_AR3HyG9i^Q2eRF_glYnIx9SXY*avZ*0GS|y! z#MPc9)}AHS9z2L9dE43A*LUBh4Hs2gvHu+k3i{v~=&8vLSLo_x$JHJO$<`kK=4@+_ z_A-}9M-IvrnAR+Cu*8RMW5|yr#y;RbmN*^E`+nx~lQ*|hJoia9u;zt7*!;JeMn@<( z8o-X>YWcUobR@m{USnQ*^yM_KZUeO2`;)}2&iMQ6J< z;Ub15Ra1<-sDfJkQb7%FVU~y~mbo1Eh@Z9!VrqvZ`Rea%etV4TrxuL~01{PY!hWNv zR3VW;3olK$FghzlG`F}hvJDz(#QEWog7ySvfAVC^v-6(Z%ZAXn;^PR$CO#~Nov5li z0_V(#EDC2v&X>pbA^8j_BJa$IlN|IlW}eA?&IHVB-S3;RD)JayW3aqvJ>Z)KD%yDe zdN28`Z`+9iE}0zBnWti@#i?4YTb=bML3XvgZU6BX)mS9O2U+c114Jom{SF~a`je+D z+SMiu&0=b1V6MFUpA951nTxljB`9$qK-)s}`d zriQsD)=w#M4NEW`JjdBJi{M$Ov{xzqROT6$mVakWyHA|1tT3T$i!!@((-ed5PDJ=O zyN9H%ap>hx`0LKP2mMSvSkVde`ZHU(H|MWKF(aEF{K%oOXRZLoC}$&^Ev<}#5!rkw zBAX9A=viIaj4(8M4>c;=-OF_)M09p8H~pFUR3=Kb-%82pE%)OGu<7cyR%cYm8GfYO z+gGoI-ghrveoZuVr5f#&UPeRLsAUliZK`bDmmHr^QT>(Q<7@s4+DzmxXoAebMpgZl zKO~p%;KqG~Z>62}3;VBXmczY*j}L;x^(?DB+ie{+YG15tFskUYW%N+wY_9%U1|3nN z;&d62Z=ysYH1#dF?dQq~dIut#cOv(}JMv&bEJl})_mXJV&lHs!UpP_fb)#V^5!E)Y zsNB#*xopZ&v@cu-svW`Hr@2k+qjur?fD*k$uJEYve<>Ks?vDbSxs8~jYWjfe%zuh1k-l%l2;^*{x zScKFoj*fcSkx@PF>%Gb$Td0;#8)+MT+6df3rSxsjepNT|^@sF0@%2u7R2iKT(DRrc zyEof4W!F`9U1Qg^x{8dBi_WfPbe7E4DON_)?Z{`w!X=o%oQfQM>HiAb&v!=o)uPLV zla(WMX>^LjqEt)cg{GI_rf@W>pl7$eNVY0AKCRDH$t%^O`xDT?)L9RlGcIuro2$gk z;d`|9lo3;gFgW9>77exs)!7$PTI+BS2^bIR5{HLWcB?Ow8)wjhH?rTh9W5dCILj7X zntjmsF=yTqwjn*=$&(ql;N6SIfafIq9lQOu-C;&OaAq!fhX9H9LmUFyo(T=+oUse` zq&T6684Zx8}<^EBIN>n%6D# z>vwDJ9H#fPyDWXP($jH2`#g^;{qAS@L0!Ve?+I88pW`v;kLKKqMzZV>9Odq ztIFxIsIzpi@5sXVZE@)E|G~$uz@cX=_)3JuzWu~CVKL*#h2eUIrwqdx{<1aP$@GXr zR{oH3i}9D^%njm9`efKk;KsTU?hv#Ie;Jl2#$VhUel+}bMCdDzqozP#llk+X1b^9w zx(yw78~W>h);eaGOCJz=5c)9^@5Ou`c6a0nLlltK?kKO?vP!U)&;b@06G%(Q*eWzk zM@~%rwCcza#yJ7L0&ym^t&k-1;9`Wuy}fUkl!pSs;@%JzuSb~bSUayf zBZsiA6TtCET9aeRiH28-9g5cVJNl=ZsCD;Fo#bbk>UYM=xOLMqJ zf&*oT8D%1};VP=ive4%BBE-t*4Fot-WvQC*e1Q&=sKJs>GPEjxngQk4dtsxlFMW=y zRsIA{>BvEOWaG-HNECegaTiMa^)RD1@=bAxA+TtKMnQ(4`ov{o1InFmWO0yDha{=I zBin6X=uyJTFcaeki%hN~hgXKjvfbK7(eApg$CYjf#riuC*k*vhne-qtpKC<+WER-H z+IM-#nJ?!4QVka zwe1>HCaU(k_vpH)cBUo52;i35`nqIjXZVxId^;Cw{duB1`jSYOTxxD`9nj z@rINOF;=6zViUH!dGk(XP+CF~GscQ++j@3bZI**qkY%Yh;aEBq17+Q4v=!D|k$Llg z89Ya(%usQ%cs^ihdNIz67%8BJ+OEqE_7upg0iW}OI(9iy$ZQsNJKKLpF8QtS&wKLr z{;<~1I6nY(b($w9&5pfJLN7y)j7Gru1c<_Fg$mQSdFBBi-@`VXHrE>B#4U1Ph()uZ zz#hS?-?rB?11KCOJ+l<>+aSm!MDVkkE6@B=ebd@F?@K=`IP^@q>)VzTvDw{^@s=^w z*4RGDX3huChQR8k!OWhOq| zdMP6EaQalHDU(t(ga_YP**L9&Yp>iFpq%_8*CR54ZS_RCy0C z^EV~D=SIb+ZmRK~J0q49itl`wUKOU-gz2?mdPA7r7^Xi5h@|h=<)JX$8KyVu_AxHz zULKlnd1#L1q4O$#7`=e^+*Z~6@OuL56$B| zG{AZElBk7Go16L5iWL(+{d|<82r(+5Ft4F`o`+8#vHPQTmGe1gD0UtrPFDRoNZw#W3}fM71JQjKeG674kp40{pA3QCQha0+^g{A$Orw2O0fNr;BUip4Q4m_)W->}Qt(|r5xgnC z^TD7k?ly{96MpC4GPm>7r)6<6k&Me9!aA%;bi6|aXKX^km&s3EuIEjS2xEz<)3jLP z;ieXehX;@*;Pi(-9){1jCijK+aD+$UT;sfj0`PQ&|7ydWE8N`j#ix}Qq4_G27gbq& z9gH77jYo_WIb|$Jq_k{jxdjqXpZCA8EbJZ?m)L!VYc`|%RA?sKVYlq^BXdsl20Zv# zy^ESa8P0gNTSlm`D>A+m4POJ7rGo+@(e1}>%P*axu)EXByHM(u$h#2Ta$Ls~iH-j^ zd+!1tS9RU{>oK+mdte5lfE)yfL~WJWH&tRKks~J^*&0bUmMqEEgNG&321PTPMe3+|~s154gVfI=#O4Pm7y6iAmBR*^cEGV2BgW3AUpoPI9PJA~z}sP|*Lo z);=>D$&!J#|Np1`f386@XZG1=pZ#2W?e+LAhs6fX;~iFa6`$Ut53xM2uq%RgBVKj5 zO(K>%V59ZKX|<7!?mH;S3D{>>;3rCeI(+Mn+W4x*PT3U;@mRGR@uWL#*A$7elE8Wh z{~L=D0opZ7Bs>`CD@3ilh8SV$3ay7s@K+3p1CB){j)KJ!>l73gmGufJ3+{cuvBb6t z5DPYEfU;C?7dV#KPC;R5*kJ?8f|DOWEU`V}&)0;`qFnk9xu)R|tcEA#K*>1O%hAW% z_%-^wThH;Dv+Hn|?q~wwtc0#5{LaSHr{lRCbTPpZI6&Wi&p>^FI=S z?09_6qF!V2R8tJ1a?-HLe`K{W_*wK)>7lkOOIpA~sr8N~)I!K=3F-d5q*`a+BR5 z!#LjYehBS$2J$Lk9_+k>4CI=F0S1#0myD-?1^<-sY)!X)>(Jto9fT*l4rm4QD%f)C zWFS|6KEBQTy3W+^UpZdC`jSrUOU^D!oPNY|Jgfwf7ejNmH-P8i7;LW?$t@CM$ft8Hr8m+2QjLEeP^&^{qr zo0SEeQXfB90rhFJ7u#Eu{oAJ8U@GiFTg*4FHW3-VEO(`kq-M4DW?>YmV4~*kxlp+smSr3UQxS@WYT@_({A#PnAN9^l^T+S_AFpRv6%dcZeSU?3RZ zKzzlF?%w#Ur-i{SeXjjaR@377X)XScmKL-fAxg7|he5#a)W&jxngx>^i{IEGn;esE za9`b=-d-xE?d=HO+G-H(mbw-evx( zOXS!2uknri9t?N?B^|dqk*B@*ZRtg8?P%$V_Kqo}{iog12h5ylNc-I2FVK4L0WEq`H)Auae9-&oW$MTJ%=a0gk%{%0Z}!aswHdES)-PXP zulr^}ro{oHBTv^SHO#-bh8zw%a&Kj)o2EG=yqUH>)pYM5l-%Xrtl%k|%-^LDxspKrg@_5Wb{c)y$VFZF#l^m)C$ z>EBA9D<;+V-$I|MKcTSPR~Gfs>;1DmIox7>T@kb@LTZiDQZ8XN|th8b=K?lsJ0O!0* znWnR3Calx3up7hCX8qr{-ijGIktF-t-cwSOBS)RHojNfV9kXg8M=EkM`4Y(HnvgIO zQqh^@2LhisAwLLzGX?k){^m^t4tosfF+tQ>6)NpR+)ifMU$T3#8XBD2AA3T`iXD5R z^k{pW8`mUqx=CPzulGu|Mogf+vcd0_WMVk6v>SGFl!AopRT2V_TF!&kmB>(2B&0H; zBxFO5l8|iEvi{1577;_+{lt+RYnql?L3lY6^#QMC=nGSb|ZoYgd0c` z5On0JSOYl%A`YYsh&zxtAl6u-A`YYsh&zxtAl9f-5l2+T9Y`EJm2a$6Sx0#y)``$z z$Z&zioJm9;;z7nCSL3;@S}C!Ul=mV?l-^p$!J~Z-e~&dalC6`Yv4|=-5{taYQszwz zpo-_GPQn86B>qJy->9z<;W*L*?4?9FCR!iCk4r4zjlemoD(b0SX;oEORZ*)dEhMJd zzpIkx;gvE;4s_@wv0AE*RIKDYS|u>LE#<^jH;3jD$@G`WOvOhkO2|}fwJfw+ijWGK zOa+qZPm`JQk5oX=Tk(4c{0;9$8Vqi^-pm2ylDxwq0%))>IT)Meo*3z z*f$TQs&#(7u8Y zD-D(?gfAZTeXreEm3H10e#mYR&N_$)Q}dNt^Q~GaHLue0VsM&F8P*5$1z6^q zD1{{g!V}H4=MfQmHIbJCc5GyvnpYIHe^Lg=dlm7}Jc8K%G|b>h7+Z~ND|@gR+LU|S zC}X;4R5##Jm=@Nj9eT>N)=cTt2e%6g=w$ zzN-hmE5Lame_eEQ77kZ&oQMx-$WSYe9DSe4(c7}2I8MWUD{fpi<@YIP?Ex!p{59qG zY4`ZL$hl*8Wgzi&PjJlaU}Qv)iHQ7|Q@xX~wP(D;U!0ZktxoR1BHOqr@3fCAKg2-I z?w6s==;{qDj}HTbXwAXe6hwN2tck6&geWB%Iv1R>W2dZYk8~16ic9RdMD?ZxM=|n- z8V)AUQ*jRPLp2h`ulSp|g4dvOl_C%?`zkI@VFpZ;ZCVjTiB2lQ-wKua3Qfkt7y`T1 z&0v6>8;K{2)(=T-mfdjPxsegblEI4YCnoCFMQjgT`=Ks)zwg@;qJNe_$%jJ z)x?Lt^c(5#jzV0df4~)+d1-7oyiN2$c$FtY9MB;@;cbtQu(*0Bd+4#Y8xny8KlPCO z+I=D^9km&g9dhJp7!krXBDd{^5uE%#Xg{|=5FXKI+Q8QhGs;2igY6KJUK!`{>f!j1 z6cF11&v8|g_YEl!pLb0XXwWyT>Yb&1?eiB@???o;Tr^1}-xx@HU43s+y4_e{x*7k^y=cKV8@3C=laYR^bW4T4(;oD zYLvdpj9<=HjMlL}hi!M!3l*jJ2G^fk^wN>o{!DcJ-~ysTI1)R`kHf%|!44dWo#BVW zB4phii4F5(h`ruw@OV*p+v73-@`9bVD&*`V#)J2W5VpCCjwS-fk|b{G86$R@GX|@s zw0iG73vIwFLp@!ukGI`eT6~<-CE6GL9UmSHOY&Sh<`A$Dxxxn$)nf$293k-K9xFcL zeP@)l#xWn9h!T+X;U*e}mX)^Rh5ZLI#jE#tZ@ebn*h%T6_Af5diqGu>*#v9uo?C z+|A(Lar=ndf^OAqDTe!okTx!nh)^w2x3Si3T;n!2yDhi%R|N9tOJw=!v6Ey{9BAf+ zX&m+e*+J8OQ$M7D41asDwuy^4aXR0(5wr+nOt$QLTA+ zqPVSKACC`kT6kx?oOFx>&K#AIW4wC(5qpIwUA+Xp0c^0DZBaZdnjwbn z`@|zcQg6ZKBH&!+-V$6Z!Ey+#MFBeG$s+Wj0m{i_@G0aK=@*{SLfY;~ zI#mp~EJ|@`{vkXzW&Zj>IOvTGCrcFSOmJo~4lfcYi6CHXt|#5Hl1UvJuaK0o;%Ryl zOA=gQj?-~#caVdthMg24A>!8V6PFln?03ts6W=kof(%@yI+U1QW*PC^G08|-MOo=r1CiKEB5Sj zpp6_LCKAk%35!H@D0O@bDE+1NtgF}_`PeESo6h``DUdcQBSbUL42qk*HZ;~lOBFW6 zH1(BlsfI)rt1_ zPsjF~*3jXXXbqS@2TytU7`teV!1#SGszGZmmQzUhdWoxwXS? z?MVEUv(5yeoPK?G4Hb&DVN{GVLf|0})PvH4$Rr4j6sP$s1Y5KbOkcqk75-7o?J7eK zVi1WR0PE_iNgj&6CUz(GZ?HRW z35?zQz?)N8XcH{Ztk)!V0_$(>c?jKFZyeTgpw&6X4oLJOiB<#&n60C6y@P`dF|dfp zh3XL5jD%T`ZD7Xsi;$5GdLca^AbV>^^_Ohd1R$EYt}JqXBBal_fmLF!J!U4?dAMn} zvcw94#J~~FBzi=+!zL0Muh8HUIj<6o2yvWH7>MctTqMo(k>?$Go`+j~k20Q1qBSJj zFm=N-^j+&mO^NAwc_)#Vxbl@g>*Ze*c5qQ^nZx_uosKCS5aH=!|MZX zebB8Bx%CmZKG&_!V|40s46J#T>&#|3Z zakk2*$q1Nb5E`#%hO+oP`^RTpYi-1SOYL9Fawl$G`EAL~-8dSq$RcAPTbASggq`sx zY^-I4^5s;AOj()nT<3C+*O>zW;8lA#AHe+$Vs#6sPCS_XYWAy%!1ZomBPI>tFC_wB z(F?StM5^x8Yk2Sx&PYV0%7<>AJomcHUHNma+vM-^v%a|a?3uGaV$0&Z$fQ2xzVYhe z%=lFd;lXqNEJk6H_QYRvhv`nIO-wWy$bEw94VOVJo|p0e8`?hS6>YBvy(M)D^)^R7u zd*xT>lSjMCFXgtY^UXZKwu}~vW!Y_!d`>nCjxed1Ef4*O14`>zr{%1p{BkIx^{gvD zbd*pKYJ3GZQ|Mi;@IlSu7%H4j88J@5SBbbpdu1Lp=ioN#67IE(V6S;@8KGYD-Le7+ z_{!91E_BNl5(IWF>>Ts^->p1hBbtkZpJ;}qC~=!h+_EKZ^AdSal4SEf*4hLMHhny7 zWJMx`0$E9s&=OG%Em2Nr3G5Bs<|=zR($kgcJ!N`K`zt61;_2gaUlgyrY%+q?)c#H9_2mtw ze1n8921lVkQXyXIOknQUdI3$JF@ClmkN)C#g;U=@{r+OF4j~AVsDg>&PB|7<=Gt@n zei6t%f=F+L=s4{a8tY?)PJ$H?0;}39a;&Nxdj)dnTzf?x(r9}HV(EN)1@h?vdqtsD zRcNnRh;Z6oQG|TjUV)&x*j|Cey2M^Vz>Xzd8=@I)pOJT!7ey1*r|J^G27n#}v~%^C zIJ9z_jSbk$9i=3z`lP+GXr+LW0Am>S z&H_c*`G5dd365)2V3m_H2Xi)?zCXH#O3Y!dk?Xsu-%wT*#Mu?OiS|== z71+&9iS~ge+++jFJ2;IRz)4~kw&d$3gdKcik)G#3b89IwY3H3rjp?NM3QXE*r?EJd zuh^t{PGh0raCo-Rq&ZGwNh)87q|x{6F=bu7C^^PNeO-fH_swvW$&O1uzRk$5+%<6+ zgLCNuJ4`S~?#vh71O(xmBF>!!#9b+Yn{nLzntISn~va*1KW@SDbtDsq^+ZP-i z6A(7+5bWEzgK`i#-0D=HX%TO<=P2p0Q@zg=$Q9?X$C9agNqxqVU>3X2J%_8s38(r1 zCo|aI0mJ^Wwgl%Z==@x;0_d?m$i4FmnZJ|xLIgaAh5B-sNV0_|EOp1wr;hWKR6w4B zmt+DNws6riJmmesTgVy>g9~#w(|TJ!F<;>H;PnF%_xKgDVkQtRQD_PA#6hl}F-{P1 zxZx;QCt2n$2!t*&abWAsptCsFvLFvL1hR&eH({F{+QOp zSBQ8fOQE-s!vxrC2b=sx}O z%XG@WHU{X)i##umKHj2nMI8{a%HyhYP~&eld*z!y&l z$$%qJs&SIpq?1X*Af(Nw#qB-eZ66UWQd#bzS7p00V7cneUB`CS=?0$i!)DmAO(3VZ z*uUN@C3Ne^AsL|<|C*po`;*rPq`=r-XC?2W z3QBnp>#Qs)WEmUOlh~frU7=jafBd`ZHYyJhm~FS@k-H3&c~-oMNeB!rCcr!+87wg=OTfCd zlmN0Ok`J3gf;t_Q)RRHC7Chw$J*+>V6sH{nE=Mp72&)lNLB<6kL0bjE<`l1>Z%|7J zxubZikUc2yGxA3@4>1)~_VztQcSmF%7D=nVCs)NXR=8qHiimA+HbX==!Lv~YjVt4TS12NZ+ z^GEA1%MQ%2bHxY62l;~0fra`er3@A*B zB{Gc|gWv*++oy5$lo(Sd#o@C7P7QOC)8guh?~BBkI-|j)2E+vvW)+)^266Hf8y1PbMQzn0P5+ZB64@V)ky!o6+<_Ga7; zTi`u61+2z35~}lRivV<3<1CF@HMa@R#Q-a;Ps^*R(VRGo%K2yd@syK@X!KrPd~ej4 zKdg(aTjKrdRy`b*9)X=d>Sfje)-E1ol~F_~i|>te{2BJw1X#?Dj{;0Awh6*V08l(o zFA>RTByEB*#}o*D9J3&xlo)Gb*I){SiVv-uXgQBEPxs02H=L0(1R8nohk%QB#YStb zij`!c2gXV(BkOle!x3{{Ltf=WkhbH2bzwD?^S=7K%=(Hd_tqRNwyb55{$*;PAqeN~ zOilY5c>d73m2y6@?+)TV6&q&KpqU##)y46}S{A}kMRxh{wvUoutcF8fN1`@^GG91;?xoO3VSf*Qu1Cz3HHj&D1xcFyH;0~)z+?-C^Gh1SVg_PW`4Pd zW!Vk`x!H%@7S3v-73w-v>$V*3I|>Zl z)hSin>@Xr>scG%&?kcMlUJY)4VCtX z6HhW@n2V9dL{4Y+3*Q}}n-}~#vF7{v3r3SF@}&a^H#&fi&OT!2MY0cCw+z_L0qd6Y zc5~3W#j~43)-6MJbHuu3*ly0TZW*zgbFEuO?dCk|mNC0I-+G_jTwvW2u$v35TY`4; zLhF{0-CShd60w_$ty^;J<`V0cT)TORbxWSz9JOxAx0}nYTMFE0m3vE}9bIWHUD$Q7 zc2&6hbJVWo*47DKfQ*8Xe^ABuPRK~SCSOJ}4ZgC7$tT;&AJ^PTo9?N2)icqbwDT){ zqd0iQ-w^GCk%~x^+3MZiTgVIX5+_Gu$Jy_p=AYpa)p`Lk!5(WtqxiD%;50!4*FACToWCyDAx_TeJ92)xM1Zb0#9f6+T{mV z)_P6A^@=%cMZ|P#_H$LphIV6y>IvF_jX7!!`8&lpDJQc&`#HIDaF9DSx!Ki^NuPUv z&(E$71WcDpNoy8ExOZAh5z+wDY@p_j|0*>O6MGW$aV}v<#rkKAZ6L#LAH&6@t@KS9|EH7Vzs4W`QN|xV5q>F$82|QB z$o6*3Z|IOv_6dPn`aMvBpv6pq@#+!ApUEZJRBzf0?!2HSfD7m~nEr*VGO?1&qrkSND+OG6= zQVA?MYrWsox&>f8jd^UI*wB8+Bx;9XJS%;2q#`e~fA&~TFrq%H6T8t&{1Wk6sm6qS zLFg6y(|Bb$nl?7zn?hD>x0vKL>=8RkjHls3;1R_-dY@S0i7hmicvwoq;C@F8ykOBQ zU~mtsMYQ-ts4aA{+DpUWp0*j1m?P2Gz@NNQbjeHMgdgjV4N(&e+6Le`MI!W=T0i^7 z&SLl6D0;w7rzs`cY&D`y<#nB=lytMzmu_xyno`ouRwLb1oJyxDCE#rJ1)M!jQ%bii+$PGEp7JzB%}T6$FWnQk=~u#LiAy{^G>v-DJm#g`w0 zP{j+i2GGdJ6B1e$Nq@{j@c!y|nSC)F;d6LK40(6aHSA@GY|#GgkY&6>mvMYY>9gTP zr`&y&rL2gn>mqgDTebdD-YzD+;$bNEW*Gc^*p6@SkIA8j7d*y8SKvdR4def8z4u2L ze09Zk;)Tevek}e|JH7+SIIq3Bb3>jQ^?p-}4fI#asCc&uTF&)%%grRO2fyZ<(hr-uXRM(<7 zuO3yMH|B~Gj=Et?M4QOsGTzanqAW+@Zl1b0L;?L#Cw?bcs|nT0hU#3u=or4B65c68 zUWS zoTFb&LSk zjly<_sg-DfpFx2545j`=g!$z7nTT>FL&*81XaW$?oy4y2XWTn?UZm0cxob>z4q&G} z;QYY!GRsB1y%B&L)9Lt}r=`QS-w}99z@_lB2GUKJd$xJTdxVz@^fx!=U|FG@GX&%#2oCS87 z#v$U-<6tgiztHu1DE=4Y)njMDx{7Z!hC8O=nek$KS)P3pKH~%Ke3W%H;5m ztfwl3KelhS0;nu@%0H4%)c+stO(rML9|$8#?2S2e)gnT(;kER4d0dPC$Kp@7&0Q+- zg-&T~kc0N$Nv_f(?eDSwT=Lj$UmLTE9~`$NS*%Obg;^{)S#L4;d3ixBVmqA8XWNe+ zrT^dM`#w17eH_fqD{ZStYJqc7uvS>K)neWYFO;mt^y{1WB-9{{pJ+lU1BP7u>b^u3#BSgGyI#xq!9_+ z>s?Pv2W7C@g{lBfjm8zD11V^~0X0Pjgj|ie$Bqhd=vV10kMIl%zmvY`e(jl#KOKyu z`HLDr)JJqI^bx!GLEo`|!iD~~ZIewsw(ye&MgW}keMvYO@8eIir2-|O1rX&@cK6aP zQseczlKbKVKTGY4$ zwr!uVa6op88wE^So-%A`z7OLDi@l(-ZAXQLt40R4P?)p|T-#vSir~^jSXl%AHUR$y zI*^3o_QXQYIBirSejLI?&Uf8d6)_P7MX|1fY&Rhh?X*|HD)!iw1y;i*yD1;n4SN+T zT(}IF#O-zy@7yi2QpODXl2KK85*pC~D?%kS6)Lyj!<8vQtc_Ndv zt>Y=ZX~gJYOrXx|-a|fQ^WA&-MoE;ciUH0hyntxUj6mJiFu+#gu%>9mOc3Q)4Ehi; z2IOU|6OEK^P<|BrM>Hmc-_hqpFJpC|l-u4Fkh21S`L%Tt%P;L1 zr0QHdS_!b>CEY|)G_dB~#iW=JM^5)9y(<&bpPt<%a`$0Lce;D@ZrX$;q>o9Pn5yX7 z6I&(sFa0RHeJI->-y=_+aDe>YD_3Tc8um#P&W{ghs^k(a(s}(QIpvopzlbO}B;Uu+ z9YM*Z%LT30N$vw7%zk{lU!z?LeMN7LEyd5=hxf??7*ue%UoT7K@_<}E>puJ)`K~O2 zufp!&=VEstyw>B*lB_sqFj~gzs%^F(QC^e#HPE&)wl5jRmfIpCal577{oM<>Rzu4H z6Rz5iZYZV5EtAb8PU68#NO~U1AeZxF!y@|}uii6hJd?)kIA>vZ^p%TVn5Q)}6_AdY zr(I&^pkd2qo0#^deQ6Cjn{0?i@+QclHbhXar6GPKMjG(2B>4@{sEL^4C=&^Cs@{>_ zn~qg;bQDdK(yK3(moiP&O+zd#t6P^zxcgJI#64)(HqQ*QcCLms#qL|}_=SXF?a5?0 z%$1LOCJdb)1=tUQ?fEQRmdljsR+gt#nE=>c{$k2WgTsr z_SF|_Tx4-dav)!4y0wu`)5ZG3?X0IETqxeEY#E5 zoXh8t&G~$0_HQ1gI&wrq7&Rd<+LxJToSEVWt&LK5YvXA?ibJ4L#5GHPUvyR=&Vr&exPTIK9#TW)>%c`7ITXq_;mHp-_9_BP2$qt=4+ z(Q&o?WhrDzG`ypwnkn3^w0)ZtYHRC7-tjo~p8|*zPHZ6&G2}IgklL+?oa1V(B^*u~ z3ecC$yK!BHfb0})|8TE<&JCpQ;h9+O(@pHD(vL!xrHpobF!Glj|ie6iKkRs&J6c9e5b z+6*a^c!@{#Ow?DZnZn(_q)Y56Gvt4ecF_z;kf<|c0)}SfQ@$iD&AEMIhI~Mr+rPq7 z;@>`bo{ai<64TFR<-Qem42~i`J?qvmognH?l-WH7VFmjT-NhDf&G$JISoaQduXH5`BjJ zGuod^_DkLW&tUJ~pIJLeU{8DOjpvyt#0+n{qHA=PYA!5I^4kmfoAkT0J(v-{?A479 zPi4p7Kk;~29-rH<&&rBNwDqXRcX~JTKE^*T(s(f#ZHb8aXiJV5leXl#m8iD!#K5#A zU(8Kg3dHyn6-Xtj?S(L4;yH^C53aM7sJ2Vo$|Y{g61Orc?z5HUZcDjaS>?7=xs@y3 zmX&U0t=m%TR<3bd*0_}@N1ENr+i$ZH>t-Q-^xy!BmW4Gmx-O9V& zmb=}`Hn*kCt-Q}|xzDX!@3yRWw{%KlcIoGqO?s`LTYB_bKeuevYyI3Jf@yb`er^$M zmAgwnw}=qI-KC#f9@A_6+_GD*^>fP}z1Ghyd%4DVz}=-6C)_Ph=uQ3HvQMw|bIX3c z*3T^m^jbf+d`GY4)85j{r?q*2Pj|~<`P|jZXa8(IGEmiRIVs=9N}>V{S5VWpIDA=~ z&&$1s&dBvPPp%&skn1lG$+ZFpTAPQtcDKk~cbAmkDaSf1+yrAX4UCesKJZYZAhD4b8sIFuo!kgnZZC|r4Q>;(a_5>7Sgir`0 zFhUn+@@pFJTDEHG3hramqQ>*8M@x|#@M&DHKSS1-yM3QjX}gRHhmP$B_%^KREADn3 zm|2?yoP4Krmetd%pVC*B9t*7==^uBybg0#%E!8uSwOw1fQ~I{QQ(6iiH{Rbl;9t4h zrFX6EGGy*{=~rvJbb-|~CMi@z`p)W+-jQG0d)u|&j=8ls4Y{>BZf&kxo9EW%%dZ0Y zD0FKVy0t}aZ85*_qrpJ5HtOOmQj4?5$_e&O_$8sUO5&v#M03&ni6m`Krv#?Y5@qS$ z>?cq5XKtEGpHk(?9Vu_k^D~zx^eo+#vB? zCmH<}=Ag=c+Sw_0YQSXq7Bx3U6)6PNP3@Hc9z>Lq$I zic}Ayp$v)u*769FO$F|i`d~ez&YF-tHN29W$%%MiU{Z5W9$3OFC-*1qp*!LEkXY5$ zVJcAX-ARO5>=l0Z3z<_f0?66fK_j$%ZPys1ugJO#=B{X5pZS>Sx@TKnJ{T7l{+Z-) zJyc^t9*4b?gj6F4bHk4MgvvBABkSa^hrjLo?W`9ohJZ9Qlu+3iiI3G=aZI&0v80jN zy%>J7p)lKOc6@gDn=1l?*|^c5xeGt}EM^K_?K?B#AIIRyxlC+%B_yF*0gJL~ zH^BlIWk9o*U+ zA(Fo4^U-HgN5ABBzG(g0ef=5fD_Yk>us>s%X^-v!#lvKp@PFpx|Cx{f+WC0diMOAR z%)g8FlK{_gB2=QCE?bZtDmhapzV(XRI433_=dNiR?1 zfolW*0ObQAuKKi9Q;hluqh2UqO^F0Ytv-{Ym%T}Pd06QsfzHfRhxMtWcJ)#Jsgri~ zNgVa4Te4PJgUMP2v{so?E2$EORq+({Je8tar@2a1EMF^DU@B(S6#CUlQP1A9N$Po+ zE2yWb8IMsj9+R5sW2RmU{dy_=3`u7N?74&4;cq}mO{L-~Qc5>Zsgx4L(-xr~D%d%s zt1Jx2agJ*^V>RYYkkX4Pby%OogbGYao;+$d9JPSjTniCBX;tK*>6=VPD5%Fh_S205LqhnV6MW7{5uM;Dd})20k04nWCaq41f2Z}A{VGJ=RC-hGO% z<6I{`|35p?m{=7!NfyuC`zI9>_%2xT;7Pi{i$_>X6v(SBBhMi`83p zgLo|#iDmuX%=qW|QHiBDR^9OG(zO0%3*loj2G?S@F1?LZT6lj>8h3Tr)xnCg`K)mc-m zM&`T@tL;>=tu6h_ilpHaNy!rm{mao?`)+Bn^{X*K6I<6wuQXkd_R2{;0F2@I3u>G^ zgjl&;Lng#+Rx?0xc`dJkwEOYpZ%Hmis(>Nh|{B z_p{zF$CyLHbt_)M+px(0`$Im9?Cpk~PYR88_^DD#dXvq3HGo@izye+wUENgcwbf_h z;Tv&oFw=b&?ytx?Dsr4w-=1l7A)*r8G+Wf%Pm?IA(8WrqWnr=;3=diW^qPDMrv3=*`ZIEw zlGE{H+`nkQJbh?p`a^!~b?veu5d$udVq#5{0u~HNyP6g2X=p;{J&6fZn(N zf};#j5`LJI>G{8|q1)0ABg6;j!-mtoyQm&J=zUgvKlYtP<8V=_~JE%95mkXFrrje`rhv>TWFQ}(lN zMS%m53_fo7ljAOeVH!4QEk#g<*<#;UD{kVGigNx0gH{Ctz-_1^-?@W_aHPT$ko~j3 zxr4&+E&?Aw5dhdVfCyNagvCiMp#%5PthDp)zo9)?yhxL%c11xgjgdxaTaG(l;A}YH ze1T2+yz>QiXwNJE71jW(Pn^K;Vr2Wqf014PxO0cL3gCqrnnWAKquXkvJv0vwFlkRk zq4;xSHC1zQdzep(|HRyPCm`)G56e^YLH75+BEiqYus|o^4Cnn6e&6~xq@MXe^d0D- zr)Zku^c5;j-$KRdivaEiWl>zeT*dXvOXB*0nH#lM6{n&2nfdO$_Z5m2{KB+Zg){`2 zP=*+uv+*5qE*FbMAuMWx-m*}o7Bh`cGp&Z-sSO})c_8RsBhGV80k&2bB*my_P=4J8s8e=r9+B|Wvr0@oB@LH6%iZ!K< z=xYZwR*rQ*ZIY4DF1T4eKo5gX&U9Y<(u=}+qzHLOBcdbXch3p zJx@(eG9L)w{;Yc#cqlEtf${Gpq$5UJBL;_|VHm$nC&ql)r+5jcu?NI9Qe27mVI-gy zSSMBIj6Z(tfEr~s`1YR9Dxv{cdib78EVo%Zhyax|P|SmWSDV_?0_YJIJP$ky@rqm( zQUC{#8FA;kT0FC1S7{fm7{c7jw5W$(wxtM!2{1L0g7H_#^oYm;hJxh`bV*fDoh}xDiea5Yet9=tzeL zLlgFG=p3{!DN4+ppYt&=u#_#|B4gca&w-Xf$tp$}N`j1zfk1YGUuK+%u_+0(Q&2GY zz&qH99i`b~@PXmv2{WMQeIrolTg9kmuNh+)d2;Gt-hk_$)P!jdA>jn%VRg+>54&mX ztYtiD6_v$|jmqwq=_56j@!a>$>$*?sDh8>s1IfCcO4gNmsRpaey^HGVtR2x&q>^!e zAf=9u*71y~BPIj{@BuRy{bsoPE7$>~1~$o0dEa|k=t;=X41bsEpS@eRtSCz`1D-`- z--0Qh7N4FR7@bW2eflW&Zb~10dUc1EA54`TyjG>RYTW_7YAKMy{nt;TR|jqi8k0pUb~2ttGGiV`4L@X%!|BcP!B6FgJc1Mp1Y z5+oDmHJbzYD(u?=$HF$i4+dJXSWdd&>5(WvEVJdt8SKfyWmc3FovL{F0+Qmiy0Mcj zelqJDTPaTdvD4?C1sFquO@bh>52^@?B(s=0?zbZ5r`hliz&Y|;fl_qG$M6-q$?74d z;P(V23wm?XJZni%Z=B@yL6H0d=BIE9$O8R+RdBVyS@cStx`=Tz^PzvX?4sVET{WTA z{q^Bwi2=B0VLXctDYn2yJ6e?t3%77OGJ*|p?w(C}kmkC7? zvWz=x^iH$fS$-QcCRLEBPv5|->@pHn|8B<$IYnsFLV+ikYXiR35R~scR5J37Y9@#8 zpxux!KcTm|^3Cr&zVZLAF0eA!9xPD%o`2W!W}0scF@#;Mam@hTZ;ZH&Ig5HZ>p@as zC;=e23H^6o|Li}8<$_JNSmG6hM=FX4EwDsPp@IEchI#XlAStZQTbD)fNPx!?Lf{53 zL=Xsc?<0@CC8t~;TPW99t{7trDVC@YNw3->v4lE!txgCHV?iW~0;@$|sHRViElI(p~L_isj)Ok?uh(7M{*QcFYN&3wYuT4>4(Fb($+>vNN)m;6V@+)=wi;hBKJicQsS5OG^pLqfDtgTf=2PkgvOa{lh9?b+sHJ#a-uMfh9f1t{XdGD9 zHUfGOW+7Nxzb<3&3V!FOeuHwzG~@Kzv@XND15K{+UTD?mo2gUV)b?-;>*~N>;{BaK ze#OJ~Y!OS}=hljIO z6Y$Q@hj7#c5_K5z1vLUt2&~uc7towvk$sds{fg|q74d)Rr#tlgm|S3X86MK#dfkGTBao;E^upNw38#!f#?`cq^_SD4PcUFB|Y64F)cGV1CZkuHw@QpJ2BEay}P z)6%G*1Ad2oIVz?AwmXJ?5T^^*15_fE?5>lb_ncCnNdiVcXV=j!tD4%nxjSD5dOw0$Y=y? zrx2pyKwTrUVeiwCtSQSNS>CD1W_+P7fwQ=NBQqY7o7dW9 zYb2rjh4?(I3lXG&!72B1+PR-64}*Fy4@<%un$Ro>jgsX$NeMbiaKSuROiT<$5mgsl-YBltytWb;*8i2lxfIBL0pU5Na zExU~-Llr{S-67r4o2}16II@?7oSuLJOt5i|PN9)Ah8e!wmHH z2RSp)TQiIm&GwdAeB2C^G^j1(JWm>aoR!W>D?Mc;FX0Gg4d;WIj`ljbk z?r&}HGJpBI%%Afv^Pm5lcUS-Y?=t_UcbR|9yUbs3>HITHng4#(#ZxA^)?avHrXG>` zWcZ6kz04G=B7gx~4;sSR#N_int!2u*v1S`1p^2Ge<}c49FBbpD3#jV0!@l&-cA%wR zHjgfPiOn5r=J&ZO9D8aQPb7DzNbT;tvFCQa#M_oBiZ~Wi>%ijHytW+g_?1#uc`UC# zV?7pUgI9asNTnhAm+f61u4+y+50hM>ajb47m7}-m297_i7*Ya6ieD zHY5AdJ}2jG-fz8aPLRapon0Gp3KEq$_#_v=$ie;Ju3saf=WNeTAoT_SVXSNPb}l#Y zd({8E(EmLm2ybJHZ)ls_L%8-iOv=F(3@H#Bp-k_u!Z4AeGFjeiL#NKZkSpwEY{Y5} zSgoO+)`)e3)f)8fm3$2&!fDPyG)Y6oC9-gPaB*v(E!finYC>SJ&=i}K=~a>4%)qMv zL#w;1lXEsR>LdRmUM?`a6_LfQ!M2+Dr9mt&%v z_Ffsm;jAK8mXiN~6qo)R{&s?SV2RPBDZi)SQ;RPlu%BGcwRPULq=E)i?_H(0 zgPk43f8=h&j>3cd=5k^C$u`0TeM}H4RK;1?`{C%1#8_idAM-&W_n7Ls0Qo|N2N=M6}5V}8B&qTVmyh!UZX zj4@%!`e_ z=3LtwHRt|Glb0GwePTQx6%H$02-_A3x0Xf1YcC6baBld6OCuNaTa(V~Jbx#`6lqYz z3f^f~=iX^m&4xDIDH5CMq3?N<+yQ?c`#$jJ5p)*<{UyQ|&>_X*&lgU}dX#vcfDeGF zE)WEVw7zNb0{J~Ho}9w(r*mF99MWEnC_y`ubt&%h5JQ2A!2kdg01;oYwQ?$+W#1AS z%azFEe{^6fbN_BM#ga_Pag z8#(yZ7uc4V6Wh^(CVO!`=$K+rGkd&qH;HMg$z=QV@F4NR?PyWyDJ>$JETUeDsAYQ* zd%2H~Z%!7GVxvS}k=}#U2YVuFBQ7WH5&Ka1$^Pu&Rrg)#w(9JD={Qsu}%r3i9aj>+FZLIZzWNCF%xJ>wP|`qma0Fy#{Ib%kO$+2_^!6 z{hN%8o9_R0{~W0rFPa!%zrSi_f)MVvHbD3) zGME+e{M7!xOirAc((j;sj-Q&eNZxIJhViaW+MYnhbn2~)4F5;eJH!uYroB=6Vxm1$ zfAhRuu`;_N5^t9xe)CUKRMh(yz9ne2yGxuTG*I=O{hRx^&d9VjZ|BR}yhlE_?a=F; zeD-gCj8CzDu(s`%&&_-F^9lX@j(&=>g|%5+Ev$<5*5*$ADP~zM{z96lsi7BBe@{Bh zk_AAoNm+EfbUKv|x5oravdRA8)1y$A>=zcix!?$3l0kbou@s$U+w~v^W%v6ik|0Ea zA_+od5QIqR($YZJ!AMQ~2d%eqeoToEPhR}~$^40#FT}r?t+Q>?*h-7ceINPmxml8l zl{s-jPdO{`LM(Q25 zKNqs|j?md{M*`O81JSO7!P5TqKc9lAX-|nyV#-T|z9+pPuDEt!QXkGF-zV^jgbbnM z8|~sOD?V(uWf4eegs-5W@U~@zz`aGU1YRcoomXd{g89yZkTDS?miW>j@wqI#js$FK z80K0-jZ}}SB9?gAsx3~+_9upO2LFw&A%K2DI5Mt|IPa%gjbMP;@nNwM{`~;jXl#U? z-w#?vc6`LTn}kt2K8haVZq#;T0*rY#&pKHxf*i&p;BfH>HEbC1k;7E*Bacx*&~|OGN`ezGmPjm#7c!LG`_pjj-*}nv;4gUnGdddCrR;EPh zAw#mlJNkOI^^!1!cB$8&wJVKwslVSvv`bz3tX)F8N)N@a<(#w9%g@rO#z|dlau3e; z;hZ-Mg|&7mf!n17ZkG_aR;y*brA~>~E}?L82D*qG2tA;A-p5{_;Ah|an34|iU!2|K zj|aOtmSnN<#V^C6u{N_1arO4`y)Yl zJsh%!p9)e10?hpStIR|6+81GBF(XBLja^gqh|EVOJ7`&~Mup3oW;Lom{1nX4TQao4 z{S_hJCR^sDe}>-BWmow$`&!Me+!Wm5RQB(r?x*4rd9^5%M3wZ$ctB4vZ&UtzvwP(~ z&hy>@ePa4o%+&uSjZK2QH;xY&K(KT|BJhbv7aV+klxm}zwc1Ca@o@2iWTkvJ_+lDW zkgP(}UhMm6K*Jf0WFM^KM1xde*jvT%eN%7S2RXO0ExT zuK3U89XADSVhRVcV02A_ zmD+F;>8A`khv>zp>@}HG`ZQmFfjWGJC|e*re@KPxC0AM{_WCOW*~hK>0`~nu>pt|; zA%Tt+qXv+~nHi_D4@&f<39FgP8bhtAZ|Pg%ZEtdvWC{U+#-U*h%jIR9^&b{AjRzu# z>K+KaEZ{Y1CN)#%N1ZQFT-*^1Cj<&$wO|2g8)P*9@i#L5LEDj|&MZMf2ep<{Ovk6b zC2UFJrr?6_+oOpx;Ye;|Zc^R6V0NE%vvMRiD@QV4TC#Qw#bZ}rWK~?f8p!7UQ<`Vs zn*FtLZY+ImFk)KkiV(27NrBO%-q!s9a0gvw0g>YhX8LG&>#wQE>hQKl`O;w!4t3Hf zd{*By%_#g{`Y71r|JHE`O=*!qAZ`%^Vi=%h_EGyqcp*TD7o5`g_A=moZCBejSh+Sr zyyk%28nl{&c5BFL4l&p(;%7O%-P(!{S?C#@;;o|9@UPP@{eEKn1pI<-mp&r8J(1eo z@YcVRfeLTykp7uwSS#MtIhsDK$x*$LQGMG%O{aHrwXEb!lvP-5IzflE^h7&GAYjCW z5`m*UHrM_nOZbZc)^AzqiS@r^*9;`VDAF!dvq0vjwk*gZD(#DJm;%Pp{wK2f<|hI^ zR!gn74jizl>#tE}>$2wi$QwhfH0qb$rjKhG$u_00E^)hTq%B_} z`y3HE5QZ=vQF`fui*?d_#^gm3@ieOvpC!P;;oAXTfO*u&8t2>VZ=MFeky;nGT?LbX8m7QK+xtShQ;I;aNL?K$sS zTE!iEcmcd`Y?yv29S(2(38jR${WYJ9?}f@f&IPWsBK2PPXaKo7z6`?*wFs2N zr9Pu;Lm;E0lKDr0y38R%3*ZvioqTdzDde_T$nEDw?Yjfk=f((UVSPl%ZG6a(TY(xs zCEM1j5R+woCvxSsP@VSy-l(qwmRLClndX(y+*Kj#-XOclM_B7W)P>H;fi-@uvF+%` zJcECz8O)bHs|($+?llWrV(TUaCNs^SySg4gsoWIa^i!EH_k`9(d~Rv?T^XeO`m5gO zU!#Jj=#HnOyot@zYCo-#HO!T=G_EKtArY=l+eEGk7gp~M7s8_zUTMa=df$W~Q=ACq z#m?8vixGJR6VqFG!Q!&Q@TTjzC z7r32G^|bwBBJeeynq}W#04-@hT~Yd4{NA+s09!peQJ=MB1+qF-pB0ptUXvTBS*kMI zr>RZMArZ#zMtxA~K?SE)WqF|$tQcG*kd;j4Xwl->7>fltS=bl0sqeP(Cb>v$i|&v!u^%(?RF6P3uVEF;vpb6IC0ne;uv$LV ze3?Us^lWEGiL$-frsDk-MqUvcIZ{!AwOnz3g}%eQy!{3rREvjMBD2jB0ak6}>+=5f z*mQDI!BrP?^8Sj2QaPYgtt$z~MiB2Yxwn4fm^-J6oy%TUK%ilKW$+)BXhpl7+cAj2 z3jGPOzO4*8w<8WdZN>K3%g82fqyXGl)qCZkUWsjZJ15!0c(8(XLwF_4-izIzM)Jfu z_Ncgz62mi-ld%L|TAc%v>Xy&i?cC_h1+#O(DK`sXV&_KgAlT!kGndyURgYF_9d}D7YshZa1NYx)x7`|xu^&-D10x- z)y@jo@dM`V6b`6CFje?%lM8Qn+rr&td4gsrpwVUFZo*n*m=MtXct1b7HWcDUIl?f| z`=znr@U~~$(wJUPmTA4uo$j&rtxkz`?vjic+;KG=dK4rEUepvQdj2cU=o-@wpAwH zr#kr#KP0h{!U^HOxi=7(^lxWvbkSw6ee9D&vWmV=gK1V7GGW#p_rCOQx z-}VKt@DG*w5bk+GXnDf`rNd)Ey{XpvpNTkNb%?MHol;t5zG__URrz-Lg;9Iug|Qk- zd?yA<<3r(Xb7bDHqQbe^$Jl7_Pb)nh-uiPM3~&27pNm)Jg%cuuxFhNV2a&cYrIYIg(s&d;=I2c)@9JQ%O&hxmq$?konFp1c1(?RkR9Z$n( zS8Ob}6mGh-VQBqHUO`_%#PYPS!u8(!&5QM|sUAZh)s&ATk@T1a>~zC$>GSO$;8lsx z^Hlr_=_g3pfWBdvLU{pM)}OUj<#xTyX>ABcB#t$o?z&GX?K0^Uf>Se!p?|hk{&T>t zDwH29|2fDX$|opMA*EYuE<~*I3prg!vR@AK42tAQ++0PAUzkCOZMWOY3xK|nVskFAhA14B7dC|hdIR@+c^?9~Q+#P19^1p8IM0-#?$FDw;{Hp6gLtk>@Dbdo%=L}yx`$kUMdMgchZ z&=3<0XXe#GW*3X4-o7WtD!S9Y2Tl9k0lxG2o^5~Bx;tWjK49IQV}Cwq-JNTH9`njP z`yy#~lH4`oQs}q>$3fCr3 z4Vm?|Ry^VrVQ|D_JYmOj64en*h_L5JNX3YXY|IvhEXp-UKF(=67fCLjfh~T z@x)6bqD&%sF;qZ!yb9brS{r1G8cs!KJiiEy$tn~VSn2Ir;-QVzaw3LIKupk(nojsQ zk~@Klu^XdkOX>Am=mBZTUU~cZRV7kusmVP_S-#Y$SKoV>_sVLz2~)6>2oyN1GYhCF zs%4o4?EGg$AP_GE1EgMhkP$YAh$W$=;sct3=ZX8g&N`SO*}gYhkU8D?^G&g%^q zrt<}-($9I-1M{lS&#U&r-6B3AtjV1bfeg7bM<7G)1obE_W|^=lXT+6C zF&1&wJT5L-<_BRl#2Gm4j<{*zQT#)g%ySY;w^F7BT>(hwDZdS`s^rB z*^;Iq(%o4LKZY{MpG*jm1p?% zkUV5GBxpuzMMF|iVr5R&kT6QD85Nz^rFIZtRUoh=r+~9>3 zUs4={F1`FZ+s8AkqP9=^i_q7QF^C1Rex=vW!;p7#wx8cA}s$5I%T_2T^xl2RX)xBOMVvqyyPXlH+%q-i?)68v42 z*rnKy=-K+orpSRCx(DlC`^UaFX4wb(yc-g0vNENIyaa^0>*+mA%8a%u`=K3tho4-v z(qw-*mAx%1vr*Q03)$_5B>RGAneP2DIf@|<9JxQc0k_oh^iju^dht_i>LL$bVFv}_<+8A1IUq{$jOepf?&Y{4nRz-=e z7wm)CY+lwkH)&Bu^;r8Yp)n9GqI~2?blRF+A!7t;gNzZVjUBEA&VtAug+wbsZb_t% zg+k3?1HBI%(rMIRv4n$v28wUlw41~#tZAibmOp0xawVf_uWrUMl+uI{H1X5 zaCHEzL3qHoE+!N6C2sh)e@=+C;<&SMKG^Ec0Jh}58AVx6N2&5XFsc9?GF;N zpwFOYfNyb9B8Cl19I_Gi*R`YIBE*KpdwE1c(vPy&&QbsD8yvujS_2OFM6JO|zz&O& zj>Fn}Cr_rCGHJ?3dr>L*_vFj;FB4#PwraPHA!JG%jeYEqGKAb{l+_9H5S-~Gjrnx; zwlTs=w+9gge@g6s8tvC4ChZRw^rP7jDQBMOpYw_^s39~jHdut_C6ccysh4sfh+v1c zD*mEx7c{xH$@(G<6Yb=N0ToB2n=F|2u>SSd#r9O@zQUuqnZ$OVpR$ZHoifvYnkyy= zI4rYkj$)14i1I)c@Jy-22%+y6g_f_7zCUE$C)#K^_>Eeh!zBre+p>_)D^BV?(;mX{ zfsPsr3cdLAtxP`ziMeiB4q`;&&bwu~i2Raq=hNFNHJ+)yg$PWahc9ciza}KxAha~W zi7{&pA_vn8lDqBC*)^E%@O~?Zel;NM-wNUmo`(apAJodM$OYU*&^Cs;-kKXuC{XdN z(VSoe``ksxy52&;^S^jS0+8aRL(U0zspmYyoL)NYJPRy|3Nt~#I_ug{4wl5yG08Kk zdA{e&oyaohtedzWg_Yz9NuHo7Cz4){X38>|@+4EP$&`OlrfkWCXgN{wo34Y=(xc(6 zYh`z77%P3Y{nPeeNYm^-CQ|k(EYT*_Dl5ILRK`QjP#ABli2X9DJ zkDmQe-C3uR>NgCWb>P)rJQomb6!1_EN8}CKkM>cuuaa(NaJ83ZaFOTc%HTGKQuH&( zfCSGXu?E~B9in79tFxKLzsiSQd7e`R1o#1t5vX;;+h%1kQk5b5f(vd2_KrwZ!`p^+ z4q#Q))gMAoB&R_%all1HN6qxnxzX!J$8>HSmdV1TI4YCkq)Z}93eZ;myHAlxgYh3` zicFfgm!2XcgC9KmqeS%xQ{=1*Bi+jsIqTGVUppg{#2+S|D(3ORP_?pvJ6$nV8*08F^P?xYrxSXQ_-(l8#TxW~}f2wY4*a4|zNrtOG- ze;Y~WS%)}+7oT3SuiYr*VuDCb{XNrs%WV`I_IL8IY_E1dYzSl}oupwNNSM&&SOOa3 z#bIZ8u4?^evMDT!Bq}qt9Bi~NRniD0A?-^Gpo+y$(h4%JDSm%^JC$=(&6InPjd{4ky-pa@f6jK(n0JES_c=O1|xr z*)QJY-YoOQy;&xUd-JGfKpFjCg^1%+y>_hh&Gt{K9ovE*CyW-7tO1rXRu-rvDN5ZW%Xq@-_-`!i;+HVBe!vtbQ>|u z!KLS%pOR2ycB*6al>~-l0A(i$l?f z7z7eNox!nn5@sVWdq85?E?2P(O9v7FhUD^GH;cXRAPfQrC~Ou0K=7(ebN1zH3@?l5 zBiFsgZXXb3`+4DW+C4G6YabFm=YbrPuMA&#;CF7YX4?SC;Vgp8vVw^Z%Ai3>YacU> za?9sL68zC9X<KX<#H(j^FPOnFIug1`U2v`c58-7f7ak_lK|QY<4?ULv9;A-D|Q zf4W(SjwgzN53p$hL5l$^7D}VxghY_+`z4fqXn|OnumVSXf1&?K3-NkcVAt3WxxfX_CV5S90SHKm}6+mB(BMW^X7nT#+l?BJ>%r{Fv z${8ukoevEIHFA!`VMS-w1TQ&cUu|6@CS_nsVp7IkOv<>y;`cCJ6fn*TB%~8OZPLR~ z{0omnIZ*->rA~$utcKFq)Oj>%fBlsg{4-tvxSF*S2UDLSi)eqc?cNvlfu!x;RQqmm zF5rPA@)G`fwO*BZ0y97TKY%r=*rlpl81+fBj}0d_GgH`k-)kT8&!BG-mJUUc7z`w1 zdK+`!k0DHK!s<=z7H;5`#_wpp9C>fnBk#>}WxA$xzZEigvb*oO@-m-W|#_}NKOUhAD5mCqnDZt zyS4mjC&R7ORSpjop*BK^t5vG#kObZhZ(mg-RfW>+RFkcqi61V6UA^F4|2r^`4@h(# z7#kL@dIDxz%41pEgxR;$>IWRi06*-_nq*A0-y|Wt?eFXR`Tx-NK7dtK zSE6@9Zt!Za+@MjTMw_;28!MgClIetoX%plo5e?h~l7KaV&e+y=+V`j?Eh34b$<-VW zN7FaN;mwnl&fr(R_c=4n47PLz+9c3~0BW?1Jx75-OZzU8vC%R%YR0_ZTKk-vn-HkB z{m`6y{_MYNuf6u#Yp*}0Z=j?g>i#0X`SDYt#d(%pjU40?{N=2zg`y7h8I%^#U?WEc zLO7&G?>D@zute%wE@Um`bUbVkutu!+5V*(zz6g)-jM?&(t!)%&xBo~EMOBA$4fSbd z1k{sNTmB(Zo!TvJEV|pD7Ts;t=R^d0=Ar<R^c8D;S5T~;Z}s$XSRf5+f_s?2&?E1y<#R=@Zyy(bb);3`s$SCC;p z-TEcqEQ@a|LiRC-QEeUd;q8@=1q07^_oP=oPO+j~EY)QyM=C%`V@k6QNfqlOEe8-a zZ!2Vk2U3!iNQV8?aX0w@Z`01SCr@vQUmCeC-Q4@go+aQ&+8U+wRdFDQWGdJttH`V) zN~^4I3t)q*j{LU;{e2`;cYeRgPszB={%}$`TxXC_oga{ACaUvo-cO-A8xEgKb+i;r zPD%p8!$j~W(D|03bjtG#aYv;Wh=mDSYNii8EYO;!%$@P6b4PpnSwjYd9BC;RqDb0M z#_2!bk13WM?x&v}2#o?h=Lh-2htfbUktwCAndibjdH=POktuI8nPxW6Q+as0{Wf#B z&x@}oU|f8oRkhCRZl{W_s%EcShqg;j@VssrV6R(1=53WWOd^c(GgJL1NRfjXnW3o} z(UYuhRF0dM`SoguB!GwVLLD-iNtbU8nRO*LReFb2+Z~9paj&*L=Me z==GppkLYz+uSfOj>h+jhx1N-1EZ6GJ5zRN`-dppz@nU&acdp!QE#PW(<#BB^dLFV2 zEtpvNBYD+!!wRQ~IDbzipIeiWQMqYG+|Dv@8-cL^~n%*NNXrJlW-x z4&Rk4cAL?E_;}W6>2pnY&o+Ji#3W;F+RMa$4+hkjwy59BY4Dhr)?(5_yplgb;Xd%s z61zZi{qrV<-U;J|PWUbs()z}}7tQpSHLeB_}E=8V`x9q z_wuaQpMAYQv$;@;YS)*;sh82$`?8utJBb=4U=C*pnA1dqTBMW*zos!-fy#=1&g>YQ z)tpV_R{Vc|^AxqDo-g`Duf_@q@bx{YDlpbq5jFqf!To+1k?Hs(_?C`g3Xd{KDqaTJ zr(Mkns0are>=Ju%?uv$qVX;ZtDV=-x2v?^;Vun|rvTb=1m8cb{Rh>jfMNbIM!U}V+ zu&^TBa-|V65}*zvU;a*_lE)rUJoz<$R$o=0BsfTK@=y(h$8tK>hcoT3hwUqQR}&|D zE$8}VB$XV<8eOmbc5QftIjODAaceXPOZJ9K)_AKUstdLsIkve;AD^4cI_{y?sGKwU zb3qV7tRG=Immbjd(nk@2KmMgPfOnxC&nZ20*L8BZ7yXC%S+A8o)pU1Mlziq+z<`Mt zKU$p0KLXX(Nj&P}ZbZK9`}SD*-L%Cj-a})jp@6}s^Pj&?pWh}u!zTu; zSk~n@x!)8AI$`^sh*)TR3cm`W(t(X5#v86b-_o?EZ`tp6-(aGY2LiD8WV+YLroY_o zZ%%Uy!S{(v3;#SUetJags-anOB>04fDJ|9bcD6bmrTLObDt-DVN1S_dm

X{s0vH-d5B$eW{Zia|Xk~1OpMG$)m&prMR0*NvX5}1%pEgwsc@*~>}!8%}(B$lL6WY?`Uj^R=B)pJ+pbeMENe2iK(z}3QMUbvEsj*BC6=>TA>uUHAxy3f$E~m33kvj;Y zMPO6414aOe#4c$LF%1!!+myQ$MmnD_(?cVIN0pES2$=-y=uH}M6V%V-qt6wa*i~yO z5`8nx4eINNlv;$__ES+`omI4#W`EyRFMV=?YeV%6x`0HVl;NAKOa@&&QbB_K6Mm$S zU}TPiBLfnB$6Xg?za_)q%diGoQ1DPl8od;Wt>sqs&j><{Cb89ZE3k1O?N&duQm|)I zc%^Bfac;B}fekqdkd#}ggi=mgn%H&1wZj)XvLqTkRMPM|>7MjLr=h-;mIED-Ke6Q$ zCB|!LbLMr^nV4CkCen;xi&HF-*j3>MUJ46kR>t+W(gmum%E@W&22*-Ml*07A6qDOp z+d9Q%Sso)TW0yu!FIWS(muybZRZeTXE{nug(~MB_8xrd8C{g_d*Yyv(vyJmjDlps# zsG;$ilceO&G-F)bIa)YJ7w4l+Lg5;=A>4vCubYw5DtX@(u)}Sa;sQQRW$6fntUyOZ2%7W}3`0rp^%1Vj5Ir2g(TSNsWO;(GSzLlGy47Eh8O6Y&mDt1CyHg z1h^$p`66jOWOoKheS;EeJo5WC%~l7cnoMh8upii%%dK*Y05wI1k4X?S$Z1O2a;CXO z^(Hg`&Mr##etmH$6m6gsF5hm4l%dLBiILkd=QUM)8RopahOhPIHCI%}b&|5r*9@W5T?3+g0BS1;+NR^ zYRWK`E`Ib3(XYg#VKh{nW+xtX6P4(T+1LaoZsg%ffyCAi((Py}wSa{zL1jS%zfsc2 zEEe}k;?dIvb}h3zMqM*p^NXr?tPl#aR)LZWl=(H8F}R{iiVbC?6E+06eJ35TL_t)k zeXi(TYNBhKmFCuhN=ZEGTK@ssr1mVVb){GjS6HE?HmRrA>cZvQ)=?Mlcp=__CfrlR z%twy+8B;=-`K_CE^?}c~G?c!5ztwP7+i+Tg+}!F8w8Ql+lZ&~YNEDRQ+}fd0r7YTK zYKxe_L|=^x(Nfj~Bp#h+xe|TTaiiQQ{bHRRgFLFNHP^SBBAh;*etpv3Eqk52b-#1h z*13yWpH3vd{m{g3msD?^hj*|VR54~At+3iMTZBJ218uoV3sM`?X73(58H1CTum6X5SOc(IgT249C`)OSG1NBPpD;^*qS4?rO4Y@zh%kW*X3|_82XL9vjCQsuzSotqHHJs` zI`e4jsBt>c(`=m#H8>ADIKyFMuXGZ`%X~lDQALBJ7GVH`q?clU4NJmXLvm=K&7tp^ zp?+KD8U2T~RK;k3^3Y zH2ytzz|!2Qa@rTHpm%|^aRp0hOYp_IsK1shH1;V^gP`YbH>w8!J?BxcHQfw>W&+v( z9^jVqFp?QYHqg*%TJ_D{)9~5Q%r`~WiLhTwFtT}V3vGesr%ZDTGwjzg)=I5ljSkov z(Lg8q2DP$mdB4?)|4|oLM9LF=hloG((y`Hp2a7_=hu#}E%F#rha(a!5_t_72Uoz7y zsG_$&gG)q*+ zV&M)=5q3b)0EF6~wsI~;GQ|~|#j3(j|Gk|gLzI{03~XR$ik&2DYD$NP@C1;x^s7TF z{Z-Q1F|$Rx98VW>oLKTOKORj9)^Lol^fDw(bU(t+E{EHxGyz{Lf@_U{i?9vTbMa|B z=qpHU@yK|WCKd`+X)PqSEVc?q9wI_Q5(YD9N_XPGR89Fn^vTQEu%#lYaTO9i2{HE z2v2F*8i3UJ2w)a|_U~-oZ>H&Lp;4!TCO=nQfHzitrhO)ve`1;wGtzM)J(@vl6(8wZ zN-BcKT@FlU){_JkyrgMjzD!YRX3b2~A(=5%7rKqCVp|J?O7vISFb1vDjfO@mvCEx>i6xrTE&ed+d~sP1(+E=rvi41Z}js~ zPRNa+8)kL2xNXwU`-H9yiJUU{Vl)jD*XJ6pS~6%=MD4T8wq^v(6c$W%*p{6$!nSnI zaED0rx%}u0jd$qaEaC{L4DF!SLCV;oE$6oFR!8P2?%1s!7qQKLv!${O4t8V=mideh z@yg^JNGuj$1Tw1FDma$wQxUXlPb)u96;H8*scUp6*dbyP#p|r0<)+xM{~*~l$?j|m zw%VCzHgN3_^{AKYs!*>3)M;miCk|ujXESLc8n~ax^$b`$A|}5#>rq?Q|!Qm9&9O4H*tL zglB26g=-DrWYLZ^-7u8te*ywV3#noV@6x~wAziw9I%1m^PBQDRgUL;W9bW*~NeEEcZbc{N(|Ggq>+-v?Fg1mvB?*s+1PMzfv7bxbyr{cJ_A{X z;|;nOfw`5}{Ep7NpHKGYcMm?BPi~O&%2WAd);^xjKgSE+>A~moTeRWx&kjD9w>|yJ zvw5R<<PxnJFB+~C9OZ=Jdn9jDyoPJKMksyKp@+-Z()tE9 z$%tqs>S1Ds_K$lW?CJHmGy|l!O9(QVpSdgoogx;xEZS;53t^e$rbSVI%);z+*7%By znSQ6$0bNy=ey1&RDs}ChQsj*k!J>+F?pfq4?(zvPtU5ZMZdKc=Y%~+Lrwh-++3Sjk z=2kXhp=HyT#IR|Q&F$z7>tdr;&x12`<9~C{h@a+pt@xyP1ttshPS;*B>u!5eyhzW7 zyw@EY&r7485#=v*-~4__VN~nim+oT0D80Yk6y-O5qhgdv=8H9G!#6M}()%CyGShf1 zF+dEe=KB7ekdE9Ocu=~>{w?oWl4x0l?9HXQ@?H2wi7Dmuy`Hgx2JA~pqy-VM@@sbY zy|n(LhyiYf8D(PBRS`|u7+!|-I-1nV(TVovSqh_`u~LwcsIUT>W2lNM3tK9}^|7TC zN9tVpW>{gy@`e_|1Y7Qu&X(R|c_2eU2CP~#>x~u?o-)y|ImN%x7Vn;N_l>Gx_dioV zbCi3bJo`QWY`?v~q7#`FoIHOe5eiL-skZ8;S@ktm{dDGjZtXL3XO;~UlH6BTJ^n)c z4~jAVhZEzZ1L(XA-==QAckG6FahScr@zP8bOg=8qs{46xU4c*BsYjiA)e+}jd&s$y z2Az9(4g3-pSuwP7@=i=4$ldSVQ|1PfU1^EQ<)2r4F!`Y6PF%Ow#a}nz{F7ro{(z4k z@bUY7eBk5v`uIT~zuU)GS$tx0jd$yO`X_z*f=|EG`KN9-f8wggoO|tV?;dgKu6@j> zJK_9Ow>$q;m9E^Yd^;!C`+6Sn^^9`$UsvnGuiNIruRZF@pL)`{S5^CR{F}wpY0f|O zVV{1c^I!Xr^G|Ma{>cyedR96Abq_fI*oR!XlOA^N<=dP)_7U$s>D>d)opjLq4|(^v zcTan_!pcvKo#Nf;-ksy!2JbF#?xa@lU+mng(%#?c+^IJ^_u5;XJ9&kVU+LpFn;T5M z-}Pte{ocRX`ycZDP2RuF=iBeyf{)L-`0G|X|8?F!^@NY__3;~g{03j%NgscgkMH&I z-aqw}k6-8Gvp#;c&p)c(*7wv^K7PB8U+Lq^eS9(hP9ML*#|J+CR-b>jkH6W+SNix) z=byaJ;uDh}aPD<`eR{v%CJ#FQ05n$(=2`B@|iB*9&qk;2fhE0b0;75{^Q;~?cJDb&)5p@ zR(W@dcWb;m-Mh8kt@G|2=U!g#{Y~Co;M}RL<_1&!_)lHz{l1@*(k|Vk72aLv-3{K& zdiMcygGt4CyV?1R>tfQwKEA<^=dC`z-uW+I<@}fXd7iY*{E12Xy<2ecljivRt6lh2 zy)OJJzn(AO?EIJ4`E>U?|J0ezKk03Phd)&Jxy?ffZlSjGsOo};o zY=v{Lt@PnlK75LEr%v_dr9|2&-DF}$g*SO-G?xRvD-m0n3%r%i7|{J(bvkS zf);x>?cGk?eSMqxy#l06DIW1LPvRE!dl&w96y-)0!Vc^;Zp>3*Zh&~W;0UtMFn7=b zVgzilPmr_RpcMw41y{Qb4`t#Dfthc-4@eaN5$%c!e|KQWueZiy0CeL&6O)5$3 zcu2+P^y^;y)>e{A=4_OV)R}Ki)3=g7>}p+0TQZR3f{q$uq!?JQOZE)3&7~Me7-StT zP?>gGkmhlQn|r361{kHgT{ZV@cZnNSArIo2mRPuHa&TJBQ8{7XA%3GiSF703GF??+ z@8n9Q@WWXunKovY*O0%;^1Gr{SNeIfOd(J;K4t~#mxu#|9N%<3cdRI9D~mgO>zM(RYxJ!o2blkTKU0#9-5Ymbp*mHL z+`GK1PKCJ#?Ds+a+A1j(wrFv#Pm|Fa^(l>i=u`*;Ze!u-jthBhhN_wQI6Z*|8`zM! zYu^`{A7dr@qE_n`jYZV2Uq?<{Y7?vhzbng$hR z>#m-Jhw!0kB6Ra-!Lx0VV~It#ZUds&m}_N`y45fH2ES!u_gP}yM&tMW+$MT66|g7J z^8H%*m;tq`2oHTjY=8Kw`jxs=eEs5_tzHNW4-k$8Oznj2d}`T0=G>3*EvOXz$d;?JxGk`peNm)~>lxD}_sg!UI?8!KL=8cshMTQm*jjQR|KS>vLddKy|6LfbiEPRqRT)wk8j2Nj8yxqg+7#=7#TUwP3Lul-eux40`iXt#^CfMBwFtSB!5Z)rs-VaPb!tXrnHIhffL{5eKCmGE5YgTkQ zI|(KktoIqLXo2yw_k)A|L;GPhttxJ2+YbirZLd*%Zk?iSx3a$k{D<~~odM_H4-2&) z{IcDo60p43v?$l#3jD)Smu0!tgZivez$wrKr@*Qc#?+#gvyEH|(Os87ls-K61Sp}7 z7Oj%P=aj7yF8trII&3|OYGMEt!in)aYcbs0FU%#hgGW#K^lFE~^9ITxN!#8r%ZRod z_4Vr8@o)V^8?|?mn#_9YfV)~-g^21ir+q+W8Z1H~NCr!5 z!oF%3y4phZ>QiE7Sz1f^+Nh%4TSYD9aB{fx>nwe+RbH()zozHck(6y5wA1w(m-H@| z^ig@0)Gz4+Fn_p^EpBrJTdXa%Ra~bz3DVeU9Hnxs2G{YHV#iz5aYn7&5){iNlPJ>d z3`AmwAR}xWmO$TRC=O2hw6L$jg3@MOMB}s^?8Nk~xL=`^A7hUPThwCQex<|BsUFC= zIgJH=Uv{T;o%R(mjNEpYPTSicw`+2cyWhe=^)SQ+!;g|Vv?T0r)wd-M`xaaHla|=w z=wW{vU)a~luT&-(u#NZqS9T-JHio_xzI6Pwd9tJ_bee~ADn6r6wXPZesoihx&})&M zb%i;^^E5(?&wV8UgLWoHxdjUDS2=_B020~FI&{wYkuwszGxw$4@dRU0s3FC5N`Qx$ubee0E8~gLigH*k7&`=&#Mi^kMf8w`{>A*t%vZerUUd z{S`{qe~=1*x?wKB%j7YEETR5N^SM0}`5Q|jcSx-Oqce~R4p@-SDe4*-7u zGz+OfraV_;VQk4;`3U=`TMV1WUB|*)t;N&xnHEFO>nwzx&#@4CUT+~aT=*=64mMdx zZP>rSLS}}!Rtu>M`xjft9N4D#&;Z?Ru6twRSt;!AbP+oS)E75O9fdHrirH-?!=oAy zKP(+b2-rGk3H6)pJ4vdjuYvEde>LC4j~JA7DkYcOjT83Y#cx}#Un^jdTR!{x6jQ;u zQ26d2myNpD%40Najp63}8tV6JXp7bmyRYvA*;JQ{PxyH3+7jOj-X^W$QErZ<9&S-5 z=sRLLI}P)H z)UVnQ7VfvQ;Wz7d1%V75w-QNMZwb{AB~(YLC~}(}8Lj?K3qhy->802FpsSGkQRxY% z#v3a3j~f1cB2^LEb5Qv0O7{9NDMi*F2C3)9it<>wxx<7nDI+(xx)^08Mng%xzhXqZ ztB(Tx+e*=2QIdz#^VCol$-~VAQ>bsi)Eel)P>kz<;u{pd`26v2{+s{${u!6leyzTW zo9_5ZL6=91R!1fa$xW)T^Pp94I!!3u^%`lpqwS;1W_VzwenWp^;$y{h@cM`YBjc) z2qoWWIG7uojY;LkS};D+LrOwF4b>S}e$-NHhPaPa%C;gZYleryr5P;hU`Q#ar7kkw z@#s=j*G8XRBl!FwRl1tm(X|_!ePgV9Vq}6WRD+nJ6&cdUOQX>Uh5wKOi>yrZ>L}ii z%#^PUJ#4NWDD8@pO{ELXhZGhJ%?HYn?6EnxKISZXS7N~sA-!;Zc__p#fu{40TCs~d zUeUsiZyL8?T4&uT{%$6bF#^KQ=cpMiu;z$|&N;3);b+FY3-nJI zV>rb$>E1&u>gJ7FB_2QbJnvue#a8Gz2FsZmRXmOCMNu4a3>Jk`GV9?s6sy^qTC(W6 zRAlA1G87=Tzr+r`bK_a1WIINg$FagIQ1$O{RV8*z^C{H^pDnSYhCnmmme?U)K{6R% zp~)u5qaidoM%6V(iHjmqMd^f$Pix$KeyB3WsY^ZZ5uC+6$haqVG+0%MT~l0@>op1{ zm~o!cwTOL5rP8$#eAuT|o^}so)$iP34Fg$}4l6b@v8&2g7LbzhGl8;FiJh)Q_$w0q z;$(2l3SLA*bLx2=T&`O!b;3(;C3e-jqK${dF=-e(6P3K@R)psPEONQJoQ@mSb}1D+ z>I$(H=h-`oOercwoF4`NaEttk1P>IO=0ymaTt9@Hzu=_-7Wg19-QmVQV-Ux&FYa(d zoGG#%{?ZG#xlzuT1RLcEOM{M^-pd$Cv@vnpUQtm{fR)+{!a*${5muKM64EF|&OX~9 z>%fGD!qf(GBHxN>x4v~Qb@D*ed>}M#OZ2o5-F!sJHD94*uM!wTOCAi9z35QFhxK@r zxTH2I;ZY^jDw5^biLlS*4ySl-tNi1U6~4(}_7cvk#mJlABs0F@_2sZT zwB}lZMQvhsYpyIrBDhf|M~Kp4IXV_*g@oi zEEAGPMxNn;q2;C`LrbE_&=SXb#G5jhJe;xR!Ry{X6Rt0ZG2{!fcRND1TnK{0NA`^x z_2G+A*8Hh(RS?}?9%vt{2=j!k3!+5W+RC_v zt&0(?4_nhw8>eGuB&F9ko6_rBP3iRtQ+mD9lwPlj+E9AEI%=y4Ti2P=>${@1+Hms* zjXHX-&1#iYUvHM8DrvtqOI56i4Fd$Q5|Tm(Hb`&P1?>GY`GsZw;r%lbTNgXY-~8ah z>?CfyDsGCF1RR7tv768$_jcinQ$WkHx;-w&C}vqm@qLYf!?7CnH8xT8sOU$%ySv9Q z-@Ut85vWT8;d)v&MVLy%vlc}iF=o1@A!J#ci)R&4Ivv0J#;cxaVMElhc;9@hsDyVB zn26?s918EsEes0LzXt*M;nJ=*U;k*?ko~w1fU*_Z$XOhde|O*lV{$?K0n*YC{&4RN zn1mMU@QsS-o!)1=%cB#=M#~as@U0CD4qLUc9?yNf&ePI>=R2BEV%nSVd{_7gKzKKQ zupo-Ck$DN1TS(@oScQ+@Gi?G}D7m3keg4c^!#<36)>!&)yWb#3rlsCVb1$a?6PjO^ zHG8X>^;BY7Fmm-qOb?njdnAJ#dJ~ z>~eJnz70++R@jW;IYXil!`hhGksc~TDA)PRm^Pveu%DqG`T9BhOL-;rl96ZuxR2|> zup0fSfL0Baq3dr2qW~hX7dg4nc^}|XS(GQh2ZK#Cd{jt{j z66lp1O)KG?UQ}BNr!LS7F=Q6!!}Z>;Mf##r6Fa615fxE^cGL-Wxg zm>O`y`8@|mT)Z|I#399l3^_(>kRHPh!FL?v>IEtj7#LQWg9KK(lWLUqJ9ooG31z=f zT~H(XP+IOzEKfL;=Yv1)Io|ti7u(i7yKdV(zE&`L8zi@-i&s~H1;3vf=DBQz#{hZ>d{F+xqQijnZB)(1XDfhBk}(+BQUpv#unQAZ%~$ism& zwG?59g99s6?;Mx2G%(D+gGcLzT2*9OSihKIZ*M)Ppj(yHEyp5=(-ixHRu3;o-&(80 zN`@%2=4M(9rXQFH)|ypC-vcDQ8w$6)YM#P94}e!v;NZ8Esrc(j zloE*OQw3Z(bVCt$H`;r3Wl5o@H;Yd?S-59d_s~iZr4DedV0(tC&5AvU>PRKfz=f&% z6*L&71{I{a;BKK3-EHJS#U4T5s$w;zVd^0Tp=tG~f~Hu|!wPcul5NThhJQRvJ%Zq{ zMQ@M#wRC0MyuZ#qcEV$CA5TVqc>t^fV$8;;j%|DoZ2t!$INAQB{4IVT_v67|LOZKZ{M))niAxOGZxwZ6t@Joo>S_df7don^jn zfDKJ+>|Wc6nks5$nW@*;uJG4pm?(9&UX%QVc(n=_;1VfY1tS&Z9 z&mkQ=7nFMsoy%u#JA>zxik{L2iiEb*Vu#UerI=dOjfo>FQ>kLP-{14Dy|WWSTOH56 z_wzY?$X@Gx*Zb#r|3B~ZS7kD*y^O2l6@{OJ>7jE8u2zL5hTAz~ zLdd_*5b+J=e>D+ysS4&HNI+>xpEo+RRr%l$IVjitVK)2_vjVd_ljO*4`J$B2^*Ve- z?(+)d@nvx9yG@u(a(PUJf5KWBt&Ju{aYJFZKEv(a#?Bgl;Jc>{3AYmrSPPf<`6Quvb5a^)oooeU|nl5ltJ2 z%k71-be|+$?jP>EAgkBvB!!E~q~sXrR$-@)k*~Xpx6&}{F5XqdUY@z&ORrvB8Cb7V z%+v~M`Rc3KnO^zcgJ1iNJr8~L*J1dVF1*ss{Y>`|1&Aa|U9d1z87z!rVd<5`<{sYn zD#TcUkLm8cjlnmUW$F^TA2+&$xe*dzVeK8%z18)G7vCi|AdQMy|AYxYyMvOfs@%M^ zUR9lIVHYjTz4RwAjMcGd86%M`}@RsYN}-*fPPvG1Z6k3n(RJ)koT=LvqQK-`yftxpK@i{~rItHJyhd z)kijrXkZxq@%k#``8SRpk}2!fy84vYnEP0Zb$y;i0?AeMTvb?Ni z&c*KUZv4{#ZbNzJ{QVfO-H$7(Qg$|o^0j-@34Ot82L66c>-Iz zQx;Tk1{q4})z76684d9g^PKEq>)uc^jU6&CYh@PKK(^TjQ@8dzdo!!q+mb(U24{5o zr({Y_cu5L8ME89Y)X>Rq+UViAlVNz3n-p! zpn5B+_>lzzQm3#BBq;>2!sL&gJduEKc0p#SBuA!=Wmbd@g%&9!QAB{yWL8RK_XscX(f(rx z<`dz>x^!n7TLMUk(yDt=q(7(kqxpQZEguJ1>e75_jB9L|xwv(O66RS6B6y4`p#U@0 z<{L7Gb&-d`(?z3T`^ilI9ig!A0U?xdPloR#&LVu%%d1HWF$Fy`0*+aw3)67adJnn| z<*2m#rn8cjr;gA7LQq@-F=f$&u8%;4bvoCkVa)~t zw#(^TNg8xD#Z3s&EENKR=<(P)>5dpsn>{9?N{CuF`GZU~&+%nvnORPkrpje2ElQ*N zIN8eexaX&ab$DiEhscOUeuqejBs|ZPjg8}@x!jKrh6Z^Wh8#E3kDv4#P7lO$&mk^{E2S0cGZJoIfg!uSWp|!Xp;~5(XB%f zlXcezO>X~yX4fz>JuCEl)NzLB6+gZ=fWED9r0?(o&>z%cqyH#eZ9mRjoX6~GKdwQp ztMTJQL0!FWWX;p%amRQ^K~2RJN497Jr_LWOK{MMir;BX3HqU&$Ge$>UGdy0_948Hy zUCG;aBEYM-E?%kM?$EKbBW6}AbU-#QWDocOR$(a<)3D7;+?HVH3ilm)LmxO?bQIZ< z;|$m%_L{>0aRR4@c~3btn~2*QNr?%9z@0YfillP zcBhyk@~!0{8Vkt`D{1sV3QH=qMF&aPKzAVJ+cd7E@L)>2E_t1UDI5nKFTz&rAPOPW zL1E#rGshf3;mBZk!z_E`Z44F;7f31{KEkO|6>#Q0u+ZU~tY||o4aumrK~pad>6Ks$ zrZxP!>AW^<(^vIs5Z{KQdPQwllm;qj+Kz7=W!MqKciJEo3%a0OmHwcz0k6b+j#=^k%mWtrv06^86eI`$k>aobOgYxi&U$Ir{L4+#Z*M z`U-Th)S%jIz3krs2e;)&orzeqg-m=nX>=7rQ{`U_uLmMPmr_e6qs_}A1_4{Z1e z>~{EgpEj*-sw zH55qc0}%-(mJBZr>g7ZrSQx7bK8E)=Lry=+bUcxn{x`EsuB-mlhV7%1WR!eh^n-zE74Y|SUzW^kU3q?KS7>QJ zTNf^sa(_AhSui*=sF;L+0j}cKk%6X~&dE1@cFRz6@^dX#<$%x7Y zm5l=#JnG!cYR+HP#x?kS-UKk7S|2BTnn$88^aQ>(JAr+NR#)(wZxz7H` zqBi`t4N?ky{c+2T0FQhzsy*=&naEf8HdQhyWH?a=Fl#Rh%uVBfnKTZVcNYUg+RMR_ zU1(!I5gd!E{L(tSHrCESnAL74<8BZS|K#Gu?w6zYbwhDwFs^LL1+KKbk3rGIivQVt zWW0RKJ+ELjG+sXEDK|6o>j`@^`2S;1Xo-CIASg)B(iy>%2)SV31$Q%uDL0kf;4iI` zf5v?rszp-6qcu{Wc9w9;KR$Bll>g30^HV39i}ywJ22zVxRHD=_kWwhrIH2pHG#8NY-PLJg6q**TC1#C|gx}gtUD691IH{99S*?zhNr|bkh{N1VuRJAyZ_E>!qpB*1o!RJ5M<*YMw8b6i!&rW z5Fb(um8wIkuP=*k`Kf@6=U8!3tB0k&j(VtpUDGUCN4x$4fCFn2N(f4C7FeCVszmPF z%S^s#DX6h0Wh_s&PQCS&?ozpm8`}4|39LXxN;jG{kQkxzq$;=xyue>b_*?;|EBa>g zhvZ!YhMp&MdiAp%88iWoo|4^SGbg#6mssXFz>r>aR#R{k=~LGW8F0@V820Mt5i1_7 z;sz~;GdfJdI-tr?E(A|he5_8fAeRE<3Bsz=ZlJnm$NJ8@%$Wgp&eyBh1Eq|Gir0K0(c5?r1*kofnRem>>$%hzPe1f_ zVWs9}vGpOg?-&I*36b@HkS{o2h{QwIm#Yvb3bI~Z7z$-8PVz-#@*N1Bc9}AvXp?X zhldF~7($EqfEggQYi2kAg=?pSBV^#aVnb5rX=@$~c#6unwyGEnu1K5SgAPLTA>d`y z)2{AOis_JYx-krip}_7qp5k*#ZWwB%;zpbRvo2^&UAmw(bm+>~)T;|x!xml8ns(?q z*05a{G|@pJwC+KrifVmsqBimU_%>bQ#JJaX<2URLGAH#C-=zy(!(+i>@UMqZZYL5I za(c-Q->1F|>~s(&*=NCpSJgC! z?9yE1hbSzXrR79j^*;6KhT{}5=Vdg32~;16Zta=M!DGuLxSs9&%h;g%I8piYr!1J~ zw|5|`?6-FX>(Pm_GiF)E6nB2L_XO+97_Xb=^0Q#J-@YZ#g!TT4T)n~ia=(3Bu)e}? z-yW==?zis<)>r!Ny8mk93aU!C4@@c>Qp=fmmGf6Bh0gKdn{~r3$)o4HoDYLNq5FK4!un*r==|SBHl6@ z8tZclDya=|akR~?|C?8 zGXm8vC3LII%X{<*_Xua&-0E3IS z9Z_=4aw#NXL)*ea5Cychq}nK9EF^9lwl)=)RA>t&NfKq$IDcLq)qS#kqJH z!wq6ejqZLScw@LhA%un9FFa_>?75>oQM+wfC_K9jcnm=<+#roy9-f~o+*TZJfX+co z&@H;4Zx$*6*tjMtSUnna-(ufpxZ(xM3DP(bL;H0e&Cp&{y1oj2gHSoCK;<=)p5!u% zpn?55?*pn$1b5YDdrs(}V3oziOu#Sb7Ol~D6glUlz*s;kPvp?Retjds}-To(OL1KJgaHHg>BPz3D&y=xqO00TmtRq zcf3Ug0Z^`g9m!Z=TP;JXQ zRNR)0!r()W&WtaNBMmx!PP5z(#zsI?r&UQ*|42b9WQ=eKHcB!F(ECi?>7R zBNX{GbPAtT?ED!j>qPzxrR#YI<%b|leE&jCuydroxuyH5XxBr!pM195R1ERb2oEeG zg`1kPYz>b0E+~Fk+2CKz&NlUDYvvCA)!b~;;cU%3uET?WH80zAlyR=9%pxG*wN#$M zltN0LfKn}x7LI074@f?ZWnoMqoArxdWn0gaaP@hF@1pm}-$-=UpPV$OINrY#+sl;2 z(xAAprt{I3=P|fsrpjZtg6hOth0iqP^l@ZeE9JEIV+k3V1rRUurq+5~k%x`{>mpeB zUhC07IF^fmiU4_Ikmy^7;AQHwN`;-&aFW{_;RcYPk!&0|&9V*clWlTu!MsKqa}cRK z0*mMJ4*KrhG~3K&XX5?HQw=G_NwbeF$eg&CNuf0zlWIxfA~%2hMed#9Mb2#De)7s! zFBZrH9>S2L_qnBGKQYD=%+EJ4MRHANMz8K4`!AQDzM`N1UhVI0m}3;7ej)j5=Gfbg-QD$k+cZBs@AXv@52mpiEQo}1`p~V|_4zf0@7$eeI0Wu^wxdmw zuhJ}^NABEoZIxCHEOHi{Sp*Ep4=gbjGd4MdB2Z5#k&hO*Nk>Wd+bMoS61M|5sE4Mb zvpGm$y~2*ydaPI2nVhJU1a&<+f?YwP3d!l9ex`0l@;=&&O#mrf;(gRwCVflXkix!$ zZSNrit;)G1Q8RZYszt|=I`M%@h49X(t!aNiRoswNPruc3*y=fI^&GQ$j$1t^s3-d9 zQt}T{OJFiojgYm21?7nrgl&Nzc@gWKs}bhVaj^Cy(5>=hq7QI#AaoJh+xBd0xlX$H zA`1;)jX9IzE#=n{NJQaj`mOuNou*EH^7mnpHMo>smQ(&0?&<^v$(?+?qvj)fBEr*KA_CQ=zf7)Dg&IaDL6oV{m@W zCh!=nH9KatGJ3lNE%IC#jA+<{*?Ora)Z=^_kHzJpZPZuz4g2v-ERV(Yl|E`xs^M@@ zU*$L8v6vc;1@*H3P9A$$OH7vtA(UC|oGb6XKw@aL08*YI1mj#sK@3Vj6X9)-cl$L}Sz& z=}zq6V}`%n%a3{OufJeU-CvxQL}D^GvafQa5QWmGalfPimRo%#O$3RPdJfCHW-pGV>w3 ztRf9_?@C?iyiMGtLR6A8r7rOPag}oyKh8grvj(3$=ESo=a?>ZHEvKV@dh$yzA6|%2 zUuC>&8l77?JjW4hv2X}wJ3sk-? zE2){@4vS&^+86^&`U;?@%gm)@+6*QnNSLNxX^+I>XZeP-Xai3(A{SQ`D7H79^ZAag zuS5uJ1NRh_4|7`vaO5yPcu9gL;lFUbASKfy@fcJAN(V|-arO+<$ml_d z(O%L)x_?*NmDJ8Zk9)bk>im-c!JBk+>M+F>kM6Huq5M(!?~RxC)S|jzT=Ic&{wRu; zy5+bed?|mG4wDBdj|+H7hhQ|no;5R$)Xc;ZRS_><1zu9#=+-Jd$18xqk_k;~YVfs8+`FUn=qil*`ek^_tVVPw&;Ds}OA@}c* zf96a957z2up|TxpWM)_1>?SJWJ0(c4!2T}ro2SeAmc)z7N-%BGAHLdbK>s8-**Q|$ zdHO8u%%lphL`@bCSv)1e%9_+#D@Kq5bzzb zzCf3UVjk4a)?hOHsi!5$TMGfgRM_No({_l~9N9%2F{((qbuUSu^W%qu+IhsuC4Jf) zdPAs*nY9|UOhlXTRv!&&A^jgC{WpxfpI*J+QLmvV%e;=51fs38H?s?++D?ZY2Y^YE z1Qm0a#zbw2aL(2pSCHK){q5+VzI8gZ9lm@-F8TcUu`j7H7dU0^nu>|T;SV_8q!PnR zbE!CDWS8>>9L0P)I_sNvT;&2zkuqd!aNeb*NBr4}Xv;67vwpsggO70P&~VJf&id9m z_#=u^H)$DopA}&WgN)IZ0Ztj|h#nm-jn4Xp^f4i2d(s_1M42C{&YL~lpYeoM>ACI) z^tMN=H;s1hiHMXYX-upU`i{haXXzP@G0pYpe8IR4bx@g$0tnw4yBF^ zf5hlxXjQnfC?~#`I$DusIG&9^;;X_|GiS6QGT5*`+d2r%&F8>Hfxw*K{ShtrRD>VH zYCfusFclu`^uPQt8_~^XeG^^kHO{4HG>CMhI6dfe1`TRA!GE?hG0U6ORbrszXo=VX zNxf!j$*F(X0ZCc$3pW>z$>RM-C!Y+^eE6Q%+^^oi5tjSce;&Pm4R5}DaVDS27Q2fr zmZRCPN0PQoc6w%;ou0XY?DWhtJ3aHwPEU>5=~);wqTN?-c6yeWogVD^V5bMWKG^BO zuFp)f>oeQz`poqc*!7v`CsxTkPyMIa1XTl;eXs2j>@_V=2%S^v_hrM7SXqUZUA!xU~U?FIaUSuKYA-!;m zj`_W!^b3&QN9?84@72vy;PgIbFF@39FFXn|0CJt7Wr2_k~#|JtMPu-WS#FJU43`c>bMwpXUSWYM%d~ z?sTZGq+%q{0sl~zdQ$kmU_VW*pXb8f9NowugK!D0O7tzkX&Rg@_Pbg2HEk}8NAJPOH-@e!5O)Uc&E4}iLsxPEl` z`@@z97S5!xouq25lk%x(QcPU^{8*$C0>qN!J63=c=V2&fO&!Hq1jm>xfU4Wzc|ceX z91}Q64u@6nAfjUl0VPFO7ZOW6CzVv$oLRB99%^(xo?YG*RFab9f(Z@Wx=kgKq`jBu z0q542Q_&L}I|=TZ1uK4wlahlBoC+#;$)f%oem5etIme)A=RsC2hj}=g-+p(5On4t zx1>ElsS!*zKk3Y*masWamjOMuxZNuL4y$+#1%>7dW5z2cY-Fa~*WYhz+BARjYx;q4 zYx>b}W}T(J(N*PK0UxC@GkD;HLsrcNl+elVs(E@R&p)`ioU)I$e8YLTMky>py9Fg_ zYe}k_%|jMpdy=V~C@x&+pgC@PA%leMVm5M0;1;&GHLS0mk2ZnEta4_Jw}w7)zLE(@ zr`WZDRM@LtVYgFl zOeS0MCO>vb3TlfrFu6xtD1h>L^7|;T;Hb7Zm0+-qD8K#%0}&}EdJ2>OCInYXHs{~` zA-7*k=&m4uC*WAsmQpi~A}3hYI#X`ubF599TJh4>s!nNrYd|Ddk$O8Rw2<;g1DlIp z+kxDwHgZemStp~)pk(p=;c2Mb1vF4nr8 zpTa_m6Y@+KH;|iOU5&j^6J4BSr&Z-xRQZiSyIETNTT#wrWXIE)Y8W^3O?0#B$l;`t2+3`{@ zM`!(P-AwY|6B_$FO{6X+FY(mHgkecxr~mM}E3&yWax&BLkuLmtmb9G>HVY9`RvE9! zWNLuU@80e#mvwLBnfuR+FUYP*iEWkGPABg5?3&EceT1xpsT}jRg1=Swc;+<)j>u%zZM#j zQ3-Fnt1&SnNdN6!J?~9)wGBkVy81f0cx+d%BgOC5)fp|6iMsl~H@#+8&$tJ_6E-yY z_5Ibgh4o#eg~)xt$w!nI`zQ904Sj>AX2$s^PQT^%!C(3JR>~XVuauAVPkg!k>O2U` zIQamPggZBuBOd_sZVTNU+)&=)@9X6a9}@cQ?$K|bh5+Fv*e)1bh0>chYL8m2^x^{7 z&EMc=8@POlNN^ic1IiuT71@tvQ3P+7sH_8z@Oy1tB-;Hy5WT(jI$r*p$>6G1xb3-_ zCx3G>+gGMgpJ2d(Zg3s@p11IxN-Q`iW?7AI+mGM3pI~Oe*v8zj0Hg3N3YoMm9OpU#8r`Q0)5VDNo~UCGuP(n3Z8% zeHAWK#yuei_U&>p;WO=OZUoh^3=b#x?04^WiFUeYdZ)oD8lg^!XZ^lC?)!u8nSM|Z zIJ^-gFhm=%R&J>)FpsQnA#_3UABi^Ym)yO+VXbrCjXV@|A~_dJ5L7w z(m##<>7NwS62C;VGy5s1Kui4diD`*KU?0}VL|cA}@Yx-2E`(=2E7BjxF6V`?{j@OI zeVPGARQy*F6^TlHeHbh){4ncZ)_u^$f`%r$1%SR;`L9V`412}vR!@_~dWXVw(-38L z-z{NLu1C^4UD(Kv88^J?fR^EjFBGTFeE(w^Qo`A9IT76U5{< z$m!`ttXt2zm8XHm5S%deBTvgR1^VkKp>s(so~MRZx9*I=X1{`75YQBqDR?1?Fl^BC zm{p6Sg~hr?V#OXH(6%su*7Af5c{;6UovMj^n% zlX2}-5M79N{f6CioPcArYmmrL$nlKmS-4%JT<0T%rkhw-S0=#Us589N?7hmqa=HB|P-{3XM5@xn2 zIXwODD!tT{oji)gTB^zJtl>96s;yriVAk~=)(d3g%u$O$HszSbpa6K>VvtQaVKJEC z8nhT>Q#gXiRq1DjEM^97q%5WiEj^2wi8iCf%*KtB#mvQxl*P=$jg-Y8PST?ne9!h> z&4!;VKKl}Wih}t(_(;Y zw#5L~T#Es&c@~50Dgeyqz4QDP(aguLWL!TV;jNap0h=@87a}h8X6B=&=6;|bj)r5W z@lphNO4?xl^bfziFm!ILv^s2{@^>8D-^IzQt3d-c-Y=dl<%$~v3#_MM1P`!XPlC-~ zdwor`Ya3fqkg>_Ns66*BM#ohc7>d$ySA{mLsk>rUhE){pI?eM# z@S;0%*k1ZP`iu4eZKaVlr^T{HNS%dWMD@scCGd>P+^9a#@H>(prk>KYu z$)4^t)LIw>#5WCyTkLlgzoasHSt&2Fqhn<7W9)U3KQ|(p_D2hp^Hl_Lc0nV>CMpO( z;d6l*1VrXYR`Mbmg9WMxNJZ2FY|TbhubIn>4(B{#RKvzBT4T)mh&lWz?^BwQc3dd> zAt25Dr)lWUW&}Dq`3~dkern@V9JLZfAE3mng@E|OAGwR}ls>gKxC%RtsL>%dyBK~D z-U;B$lyzDz$#Pz_Tx~~P(Ws`lGAcR_sO!+$Wquab3>v*nr5==_z(Yr@el4BQ5MV(>3eV$MNClk!71OGa6FF` zjpcK%F_!6~$|J6ta767wlpj!=28d9qP8({A_#Maf6Km<=>(^*p*H1XE43sDGzxfac zSyUez1x-2@sd_CbEi{WQ=fy_(1TVI(vC6~yJ-{=T6KoliqR_! zD8KrCr$Ie8`TxT~y{D-1|G9&zb1+<#x*cErtd7Gn&5{re;)vjQm`M!BgH8zqSM8Me z?~mH<+*fJnn74C=dp=mj)7=D-a?pi=KXlzC;E>W(I2xVxW@+?9L-M;LY=WR${^bYr zhkRbVDQ3s3L$WDCG&}~h7_aX00E@T>wJEu+DL>@l{$#j$fZ9O?DZq1-PKYAd;KPPW z$URW-oU6JJX~$)@+j_$eXh}lpc)oE)1QA^dIduj{!aPw(z&D(Z>f9E*xO5(#?(oZw z^x_0GnT#yo;bKL(F&hiejnVP5=BGh<&OjH*MtC+;7fPict?3 zDpx4y_qfA*`o27R(|PA~5QJ;J0_w6L67*CEweHiXS1+!NX5PtHmd<4ck=2z#gi?nj ze{vMeNouZv4@PHAb%U6VS_ww6L?p)eOxDCMSy?+%?F!d7$PCFI8hqx|!BDECAI&kzsMvK?S2qps@cb9p0Ak&G zf1TVCPtZV4cR`;6NkUiVN-WkM)UX87|k{`?ue0xCJn=F zS#F4P5|7FNm}E4OuRXkDj(qJFt>rpLSs=!#?c}y3g9DJ!r+-CoJ!u zw@-V6?hE$m5%f`zDm&9Fpxm{>-6@ME3xzad>_Q=>7`sr& zCP23?6fzwUdJMpBsxB0Md{@w|3xydF(1pTG3g|*%RswXPFdG88O(1!Xp-O4U*v$}s zTkEGsx8C$-n|>i>3**v&&%9z*8T&$ro-KM8CnUgovt-OPLo}j=d3*&0g=3MmAsT%1 zOk~nlM*m}Cx&}O=0sPD7HItVg%DxJP;uq0BhI1}2^C@#KtRnk0MXliMuTH%(N++02 zRgOX+n!sE_jt-FBIZt2MbLU$M%io`U)hz@~`@wFT_Te(h(?CxBI(z7prti*#@*Nz^ z=n%~Aypdl`<)lJ{(>$YHFF^sN4G${B)2Mhn3$!MsCVW7!@az*TJPoF3%g>@MXQD0E zlWGRdE6-L?FzB4RgkmUaqt@<9HIqB0J z`?2mEedjJhP6U9D-T?=SNPD;ya7BxHY2U)Ke#5ti!*8N5QnuF}emeqrm+}!J(D7sB zQ-Y95(v;#o>>8YUSXC>|DeLb`P?4iFLhcjM@yBF-*!7Zv;$I$6SCa=eXKY^w`JKst zb50^6{AyfjBjJT~OqHx6Dd3fSW?A}o@|5QUlPfo6Ll_no)=X8RKc+ISl+%PU7KX726Hp~tixbV zjtW=tWQbA;kg>e=?QwQ!hFp;uoNuc%3AuX*TmVTAr+JX5Bs-JEJK$0mjL^jT9&_26 zwJ!TkyTIE7>Pg;5jm^XkJZ2r}?F*j=Tv{hgLNmPs$^fXcgb@o<6~t6jdRPL+V_Yqk zCS-)uW{sH37mB&iEy~Ymh4y3eHRl^grr5qcMTNOSMjJ^9#gG_ly$BMo&;~1M{{$uF z+cc&m8Y_8+*nwj%$2`3sckhy3Np-tGDic2Y+%tCIgi91=Og-qr1(V)8!LO)dU#3$>+4BrziTH7!}hC+Gj>>lUAuw+oYjp z)HZ3A8nsOtYDR67R;f|j(DO7mLQ?fKUpUev>N9E^ilufqN}tFMN2z_9M8zP&94VQagP1d?#N;yOn5mpHB)`>e@AvhqQEH6g9+Gx&`@e5!h^fk2A19Ev zJ{WeY+GU-ZK)#wY0w#r*nN2~=zJK=ntcTUolPX)Q|8-7`)G6Q4m!O`FCjE5S{fVmA zIY+_~sYjBV`IveE<8M~sYziryEKugHg>zr$aKXx(Wk!>?M=!T=eiR=!V~-c;^-5GT zpQ?%v8Q<_IfEjlY`KUux%2aPMy$M@_tvKk;I66+iM*?+bnI-(c?0YJ9^k(_U+Za<|HMH zH~!!G=*hRx4vZbH5rwl3un**0I_AI#+fod{fqWaRq}>yglyB3RlBmKRJ&(8?cJ!3F zF>ptZ3#2mP)A;T5#DSSE(E*n+Wn4q1n2r(11m4cDWeVHT6Et{(MGht!AR~{lx#o8p>qXWejrD?M5qB8i4y{|P z7sq!AnJ&gXMjtN7ETI=sAjVRfqXJ|M>O6f$tBt(5L(kFLIw=&p2*h!;$@AF*)Z8z2 zyPMM^ZbVYKuAj+lJEYr0k0?Ox+9PMoi#4Dzdea?2Eiav$dsqd13I5X?rKN zKKa7n2m4#|s%BPf^oiQZ(I>7vIOI(+&qK)DtVG2=Ub?c{Yx{$h!J^44tLrDXU1=m- zY444*=^Y&GeCpzxzPZvbUFjz#KY8pDe@3;DG4)ZtPS}F~qw)TU#`{0Iq`V3JBSB4` zHpPsM7vKy#ZM=S=>W`Pc@$|3fzv=bbrxN}>GVO2#~#B`d4Hx$%mX zzPHk^pIodlxT$S`dSQ3V^&cUH}Lx%=g|MSS~Yf}h44*|{2T;pdYp z*eIHI2Ti*bomBKHVpi`8;=A-^kMtMTq6_#ykBKMW@YcUvL8G198;n5 z)oE?C-fJ8!`<7y;i%QsL;?2b!-W$Yw1^0H9y~E1hZe{NnUf22R6>WEnE12NN!wzC3%6jrt`c>2`@R!AHXD6p=NpJj(08S?4MEn?F~=Wb-r2}-SQpI zs=br)=B}Q+`GftTCUUqdPrTkbM^F~@sAcLBP8;M7oeS;~#dJ&~M#hUi3uLj$!3~(* zxquH0T*6`4%Wps5`AQ_Z<*gA$=HP{cXC~MB4QHo3H3gC)E|0NzZ)(D15LqyNEe-Hy zUdzud*Ou~IiZLX|_7H>9*irC4n6IXtlS)DgIZQyh2$HQ%N@)sGl-Wd+xq=f)30$ca z^p<0gRq|r&OtH*_@m#}`bjid#K++{RRZCqqcT>nvevEdD&t^B3Nj$$3oh5cy!|`?R zyN!0sar0p}T?F*1d*ORwzMu5(D)pJ{?dLiNYA!aMWywdouVNbt+OZEXQvFIa^LCDegFlMA-;8U7 zo5ESQI;NPh50uM77(c~?Yr(&c=;tBgAR$vS(r^Pmf^k;rfUrfmCq z{(N5VyTb=>h<+tGxh49QdC@1Hj6U&g-5w4hBfa>&hpKBMw_ka4=h2JirT{(TAaZx- z`HO4+LvwIjeP&U~%If)>Gv(c!+a+yRtgKGIZ8FwLYAb`tWKy-gxcug^?hTG113%7R z(BFUGFyqj7&3J!7m!`k#HPgHHa33i6FZ!^h2LZn1t|R>P()3rof%IlC^&kVRMqV+e z-^`^3OXp@Hn+ldwReI=Ob)3JW{V!UJC-fgQk-wpy*GLciujL=x-+T>I+rzK@^&o@+9eQ!hB*#}`H&Q(eaQ@r7|~;sGn8(C~4NkDGCPd@ob{ z3a((-1n@4_MT7)0!JYlV-wxh){2t)_;i=*^OvjIL)cjiTYJV#DBKvQXdJ{rVcezR0Ds8~^&e)1f7Dgp+j8_{-~$)Ge9B!(U$G zFJFiniE}zOPl@K_6U{wRH;+Vf{pBilbM_W1tW=(}$IqU32G0Z+ z@isDn@9gpOr`ds&_Xid~Z{vacrH!T419B-9Isq%Vlp5GDtYe4PwVsp1viq6b8`oaD zaeDh_OyR2hfOhy#dCPLcCvAEQJoz$GG!9M8?;4J;E|}x1<(0woPXC&q)j7U8B*#~k zOxU+|Y@AV|E_>7pVSZOy{`dv!UYu@joNoTUfUt6$(<>mvq0Gh#?orX8xojYEH`lI< za$xo|D!;h#t$F`ul>?DYqo1C?w(0!Fn&HyS?is48H&|L9ePZGL(8$W}50+PE=1b0` z#K>jA(%FmNdyu{f$~O6B?~%dP@=Xh>o6c`|8n08~0<-b-Zh&6VUK)Mk=E=d&qEAF| z5qj_??DU+QlC6L5U<(rWt><(9C*nyuDa&!_DI+CU-n`r|yF2u=hAU923l8~BXA$)6 zz8e_E?JKU|BVoTMa-aR~1pPj9T))o^`~Cc=!@Iez-{)Ds|AiGozt2^x87`aqOXvC< zXVV@5RmU0D>-HAeRbg)dP?k=O!0fyf%r)cm@s52b1hY)jqs*#lt}!s*NiX!e`B8r< zob?XQB?jjMRtj*|J2=@-SjbE3{f!F^&KkV0IyfUi6nW96a~FZId3O6d==@v{$f?FA z`otA3wg(P?zp}Ty=v3QegKKKTx210F9v(t zn9$FS`bp|1eZ=(jC;11`Og{gB=|dlo*!K~$ZqEl15BvkG?Dy095bv3U?+f3&d*TY? zNPGw3?YOtVw*_T_U$&pTc4Jj^YdunGjvNf~&1A!JUa({m&JMYIS3eis`W8vC;XG2& zv9guH{7rbMJ^O`Uwx3_=w_jKpl;6M7Z+dlQ^|v?1qF)(U8T@KxaNNES8-3zwFunj! z9LT|f_Fezp0qlI^*|pltbAGlyuoeZ8do-Cp95iiT8Bk{R{JTRUV%wXDpzsY>O ziP8}e@${18M5S*$&K)8a@6X2jPm&N0MmElkA=Y&C;C~^MDM~J@(HGYUSX^1sgrMly7SaMR5JKmg)@7GIovuI^lh{Dg(AOb_!#gK|w@=25Y zTfJMQ#o~LEad$rB9+&awKvawU;cCF!@(Cu3REC)K^R*2Bfse(zh2MUfS&;c#qFIga z3I<*n3`Md3;@eZgi6fc%_=wh|?Dn8U=mH|xXL2fofZMcwL$YOnW$Hw}SwXwttgSj7lYqS~`?Lw=eVdO|% zJ&Sgszfpiop1yA3+P4Ric{nV0bVnt#k5e5a1?tN#*3CD)D060a6(r`9;y(=kF=XbG zFAQ@3$Ta0v`tctx%fv6N#H)>%ICM+y(>OP9OG7p$TP{{(-)qZRHUQj##!qq?o54~z zSzT8Z-SQ-{FAVzaL$;;{H8duurZvUZ&9VZXnzBset5CRt(t|HXrYxQAMBSTk))(Ef zg)9tvotD1r-FtOf|E4!zr%eFY&@4hq+wgX-b0={AsJdB(P*0gq-p4h!Wt-Id0Jr5S z8>gFGu=jaI&oV-oEQQ9}PZRz;$-MWpdmj-w! z9N-}v;13bY6p*y6lcGj8F@D@xV_K>~(~q~5(hUH;cq&yPM;TR`0$ACfIN4lBb?Nb#PF;xop!}Y`vLhVB}ss86=Gv zS!C&&1)T?HR5w>`cnq;RbMwR2laeA{Y7-vck~pc zGZ~4_8dhL9IxDNE`Z@_!;UxuLiq0C;^GSG(i`pK0VQN48Lp(xgaAa05_QehsX0%F# zA*2jrU+l6Vy%l?W3$H{o4zVwW)-MoYOrsoR+u&yLX!!Pk1Ymh?-R7Q;@8HR%hN9Ep z#x>d2v+LdiM3!2ljw?YB;hB^Szc=)HIs6VjZ&hJ3F>uur`p5}2JdGbt6+S-lxU0fz zr{_|CCYe*}+qg&!K9Dz3OfFg5=h${zU}stadqW({sKRcc?vuD5|yu8V^_a_`jKr7 z#g%vdXiD(hKMjSocMeU#K}KNP!@|u1d&A@WVyiYML~X zs_elwH%7X#*jyP7e~Uh;$gwx0Rr@hN#bZ_XK}O5{|?-@@vJ<3CR# zIG$X*GD~OJQLucWzkEJ-vE18VM(M3?y1PSFD)EE)#M8N6nXA}}BXyON%mwr)@UDJv zuv|BLf8JjECtcnmr!_@6fVZO^W8qg1Z0cZ2?7eT2r;D-+)!gesMd>Hzp9BY?XJd4_ z5l56ADksx)R&LooR*K-;sHJQvpJE;me)vIWFmXJ$`f~hG{1rRH%i3eTQvMS1Cc$&L z_JWnHfz)8|fNJKAJzgu-TUkK>zMu^@|Mp~>f&^4BKdT&)^kcS*CjD;kW!>K z=1ASVjHXfw19*}eG)qJ1aR5?GZfG#Fd6BKrYyf%G=B*N-bqo1fTEh=SF`4BXqrtYc zo`}4Pq3ygv!cI^)`?}YhBp^191#TgrU2gdbUTB{&cb2arz;5LNYj~k$Ufo(=06S*= zRkxlHh*hkn)M>+1=)EjlwqwU&;_>Yq0TSaFo35;N~QHW=9OCPivxWjW`0c1+rUurYf&Z|pqMU>xUNgV9eu zBj?k+{D9C62+@okkkPTw(7fJ2IPO|=g2I14gJJfSH5jqhq2TJU1M@Z&1#S-OPuP;j z_s3bZ#A&UjTUs6x&q=RDvnN)f=yQrbO{j*n=M}?GT3e|biyC&o_Ks&Tr9E9 z&v_*bZ?tQPL*&djr0O&A&Typ_R@o3es7}9hTmjlGvZ8TK$*-zQTSdmg;L;Me+HCv? zyun2#uvW10+#*}5MYde)%i&nfHPpcjNY$<22bo|ug7pLn%jre}C-`DZDoLQQinP$Q zz9_(}TSY)B1QuAsi4&~N?_O;6WcVu#zy-d?-sT*nMbGKYR35T+hTrA7A$^E zJYHV4}O}0ypCOg_8K2B>FJ0Nf^7~ z7@N9m$YYy!@wH&gmVN(n^|K$B((ICKO(ZMsw6ieWeDWy1#IuK*Pd!uc&?OTX7#!{& z)O1Rj>2R~Gr930qR!M0LqL$1opC)e}O~eT+zzn?n(BZ~Cw+d&|m@8On2G5(zWk6x6 z89Z;Ep6{=M6?M)t3WaDKg74cFw?(q?2o@cW3 zD=~_F>eLyejxIMo5?SC())4UrKh4>yY}8y`qmfJTlRnDdKS!%LFjLsU)Fnl@Fqf?K zDW~Ir>I&ATThM#@QFQZVQv0i=FRyyOsm!4pJn`Qd1b2`o{t^Sd_d@a-Qmk&4(n z26{yVCf|TfHMN`zLMaMK&twnkjzG(Ko(fTdC!@2@!xX%II@$h^KQ7uID<7D|DA-)% zr*!y9BsBOWF^2SzOK0kzG$%DUFd1r(8qHtQ1V*%C<6JwmFV&RVO|I!&R-0(@Nq4V1 zuiif|+DJI}vs0_Utcw>yhyzXB-;VxOWY0>S?@?V9e;?pab^+N&O|s-7#8I=WS2#64 zp|M~COy~I1b(6i4s~lOZi8RYDuTJn(*UB^-SDI0?%dNSv^UNh3=M*L^oXiNDQCcl; zJxs6Ytc4cDTN76)HbfyE?gC6v7T~}}$jd1&{n&Z9X3tAQJYLfUd>M zPWRR9R{kQ|@?EZ@(al)GCk{snB-NDT$aFz3l6}!wWSXMYQwr=4$_@w^-ly}`Q@48>3Qv9SsqekjI|39#A#XNp zAD&9M?JM(c2NZLwT6(J{xg{Z1lc0^B zNS{4P;CS+AHonWKrSUzEXgc_GHb$BXa;gi3TAOW9VMlH$ zLBC{UOf|7e&hk`GFx%C7839Xcb*`r3Si^2A;6r>!+fR!9UEmycd(j^4MPZfN=(CNH zQ-@Bym`!0z|1oZ?4HxD-o7*rWZwlo}BHV6u^PFdw=l**1gY8(C+pmNb6zx|xl)L*C za!FwLpRJJBs~e`XPti6{gc{j2_HHH=yLFo!+cj+=JQ=8m6xB~a*jTJV4WUi3hP_0| zEsnOSnA}Bp@-?J>g49MxhWgW>fw5aj?){-&wi82fw&`JxmC!*Hg?*DJ_i|--w(6W$!a6l{tePvq3T5V5aoMH@`FZ#S};5rEjes=n2ExQu9U$yhs0NVigVZ1rgd zH$NfRIHbdw(#=uWrDn0$q!gN|ObxGz3Uwz*|CFcDRFKOQ+O$ybb;hRR56NE6-R}co zgO2tuq5p=>?<7oaJFwWrqa*u=Xc?=doxQE)* z>ZY2cgS}d%;v7;S)$|Z(7KYv6;Y2r^Euw92)wpEh+Y*`h=|p3&#l{K42>)3F(z@Hw zGPhGT?e08`c+o-al~1qxk8D%-9-6^c+3&W>T_G)W&&!7->c}a^4a>5)7#_hnPJ~FjSvyp$`7nD`Yi5tNSwSi?1F~a zc_{KtCaAg-Cvcs=Vv~H(HpzqQZqMd|_AP^LnoqEEsH-K>NICI61Cud;03P^I?)iU3 zs~g9T=)~KG-Lfp(v^$Y~ckZj<2j@q!si!5|)Z3U{9$A+AlKawmct*CF-Jo|czMIV= z$L6rSj6Ea2Iow=X*c@PsjM*L*j(&0o&3OF-;jafnxe0UV5HU9q9>z5NM7g<%s4zDX z)8!@sOc);ti5cc5qAHM^h?#PYkeD4b;wA!j6S#@M-Nbw!cXo|6exfF5T<9loH&Jiy zCYJaK+)X6>L?URs(NExRB5Cd>(&lbrg}Ix!#oSG-lDmnB^}3BQA$^?UT|WIfbE(ew0gpav>tNy(EYPXR*(gGi;K-5I`Kt_I5`Rh!qdFgDj; zdw*rmjnn4(OEy=IO7^?{QhJ!x_ya?&sG|@fXj;_GwL|pbMZJR(=`& z{a_)<+MM4w6HQGQn60c0rPY(#rUv*%o^)~1*u2U=@&4U8QWf6+B<${+Ta&nMQoj6} zKuF*M)vOoPQb+vSc`XBzu%=exH_j)rwq{^cqDsI+r`uG+8`S4Y0*`Bd7^rbF3-_n_ z09s*Bnl~-8{EfL3}20+_w({H9~o~Em`VBe{u5OqU60{@(!UU#W^|(Ij+p? z*BL&Vsn>$8nvVHn^YzvcF3#$W&8py^l+d2{wOw#kN4x)sxJxL$!KNE&596ba&0~vt zim{n7Di5I@OwsIGN7qdauxWBMSj1qq{m@Z<$FHCLC4bB;)5y7h8h`UI7(lT6pg+4< zS41hs=fB)|>%Jcdo#)1njA_rMH~xn6&V7pCGv>zslyjlNx}Y}(Itn-bp76%su}kAu z@bWfs{hLPW$z6mmKxk>{hgOu?dY0_|R^5^_4|x2-{?BZk&5QrQTK#TaZ=VwUV&2^G z_tlIJ-*$Tcfem~a*evL@LjA`Dm;bon%zZ`||BsfL@r>950@Q!}FuxE3wyFa}Dtkaw z4v_?mZ33Che?Sxt#?6xO{GwyVcz>sUiB{2xc7+88A7h|2zCAjN6W1x;0HJ9oWXxyi zx;fF7qeO*Q8qe4}Vboe?l`6BsT`Hmb!<8z^c|?rmF_=(UBIaBLb5zE*ZGHrxE^bxx z1M7sKYqL5tFmDBZ*Vf%?-ezm{mdy$@*5ESFx0u}Wf1!gcwx~8MQXWXdJ}S@mQANIw zrn^2O4bCdbNz+??A_t@hm0`gTr}DIUts3R*W(}gTiLy=G-+wS`jIm7FQ`tkAE!OZ; z&t%H`vmCNJPCbpOnC#P;#~x50Zuu25jFxt>sn4}0Q$C+pU@JOA8sC?n33+h(}+>>Ge4vFSGm1){z1fTTc^F|v_F9Us-pu{`q zDWUri5{x?j)`}}cDKHNW&)^|1de0}PIw2broNoUgLEiN_Ny257`a5?Dr5<%Bj@PiQ zw)4d)&Pn=|r?@ig67vstxDlK!2Q_R5X4yjoLo9-E1Q;_=&6`eBGWL7Nl#Fx&Q6Uy1 zUN>#IxWmostYH$yJEEP>N7xa$pDw8E%uR+G#C}k+HdViNc75BOnTBnR!Q!$^d<5fb zrI0ZQMS{0TyI28vEYF=eD-)U(!ynbHgH)M@kqJ`$!H80o55LQ$idd?v@&)C(eyq=& z%0J1r_PE<{ya!LQ(YxmW9^B*WD>YCF4V1a1_ieLNoaj%^x3eF1WbEOj#p&0#O;G95 z7N=i-vyL=fRyJ$jq~(4?FWbrh5^;R!JdXm}F0O%@1*O0IPG-qlM>03Q^{k&+qO4pm z8n$&e>7{_tX>=U|S!+LAG^SnS!Lp;__YT*Dqsjfap%;oF&!+922P2(_e#2s6 zo?;vI?mICWz#^$`*dFaX!i;5oz8|vX4rs>R&^Ws~ZF(~$Gb1!dBcB<`M0zp{r=qai z6_ogqza>71cV*LmYg4s^G&P#3hQA~$elPv~E>;Da17WYL?ylnb8sG8F{#sBGwc_qL zlc2MGTeR(}fz85IwGIO#f8z~8ROyd&5cAp6z`hPo-EV9*3v~BKyZ-fMSsonCIy^hD zrJX73zqrLvKL;R-OvyFcpXUB;x=^iQ+MsQ(|H|cRhksYGE#qJxh zFbs?Qnd_7vPpP42YE3kR#x3;BO4Jeg_+EX*jCp9?SjL?;Dg4fpZ{y^^pl741vRE zxZ5(3cDm4ceoFN2uaN75{e{bHk-{_b10qa&tF$=QXd9Qiv-Bqkrf(ydUWDzX@6Mq` zFnjoEqdCAt!8}_>7$CCb?=8kPlKe%im2)_@u=_Y4W#9xRf@2rH!otICswpW$ZBHK~ zEC!bDHgaT}g1h)b9=DQHOe-60p&c<%+sx)7{o!yBT)0O!|2xf~{5&FA`nMYTr7v2i zwy@o3=!LjW*s-(#vPtpkCdQdG$=7Wr44r&495vZ9G=IBZoT0Y>Y#epq8yo+gYqw414<2#2Uo7;yJMhbMiAd{v@jvA8#^In%w0#4%!qpn7p5E!GTKl%FQv?KW3Dh`h*5GB{irQs?;tFP7a#>@>=lrHRPe5Q{bq z%56@=Zyh3_;ni9;-yfP0^pouPg2%<0MxvX4;C2Z@H}-hUxZh%Jul}BO-8u*Z8sI59 zSN?5ldoLYOSlf>%@Y}BK(XnmVua-ZS8#rZ99!K_CP_{DA6GAy=OpDfY!HvZN4Ta9{ zP|#jOrV(^gae*6CZ?RG>@`4h5Wf)_&VJ%kTMM^AQ>gXWb4rH`0^62YvzRD?SnD~T( znhc97G7Wp3y?^ndV{y&+gkB!g4+HB2kK-qE(-;=9g$v{7Vz;S+y7)jNL6 zRPS3Wr+R&>rg|ZH4Es489R6kz;rz`>`!!PHW|0|luYzSGw^&3^yWIIR8({}$!lAYz zFt_L{XoIKrd$(&TB=sf@Y0vgT=4q_$oZ4}os0DC9c#4xK7Kg+d#3uCGm7+}@^g+Fx z<&$D0T$ScVDrpIqPG3NS#U8f3FV6lbLemk)l2j4m;dpJTrEO3lOiaM@rL@&7i58Xw zpY7HrIbW4*may4KJ$YOdX!6%$;;}0E$!OJYTsre3*&c$Q#0&Y6b^jq-3q!9wTU%iw z7V!}{n1>t}Gh4BDk|XyW;*K?|?JS9F%8{|i*6;-{;V?cC%_tdT$^32i^J zM`3bjsT$yOA)>LdLcHeF7^}LPQHqfGl#Pmg{Rt6g6p{a`fC|tpRAN^;87v#zGHoZ4 zXb^>BIbtI8rws{IZtzLn8_#A-kg&-9igvuC0QLv_3$IMN%gvuEgfdZTv5`|}ciX}RA_7r61)#I;rKEu$CHb{8vb(Pk$<{c$DM z=I##mls0QRh#UUTc`!vCliQdB)7%t<;AJ0Be&A#dFd|mRd0>4E;Y5sV$2MS%BYLF} zf>a}ZU0Jp+lAV^VE47&F|A3u;ZGTC;A`w_hSiWmq{70RHnGpO`GoX6!=0#ot^5}^Xd<^wpj@Tcw zak=Er6{}lmtl1mZy0_{(K3C52T5hK%bRAP~xZ3ihB)*%n2Ad#nU1{6f$W@0>#?;fW zNIzRTaE0WFxWXRE<6A78fh&a1=VwT>@x8(svJ@$CW{8HD8Nz(|TfJVqPs5*|AvS4z zG-~XnFhewne4ATy3do&R1|nT@Givn3GuiE+@E2fZmLg`}=0zW^`|}oz(^Madc1_nA z+g?Ufu2TnUtUa~-dM{`#4>I5czhzb@r&ib!p)e4 zYgv}aJkD2vXPreqGeg`ZHCLQ zT}-m*qc_gs+ZXk%bXfvD(+iDECJ}4V38>0gZ#OT?b)LR>v3b*gR%c7DjsVn}o0spM z6vA-x@(=chulb|Mo)hm6n?`zQ@hJa;MFL*}L5cDft(0Ck%>Ks8foUXSp2aX>-oDM> zI76@715_3_R_S#|(7wapIMWh)mZID~R#^OW`d!Q1BqO}r$pM8F|E*4B1}zp7ky%BG zd)hk~{t|F+q@VzsR8w13-NUJ-+B%lWs>Q`(WBZN$HAzPf`{-ZH+Y#j{K& zc=K)|O*+Ysqy194w}#q<#u)a_)UcVbif&1A9rM-zi@wHg?Nz4o@#$!lOIOMlgaPRk z?Wx{k!kQ}x0Vy99rMMv^M+p2Cd19-#^Y*DKN5KD1U=tvWawRRA7lczULl6UaZ#^6iJW<5a0G*vzM zt+rxK*v`$!y@~?xf|YLi#8SuhZD5xnR~rgw7nBOfluBrp{8XDrk_vGmiQ!bwXZKti z$Wv6={xGYGG0;X+8)#~KThEv*1|PK-)y^BO&#HU-V&zc{uXQP$HJdqTNA{O>icoZu}KZ*uj#Pg zxKNQNh%|9VzqUSbw}NAs{_|@S`Z}nu>P1=PXBB( z`>s*drMv`8E2JlPRz5kZ$&{yR)r@|;96q>deZaJ}iyC{R_f!*$DUb+aI-yWXku8`->qkO-1O=WcRJ=8K>7P4}O zr(oZR0bID(3nF_112*A&I4>iYxx=~GGz3LTlm~C;4y(m`aE&#}dsv)vKhr^M$W7C$ z-`AxGN@=%Aa)B_?pef3Pt`^l<$rE)X5$NHxlB9kz zoLbV+N!s*MNA$Z^jG1=TOuM`P);@UQJoUnP>wQAg>IzCf5@ej4h18EIPtCuslxTB; z;v$ltm~C36sq47GYA^hZ$A_UC$72g%J z&t(W#xdH2z{eyzvx#XcWwr0r-3X=ROf1jD>*CH#1BPK0o5ot(d-n?PH45uHw1_p*NFJvsI|Y%F6l+6i!=Y?92l2g4?c5RV=U$i(?+o}d^MV(>pb141 zTH2-+^&ACCTXYVFaZnjc6`S|_d)D43358a@b3gA3pPaqd+Uw7=p7pGM&wBn$4UeH+ z>iqoNn&F4h_OT>4su)1!o4njO>yes0iPH7s&8DO6B&Zi}HnqCYRAZ-`nDyIVEv_Sz zk91xrjdk_F)0!cBm`GtSEd2W~i{Haj#Ffqu`PQaSHMimwgcr zZks87ij|nIx19E&v5LGE( zA-eOM!hadYL}D2fsik25`1dKi`SjO4x->YKJs_cgUuA97MXY#nck`r_ZXNJSIQ?L<2=z}HxXQ~g^O&Nt`!Qq*luJ5lpT=&6JzqR`COdhMM z57!deLIy%>NlECwDh?ttV3PCSF)-HFYj$RnUkEm_?8UQ1)+wgOv$3n)^BvxiaoS_Hi+`s>kKsN#4$mvs{$@HA-G@%QE!f3ME*_vRS)#)cn*s&0~FO%J+3Rp$nkjU|5$SGG2&#NS(U zbLnH^-XNLxO+^;AeV0Bz2j(%}QDZ$n)bIURJ^7HNU}Fz2XSX)+j$RzBva!0p_bTba z3|!TFl{g7Omy;ebd6BHvZjijyZ3%Js?!sApCB94Feax$LX*1~8P6;yFKlqI}Ced{? zRF9Fj7Mu&-W1-Y{dTXo1*n$m+O<&Y3Ddb}eNKdzf-redxmK$xk?3m^BJQ#nTc#o!& zWAbcvi}bQPFzGO9>~kG;7LYa#7a`upV3r9Wd5cd;{YAVA_~z#I1pM{Ks&&71lt1{|+Q&HiN`k{cV2f%BL=6!HI?ic}l0G48GZ3PVEK+l12E}|;?IdFT#wq$xz&{et24gC>O!aH0=WD0r zr4;VHT~e}ncUC-`ubp~1M)UDV-=)2d91D(_p{Rwji=!>1^ywQ;Ih{qb6i_?WhyAXL zgt#df^E8=wzh#QAZ_@|0#ETcCYBg`vZEA)>K{p~9?H5?sw- zxr>;Var=afh#fmz0r>Lk|GolJ;RWy4V}_q8@2KS9=gyQBoGB;FWsdbTcWUCe&1-FH z36GbBPY#3!%OO{U^OY#0-{^oj;yh`EfG18iI$?B8BB45lUb66%FkH^ZOj2cK^~@=Fzr|>kY_qi1N<_A&%F_n}memc)W*Ykse^{d}jAItoZNyu^2 z&tRtncN?5~Rla#e^vzMGpV0=-w~b|C;8`_YSfWKnhdeo=LB;WG*ifDFEF3npzC3F< za6%h+K-J+{4RRo5M}YR1h~{)&xF)uK8sKyuSWg;;0B2`(8@bMd0TbUUn3WlDTjYnH^FsRiyg9n|s$* z>s>`N1tht=@-$BvH*43U4&dy3dwN>J+-v-R5RW{i`*7fKo_-wwTHRewC^$znY~&nt zsayP3K>;{&;TYd5dZn_xTX+T;h5S05ygoo8&PbAK@YXUZK1-Me+Hy9$(VC<$@K;;q zJU*^a%ZW5Rr4UiIQ`TD5d67RkM|3g5Ad8K+Q0=AHTzh*792)AJ8_{ z90GL98aB?xEyZMoE%tvV%?x` zW!fI9$583(P#hNCWrfd~s=2*Y^J8nx(1Gdnnc{)S-J3b48(tU+M}|HPyK%Yve2KmZ zgKs$hO`JE6S#o?fEB}JNc`NrbVtf=f?*BDYCK4}>(R7=(GA3Q-H=JT{-K`BSV~U+@ zA;>BRvg(v!8hM!#!ZgA>!g8=+7u=FHp?j1`doA7Rqt|=AnbYKOE1>DNngj+S5oC4Z z5Z%0qd&2odKP=>?!EaitMLCHtV`|Ho1r9#aWQ^Tqzs`q~;}vcMD(6woc!i#}a+z^% zAD3CDk6rq84xh3aF^QPZD#HYu^vSt~>e4$a$rg*c+1P~>NEWdRClLb}p^s2Vj&uqq z-7}NsS@@Ty9Xs&{#!l=GGUF*h?8H7Z)yB1Rzw#_{dG;=L&jZWCG6X5#jkCuyqsAFK z@ykF#UquW=fe!<*A50sU>jBaYHa`*d>sEzHzI_tWJaGmmMTDS98+IUx`i|cZy0LM?Pa&dNI$eMpezf0W+OR?ZY%kKPPD*;ogXDU^EYuG{}2qz_Ze@&gK1?!3C%ECE!Q+K;bHe|`BmTU~ai+QSf zGnsf?+(qLHJ1TP>sX;}&_+>_IpvoA{LYe#KsL;5J_gi8wBv&saUm8d}&T8COCUm=8 z(}F8oDQvt`cAduJ@FMR?a3Wh7Tv?@b=t${`O&nGhl$0bo+@;fTB9qLe?*4{F+S4sw z^2g?HMuu0Ci>3nPR?%qCrC*|0UgFywt+_$QZo)xjjq3`lb;2Q$!f_HPrwD^ai*!!L}ICkg+Fg)Cz$vo@(69 zQ1b@H6~6ty;>LuA^mlx-?dr;>n&CL&r^> ziUG8Ao!V`ET}WN{HLk3c&8-!MdLnY>Y@O7hY^!Ie%WqZpx@mqr(|q10Vp3WBai+KL z&zltHu4xdu36llb4vK>02-{paN^?mXOuF({rI9Of$%o$(Ub{?b{ zusOReh;GUzzg%3f#ox?G{B5%iWBMsIVb_^ng@y6LWkL!?kx^@|8& z`BM?L#=KO-)*I5#RC?z>Diojp{;6W>;*Q7k!9A&vBea7k4sttOD0Yl*Y5fRdQHABm zuA`fTxYYj%3_8jmsQBg8@9L$*(HA+$V zo4mV}sEHO;s;E+;#5>kr?XHpKLpi}H{~_sE2Q$n3ho;y|x&P2qd#Ugrny#19!)EQQ zh#Hj7gH>hz%CcZpxsQRKc@;kA%u;Em-{LUhPbpW5)#XYux@SZpPl<#Kmq=en6f{^0 zkXWsw{jdWN>isik2Ofom*U?UsVn?U-Pu_^<8OL(;3{YTSVg^cdyrzHJrsh9RAbxINZlI04bm3F&TDzY4vBf!@h0|Od|xd$>&y$`sdOx2S~FVILuRy zrr#n9m06YvVHT0XThvk=X2CnS)gqF(QWhwpF_yUriU``Uk$z#?;f~hJ<6?C8O>Gyl zvwDAz)*FfRQLJY8M>Mo;=Kc%CFv&lnQ9ojye?$|4#ry$jnCc(VRPmMFrK}B~9rd|` zMr!5^hfbz{gD+I_9g`-q_8~^6)Xcw7Z7=14f3sfdE5ZqXa`&yJu}6od@ynmzJI}6x zWW?R{zJ89Ij-@wZYQU@!ep=*F%HLT2{#t=~WppMQ!uM+WU#plu=5GbDczECw7wGYh z9nKQfU+MiEk<1qHj@_htkPK-y-KKD2rr|AvM3K^cL@znc%PA7-iYjh~Q)N@nS$*q3 zVvN$HgfH7nCURwBX1Wi5l~fYf_u8n(#Qv8bqpVMoJH;l~iSzFx(6)onIa>pzOn`;E zReynW`l?%kIJq4o0{b~-LMsZ-u*YXtEbXlKO2I10bqf%$nQ-Pc?toOAnkNkbA<&its~EkDptsOPr;gBZYSrx6 z#8+3%3e-go{BpFY<&EHWEazzD5cs25J_PwVestSM8}w5-q>Oe@Lf0v2CYTXGPl^p* z8v`=cah4Xm8jA2AUA0E>7c|;e@YVq!y{i`Uw!C2cRx#xko_C65}h!93#Lw z5pNJyW|s@9(L~eb5>!EGlxS-J0SUcHJuR-|jPSi24in!UzR5BS-)$6pDZZt`@!RA3 zyh^4<(aq)XjTuFU?Y48PZbu8_Jm|hYf);ZREk-Te%Yp6|LpKo>0(_i))P(xWqT2|@ z8HaBJ$%g$RrccKn{+iomx1FW>*uy(qsN)5dnb>#Tb+~F-hrj}eqJGe$%2r@iTf-W>gmcoVvSRx!G1zdCJ)z=y; zV0VKOp(6D&Yq~ibSl}>n88lcIYwL2wE~fEGO#(>`ChtSe%FIHWD1j|y4l)E~O5fuh z^p@ALgP^5&Tp^-T^lx;B7+?OVNe_7o3Y04Dw2iOt6JUHL8y+QQu8ps&DbR*nIIc_} zmCjGf#EKXMBAfUEXoEf{1j6Db*t>FqqYH?;Vc$p!p!e@0nMsOh{)q0?#$iPNO6v=E zXL3OcTsQLfY4sk?SnlwsQu4p)m5Kp}MxkjD91{fbqBpOUn>OBG;SV(`x-#BT02%+r zA2*Av1j3Z=Vx3XLw$pBzXj39-Dyvm8>uT~$=D?F-Dj`l!^+WF>3FDXPnppPyyw3nZ z8saeKnNvEGWjt9&Of9*M;?P2T%TZZJ0$e8MyDEA16HdkOVGY<|_6 zV|VM}3T8_YQ9qX=*!5;zr-V;7$Q)20j4=v&m3jKzS<8gU-om2ItLE;I&;po?2 z*AC^6dcN@+dmjAyuU%NOzTbG&#mN#T?scBv3iv97gyajk6`=`)h$>K#5-omYe7MtT z^p`9UYel*`W`-|yDTsRFr*e~#HG#T4E#2qX6Q@lXuMZMkr~SjZGllK>Ez5OO;^}17OVee zAJ_U!k|0INV#5XPLtcbtGFMI1+|_VQNI7Rn`7re?S7nA2ZImQwm(q6Fu&RmWc{3@h z+)t4$!p=E|9fuB^)3%GF4_spRhf@5)7dIff?lIL*XQVo@k!Wr?Z01yd+STV0L|Rn0 zB>q#tiREdM(T`rkIu%2y5^+(pz2zcx^}jRn>J$*j>~sx?7J^dC`SH3!sxt~w*|<=O zx`;HYj1@>a62r+y)9g#JiD!kW^MF#GA-3_$ zlBno7R}$OwsJ)%%?Q`~afwy(MWnO6egIk-=wOxN}^ZB-G@J$(8AMVqQvq~!x7b$7| z|D@1t^Okvt&&-<}G#qVeEv$k&D$usj$`~(+M|m;9L~TvdcqXYvL?{oCP@S3#O{%rt zb$4EWy+@SBhCDU3e-v#Kj3!~nhRGfyHMpQ|5duwpG_lR*d1n2kH!{@+E#A_}CYM?} zPL=K;UF~?i?OfXV^3_ZKtc9YHr!sPi*Sc+sXGmE8j4<;NwCuMa`crJjKQNNX|GT$C z^V(1&mSfB&KoJoO-tUmEOeE%KtLf4kYdsb}K_B#WzQpb}YFy{3($Pn*rPt%NV(aev zIqP4sNjsMQQUB6wnVD&jYa&h=6e>209$o)J*f%t4eAqYNLt67cpiOIji#7%QwCLqg z&z~9nLg!1P_&HSygkjfR`_#34O9QF6(mZRyH_k^5q_s=`=-^Vz}qR8<$ONL0@^iq<$NYwiaH+4C8-5^FECQB~+ZyZvqy{n12a}7${?Fn}XNXzHc~I zN{dIsg8tD*I$tim_Nh)7-*niBR&D#n&!SejZn{#tV*jJo8+KkFUm`!VLslCOjxV|V z{t5fDaJg?e(mK@)^2F9RGTZ?=`^_d^Hrf@R-8|W~oCIv*b(JCIai@3HM$0C zhQX@LtUY7Apk+}|yV!rMgs7lpnZKb-FSYmhkBP|`JhmI;`(|V`cudm6{)P(0o4rea z!#I22Z)wK!?r)F|H0|H4@|XcOd-jaW=bX1$Wh>IMX@eEFMQ^j?XQnr+2;3g}kBK?v zZ>S3MUSRJ^c02v}kBLcU)P+G7e}gz>S7>|Kb*)mWCW8qn4ad!@u@a`>mSogFm2TWT zi-JD4LBGbU3tsuCrs}uL{Swa=`e_&WO3cWNH>MXVC}E@Y(($p@pTt^!9&2R-;Q0T+lA-b$1d$G zAI&lNsR1^*<)e^U9k`koIJDL(z=?;QO||Sk;-3;+Eh`1YBHk9R=566(-j=T6ZRs)! zXMIZQqlvQVn{gQQDc*cewr<1z!!sA&ZT!bv$HkXi2(Wk?b{FGYhla;Q4;21YQ3}t; zy4~|@d))KOhurgajgS}&FI2au5xRbksLxcn9Y#1U~L^6az zSblrz6G2{%`e^NDqrS(6s84MkqPpgb$in-NiSP;!BU?n9=3pYdZ`DM-*;Slw|K`|L z+`IHHvlzt!w$8JrWEnGZfwQjh_ zVgB#eJIsD{qkA^e;odhFyN@mEZc0cMOjOs~u*~99=68{3(U571G(sFMR3LlfVt4#* zhm1UP=%fvv5^9&H-Hdp#Akds2B|}|3@h(i{nA2!PL=o}0jx{Gq8{Bx0FSv-Ln!hBULi=7(AqErnG#wVh9Ff!A1AcC^vnVtt=X2EdA}uHma&VH zjKWn;#LR3eQ{mq~>N@bNkGtpQkY1w>eFKR)x*j6U&5p1Yk^1r$D+GygBhX@(e{CJ{ zp`h$a^@}j8k>H9^-$xW~^lT1LA#)NKWviPb@{Y*b$lRy^S|355hMQ*StB0`DN_X!cJLhke~|V<$-M_xJ1P$uFdGOi)&j5 z?BE7AiTiVh%Myr-73ObKep?82Pe%rgdK0YzR2^VelQa>SJGX!>5+Gcs@{?^n35pK^a8^hEK(%K z6DOysm1<^F6~YDBe+e2q`Gsg6rwmh-49}|AQJSLg^uskZI319%<6v*ie09-+gOw3` zQtkg`qP!Fw_Fg5pR$tRn_({(gc}~5oLsJKD5!eI*Hb0|&G^`xJw)d9#la94*`NF96cDM37#dGla@e&N*M2Ht|_^gjKr!?gfi>wvB`pbZ-D z3j5`U&hDULL-NcmtwQQYJoB3>EX?w=`;o-;yUIZWMqh9b$=08X6s~}{PH3s{8}EV4J6MfMgfwf6;$m9og*QY8!SGyn_k7$m~d2$qPj;Eu`*V$dkFAOzhqT{6&( zHPZEKq0W`6t+C!WW>nB}r*F0$g2pD_xCcSYe7|8`(3q4J;g+=DurX*{B-cPKi!tmP zG%mxfOwe+VTyZt7l;zi!)w2Gs$qn~8e)G1V6@*1vWtu8eaN{t_S&`)Sg?bM4x1?T)pkR#6;C7HwwacCIc$0sy<{^x1C4&?cb)+E zh=g9Ej_ei_cQ$`Ay8B2E@CV<*7?Vn6`eUZDhG()sY@2wOoN4pi`Z&+d*W#tqr+fe`j`$GG5J}ab)W%_!e>6YIZ4DpQV1_Jo5< zDUMNVVMf6~dJp;E7ak>yJtCj2hhnX8(XbwW)a_=La%5otp85dz3|0MCFa!m z$pu~trVYK6T2bT5@>VP&M12kxXKmhFI*Um7;dEhY#e97XVOks|JQPBZB!y_Jww@P` zRVKb#yDXbgtC}#uVChmlm;@J$B#n1%vbgy6KKJZ8%Cj)4AFTsp&Sf)#g1n1(kniTg zkDvRsdcS$kx&tDF65cbbEwi`PRbcjoyzOgTWE<6(C^X!6XMJcN5$tZ|;k}Pnv&$ex zr%)lot>)2BW5CHut7hIIWgHy2vz`3BrNr7dKB$OWB^%PW%e;shCV6n;3d}}A`^^^g z%w~zJH!B{nIag0bV%twS=5xr}P7;KAQ{w8U^h5zdZ&MfG8w>7mKCJ&Yl-36qs8?P%r zoPEK_Vt?JGmLrU-i8PyWS`t~61+7&Tej@F1XPjSkNRyzcka^W@f1h=AWSqW%mksp{ zjtD~VtTt!0iV-Hx!o|uT`R^kDgm_ZdNnjWJ^*dv(HuJxTLXfbcV_EL{oC*SPyv2#?DLN*^USKwsb#UuLTrZi&TP64RGxW2@z$)~ z8BJq}iQ0coNJRQJYX1+|8ma5b_+ggx(CojOD?0AyTqC1~Igv7vc^Q!A?bm5da*20h zj0ehJ{F!g7P+)78Z@SgKX;!yi-CNTnD}3m&p92T64bRZEa((DT8hC0h_C7(S-b5zg zST!_u@$1^oxT)D(fOH)P>b$LhIq<#E!r-$@6SQbq$`tvnO$MOWN4&#swWn!w}jjmuIa;fFI#YNEUs~T zcSSR9l35|2A~m&~qDb?ZLOi3|`A<>Y1xsUR)G&SA;{5vhGfxydl71F)&b*J*Tu6y& z@ivEdFiqc=oVk`SMGCl9F|Tn5bYwtosHf!2zbM9NKFEs3gDz3Ce>M7(dO`+9LOz$Z zbrlej^y<(2^YCIS6!QczCvq&9s@T=gdH?*9)zzK%SC*`PZ|D6@C95aoZ5hqkKIX)j zb8i>DoXEA8?JgY{bxZfU16qJfR+sD4`NJ|UI>;JZ|2%JlV>Yt-yE)d@aVRBP%mX`t zqMB1K=x4%jF)c108uW8pS&;OUC0-U5ElR<>DnBDS3Q92bO-{Yf8|2Q@4!jd-f;wB% z1f6Zgs8_07I8-9@y4covqao*N{@##L?oLD5yTG&&B`Wv zTQwU?7}RCE3dlu6F&4;kq$Zo8?0!a_#HfRE4*FJ;-xbzt1r1!~`cDji4YxxkA#(Hf z70Q=SY3p^34?&tYxD`@4Ih%m;TUAXyBShx53m|RK zBFyQz`bOT&A`^sq-?)e@>&rzi&!73JCM%n!gRGV(zf03oV952mG@gPk0pQyh3$j|W zd~-h(SV#ETvr1$7S$>xWkeg5a?0Jj7+0tuT2njrU(c;zE5mS~;R_c$%`eG9pRIQ;B z^q*-Kl;M?K(~pVjF%vyf1zqyymuRrn>~U?-`EU zw?zZIUeoQFN2$@_F|dw|@au4@j`Yp(P+-nu{4T@S4SvS(nNjEZW=6wJy7_M3Ic=&d z%N>b2!#c)JBYF4R?c2N+WQBI6Gi1*xKMAqwu#yjtEZ$ztws~lqjYIB;?8l5a?3~nQ zr>-o|770|QPUe4QSCE>Mf6O0zcyF(e$S{?>Q0QmLEK5W_mqO1;=@ay6`0i~D=XMDQ z8C6jhH6_Ez*lELwhEV&n(&KwYjg(*%C+ZNEc$%Xt=)7^_Zg9{P zQS_02@EgouW^c*qao2l69Bxab8dLn9C~s+k2ZYIk!QQJTu+JFAxlsP_`#+8R?&|L? zRk=eFSi|`t`XYYF4|cyPKZNo-yT_Ow%Lvd6zAN4DVNYEy6OZmQc(74kzPn4+rZvK0wGu?x5EK44DmKj9KW(8AD2jIS~4W!yr&skah2#TfiJ7$ z2uXCMWBz!NXc~Aw-$FGr=bhLkCKVz9k_s%QMDOHq-gt|B0D|lIye>aoQbRAS*nWhJJlYKIxYeZa#*yI5Td7O?a3-4LL}Jc(Z{C<4!EQqW8rs%d2Tb zx&ElB0!Yb-dK8aM4}OxNxA~Rok(C}Xi}YjWXH59gTqjF~Gi|)O&^mp(hV-#Kk#4F+j!yae_-Wq3PvQy6lzbK*vw4&8UwXYCv~yO(lq@g* z9@Y%MLUZhkMRH5?O2?I1`s(*>$3!X?Y?ipAwp`4$W@95*v1X* zBSuM<0IlWeckB(*2l$O+G)U+wlx3qvaOEV~L_6`0#{u4Gl*Evj`y@7%9Y`BCDl3gn zEvJ>bVl)X{Q;%-gO$hXR4V00R2(?0zJ542a#H!h%r{r=w$(mxP_}IjpD(r6`8=H8l z9Y@K9`(o^(R=piLrd1p%zrE_(qWS-dlA>vRH`he-Su?$W`dv7$-OPj~f4LG=i`SqB zGL+cOe{bN=-oEq7^U;;&8ze}|ak0gXFFrk+&&z=c+Iq-|a+`|rRlc0EFBo!gUkcgg zbBO;`U}#O(lk*A(FS4%KNE!~|mFkmz%4=*_o%OmI420AS2-Qn22E9o*NM6$#-kOrU z)i2|%UT@1*^0usrx0Q={TiMjvO#p#c(0|(my`ATq8=+;6lrB&*x9!X-rri&N(wT8O zmQNhhG+{H-lKy$hqPdc85WVQ=pdds~cc z*xUP=Hsxxx;h5iWNV6o2?S|us=b96ehVHpdSb0Bb9#vL<3Rooe?8%;$b4X-zY@pM) zXn^d=Fre9^Ozc*ufb=;L3>>rE;mKR_o(RwTG%&lzCXb`Ep{#z3l9{BP)GmDp2 zU7)F>52MU9jGj)LyBAsbPn$_bb$YrPiuoZhs@2wF?c$3(AKfIIe160fN$CJi+=Bf| zej&EuyjJ3grl8IEi#Z2`ZM)GSZQCQ&&$fM14{h7;uhe8gjW$o4awd29woI~Qh`@3A z!^&uC^X7*{#>k9-Np-67HeF;j9Mvt>N5=VU(1@wn zU7A~dE`KWjhmmv)a!MFFR5za!fX58LoB?rsV-4Vz1wjCnipB**yX+@UnTB9m`_qk$$!T) zCV6*>KM-C9fSu&ODV?h)wYJ(t#P`-)BJJkZs^{M!U{rWmc`$d#N49!>S?)V=u&J%GyH)FT}kA96MH) zQvfpvi@A%r065;I6C^o9SzI%CRV+n-8ypVs$Sz7s5~EVPiA<}==~l#MZ($3hEo}r- zJY>+bwJCyiLGJ?Vs$pQ6S@tk5kfj!?l90gUs6rIx97S1)Hn}))f4dSSfmRB(*2dRs zI0krjJSJD7ThphE!-f{%g;xU2efoIRPpk_PeY!zxKB^?Al-2bSn_$J9=EpAIFaVFc zXzmvi{rMSKNI2#rh>P)3ZW)`8%!9^=low_nmD&HUSmBVuh4ig zEb`o?ffifu8S^A%hgg#Aoo@~I4yaJVwX^tH$oTe_&L>mI8<0;Z3Dwc{NtPo|vTw$C zZ)BFh42b2`&1O7Q5%5};xf>lA26mTYwNt{bq@{K4#3?CEZzaRz^;Rr)&I=%TZ*MqU z_PRWF*`h)5=eh}G%89k=Ty|g*B|_L?$jwT>BILFod#AgG?R9gPZC0KI5ET>-yZC{r zRs=-o8HbL~Ikjt?%W7O&;y5vAr|X5IG#>FGBVMDXDf{m5kzBZvOy~Gx-(jvP_NogA zDE3 z;9`bG7-OF2cGuC)Z7*SCUJSl)=Ko=Fre?=|j3)TKqAdW7=Guj#3eEa<_jyICSPW$B zvuuLL;k8isyO2h`RXUP10+`Q6K)OOrnn2R`*=4H%Y9o~yiC%_iTxs0qwUWwQq}W0h z5>q@YLLs~H8!0YYF^uyDLUdeVTbhIrA{`mgjthsa(w_2V;!Y_|*tbe!;nkEsmjpeG zW*(fOk_Dwx*M=R0rF;Xz3NFmE{yO03^@{y=AWfD*4(u1r-RYnFU6J{Nr4jd!T4t0{ zrM4vAHN3D=Sij8X1$Q(G4RL3VGQ*j{?34s!My9V+dVa2u{te|@`pcGY@LXEHIrR}x zBg;4bl81x~;EZPj@=l(MC?NeYRVbC4^d}K8uLv4g6--aWTd6P7S2R6+>WN!!l^t(T zu!E%9Ui9(Ms=!*M-I}+6c-yITkV+aR@ivIdYa^q(_f<60j7!5OxsB8YMeHH(eNK&4 zvTe>{p$Qe7F()$Oe?q;u5e_-p*_@H3|Rh95py5Py6d3=x{ybxgk$j!)3 zjDhF*1EOR<$^MY{V>~@)Kd1BTvZ0+Ej>bE`d4eo8QPW>gnZQtbX-o!Q$itLq4 z{$cRC-?7JEu+wkefhNl4cQWRe##S_J%Z@9*>D*0yorlX}A3fqX@5TqR>{*+d^eBvZ z(1LNJs2R`a{q66~&nmxoTmC(rz2*6`^3KEM`MWN*IQW7k9oKI!U=clL$-KFF0SAR>z42+sQ zIQiE>$DUxp&eaSg@0~Y+2=C)%2AA0&eg&Mt3DPi>Z);;QP)d^c_#a2{w-G3t3eZ4Y z1h;K|(7dBFvAg{C&Yp6ImCug>U7QgDtoJ<&7N`BZtvt(Np>5y5nBeC=+Tb1AI(tV2 z-|=}4mc}-3hYQ=;T_%ly&6{~27>$-NdJoh3R=+Jf?t~gQZ*EiJ_5Uu;0>mnNDNjy* z=A(sszi4B&I0T{h&a?A}`V-0z664_CVr&T`OKc4~Un^~6BzQE^>^I{4QEz+Bw6mIb zzBZ~&?7`bbvtz-EdgaME+82Dp4OUgX~R`r5gfe2$00EScZZ-U6qs*rt@S%U-+0NHZ85?d=Y!whQ!i`W|)bXv^K zUPJS~&hE-P6w67jjQ9vIU%zkH8!EM;p0+ zk7~0mzv+A+dz?FSytwbD0Wam7_r3UY7?G07NjEU^sT~rY*aKg(y2rnki)50yQTJ?(NFddnOrr??yim!t13bHMCZj-E0+)^E~=2=q>Swqwfg*sK}T zo_*kGaP5g)oplN*QK$SWw&^QS_lca%U7ESR{(p{~x!iu^O8kZbJvwYwyJH+qZc$Lz z`uaismWmKpRLYFFf8YTPqt90Q2R7PE6$~8zz$AOG_77Cq%TzzRo)`LdZ#_@{K((R| zxH!qtq(@Prglf{7@VLt7ZJs8GbIf>X_PG(v5jb*@#_?URjH*a%%4;WQ>UF2I#W_2{T^Zj2DhAiV*-SzQ4N9nXfj;81? z-SF|DRr4Z4E}q%p3TG&YWeLBI)W%z~P(h*cAR}}|DVWmaIw`(&w?)Rc?g^j!cwz*Y zUv(!b_HJ`u4y?APcR*+)cbq?&qi{7cJq@cdr;u=H^&fYt9)i?F1e{#mTs4b0Z*G-y z2{2pDQ9I3-b7nf#xQy(M<9pXxkQvfWR2(g}ozhWdcO;tpm>N3S>4}Aa%e! zy&2Uc*EASPBc`?&TmlfJ>Djv*2;v78xgtITd@1WA=0@)`d@}ns1h5(Fk`&8vz==)b zG8FkoNlU)+lH`KN`D~1mLrg=Z>tR-^=AJlk)P@vfYE?mh0F z*4c=WxGcMrHUAQsS}|dHr$wgfDiH&)*W|zHoA)5!X1Q;l;hV!l_|1Ah-?hwrHjL&o z4h`{LQ;4+^b_wKsSB*=xOK8cPqc&b0>K@PSmp}zny>Txa z=eYjJ2oVo0?Jr~_{WlmvS6Hiza+LN7nV z%h|jLer8nWvX4^`uQsg+>>5GZ02c4m%hST*A-z3wvL}}~cJfSw#@Hjbv+iBmaheVU zmI*JtS;y(j6oIvlOnH;b$k||2ojKK61Wq9VXFV(6X6ds5XP6amhlj)VRlX00Z8Aiq zLb6yl1w5Ob-wZqn6y;5H&LJxTbrWR>ya!Z&5xnDs1fG$bz|$ai;4Rjh!29+Pcqj6) zM`FZZ3ZRO~VyN%80Z?){4h@ttX5BT~yy=&a%}&RA82J79BMkg-w(`1!J*3{OX1*De z`mXpK>9HRXep9U8qv;CU=L#8dsN9^bM2b@`wb5$ZD&kRI#e^f^U>=#ELl&OOZ!T-& z!=|QOb{jFmi@GKnGe&$yg`f@p@|s*+e?N2b@W~(LkA`zgagWV_W>#Tt88LP;{_wH0 z*CyC-x^-?2bWwT^U~Rs4eH}T86BRRWJxJq1R>MMD=wj7}m}1>bWv8&W*Jx*qw)6au zzUVvFW|z*^xZxWCaX!Rzif`XX1#d=v=G#eqnb|{1;dr+&WD7xrK~3F580^<~NKk!Z z7_?3m2AQj$W3uTDNA$D29Xa{q{4?P|{#opi1LUEOkiYw*SgZB{+eM8IEeffJkZCap zMxrUU;cubEOHrjf8Z5MU`&oO&!)NM!4rwp7F9vCXo4jFK&&EAr6LNIB9HD&sN|IfQ z%C|R*WYvGE_rLrT@GX4WhnnvSd$39u}BQR4u5VdirltstKEPejzJs5 zP^8x0lh2%F(`9rB26?shq#<|m(8*VdJHd!1ZQ+RafsLIbXmXLm?)&ZPtMx}$o)?c^ z2D`E%M%P44pIus3V$~GlrbJ`1MYE!3vm&36h51?gT|@Z2gd$-!eR~8IayMw4^?!e} z^+cKr18)Nst7SN5wF0}*oj;h%P9-!Pr-nADQga{^q-fF2l@d2{Yt1EO`46O|o?R*_ z)@pbIX}gXSEG{Oya)h*)XvAMilAV`D8fVMYfwLs1u<(hv-o)41TZb?e9^j(217;Mu zEtW;%N~wV%v~l!(Gi*tz$Q|t5QX$M}0UC}OqpfJX4#$iQSj~?4?vu~5(vIBdGjN?F z3vZW71XCY!qy_KwN?6he|KE!xH;4R=Si0V@qygd%V2sSCbJ=df?zGjfFE6=y>?D}d zxD*W?j5aC9czrs@pzJyM6E=xyUoa%*oJN7xgs&yzE5qX2pBceA7wE(Ma1RLr7=gvk<#X_ zdQt|VuBzEJ`3W0lW&(WSxI%T^tmy?NiMMndHE_W~n$!w*ftGNE7&K8TUCVh(tB8@+ z9;c)#*38QU*kI}=G`^_3^MC-<6lAw#t8a=RkQ<_Yl4yq+`C~NcV9XlY2ZX6IlgR75 zdl5KhP{vJxsPn>AvCXe=fWsrTMT-q)4&asoOgyvOMT&bCkM^%(o^5pTYXm!-EpH`1 z@ytGAW7Uv9O%3me^|kenR$KuhNV0fWBOKVvB@u#+5;b?bE%lm{f$ zk{|El^EF-=kwk6aw7%3-Mlt1QG%2avP*kxfuWg&{P(fJe-?hgQ8Z-tka9f%o_~ACD zW&*$N4w+{$4k!Kc)I1O(Lvc@ijRaZaiQ6@p3e?02{1sr&a7+*zjM3? z)Vg@_YM~)99MwJM@%)oOc|%n&|9Uag6%MXXhebL2B) zoV%WpB0>_K0*(4lR@r;l50|MfMRQ(e>JK+`I=f%{+i!^z^QtD02H3e^IVBdWbAl9NRnAO=|0Lv8M5uVK z7O9hC#HTLXKtK}X-mDGkBjZ=k+5mc-^nKYElz5BltXW^MZhSNfRU!w$``oOpm`FA$ z9nvd;0`b#R#E8zCRK(-F6kdMO28!jPN=qM&X9La1BFI*gSeERIWMWL65PTz$P$y>_ z3006vk3I{7#DSAyE8XROYadaOIfkOY&agEI`n&Hc%B+>fvtM#0xykAfwh2{0MWj?6 zHWx)6o7K>yBe`Fit$RJ%1K$3b;vNuB(GUJ<@I4HJWc!rNSZFv6viC#$W^rDd)#fkT zaBSd@gXg1f1)`!nF5R&x-DwJS*mD;v57NPQlMwZnn>oV<>Jq?p!@xwLCv0xmwQq`! zd@USa6~o!p%FcJ^u0Q!?zP2)Nm?v5%jV|?jzIGhPxr4dJGGc10>=4|w%|48Wi}_Tp zv0OQ;_09|2RP`6j_Dgxkn|MlZd!o3lnw3m+S&g$*fP3$WHhcO9*6^`?Mz*fB*BIF1 zn-0uSI+0zNs{NgITRFMgB_EdZhRNJPsfc~b=ssu_j!pbhNQdekiYt34$O`xEcM-TI zu)gDJit9;l+y?iF*Tot&UJCVJ^%>t&OdO`S!?ft#xq%_9LWqNmY^hb3q;2pr2`5ht z{Abd|i`k)nuVSe9w8p2hnmyauL)GZj73_TswY9MFk5RUy;ACdROA9m$y;59)jd!F0 zAzbq`?2AlC*AI;D?7ryRM(gWBO{TkTOm@cLKn3o)R*x|SbGeN4ra#Qj>W6{LOFztf zti=~q9ZgBvJcM)RfSa7Wdp@Vx!)_wTaP#L#m0joR-qmMMl2is=-C^7VE-w70H42$l zi_^}@Tl`JhWTIvFeA5bctFUctm{h%S-=wzf^<;3%p4|viC)Yr3!=z7y0WZFFlB+BI z7$w=)dmf|o_}0}fe!^pJ{S*9)tVD99wmPU&n%KiDIf)v1dzFOmdtIp}4#9@Y3onFH zc7aAcEbN}$get9TnTvC^7So*^US^n%ILAvPZ`C&lbTzq3$9*F>APm|%7vI(IVs9o% z`n*~c!aU&{PBn57Y*J4Naaw2#<6nEY+xkgdmQ?nsf@wA8W`!ZF+~ zK4m}ID}x}?l1VYehRn^D$}9VviT5R6&vq%sgh!HwoOvW?vzq=+)+~KwW#vEKfyh9- z4f={NIm(ZW5+f`~#=?_WvB1*mp)v-uO`XWq#5R2w7B+QD*<@y0)Z3er=Mu zA}(P^D@QZl?Yd75@7ukZ>X2|XlYtg(9aG_AZ(*~bV<1l)Gr<&v;hHWD15kua>qXQ9`aJ7}%|! zdU&_{copA99BrgcgFIBUA95QQEq}E8x7xtqSgoewio2LQyY}EE4J2v@V|_X=+qH$_ zvEvY8rt@$`h@9TorVrARGoNIysm6VbXRvo8sU6Yzwu^E55|>$YoY@oD^&juE)%3Bv zydz6Ay#{@l4SEyF$c8wOm`9Rjbm(h2@P#Xqsd^@Hco%L5ZOf;W4jZn#cs9h)^&|H) zjgdR{M;!tiqy5g~ghy`g%7@V6wyVQ7b|RQg8$%Tl-GG`hp{5aX02WClAuBZP!rB$t z*1^tSl|9gqZIuMWotZt-Mw8r8Akw`vgB(&{`&EQ7Mq?5Chawznb!NjK-M(Yo95y=TB6HyTMMhP%7g3XA>z#ZGPYUVh`>qZ>=jwM-U>EZ z?rhoY(cPoFL#n2l^hd{J$Lz6;STP8{s~_o@xk|g=eY-Qe2QShVuOcd{Fi|4PnH%(-`6q@Q zwWPBUm)YPkV?3?p*;&~=5yd!}R9Gxb{Bk3Yj7kc@MQ^KXinDOA&DCY5)}SdZtXp@u zxOY2_kvPqEL@Jr<>?z?}nW(a=CemMwwVp;9G}ekCGp^<0cF5$peW#V-G)>G_=905b zkg$#m!>6I!ZjNnQLDJx}H;+6=1ig`r_~_-uV}uE8Nxm8niWo&K?=6|d#i3J`U2w46 zNstfM*N^NM{^_TGSN+(B&h7)nL-JoJubQ$M^Hx0ZC9r5(WO6!=3aH|q(AzA%X^dh* z+%&5b+Z3o*^fA48dfTZtuxH9R#y%5n+pF`_QD!2b{?8yN)NZ(9TBwL ztC~Fv{EI@*qGbHk_Lkf!s&10v)_mbEc#)vo?iU|41Wb^fS;@ z?hHKo*3r_AHoIUrYv3G6_Mmp0ZyIy~RzPjD9$*O6TmuEzFb!>ZRz)^EtEGWg%eY#P zMNrYesounCrjruCd841$Buk#PQ-j9ovJ{Gr9p>5^zJ&U*wC-;36WfA1538KPoN+$F z{H+yIuB)3M-R(J*(zt6K@1r!WvE9wsmS{f{K%y_G!|W(p%v0~CnnOYBBtLPC*M?ly z80;WQW7lu)4_c?Qk_c-UD=lQdfuY_5TKC`uf7z|-h9Gp2*RZFIZID_7OH*LOhkbxX zQRjuq*ymTe)Mfz);_sTm2QazyoOMWQ>m&i+fw~67& z#A$cM9`PujEkW#&t6fD|^`s&?f2n0Xv<##YPy`<(4Kj7E5)JlKc4$~GBvWJS65Apw z>;qg*Z#f>KR0B~?;^WO*l!P}^U*WrbS_lotn;+%V5Q%FmX1Xp~_T=xxYoeqS4bk~P zw63gne)HsRyjUOa$?x1AfO+58T=E{eo9N>3)pme48wmM>YnHjw_tCoI0) z;Hr7EoupR>%q3+ng;^H(~^sN@okJ=x|nYCAfB_1AJ zC73fGx-7{lE3QxSlZ6vh+NxN+#O>}z64&Xu{J41bXDq3x#q;A#aa>+ryz^|6jACRl zn}Dcxw)p~vhE3vV-4y&-&~VmoMaih&$I?dP zm_G;Aot*s9954E#iOi8aDXRuDX%GiZead>&vhKF5do1fdW!?YAtdSiPw1C`xj0qw( zajesNi`-_E;zhdkp!Gx?nl!2Q8D$nJY?cwF-kDKej4BWPt6S&Q(lvhnl}!^;MU*j-Ik1Q<@bP%x7SVrdA&r3*#=O1V5utoJAJAP@jHN2J8 zwi)%-;1N&XpeT!dYg8G8>H;bdrkF!IUE-k#>S+T(ALN#@bGe~eh-%=_pG8b(8^7|=Hic-!HHt(TnYPv3cT%GuV z<8pFsbe{bF{W=O&4or6UloyQ6EZsBdfu4x9oKV>_ zpXF(s`8=kTVy*5%HvXRWKPF|2PeSNZaA9Muhe0TjS?Jhx z*z{%db)sCx>1LRV@$fvIC=XC?y2YR5k6p+tJoNAK^!d=)W$eM^avTV}ruqETH1U&6 zvaa#GUjpLLL~t+oOf3aI+3fIE@XdV4{Dg=W^lcc^DcLAjr0G^u%A01u{7TjW5{3&+F-ks&Z=cUUv>fgbid8&GUC>wzU0OKZQ>{W({7EKYCDLl#Ui5V+y zRJ)E5`#oi+G%W?Gg|yAZ&>hjLA-&EV!3A5~#nKWQkmM%*CF0Z$-Y4n~#O$!y#-yR3 z)}3vC7b&#PvKq_c_lOjOW46$%>ppb!Muz9=_o4=)eG@zS*12w5wPo(Smcm09#NwN< zzI*ku!7-1LA)7eLmg>}8HdEy-t@E;GJv?jHKnnA*Qr&rIyo>`McjpJ=)*v-7u@9<* zJlfxbM>)5DlEtHfEFNLq#9TMh5u83hG7f7{*hcA0T&$uqu?iL2Bo@B<0M>-F>^jt2 zYvB`zy%o#}yJ)_3&T8u@*F|HUfQ4i~Qd&(ICXg_*k9&tSQXi*VQ}Y-ehxI|dv`Qo5 z5Pz6y)R_6WI_a;~L5ifeJaves`R_QspY;n}>MeQP{L#9-Pi&$tu!<(C%)L5SPVIk0 z!bqA1`n5ZepI?%4L?6Z0U#HVqRHIRZD1q9caobBlYH=B$@vszsinyg}2N24cDs*Nt zSG-RYA-AZ({qF3fg-d5A^Rt$sG#5~Vb0FUFGgk;(CrGpc9N4WVfw);sCGroQ;K|*p zQM1AchXSe9S*E*%d&XgWtIj24_-oZqBiz@jp(4GzRd0bL>K~KRuz+f%bBYVEB5Q1G z!$Kt-C)O**wl>5ZNLoGd#74#1?nb%iEK==0aE#1gmIFpvd+_#KxZG1~D0fv;gP$}^tQ_G1n(WUJUn$!|% zE}99cS*fp_GlYfv!VOLx8=Rc>F;J*Y8O`ho`gn&(+yTVxaT3Sx`(6F=qEWi~-bfR?b>#W{pnJ<=zK_ za^D#1u$%~*BB~$V!kElOz;>OxC0p{fM!Su&A6>rK(+vfiS{XQ=O|9WjG(p7LDnqZ$ zlS@TYjn#^vt29aCHqg&FzJKaFk}gQ98yGBcIjUe&>?bbVT1Pwn7L_U@%yO1*#<_#K zS)<(x&FI$TWX&)^0yoo)@=Zd5R;YQ0ltiszr)T?~Bzk6jMDo#Yk)6nSG#zR2wHat0 zOCv|7)ZH2?f34OqnQOeCRis+<@vudhfz?-MA9TTKHx@}1*~Zb$gH--EBm-GDMQiIRNUn5$R;PWPTQ5u@Dg7m5jFKU9?CH77}1M@B2ml9t{dsHoJ;sfzM@ijrOX7MzbB1kW5)KL@BWmA`4H*8gmt^YHdar>xe zWZi(N?bZ+glxI>~d|=asH{t5zJQgw=V)LPzwKvsRsdv81F;j{$4Y{RsIcd9>7J669 zvLXGZo)l;vt&T9s}?um9#U`z37kCR z#DX0mmoaUM3YYJsKojF%w2a`Aol&W=i9fZ3pW>`(3D+wj8%FOFi>qM4?26N=Zu0w@j{7J|({aD1V@9PHy0SYh&v|nKU zx*`ko#xV>n{HrmM<$rfqaaJ>(aW{Q)$`EA3D*~30E^)kl^GM*Ea{v(9cFL^~;ZpRG zaqJNXpfx@aHE<2e&Zu2660|97xX@{{t~YJY^w_5RFq4Wg4}Fqb%g#j3o5r%U>5Jg9 zv!lNRY5GlRc88rENAbHok2i|lTLla}wH-@^?jt<&X}~;a{tWoIRe%zX?9(qNv9}#| zp&~AywDO8vBb{ZUa*mQv z;p-%a>*n$TRX-E^z;j=Lq8pCE61bmx{^tg6QDyLxeoFC{1ZIHQEqwnIap`xSx$ins zynlxUo)H;irk8{!nM5Y<&t1BIZ@-DiF-m78QX59?u|xgS`w~Rh2k~8|LHGk7R6bYa zPuu?!`9MZe=C&CV-WXRPBjl$NMd%wSk4=156|;TvbmyuKUF07PVFqf7jGFPmP#Ael z^UMtp3Cu!2A6ehdQA+6M!%jAlBc#x)kgRyi;#%^1@Yrk%<(4!@{8Vg9lN%GH5<_hJ z8al#_398VVs6BvN?XkxkS+&%ne3~pweWt(bCr;yL4j*$QM&^6N&ls<0tOB0-G&7s8 zc8T0v$jipCw}hXCS`@SiV6SaAv@#TFOA9ck7CcJ%tQI;A*bQkuJl9b#`;qo)74FWM z&N;nG(m6)kewAdm){dPQ;v?y<_wJYwlu~x+h`|{FWJ5BSETM-wCq)r!RVh`7>u6}t z%OX)tc&`FHBU6l_MLCVx+r|S^Ie*4-RRbJX5@MhyQcJc_D`zK9HoHv&EcoMIGfjD4f2`!2DgiOQRk6<@qO=Kx4LGtA>TF zh5|;gVw+B}69Esw^P^wL&gwrW3*6mif%}lNzzv*jhcuxYTRB-qxP*qt45*!w=rwGe zV9c23a(+aTMt&b1*PD=aVdE&=uUhV{7}Hok0GAslBY@PJKoIJ3G87J7+( z9|QVUbtOS#b7LFW#<|ADIHg8wI2-IbnY!0baq&S&b7Tj(=*+x z-?eTne3k2)3eWya5qD+1wNhvlt`66Fr8vCz*Q!HA0JRmd_3s4}7}Fbh4m%_FzVuye zgI>ZOlp(g?0$opB0bSdKt{p zh;8~W_?j#7Dc+jDtWm>N!{j3^!OF6%t$eMA6PqyDz7eS=WGj$)pn_>XTRR~*49(u{ zH5E>s9)-0++<{s`EE~72$6-OBV}$qaJFiEN=w7e$xk109Uv>{S{R9%`3&L8TE$1mN zwf6zhA!}SHmvu^SY(ql%t?_zel|}MOZ`uGjxvUF%qZ;RrpsuX5TMQdnTlTINT4qjC zus~1_ySUx!P2{M(q_dkMy_L*0UI(+N*G!Qqwpr}DQn0$1!fiv-$0xg{($Z#UganZ3 zaxf+Ks@eDyJGukdQc?{x=5z;8r38b07dyM-eDs}As+nrFXOwPTPO_Q!0O%*x%1VAr zI-!Hpn?R9^(>KCWJsg%AFyesGkwpg-ZAVf~N@O9m%MC{pF!jq9Lf1=rT|#(Ho(<2B?OBTJ{E@?ztRLD@Ooz&*gzqq9LG^ z=rTYl(Hnshs_Gn696rjhsW(Of8PggL#Www&yzh1NPrJQ3mUZs6y{okOxPKC`uZ39R z#s@Qr*8gSF7lAsuhd3R@*OX_=j$~&&G9Dm|_NjJ}D4t&Ttm>6yGJ-R$1FkHdqwO43 z9HpNopj~~Rl8fw(gRN0Mpr;rDsw|VeWzF%-hRU53RXbS35ro&;TJdrK5B~ zUj;W`QwxC7#RxG*mx7NCu_uv7*GW@($q^%QNX~ApfRsASxksn+l0Kv*8H%yhGPojF3~>? z>GDlqfx8dh>c;W&iSfdzM$kL>`xLb0iL+_KK1YLwK3>7GV_j;@Fzj7`%*Igzx86bu zkuB_@d#2;WF0CY1{j8NdX)*LvbdK_B>B!Ve{L#I-cC(A-)GJ}&6P1&H37L^SE=N)( zFBsQRGrr`|t)Irq^0#OZnf_+{%;J+65Gh#OjI>mn$lx&2 zoy23-SyBgf?VyQ}V?T%e{S=)zy2|Ymcq8Gx6L8nI>dpXjvGn^BR`xdd{t_qUI`fW7 zQDHettxEugcuv)7vj_?yINw;6GDY+c8ulGWEV z6&~WSM1POm)VaDjA!1a%n0yHRB?|4vI*92GK1B zeoDTglKt*>S5v9pHF?;iAr^_-q*1PA`329m)BMP@lBU&^r1Uj$iZSf`m5G=|eTWqu z(uVC6-nyuEHlTVb(B(N$fHwq%OPk*n6teuzpj?w(gTdfazI?8Ev+D)TokeJ76``5+ zyF){k-v^peL(tqKXjT@XSyqH*+3yYwS$-dA;5Z65C^cp8|II@^wlhPMYM(Y{pY^64 zjAbR_0bm@a9-Jg%gLM9*C^lQstrBzDJ3s&bBdV?G?5U#Ro9@=M@>vgg7FwPy>qeif z>=C#$tR9J-9QJk~1n+aM<`$s?CTDKiYq8Dk;vboirtQ3NH6~6DTc>@Lq^(ok1ke|8 z$dJS|ZgA@QQ2eEg0R%tx7=z00YkB_;?+Y13sTRy$Xbi7lLP(hyl7^hN6_JP$>$$u0 zG*dE58FboNYKbw%kJmVr&>{Z%h*_{((fg)7AIr!hJxeBg;kUs_8^D9WahkyrcYIo3 z$GtEF-X0!e-}OD#(&i^P#|9VVMj-RT+9?4Oc<)Wm;gfF*DgQ7SSIR+rekncz%Xr|@ zvYZDCZAE7{OZ2I>ABY=~*g@Kgros=t8y3ite%!Fyu>+I_2ZgG{4wmAM-TqX375GSq zcI?CDSJ1KFUsExNC%nYznRLLdOw^mCHrCm3vE+f9gFCA7w^edDm+*4u+46k4stkpg z{B09hR^~OK9Me6oP<2i>O1NAT6f7A#ZOLc^mXS+FFv6S1(WAYkNz`A4B+!Q59M8a% zW`@`YM~C|!RP=EDFO?((ncOj}b(j~}p#n(H7R)m#<7RFto{Dt_bPQ1mZVUR*f1sE0 ziHh8k@)Kj4xC1FHt{qd7Z?4FXnNRVgyG};mmA=AEHL%bmdj_t9H7~>Ol00-?@hT_; z>kj`fb?+Zv)p6a63UeT`BS(9uN}{w%sWv(8?-e$Zg@ZuCNIEBxfaJm} z$u+$=PqJ|gv1EzE+1?yk^n(@LXM@rQ4#^{~?1%kPdJPen2XaDe;?h<-Aueu7dK`EO zPH93CTHg2jU9-#R2a;Ty26_5sk)m` z$Q+h?&W{T!ML`PUZ1zxyKtzcs7WRR^JAEHgSf)4yltNyB>S}rVt7=2(^jgogVXTR5 zC{&wogH?aMHn3W@;d0c3!pdA>cU@K3e@`1O3wLj7BU^yF_;Fn}QIEF79)v{$M`r{? z15;P|Ph90cd6l2C@j`*o0BLctKY`&;>@S<)e|TUVylQd$fvfxzGyD$^OwRBF{;T{` zGyD$^)X%6d8rX1E{2yHvzi&o7^*ftzRg$J|9b4UVBoV*1qxW;RA4MESQ$WmN2EeIG zj;U(I_yc1o^D`fzQZslajpw2ivoOd`>Hu7UNB|zipP4}9T0sR=sYkEJFD?Ov$3-{5 zGRzLdca$9mGqET;%{CrOv*iS@^#Xo6E|x!Z8yG({AWHAS<~&Ltz%Qe7QTnJk@ib!3 zRFvMQ=n4H!@waC>N}rYYqJ9%7ACJ_ZfMUouqi?|W;SrixJNoJgXSwCY>DDP2UV9Pm zMNo?7LE(FFC!i6Z#p&3-;INP!IysLV>sy_ebcAWo} z+HpQ=x;(u$MoP5`%jFAe%BhfFwH;RFyM_1yS@bv#(2R4x<|6U|H{byN16+{Pd(g2| z4DUWn#qcBdB5C%eVm&w&*m-D!N<{0X;@0~uI!w;$ja%kFkwiwcA1<+Zz-npKv5 zFn2@w&^Hm+Qa-drpDhg-9=HqxD>9pZgYcCN1Pm&{A1WT;m)AXEgl04Ow5GB_#e>ZI z1Jy3FZ$?gl%1AGtb8dN-omZ5*kqPQt0#|!+>1^}B8RT(Qmv7;*R8av~zWjj;F2S|H z*H+6PAQ8P5_|?o`aZT_`4SbSU%Q<)RyNVFgFF^bOiYyuL=s(wM5HuhdX`Nm*Z_5n* zpGe=*dtXcW77}Q@utiK$Mj+Gzj3%sFm$8!IrZ|zTC?a-dRG-3$)0yG4*Oz@pFnPl0 z%;@6!&KTq~>+8Fppb(SS&qyTBjLic|6wZt?K#9GX;Q(|q6)l9gnNdXHwdw7^`7zX8 zbRYOGTuJVqm_%R+n7r~WRWPRAzp)ONMBfePr>k-90@b{2zA5V$krto8oHw~F;4+|g{K4qH(>RWl43}S7=2X&0oC^E&G!T5VS z_gKcBA$y4LepD!hw22)SL7P0O`;px5=tugIAr%D;0-n2j<6}x?!pb40WPXS)rQz@G z9I--r9%ohk=IJ|N7jgKShs*_eRR32UkoF)urInix#D(E;7N`{3LmT#L@_yv&+v(ol z03KZ~*8m`kCHS&@V{d@mPddW9p0%cshd}~cxg1=D)Pf7C@p25dYWbR&OYph6yjuVu zWvP7`L{Y*mVLLlUmy6B=B#1e^^~B{5@%Og~z{bg2&$<~a4{KvA&?jB&^{j)j7(adw zE`Ltq<|tNZ_$cc0T2Y&i>&|$^Im0zy0>By1s6s;t>7>vRWVr4l1C%IQ0huX3weAcu z`<3-1gU+zjksvd!Amo)13$L}G`A*ZDqw9Q-A$P&sl^OC8d^$Vxyc}nO%%mJ=gACaY zUMnbU9pjFt`fp;lG07e7>5_f~Jj&u1~Q zDAoBJ0J5G4=mS#i0bi3({4)@SNbfV)%>sF;A2AcUR6Et>bx6GVanqy!k@Yu)?QmX3 z45jAY@_Lqvp7B0|{*^3VWmLmMffV`*=CN364YTZFmIlPJ+ju;{n+cMP0?Xd&BtikN z*V4ko``~I@N7n>?r16Br|LBA7tpl{5#8>PX1S@er`x!k5R1zoPT*_ZYHprd9CceudGvgQoKTX zf{wmPQP|58P^)QSGl|dIF$)bq&Lce@^cB}2M&J;d`Mo~ENt)tbdG`Czg!~h=oU+tj z5fMotnRy7GqBxZL2nuORY}*5YsUCopT8rFnGNMq5%{kuU3EMFljGW=P*5kxFUe;7{ zbCx`JFp^V(lflRZ4SY9ci4YX6$r@zNGYu3FGGx$M<20a>J~%ZJMlQL~GYn<_h8=KX z#%f|-Mr<%rVhmI3*|7C2lC|y#GUW4FST2;6nx^xdqa&pVg#;zYl+gj{(}<@K%+U&k z><>mN;RrHS_R>al4D0JNh{O~#uL>*1tR_}t>t%HMWsAgN!9=L96 zKoFKpf9)82tCKd>sL;>Ku!bd}#t#B(7mBzfCf6XfE~s^Ipzwa5Gjgy9-pxCamcwcT zrVE$BkD*Dn{&CFkPMp?1jyi9{YUGs}8F)Aa{XWnhXbh!9JGdIzO7dA0;*pZs;`g{K z37Xtm#I|IYBhxZ&xlsTjx)iCRzoO*k7zZ-WtI`G zhPZ1tqSX*HZA7afP6J1@8sb23M5`gr1V`Lz7&Dq51Vkf_`6O%U?Ih=k@BLU7UjuT$ zP5RVv#z4c*L8HSw5XP&USO>Hkk{vR(6Ftmj0==AH5uvtdR9fQ{d=?77r!o#?O&F@` z#I0gZXcH`P0ZxG^_2sFJgCnf z@WO8KM5S6h)kKEoS5B4)8scdjAzwWGomI({*mW(QoWMwFi{}tLjPiMMpCa?0B1_Gu~qwJFM9sWa_xciX9F{>gg5@2r>-{XK}&E0)?~0Ak(OD zD|C^#L2G7s|#2S9IT zH3C9X%r%NJ7+Doz4l?Ty8jRenIB&$-8&N{8rz&ra$k`!u;;QS&TL#bHT_kKFyVgG?BAe_#s&8tJ zy@Ty(KoSXY7qaUB5h#LmUZ$DX`hi$6ON9L#I>5iflDNQrjhv1>GV=B|#DL{A=8EH` zGZ8E;Wq?L{CoHd3Wn0JFvn^ik_rLKo&=E5z@_TSl#m_@p?@##ChLuz*P$vv6deUCp zD_hl4fl!)=qw0+esBn>NtAq!9j#kXJ8hFHXR>$Bs)`R;-D9EBDG|5^k+g5#r<9hNiK7LZvMYnrU%LC}TP(u`U zg+J^6+9di4TJV*D?wt1Cgvw<~^Bj;8zldShLE+!(spq=4lGlO%=F_^$=+VTB?`-*c zTPs{5@<9wAvWuu&D_uC*B8F!y{C24POx;(8Lyu!yZU@0Hx%VyRn1y>6z#3G0k5k^g z^5i-8);l<=`XBsYNtA={i(Fg?nN2|N5pr|z|HY(LvUkiX`J!7__BwN+Un~JIW@CG= zvli;^Wp;y+@So+CLVUBlw}^($%+afdI(EbOd&-PMZpt2i)!N%sPj|7nxWTd!)9T>0Owa-d>9mbL_4UdV852$>O+!mGb+0d0QWO+#OfYTVD^P z8drpUmoMAf49+3t>#t#|n9?&Y#eo%y#kjt1NaBGiD^|tpE}HZ(uZGu@qmGq#9YqPa z8Tc9@GrPF|h^hn>^l0Kx2GNvkQQKU5eD|EPL5a5oUvD%axl9+D9Yf^W$|b|Po-JAd zAkBWI4FH7cj+whb8h3>H5xLd7NaKu(dRuM5@pT_9l(N>C{7aTmwo8^@LY zWGm0(`dO^I{r@$dJ@w7OkfWNshs7_Yp=blzbF?qD*U`DXZqgofUq>!edrO6FUXk*g zJ0;Q@HdmpxI9t9rM?Pi8hB&w4EW1(Q)Lxe+pVvUJV%Y-g=t5ph0~Te$Da>InYd0y$ zNF+I@W2+#JIwS?HOXDbl8Bt(44g)b+dKvjkS*9!B?qsudNf93#Zt)A^4Ab_xEMqut zgbos(Q`yRcO1wIicUEP%R6+Y5<^_YMGvRHpmwGn5tsz{#019ED8G7hAAGX!eRBBs& z0Ok`WIvWwYKbyEQTediWBta%zkBg#WCX@u(MK%etjT!}hc9HwGxNn>Lu5jO^`;NJ< z^?|Q9c*}?G!Q+%@`Xg?27yJK3w~DU?pW=PZE!~^<5zEAokvhr%zYh$7-uW^Rh8l*!A7h@7@=UW$9i^NU}DZQ^!o%A55^WW^8f6fPyxS*gEo~jzD(r zQ8%dNf=d9HF0h5_j1Wc{1?ZJ7y6+tKwe7AdHE(X?C13($i2?$Tce28ga6$Xi~hj$>J4C8lOoYj%o z*0dNIv%cb9sY&5%BDuF#4e_156}8#ToEP+Bm*)tyF+wr1KB@MgC)vZ^-c#yLU$#}% z`fg4`$8^Onfpo{nQ?FqjF-e)>(b*hG;h%sN2u zC}&LgC}~lUU81xJ6`lGEGuNPZT0>W@xHPkk<~!hGU=2BLWC+)Rp3DwqA0o(h zqYCy40mX|`R+X4SLy_-{7WiV;0@+dsA0@H_t*xOry=`l5wGX>+$*`iimy=jzvJ>jm9TDhG^lYuL8d5Y$d~<;P1Krf({ac!BUKf8jBD* z0|W5{Fcu(mW7ZaCJGM3+!nJ|zi`&pm_Y9chWjW>`9OG9@f&gw(lgJHSTk=gI))_AP z9#Ua(FtS0U8qwLyMp;74s_xg^*qMh=G%g;_8gfJr!kmcAbLNCnj-ToNne~~Et?yXuBbSh7n8A=>pD2C zJTVB&i^p&BIB?1H+4GIHm3StkaTp;DoPiQUoaGOkc@JNdz=kNRTTz6_z`fp$`JU3G z&X!S=sKgmL=`1s}*wSIvuGlZcEs84hL#FWJ?b4E)5-z}+=`VSx6w%_uLz-VRe!%WS zF|&dQj|DC96HLQ#h=7oGgZw1}khp^srQBffvn>XHJT+sxQU@*Ey2A*5zUT8?4tqDA z9Vsv$q-Nehm-?V}Xo3F3W5I|(_sg;Ac_S$pt7ab@P|E6ygVTn>o8c^Vc)u2$7Uv(d z_1BZV9JKjO5XNP5)gh|lF+d0dtq-BhodMe5ZO!)e zbWk9xIN_E}NFHcR1kN5#87k>NC;>q41N~oIq;2$yRdi$h_E{p#9Se$ zsxNb5EW?n`70`F?{2WIZ%>?^`Tp|V`2xHCc1Tj1NDx07JdjH-a+D&0@r*M4O@4wV z3*37XG1c@pZ7)SIOSjSHFJLM3v+tM>RWaWp^R7YqPS0x57Df=F4oZDwOW#3*Ikyyc zlGZ7kRp(+@qf)a*3uC1UVx=w_P{RUPZ+RCs#6GHieHFM^R7Bpgdo5NmkBnva<{VsP zKD#BpKUk|ggx;}DDKYlpqpnR_A{iUw%6%Pgk`nnxX&C1*!9Ih&&a*DX*#f#iq1W6eFs59_7ok9yBJ94RUj<&|c0K z68{-E(KItfGE8{G6Q>LCB1M}TH^m%ZsjxL}j&V*FPpo*&j3*XWhvJD9FSSeJiIqKL zXe(nW0L-=1UovB2dG7f}pZVem#?%cix0_HlE1F-~1ui(ATv-zV_^g#P%ROo8 zlj3Oh$+};DTBBAZ65X+FX*IlGqvB;=1dxiww7-W3Kw>g8QzkGZAmZs0@07`DdV4c2 z&ZdKf#p!JT(Ui5H;Kj9SrBwW zVd#$=#suQwgCLN;MN9=Ru1lM@lp+D!&7*4U=GSo+4gD4%a0_7hmN`z+yh!-QM6<89 zHfZM)ZSYG#C3^!nCZ>HKdy&wzL@<3*P!r`I);;TIDM&3`u$A3?l3lez<@@f%KTmrW zmViI>JFQhxndGuv#CNgX&={b4hc8o;CR#mc1$F-&3UvgpAWCE1NL~=7O~(O1AC}1& zcW^@4&R;>q!5zWfohg0&b*;W$EPX$?54qoj122BS%Z+owEF>^>j3yhgo?eMX8?eFf zw@_f_+OVf6X+~~Or5Q~pInGN5tt2Bwc?qHA;m+4ZuzASEaX}jHPDHdd#^_)OvnbL9 za>!}q;`{ld*6^)%zUJ#$5`z3VGT}nrua&?5B+c4?_NL<+zeo+?W|i&1SZ9r^yVkxE z5`L)!BaFvA-qwpqe{K$3&51$n39vSs;JbY2hlu;`^uAfWWtx*`;Ek#tM%uSUTMuCL z4xT6RokE5sH6Cu!l|_Owd>QW2Xe%M--MVNi1s=FtmwWm1H3CsHk^BNF# zIu64~OHnHXb5^=;8%yByPfju&i zbBjVP6&avO@hA08L5XtO$U?Hj!`U*78Gn|zYj+~@%KTRlB*ri%@D-D8 zcnhQ(zZ( zw3~#yfsvntN74LiOhUpIPeQ_LlW-3ba}wUU1C#I(W-XqC0v!!hQ=%$Px2;pmkwYJ1 z2E`j1&bt{?6MoH0N5LP8GivUo=Ef7!Pq0W$ws4Bm?2~A?O|$=K)9ex|a+5!ak?S|O zsRkh2RO77{O|`#r)9kZvH$sq~W)Gjc86y~hoN0c=!&gr!&a}d$v}nw{=`Zrm4d>aC z8S`u}kT)nkcNX3-L)QKkgu1KR^i6lR!3U_n?7s2*+nKcRc<%ifb0Hn49_Fd{Wfyo9Df@1pcJZg(_l$jmv-aJdbK&RRmjj*oD}+0Ld!>DY zD*NhA8RL1zh41!S7eCL%*Ev5IIL2Rc<&mPoAK$793}^oKQkR}1f$^UE+Op&iaDid? z^X|(DL_a49zbEiLw(x_qav%G~l`DlyqPkC4hMS;xT&k`8({Bl1%6)3&hLYoRhk)#3 z&2_v}2V0q)jWBu>EL{XkDnVxpjLZZ}mxOHyZwp&0f~70Mw#uM08MagfOS{9i>Y#IV z*isWLT@$v|2A%7|mU+R_yLpb(xgl(+50>`g94YAB$fKm@O<_|7Y{U=uqv2s&A{ZT_ zqXi)ug_H%O+Z5sj!y^i*2u61(q%s($nrGNn6^uTvkm_J~Od&PF=pKdC2E+RlGA|h2 zuaLT6cw8a%!RP^nGz7yF3R&=QQ)MvvJl!v4(v$e+9-tM8V=H@(*fVGoClFXbpS|}| z*4Y(qE^X@y-^gwH(;G^n=Ei7Mebl@tTGbFWw?wNJM9oX0RgF<|TeNCX)Vv~E)e5PM!S_VyXtz?PiOFcUQl^Q_Xtz?PiOguXQl_Wm zR?75Ixs@_~Qf{S8pOAYJzva&j^AkM6q~*_belYkv3s^O=A43xzB2BQX?HOP^^W9Di z=;L!^`veVGQD7C}n)V55W_~y0=ZUN!(Z8J5>{-GDC zy1@PUQZj6vU?waw*a%TG+lw#Wc=uspOe$nQq7J#-2=Ha!m!;IRlmm3t*=EoI<f;3p*yyw`n z2<_X4;-o6v1McuUOt%fuyE2Hj4JGzc$$v#q9||#pQSj+%7h+O}hEz^!wBO1r+_X5f zLm@@?9t1h32}&g7I;!p)W7)t&>bVh%0^&VlA@$r?Q2>xlRcj#445 zu~XCsd=<{$JZ@5`7(fwLhCh!~9nif-VJQb=zG@WcZC0Ndn7GTR-tky=lKm^aYX=y& z-cl~)Z8PmZP9cDUMDS4c5FTjR+gk;q=twBCWw&B>8IDpE|Goo;%36~X@ zZU}LU0}EZpP|$og?05y8y+QNFVCg2Tr?>nRD+st8Q76sHeAXR_{zEz{VKBEsaqud&Oe(zOKNeO;{i{(cc)QNn-pzO; z0-}c)jHY5=(3yRPL0~v=JN<|Un|xpR>nm3Z1v!P_Yt!?=^B$gn{%B?B!^S|n?E19J ztPc$D;Yo#$dO+Cw@Yhy~pJW0*pl;laLi3Nx2@4bUpvz*!cf;QO353awLo`1Aein!CX6pA{WWpLt z?{g{W?CB>b)E8Y5b8cQO`KV zW+G)bCUC4o?4{6Lrt;ZTuiZ5{pM}lE&O?(~b~4)8RzzRb)&Q(!3!?xX@4V9Frr%+0 ztu3?_CY0FRSxDH3iVIKtr5defGgY^ckT&9tvWo#&)0TRsw6YosWi5a?rnoHNy|}Cz zbzE^-?2n+WF<(&2qI^MZEkVl?6x4`n00vwwb#S!+KCg`(%4~MC5VfaTfD-ee2p9;4 z?{=h0-i~IPUUv)gIG>;MtUO@0C(7KuF*uO5FPiwK>C(s}!vHtx!ijW8R zyAZT^#3F~YG};k?heTnNJ0MPAX2OQEOp@MH%t6J|p4HH-%h02`zRszJ|3E^C7p3`{hOrVZ8cvaI7R zCV(w&U~)cVUNU6Hz-%oHOosssOaN$Q0i5Ep+`ud<%MHvx1Jae%Qo!c_*uX^hd2J}| zI>T|s`_V{xvo{;>m2^$QsYmg`!vLfOqtLtgC^&n1I4QL4pU)!=V8d@?=f2~lJu%O* z;oWREIQ+&1h_+#iN$+zm`LvepH`d~(r7~z~pr``J?-V~VtKzRW69ef?9GwdVa&DG>h{)DKY6o^K%TBGKguINm-xh!gi ziInI!U9u_QpsDoUx8T!SdhR9)hv&fE0Ryh3xldk7V8@(@n!WK^(V1v2@~tWbXYxYC z*F3#BC(t!fa|MFWoP$GQQ*jvV(V!sMTCFrbsIl}zlX}?I;Kc;TA_?y6zW)rDEZyC^ zSct~DNDVmJt_v1&U@ok?*GFJ%dTwDIafVDc(_$SipYXBX*oG7mGnVam>jZg0WIS?i z9LI$V8yu>yP4B~n4TI+wbZ(jK^BWe<>nn?X=!`Hys)4$=5PFNzBJVD`q_qUIg70J$ z24HGoHRCZ*SJFl@B>UkAMT)SO7>=xv<7_yRlp`08bi;x4JusqsKD?_kd`4;ePKM7g zEs7((z%iBCXv~+V7Tr3=ka6I8J1Q{~J$@{7e0plj(BkK%v zzk|4)e9xyanAtHs=0C-<@6EWPjC-ahNXBo=ee ziIM#{4EcQ*=KYy9$NMsIfiWX3x0Gw#pBFXm&t2nk-V+8*@R1EQ}_NB=l}5`QB#}1{?Nb}X0oO;Wm=rv zOm)Pi0ubngI2CgE^4j!Z@O%qITD)|Ji6B!Nw4MON8s%L-(PjAV z;D_ROktze8UiQ0(`? zO8!Nl8zus#@WJTseghdY#C$<#h8r#na%$da3qq}do});&bCJ42*4_JC%zYT)!>*3? zDAaq3wVQ4&p-HVjVqCUJz#blc8)9+n?$Gzq`nNTFmAo~kPg93Qd;p<6vJwle3hdOq2EUU zjs>;dL-_BKRNealu9RgsnL<(6!v6ia-{Lx21|Ntz6j#2DRqltevGF4{n6*eLOJ82^ zS6S~A=)ZQj3+v$nb>$&;inhd3+(RVsjP5t?1N{`;1Q`AjX5;AMn@+l|&_wLtmWX}8 z0UrL%wobyPEn-rKmxn0Q=M`iY zI*cc)Cz+ozA&5ulJdt}8rC^E|HP6jh%-6y5cvP_zB@7LC(5ivsJv0s5ncYx+`1?Fs zEPET68GLt?@;+JD>^Fer>;mKfbAcbS+1P}Z56W;dZQ4?F88uy;&fByd`4M=Hj>`pm zjAoDZCSdKCnW3dcJsPRJ2xFCn!ft<4VKK;gOOY?kG?2^nN#^oUSkpy!O274pCD&Z}V-4>%=o6q`P;KMFT!JxU}mga`OIStM8dT$t?r4a?5EM5SN{?C7^DqIxcT{vCoJ0-Ocm!&w(GQD;azMf?K>+ zUlw*%oSPdiD-F9mhJmdN0MlLbq8p;Br=!~vQTNk{=r-8$ej2EXpV{?pfLKE}a{a{9 z2*zU3#a1tR8YB{wG4_>~Z|Q{iw0~j%ErN~6yQn=Pp(Ti`H)8niZU-gyb{f*VX$Z$` z+Kn0dyFY>5hWD9LN%L?{z?Xy3V=^W1vMql&7)Vl}iM5T)BOj9P#!{lA5^vmkulpVp z?62R0vi4D=izJ^yy|zw&42vClf%hM&f9G!=!;dYpuFu;WASAV0%&6R7JB4ySi2yGB z$e9ErgpIryoUSaN4;yu_hm4YXekdIBdIa=g(}0A!ZMhgwb#p0Fy)=C%3mQF4M-xD) z*X$_bTPT=IO?L-COM)TjUb|1lVtCs~(vi@5oS}eK_Sf#iPtOHls()e~agUO)q9k#D z8lp~~eI+{%z;PPQ7+a$Pl07&%IghX(8p{J?Sp-r?K5G%~t(`=zS=Kwdn%rWzz$ZxHID*TDmT(_1z+azdrrZm z8;4>ms)9*3-rKN~4+0FsYk12BZxGl1i!)iqg8voIyCeJG8rXt_PwxR;DNKZML_xL< zkYTqCK^Pw73%XwmT7d`P3q~SX@&2H7B5cL)^SuiPAE+63CVOka3gVmULwDxz z<*7|-{ZVu_He2>MIxYF=#oZL&5jgi=LYUWwhI;H^z2}10ef0vAUq6mXh??(y zI$;5u#!Hb#^1*N!6gAN(zwGJo4mgR7MdNtA1a1rvf5p?Kum41y>y0>?m2otypL0th z((c6#XeYPB-RnEOx5VEWmGK< z0JU!Kp^4Se!WuLPfdwSgh0AIXkXPS&I^-dEK?U-eYXAXFENOS;Q=t>?KrTNqK2y8P z1*^)%wcwr0r8l7!<#)o{t5A`&CqDwXG4wQ?^rX#3s83d(`>i4cOGYypM_fKhB``7z zfgFLJ2M30xMRLq?4Dq;FM_#GrNJC%u@xFKQvBD3b6Od>sTmLjg=^uF8xnD2;zzg^O zM%blbWJ^4gt$#*who$26wpGGKyf3bc86xo~UidBOtY-!|R4&af&(Z;Xqrum2=v}(F z_kD}YxBL@AP^zSALl3oKean_LYES%EXKs9G2$PUDstYOVTnY|?Yo};*DX>P(NI?L$ z4Iz+*oF8;htXSOp`-}VTQTgH2;n%?J`$K-sF9V~Htm>D+Qb<;P9e@hFAYH&0>~CY6 zHVF`#E^nFK*i9qV-C;-R;16!-ZMd`dy?2&x`Ex@TxIPrBgn8TBXv5F|(Q}Pp;$&tp z%HEVDChl?-eB{pF_uSdnWE-BLQlemHjyQ&`zIF^7Iy0uIiPz>g2hLOZr%2yMFB3es zM+}BHy3f98({b~LohtTIJZeUJ4~^klUi+$9rhN?qcN#+(>l#+s)ZdraQm}Lqe^_g~ zofmun9~t(Z+M%pi98$yPB=?yfQNg@t4?$8Yz0-M-LEJAGUGBGsEg5Xx+0iM=m(mCA z%$Xf%9fkuJT>ZJ!dwt+Gz`&#qSrhndomd6TWWXDwRV~Ye5`U zE^z4$UoyDdT|i>oOHW%^4_fQ(%Bf($kZRp#ObpEwlEIAc0P8TE{zIoMqZEsV&pOW# zVumlu3{saArE(PRd1i2&e^qf5*iQYOzMkhT(gf0?KnxYDi#K`M`Qys!%26#`{{X?k)(@z~LMS>aiXrodw^gLmBC0iUH;maDe zv%P4>hKWjN4qUhf!o1e~*)-)XvAhP=o6v?y&oaS8JDl7;_?wS1@&tY;znftwJooMI zNu)pQh{SU?C%j$O@7|Nv-+11{M$%I|@ijvkU^=M_g;wbLQPW7+F=95NaHZ%nVz4W* zm>pr$4tr@1CYU&XwN?_6onh-v5bI#H4owNt_4vipQizF74m_K*3E1>_Fx-IHu;cMy zbO9V_;UOl+H_Zs-F+DotkKaJ}FbhR(l(peaPXwi0ajDn}2QCBrgmCOY@+Pp{_{O3+ zfAdJ#G!mq%QAjXcV^y^73_ErPqqXMi!}FGp2kCk8d4iAN5G)KDV`I;uL?FZ1{t-k! zMX177I8$E_h8vZZ`!|wGCM154iRUl=<{e?jj$m|=Dj>F+b_VGd^P%!qAZ3Ys-Y~&r z(#?i(*b>c9_6CRSAUGRVtSCw;}4|=zAH~xmRy;V z9k_C1+*)Iian$v}wd#Y$!Re{_-z=&Wwkocw&UF*^~)smxZZk8)y%?r3(lJ=;2*8v+mHd&*ED;1 z&+s@nENGko@^bu1O)EPxhQOd>NLu~fcQyhTcdy-BmO#hfxriYvjNUH8Waz%Lg&{T* zycQ204j-=t<;sGqbYc9~oeZmB*ce?kbnT(59jWPBy!oBFd46EJ2P zXJBmr`>T!&Q5UFN2lCvfdQu74vf3bANSGT5GuIz~%NiFk%rJ<296&&nhU=;}y&*qX`mxw!95n+_rkHF__` zBLt7VU3+{Cp5ItJILg9rVTah+6*O(bW4A*;$JhyP0~XM1yvGcfB!!sSa<9>2#57Z} zdu0DwO?T~OSPrqY&u+uvP9b&&!-UwKbP2IzbP2J0=n`W0(N&opFYfa@Ydf=l=>l{@ zAiWHA`JD}2sE2QUdX06&N!YP1+ceU~Kpqtk{RRB;5z!*-YhW}zpL^=t1vI{RJdehr zBe(0~6ZKxL_<|`nQH|>be{k%p{g?5<66(|BvgyI#o!_PlctMzfY`>1HCpIwQv9GSi zhY=(A(NhhvYK-QKRAc_&B{f(>2AH&A)G&4~>C; z51K9pt>@3-wd|&g(8-$p^5F9yyg(s%-`o11f9GnXz-Ied%{H$*T;{PXqITH@x+dWo zc3TGl^oUJ0o9n}FgP^JV2u;914)yT153y9{Mw`54XELqNTKnS4?QD2r#KZiD`t3qm&KYF>>xEjP7u8 zH9Q~_K-9h)EN1-bm;?I~7BhAZoA6R(XoQZdq_WI7-utQSZL=QxD$(=PpB-;H3j~!M z{` zxcMF=^j>jRHnRL2G|9>!vi@`lQa7DMYz<HL{yXf+Z?GUHCBcx! zo4H@T5sl4?@#qTrIFc}wvxkU-!T(g!cT@j^7cqQ>F`jU>sN*DW{dAnd)gnAJipM)c z*JnUS(1Gi{eWf^yfE<0**?l+U-RJiu`sPNEC9TSpKX=E)@E%C*`!0v?LTiGK928wj zd5P~gSp{B$ZU+_ddN@+na%`-hMsHE@;;$J`;P>wFQiyoVyC^d)dxmmIIhQ;GdGVYO6i=|wZPn@&6piJ0VJcMyCbJ#2$q3~Li* zVt1Sh8zB=LfBm;H;Mne8$5+sJ%RoO;HzLylG&i;ElH_1AzSI!iA=PUMpo&`bo{-A7 zC^lSJi+uU}zT>YSMLWZVHTjFeOWr2sHHcoBk^-)q^)YWfD)jESQ2v2q<>`l+llrS( z3-GOrzu>x4piSBjQlf=A^ZUV#NJLiBLk=3sz>Q&~L_oX+>3&&2lz-q2h^iI?>#4&M zRlywZT#Bk*f~blY1$pS~m6@mtadsP#N4XN|Nn$mN>XhfhzKb|$8azCkdiBQ}MS0H; z{_O4L+dj(sUqvU!HZAfJjqA;ix}x=yK*VC){z+{^oQ_07Ja2bD&sLmW__IuG(&z5} zDTYH;D$|C9u=B0tcu5=6SkE3MT>#Du@!CVTeBQVdo9uHtqJFm{%Gj3Qn;1u_w)MoO z;#FX2+HU)N3k=VR?98%x27s4=tynfPGq(l-gvqNrKSOB}Lvy6QfrXHO#sYyRK zTIjYZWGR-KWLT8#qOkia9CL6BF<~`JEOG&neSl2i1i6qHDRFwhea3{fi5Z|QK-jI; z)CCiBxB^v+-^a=dfv{LC1tQfFLlSM6Hl`Fli!?46mlwV9(Kcvv| ze@3!>u9R_C1qDs}tnYL*a}jCKIUQJ(-*Y}yqW=#c?;l4LxdUb!f&1LQEEVxnKzXzf4XD+(>67cgBzqlfr0gfx$0r!lG7|Dthp^E6Tm&90+n=SpZG5Slc zqEe({MJw2%e`hV4Kv-PSPPnlJod3<-UmuKlK0f?=@9$UxnU?hy(tV|nE`GlZ+1Yfa z$qg5xuT4L$mWLXz0V30E=KmFrB+3a6Amz{mg- zr8fuGp+Ee!BlyvSJzyYAPK7f(Z-a+KB*!gvHXsp*8+kefO<_#u<%inTtUrL1%2tE& zVabIuf*&D_lCgEwR+L8-vG_eJ+i@Kz7{G1s$7{sj70W216uX6=5weE+D%4vE_NitWTf+svFjvopib&sxZ4}5%d zCPiT^W{f@mg*oRxL75;*M-AS+Z3xg4BjT?+8(Ho)M05f}KW6UdnE*c$^?H4XOR;Rb zGdQ90&8Itq7Z@;J$YQ6%JZ(;b3X^Q;;iWjon2CD|Ir<1cldC$bgpDZ5d$NvF zXT8B9a8Y*wv>CZZkbyTPy@R&(rIn=EbZ%a9c^=u(W!`$i@k9K9@D-j~Vl2kDx1Lx) zEcR6JHK9=T5h>t$pv0E~n1qkafndMs6cdiYB}t!7s(Vg^O_O|5NrL#WX$lvj!B zq(HLxu<0Z&d@JNspzCb7?QJVW(=>~NSZg}aW3#N`41*xTI}@ZGLGRJ)E2H~ep0iAV zml@9DLbwach0y!Zfdfnla*LAY!X3t92P2uIylfxpYHKj6HT+SIpdih4I~e7adz9=h zP3j^FHyFA$4V*Z)osy7oj69k9e07a!{Sa2#*A=pM*n!55rZ=U3(;uf6wr+|J<%0|#P!yR zBE5U+km6ah!fJ0~5|VlbTBY$Jgm+H^15FHM4_+DwpWDdBv3}O(fZ#>k8CBcGUA{iX z1B}o0;-_;%oY4Rua)6JxqKW|DkuT23A5*Zg&-Ggb(Z>eouf!q%uQl2L4(K;9l7=pUns*tsxo%4Hqe;VsJkfp5>Nu-can|Y zXf)(G2Li|8NZkv};p_^{c0<%-D z(b3Trtbl5!O21=^X6@)@=6RG$PcTf*JQyV(9=;T$%ka(l7qJs~Dff|ofGtql^Y?DX zO? zt9)n!SQ2a~P=DGpP9|O9zLoCl4tvvlu8Qdv08{*KBF3)1kNiH~cYbGhTW$E#m6PG{ z20}X8%ir`y{07-NU^pD6c3$vkJ>8>B6KrXqJI$1V9^eRvm0s4V!%?N*QiC*E>8J+I zyyZNEe9c2sAe{`cdV513CdXR-fuRqd;rF4ppmX{U^`qF5UW|Xl_Z#@J0C?!bBWy?3 z38`WM@X&|38wnq^MKLe2jqQS&ur|=sw`Qv_u$4e-gh|lP{fX-1(gAeEma2z7%o!6K zQ-Y|%u=A0Co415Z4e1(g=I*DnS41VKtKM+ zD^~`awylQLXYfi@`LTY@V130| zrXbk6oZa4tP8>MghTrz=MsE%sj&`;!hm0A5mA1RO0$fOKg($Nf>VMkTi1OaO+1gsq}nbdXH zr?EsV@BRJUw|;;;N`Tl(#8$K~@45?<5mMcka{qwo2S|Eg-75FDa9=-VfO1^Q6DJ@^ zgd_|7O`BHBbKiWU*m#q*2Ne+grNPoVN>V%PX*_9ZL)capbS?;6JebW0+bROs#K2|P zr7g6U)VU;VsScL5L5LW1t_X2dwlo>G&4X!+u%#|ox|)xDbgl_o8iJ+k!nOraND4ad zW(^xuL$7Mss2Vo024}nphM9(dN7mzvH^DH|HicBj#+zW6iA)9SbjF)tn2Dx^ShF+U z1j9^w6avFc`{-y};EXrTMg3S-UU}1^bKruEK@^?D*ciRudUzhi7S==CUsp!B(Aiju zx!kiaYTDO{C#&9wj$ohsDh`yr_q}@L>I3I}6hQ0pB+oMZ^~}Xn>Qod#gWxc;rCLy4 zsu6%d3>y?&VfE+ce&$Q|%|X^x$~;=REC2Q69B2Qa>>Dly;V|7&djQu<VuqC>QmHz5oJr_YRx3V+dyZ%Buer&fRbt_4{U+wqv(pV#54G=23e+X%-(k0 zKyv*A7j9-Hb>7=(p4A}GNoV`n?;C>$_Yujn`+FC;6b&fp0s_BZUW>TYvu-ht4MeX+ zrqo_rxS0ld^E+Y+C|IbrP5#J-r+uwZ{DyrV^t&C%aZOG_?7(dR z_VJ)e9VcN#j?oww9uGAA9$fgI!gU%ron4*<#ddjs@z^fUBJSOnwgk;>OgqCg5A@K_ zf}GI=UDigGndYH2IiHX*(Q=>oru~MFD4OLMLUpmJ;og+VwBg2QrI@|U*UBGBh`l@3BC50V&4(j zG2~=o9KqTmb2`P!N}5t8d*a|x36ObRpqu!y3_w!qS)0jtsm!K7=OV>p7nu~8Vj=z$ z?@U}DGa?V?T7n_{TVGlL8e zcA>V`&Jr->WY~WP+{)w3J#0NnbBa6#w;i^2QSjJq8a5X3C~<58Op>rI+;Yk6;twsKUbZV6WV>>KY*#kQb|q|D!geKWTEccEY+Ay0C2U&4b|q|D z!geKWTEccEY+Ay0u3Q(=cIDk>yYhd4EyAk$KZP?wKcwxT#!uS;il4Ug z4L^Mu+h7bYry>#?9r4~1%NTgD!~8sO;g{zj{Fb@zza zXEhEUPLPA=%Kp1FJcI8anwc5zk7z$c7C$c`OJ?*ueuL2lY{E06^Y9ytZj}3O{1P9r z^kd2@c=CjOpE_yZ;Ys^GchtV=)Ak)bnDqVP;`%-$tcD2~|$tCuEs?EN`jrM(Rk$uzM z_8ndA{A=8IoqdN_*mqabg>S$Yvl>(eJK4-A_p>u*cqUDu%&|1}XJ+Mod=}HaU})9Y z_Y)JB(W!;&oAG?l+gmNeWRIHbWN)=z_EsBYZ*_s}tu{uqw+cI~u(#SGd#kX+3VW+< zvbPF5tgyG5l)Y8hVTHZb)v~t=JFKv`x=!|1VTW~t?6CIA4(mqQVckSKtj@-2Kj4dP ztPaUds_6`}IvcB`oUx5n%3Y(~+yX_Snz4;lN{(Y2tK1GnyT_D9zs|<$9)*)+M$RVd ze!2C#dt7b~!f5wdxj7J{-KXRxafo&wmAgdY2h5#dcnb1rm->YjA>y^<6;^g)8Lp!#%n=Lt7BSvy|p7=BhtldLomwqh#kfi z!PX`EuCp%0^gmsLdB}96`?ikJM`~~D8GYBG`7v!z7t?fG|Co!EJTuC5a0bmTrtBD} zPmN)a7d9W`^J!SQ1SAxLgvPL9(GGmoL#hHgTm|E!~L~~=r zEt$8h1wP&tkOX-tj?=jJ!7%#SZo!1RCR$JR;qaXYBm6kX*YZrY*8qe1HWii-RX$IFD&lOl@WX z2wo9T$YSCmK;}J&F4sfEYQudM@Nsv;jnE%g!oyCK(!hRreuzJAE4lTgeu-k`L4~Gd z4~<~m7H3l6QUhQ;aN$G_`z%x8#t{?vJ(GFKgtHASSwwRKE&diuB!XrPj$%m!Bn~Y5 z3;@DK0!SbV;s;0^7Kg`;r$qbXG%N&W#R5yI6rP zen*>~Rc0i3{bTw44vhpC{e%1(_Ee&#vYyx9_%h6n6!~9se#p`v9^k54b3^%|$)8UD z#^moD&KuPW^#DSFp@NoZ!Gn4rZ=T^t`LMf(4Hh(S4~H2p?4b{su}Qiuaph!w-Ht)L zw!9U13V22en?kZ2@^$LYLg@OyO2iZ^2bQ7_r-;D|h=9oxS;kesMFx}x+xt-rtbYXC zhv0})%Q)utj6(bl*#OzTjS0PA`v|{~i*Mf{2a4E92ULv*9;d_JoQN&B5T7}S)2E;0 z$rwQG5^YP1k zG*T7q;_tMqS4F#$aQ7dY#xHcD-!8)MrI`Q?9%eRtN0|tXeGz|?82P}6m!@_({MZmd zxb%U;<>_yVkl>1u%S>3A+00bU%zWnR%tn{IwAGkOpH>3j`90@PImrAN!hE6Q-bg)jy1F^4A<}?NXAkxyh=LGsQ|$oC*qtT?B-4% zi4MQ|_2}!ArAM&ym_CVOXj!N{{j6c9wG>)@<>{^P<%^ePugI!o_7@AH{#W0KdN3%x z4*aAb8bzfkbw0i^Vy7WqhX$Od;ec@TPIm;qvkxYnPXg-D>G7A!r65jjTTM2 zhTE`A#lsN|W#i&OBWp_e0b^-pULgkG4GuFV&VeHH-E6;&I{!(_~(~~p;`Q7Ov zE8jmEmC>#{$N0eXR)5uDC^ui2fx4o$R2TILW4>E zV+=>u$42y}b}x+1FklA)WB|#+NCO|+$uJN=wK`7*mB$%Df<`-yAZ3s+Ffw>o8FJoP zTK;~)^uUd-?sKqRNo0I7QU3ljgqATFsVh$~2EFV3b{sJmp{E#%{`TVo^I(qv_8;44 z-+}%58m&j}?XH8sR+-`N?d&0Ikiys?^^zLYCOK8=Tfz1fa^8f@>zuOr&n zGJ;;WyD?F|<&;$~Jfq~m`rX@ZeeHe0`q$%x)-_5uetBl)4cHX40by=$z^0?b`p)eQ z*wp09kiK(!UpQ(Ne`(SIP+{Mvr|i4ETHn-BTqi-*fhVty4T4IG0^4i;8uNqmkup7c z!*g#3lZn7!^xfAB z98Bm6-?_cO!J{y`?WkWYhVdh%osbjf#y|WslNiN|H&RUGkiKZXB0Wy83-9xsc;pvX zqBJ`o+FiombcNgrxhv%^mAguA>a#>?_FuGHxzg;QXt#2uYvoq1^gOwhD_tkIa_QNH zXt#2u8x*cw=>>8tmln%tw{mI8i+0yD)>fq5%9UQC^vWd~8|`jX8X_>-y-4n)+%0l< z%dK4L)pAcNyb<#!+TA8Mt)fP|RYIE9Qls4}A-zOym5|<`yec8xEB6}Z-6;1uxi`su zx7_`5D_43*Zskg6|k zP4AOiS=0OFR@U^m+{%?cAh&X*C*)SH^z(8nS9(%z$jfVbWv+_cqCq; zHQ!2MJbLbI*rH985LLx?3Fl89TjYZP3d4 z5wx%@teOa_MsTYysGkt{Gju+}!!xq0J=BlsE4%gB=Blv6%T_$pg=>7J;r(Udf~~@J^z8$3V{tC;IOu$3lfNCUOdo6xGRgIr}D)IA)8mRt& zUPDp>!-ojC--GWG{wT+IgP~ma?`N8y!Lb;HrE>TqGvZV}HzLyKdk-?2pmXn5_{;>q z46#y=&!@77EZytfYF_d1SJ&XDt(M{QNT977gpV>XqqwKKy^Vvms%Cjsi)$Ot_HwuG zxO`>sXhl(aJo#d*QwNX(-YakKUiW*OF5f#R+jRNdTgyN2t!&e!`(|gs^AjT-FsOVa zdW=&DFj(5sdwoSmk8w5`RE2zHQAu%xFn(=*Zt(nuBIO|Ph&t!@*44u$y_c3cxKE6%EWFMCl2U1aJ1&+zmyX3&c_REP%rC%we183g-wfND!d|g}7RJ*@{u|TbN z4=%od)m`}oP8U6-y>h@t{K_wJa+$c+(nGI>^5jd*3k-Yr*TDypX_JTbaQdY_1vpG; zhWa69(qM5NmQQ-72uy`ZPgT$a3#yI_+G%E%pmXQZmS;+m$tKJ)R?JG1sKHyGV-$xH zjBXx-8)L$^Imoe2&E!-Q2H_8wjG2($qY6ffWK-du4q+&5m7`;*3CTy<_+L3Zz(0+H=UKg})2;1v}_TI3)A!y$iwl4_U zH-+tuklunD4BHn$Y#~QWu${*=Vf&JEYay#eH4xOI8oV-#YM{W3YVfofs(}JCs)42s zs(}JCs)42ss(}JCs)42ss=@Ips)44CRRbMns|NZyRt?mMw{K&8(o2VESnfsW6iA2V zZjpPZ+)L!*{)Rby9 zrAAGuRa54vDRpW}y_(XXrYvAn+8e2h-3}!sV6ufuM+=wa?!XNj$7!O!$oBta>m}5C zL*H9qk(@D|;nSA^4*2$;eD3$cfDZ?neoQ#4e`5u%1>CZ+s`nkYY^?6}#K`~6rthzG zSyz5&Qbia;8Xlhi;;}|>?0E7QF95g9AsJ<@3{LOaNh~{eI8%FbW7MLUD#3g zopRq3`g&a(j8|XPi^Md>E{UaM!{5xZUxfKsaNoO5S}De@UnFOLk(_985l-ujcH7ZbP3vK%iguk=0i4{? zt}}9Ta!0$)$}L5kT{*aKo$B>MKiiDMIG$^v_s^|{U87*B2g~k3XGM5NWzbxSyEnno zDm<_r;QVeGlmu&F5jg0q4VOVla9-F2B|+E>#sk;&VHcDH8^UD^f~5<>u0}qfy=)O5 z(C)%hq%GkxXe=)YJ3T5JHdpZF(WSVXk%W2_?_fY@3P*{ZxSO#C&(!k{MknrOKu;NW zGd6^sxSP=%F2&u9jbZ14pmS5Ww9(zb=v?GfI-x9WH!z?Yts5BZQg`$$wIlk834VzM ze#hL`j_-Y`7%`kAExKc4I2ZjPCx~omw2OK?(bj(c?z({Aa2cN?z4FQz#%_b`HSM8a zqPH~u@APHwSj{*ZxA||->dd8a=>{l1o-RWo{$n-OaPwQ^zP0W<&wcCMx88jl+!tCr z$mb2OMmO3K{^$lcQ4|$hz0qEIxX!~G1_L;qU?YP}?J#A|!KlfbEs5RyrmPtmpfL{P zMZ=Ui6SURtyT*Ojx$oWhu3~}>E}++aH@fd8d}G_lGb9Aprayr4_R_)gwdL>EHPPLW zU%mnHe0lg1LD$gU@?!5>iv}!m@HL+9bic6abuNO(J-q9!Y(cb08QA5IVtRM$nd5A8 zTlRAvm1$FC*D`_9=%)BFqO3Ag5>C)cmk_8Y7KH`GO-DXfA1yW~9NI)2 z@bV*wLz{?0i-<#;h(pVQyuB?O7Fh3kUcc`rSFYIdr1fw6WwfcV#8!n*Cor7Co>J&X zU1NQ%4hIxpSYqYZDjTq<`LEofB3G5~KC<%T!S*tQ#eyrl$yYZ2JGZ{j>o=_QxPo4K zr}XWDm1X*$2v(LJYxZWX{5aN9z>E8IynuGg%F@k$+_mL9z3o&e@bxLAKhSxdu9 z=WdvPCFHv-;1S*(zO2!^60*F(;N8ehEnRR3uZPVumhK0wVz+b$pDSavsJ)0&xE8!E zzZ~9n@(4>y!{)@Thx;Dv$}T8@xv)830}@JZeR=cxyS7~EeRno-FmA#R-XVS?oQ7eu zb93f@C+N9cXvjCvkkZgK#I@plh!is$@|?`IE=z)tQlV3*2tzbbhbOLk?3FRlFe>;ApJxT604H;R0*!iI|q z3tw_Y#5EMk&Q13H;>u5&N@w-n*qPg5nVEqS*afqTNwx~XQ|_uHqPJm<#$TnOzE!1zsA$BCe~!v zmKVVSj}n=V-~Pz0n@h<8L&|)3(;Hxcr*2i+@-5%T2n&bRf9lIy50`KG0>Wkv$ntC! zVHlI|DjJiWzos$C5s8NyE_0}cqXq%y*Y9A1cbDV8HPNgmIM?Ed_Hi2x7<5(rhd35A z7|K;_;81cM!0DX>(MOE-Z-!v3g~d=<2JnsF_)g1WlcAtiLX z6>i*a#Z}cMSTw*=y-ArIbyCJ({4e@p@$UD;iEHQ|V|E=E?8h4uY(h*bRSfqhH2dfA zQ8aHc_TOHz0za#9fC5?Tl)}Nl7uo1AKAHi-aU`MTc{LCs@V7qY3p_aatl6=>dZ*9^$*0Cj~n6 z;Q0lhLruJcOOc39F}xdb9~Va_5dm!^ppAG-K?#urCm=j|k)JP+@N~uJbyuX)>CcH1 zNHnLH@5R{ejLBY@QQ8d11V|?FuqZ{8FZG4ZC)v+P2 zs*Tg(Ko^6J`}NH?M2E80 z7M;lo?RmO3u>vE!0KWK-;`8&zF*zeUe_mBTF@_d6MWR&=PM2uaf=J-<0)`zt;|@le z!15YKn!xfVMw;N^GRFTy-up*aeO-5=U<+GG<=pd4aYGaQ%5?B7W$F|flPT$QrZ^9Q z>>yhZ3*B$$%8)M8kkm9Jl`u)6kpNwExxO;YSTcUg{O}vVbe?f=pG9z%g@sLwuxW}@ z;^>=Vj1!Vv2VRO3#*rPyexJ{0@9&j_AxYP~f8KgtM)#caJwNu@XPGRs8MJ>+jmLlQ1e+Y#WJGSKy4U zz@b)gIZ~q8y;UROmzXEj?eN3c@NkwO%dPKUA#aTxrv45v2(~DvmG|dFT}XT zFG^b}z7rUWX_8!szLUe24#AxqHXOSKcNeKM<2kiRJcR7I0V0{=b{S5|Ze*@osm}$oOFg(k@h~h}heS;+@;u_9; znIm%7Tgv?wKg@os28VtKyr1Y;$Ei(rZVL9tork4ziD_TE{lh&kz+yCT)qR%uoxp!<7#e%d2=^_X&#-^AWaW`y@Gt}j(DsiZQ#lyq=5FG{jA(t`qu zDq|K;TMyT+4)3%<_^GbP!^V4Vd}nIs4b@kZit1|C7I^Y89#wY`+ZCTlmQDg!@0pIh zNvX}>}%x89uplB2F|1b$sEvnlp62pr1=BJ?QwS0 zDa&6lmqeONk4U0>mA&(w%(@pK8k;$zE}xaVe5yX?@qIQ+1E;xN=F83k_4i(OBSsBf zX2D1*@MyfD9a*VXQPJ=t@mAFPs3Rc8$Jw0)ab)Xffu#9Ix5rB64B#A>sSOH@a(|4x zN}P$WnA2e&xony^8-ed5v9TVtEoO`pim?y__{i;FrAp^gId!8DVLqUZcAD`GErSFi zCe*yoRs{6_D&n7+f_@M{^2fSJ2g@GkRsQIV}`lqFKK0wRe!kFr< zuh?gD?+EOoIh;u9)}a;(xc0oK9&Ovx9_nY=G^84&aHe@cF!{Cjh1!U$Z1PhJ|!yDAr0{C*I-$4u?` z%!e-_fTZ7nBe1nljeoR<6Bc8Uia&!tvJD=-SY?l(hB2^Q@wmjqU&fyt7P2!4Lx{+^ zJ?o`d$9X6JWv+AZxJf0F<*e>a#?!*AsS;6E+{m(6%&PG<=2dNKjk1x6C?DU##Dn;U zy_mdbLA>NXB3h0d*frtLV(c|4+Z^-{El!{reP9Q{_hgy}k%_@uu;pM_yI*d76ww!K zY7`k=l{jjOuV01Fh8paGQ2%09Rf~UWU_KxZF*Z*a3*K+T-pd925Q*$1`b1GVcMiEQ z647)s$ODh=>^7EeW+GZpNanbgV@QyhdT6j-gg@V`_;HMnyKV;S=4Cf4$E>a(OhP2~ zWY1pKU+)xZIDU$~xP`p{L}4jv3oF@+`3x;bKZZ~*?&)8}4|xuxBrrhoq8wTaoW}AR zyySw3PdNX^u+jW&w3?ukAUIF!hzP(aSKon$;@l|YDxXkDo)xKtNAAe}1S-$Pbnol3 zXwEFa#FAjRi_oSv(~SN{ZDr(rJ=6SFrkT<%Up+ws21u${V9);4@C{)p3*CYs9kTOi z3hAgJMlfnqvD2n4MX8EPG*KHq1%AphM9Q`IdTf&POeWicjIxBDAcCWPD!jzht{@B6 zD)k~V@Ky{J2K*bLX87aO3o7RqmtLROp2c`^oVX>|ByMEv!6{mv!oxwoJRylOR$9YS z#dZvs#iuGyL{P(n6RiNi)rkp5^Whnmp@ELa0U}(clgY;4hx2r+BWgK4r2B^lCsm4v zLal}2QVNxH%(P0H;Bp<<%CZ5DE+C6q%0mt1lLxK4=?q4hYiLFSsL_#urqVTsY+F~) zPNsXjdQzsgZm4S<{%PHsH4&j%lb)M3*>>H@%{8NT-$#!oH;0Sj6UW3iyzBkmuYS+h zac~$S&1={Ga=e{`d3;G4&+O}%h^=6yjSU?i9v!vwfNJ_T|My7$_5RJtkLs%V4v$x3 zJh#}yyD^?_Y~tk@x9XdCJ4WS*P3;Oygx-b%lc2Yuz+~ucC@=*|8wyN?#)bm5c+aQ6 z4TVh)DFFSC?FvBuV}}CJ|Jcm{v?n?qWuUHx5bf1RK=gzHfM~x0faoa&kY_*vKy*j} zKs2ZTAUdi5AUdW1AUdu9AR1Bt5S>r}5S>y05S>;45S1AqL}wX@bnTpK#g0hGDV?P$ zmz!U+Y<~S6u?#xXJe!1oP>Nsi`p1boQ)>b<;^fD6i;lL*s;~^o(zJG_QYY5dm~N;| zqN#`O8#p3)eQ06{a5}k=m45R?fI+M(7Nnl=IEeunpTj>MuV59@sZK9ApA| zLf&o>fFk>NfZNo^z#;YZ7_xSdBG~Ukh!i;V_;?Eczz@WYh|Y)xoPq`&Us68BgI{A( zJO`Ez7z$b;3`m*}yeJ`qg+aW8)}3@}Q9lJ*T2kN}Ok`boR_zd;e2#D-nGS3zCw2IL zXz0kvXOkC(8V)5#h8hO&;_>;RhQX81z!TojhYC|MJDpRFITO-+q&|J)g7RJne#xdO z$T{EG0Pp8N^u~CIVU;q-kDU~hsvAq)!@xuN{~R7qqb_3%xs?9`H4QDR9%`$BDgbif z^2uvZd$6JTPV0oV!xP#TPy{Sr(fS|iTR&9aHXHrtrga8D7!R~{&|=W2O)Jx}U_>1= z-<^I1(`R{lIy>w>2GiSCDmYt88=mV8f3&{!ckA0ej)v73D!FL?%BJ3(%7(_KdXtx% zk67;lEJvapiiVI+@kf#hkE+8I6Ev{maQ~De9gl}QbQ7RflD0&wXp(}fc7|AmV1o`3 zERZq82MU=6J1Y3`%M%8*+Cj@(H;jkOcrA)-aK4BK zvIESC5}@+5m!STw7a-4q*FuVS_+;R;FI0qAvrJOi!n}D zU`F$uNtz2 zIyI3s!540~pkh}6XxA;NR8Bg#aw;(0EvsyG%PLy~=;X$4clB}&?drY(e-Gzt@fZJ% zFZ7b0abwl3me83pn|IF3LmwN$7BFh}7K>@}WRqY9ryc4Z|2 z7)uFPB%uQ2B9}xE^mAOK2ckZP07w_?f!RiqcMyxoc~NGu)$Dw zJlVREl<_x{k!dTXBWAW`&@`hDaPj2L*VZoky7gYq#U-9~VCF=5u8o}gTQ_?yN=W$n zIp79Ia`-bByEODWhOZdX5@{$$V)_7zbCC}b{oKw7M|9H$68dLUosJ}+tl!k7{1yshO9jtjwI+u$)v z_MEK`)Qi8h=MUayqzp7IrzDUXqqadLa02qOSC=o8{scLNvlfEgf>tnX zB;I|Y8Uav5_-qwGWtpF#EG7;u21q%Ks~V9+@V)?VaJbkv0Tn&LN~VK_uYzGpj3(zi z4MG;@G%*@iq!gn8TYfKoZTa6tDK5D>*}C69QL0Cl-U0i4jDlS<7n5kvGaS`#{5>@K z3Nm&dv-GXUl@ar3$TL!|pV>?PuDcB_D`(J4qn$;BLlW3?=~@0{S33YXSN2vEA@-jD z>5ob=`RvoW^K69HU-=JyIvy9U%L%}u8Hq6o-nEiJ(9&I=>_K;}VhOx!C8N+?D|v(N zTFD$}_%2WGK-+i58DRb{PtHIKczLo08o|qNSKzKBze9ngNq)BicPCn!Xb?|GP8PK$ zd7*gG$|O%5CyQ1kc_KMAjz>hN#_@#gZBpgU6W@s&$0N>D<9Nh(Y8+2E-zII{Jkg#S z$0O=f<9MeOrY>KSKdmrT_>z2CVd<@kE)p5X!*g7&#fAHLS&-S+%bjiEfu~gByh;_I-RdRuP2Qy+vGQNW|10l9)}CD|;m2g3nIIvMW_nJs0ki8>r~3lB%g-+R zbQ65W{cgIOU0~dvXI0FKcDw8|#;V+Navd^C=;;yy59cZR@IS6bO~a!_(G`D*$|V|e z#g554xaR>wz#5}d7@W8z+;s9NGJKdaYsKX^m~v<#XNLDqKWEdS7Eb!GA%q9R=Ic#d zieJO2<{38i9{l?pRy~L_dMC@5G+;;3GH)!dR%7##Wq52u+P<>2pvCW{OtUyH>VY5? z<2Ly^Z;NZfe}@)Y@d{|10=wAT7c`rSpR9(KsZzkP9u?xFt*!Rkt4ThdykiwoOa?$~ z7w0p6yW5$1GmsGFHKEhBf8YI<;ecj0?a@Q0TQzWU16f0XA$5KaPh(sqb=BwrotDdGR7}hw!)%X#Nz5SHiI$3)P_S;)^*Qxe8R3K z+%pp~idc-n+P5;=*)mge%)^z$-bS&;nB}pq>Jz0s8C}DkUyyP`5G^}|7-7+kKEw%o zY~CK`f&>pQdA5t*vEQkmiv| zu8h)c$B_QI<>Ltm@C+#vX%HK9qE87F&(c!iBG4fdXGh?((j4BaakgOL4xca*H|YZh z!W0&o*d0hU&EL&5|0Cm1*wW7fCe)+rGm1_;UZTVy%W-gqXW|A+o2c;w@)Hief5gEQ zCo%;Ly{_Y1V@Vp47?`tV_lSTYs(^qYnSx-hMsvAmGqpJUhd8P`2n+$9#v%FVNEk+* zCFc+TcETsC`RP1Db!RiR3F{{1xQTv+L4)D*q&f<)Ex<|mAR-uyk^2Ewz?i*S{OnYG z$3)`Da1$3(RY;lV)*_Z>4s&OEETXh*#tkJmbE>rfTq*R;orM6Oc*4gt(&x@D@}^QY zXz0v4%&jjrJz>%jTxDs}o2b2?=hAii;Wo=yMBP)xhNs{o%VJyi8h>CHqkT_!B3E+6 zwUFc2r6tJNh#cT5&P1-@%*53jzb+|>T%zvdN(>Ey{KB=HIk|4PAK-HOo61a7N;7!~ zM^LyUY@Cm+{I|+SUL6nn-dw$KjfHbu@lBfe?j|FZ-rPzC@M5i-jFczKz2u!V_uWlq z3ayJmZ+kfrr^OwD(EH5fVuuRJm!}6kXN-6N7LQC$v7!m z*r=nN&;776{xa6vy$J@`J- z(l+JP^pV!S7xDCT>%ax{Xag10E_2Xzq^p|;U9e<1YW%e;Ds)re1L(Zpl7xMu$*XAO1<==vOH?rby_uNv9d~alYnXE zFHMrFO3=RjQUGF?ZO{ThDlwww_!59YmNzwb@52a`Y11f3M?GOBA?9YGk$~ceBV;+$ z?x=(s01!fQyuAkzMO$VHzT6grGfG{^$a4(7?+K=A(>Kw6`{@^h<>)^Kpkkw9_BZkS z&uH%joV=c}om>7>7-0!~zwjh8-Q z$v~m*Xd=H!#nM!MZa6elvW5dLeYVuHcn|*;bQ9GyNL!L7X5z432YL}@U|nPBwhGol zlDfgsyRVmrR;)IA#V~G3>=TiofoKciuRvdSOrX2!QP^oJNO2E;N47ycNQ6ujBu`Ls z#90@TM(xcoAaGDlkWTY!`OGS#3JP6F$)a?$b8O2)qg#&U46(A;3l5M3Hr-P4Y9wkw zt9I_?z7t`&v$8n@kh-746GpiAD<}ul{|;-QI0>ou_ZHLuK;JJ9ndY7=|3#%5^cNkJ zCSTHNv8Z&1rIzSf1(a0}(u?H%1X)W?V)Z1$bohK;o@(SMcJgnMx~p^-LgsYQ{ddFLqA9_(yAoV&MVGH(9RZnX&Mm1B*YDn& zqR}%xjv{Mq*J<2~%G5K!)yvlwXy7=W@=+Uu0cx_}gjirW-sb(Gg z8V=nKxXWnYr*rV>9W>p+f!E6SW`K^|JDSFs&N41IQ9uGAe)mzpz;Ab#exn`^aw69| z7fr6ew9D?dDr8PKT~R+NCPiJ)5k-P5Wo$mTI5GKe{7;!??DzB*0h<}9z+XUY)X@XZ zSfl=WRCi{XU531Ay5iVfU)+@3%V}N%bj>nUb+j^>$o|cX5#A=55%f{h2HIVN$c_Wz zsE^IrlC&H&qKzuG2Bb~!3sgeI0Vz+R5I1s5L00j~DJ*IXG%N@CvYb?rR@SE3nZqVg zSPrsQ&05aMm3XhP6g6j2PPVuBi0TF8;>6LK$*lV~ zWN-3Ud@F#gX#U$`8UMXLk*6r08aS-A6#+%E+}=g(?H{Ih^yuE zOb_^&%*jMpw(bZl$&*k)Ua z8mEl#Lf-6&!|9ClAc~O6MmAZPzG1JcKtdWC*;j$NkKI>oH>FFHqd}F_zY4oeYg37v zM`>=7hkzI)ps-Tjx)%w1t@UUNnE+e0E=e&9&{!FRB0|lkvGrPLHH~@Fr&s?p2zB^sY#YZNo?8WIiA2b;av}^9oH#<;_uWfS+P^tA z9{W&k=*P9ar~LqL$LJ#Z>UVCh;v-E{S--TtO~+Z@(~a8S;&EUNkRg?9U`tB+a1>Ys z7Q;T@#vbs40G*%#L)d@};s@%8z?1f(AWkjxbkY*Ip*15zOOD%jUkQTARzR}0g*w*N z;te{tjUqT}Hp_L2C+ubf1|~W_g@RlqxoxC6MhsC(d&t$u6(6wUiJ(`}Jh11dEQOJ% z<$hMR8O4^Iw!M?4rio?;Y<8OK7gc04lnGG!^ZY!iLL)l5^m6MgX;-^`N0sMk6q;7~ zQ)pE8U0Zcn^*7ZpJ%Vq?@$a7se0LJHZN#4@P*vbA2#4Hm5{DA2X&CHt2zh6(t0M=% z;ry;$ydN-lpb_L1ILJC}LvzSRNpu(CaNP0WU5Y7)b2KP^U=xxH!Kf*W<{PRwwrV_VdT$t1J5)T;;wc&LNszlnd(AWrqE zF-H}oC|)^5E$av&H~}vPVM+!8Klah3Q&X2c)JLT{(V%}(opH<$2$0>y6Isi>_>RK` z%yPbGI=A4LEd4!Z`e zD@r6f(lZby9N2FkZzf&Jw=WXgSQG;w0h?&iEt@}i3%X3lM~oJo^Muu12%!H4v-*Ju zmJsUzL0AGDu_wI1k-j~?W{kRyD@t+W6^&+Zs`9{#FqH!=nbIBLLk2`Gg96Av znzaoOepheC-j(JILfuA$Xzd_yvgNWfigTgU30NW+sBJ!h-z?t4Fs{d>O3XALLL8Sz zJS~qf`cO8+pPNt>?7AW}dt;UUdQGb~5_pqLW^wLB zk!voVj#Op~?z%ZAbEPssU`-Zz0zrUAL5Gx^fMy4yOZ9gF3_K)eu2o%ZYw}R zBa+)-`Sg?oF=U>YX+CTHV`Z2nVfA_MFDpfYRmv~Cg$~dRZw4UI4X9X&I1Od|Zq|+3 zPN*H!JlVFmEnrPQLGPEH1}bo>2>~SLsgn{o4XIki>pwkwk>ynoeGYqQ%>K9mRmXI* zk;TO*#w>0?{V`33Ww}a`FuCYQGu8-Yoa>zU+$=^nbm>B*4qB4KGj##dO7y#5(zv?@ zYbOkSBWJ^j!*#=4gYSW%mSIfRaWIQDGtb4f6)ETW>Em_7ZSVPb-TBNqu86QS2Mbe# z09}EJ(^6GC>ba@IhG8C1$FI@s4ny$;ep~}PRGH?Z2+r#;Tpelc2;IFr77`D)VhwYRze^_@`9!m%opaLM1iw);8&2ONF8C^x)=*Pg|o@*X&vH#H==sWmVN zmzxU5HD&x%!O4OOOfBE?+;})gI!9F8J{c$UfM7_NE!A`kjZc0THM)!2@Dbid7n^ZG z21XxMtx5EucsmE9?o=|l105_5o_Q~#%KGw9=eINK{sc(Zn1&3j+#5_9ea+(PH`LSd z=*C=hgDUCCWaJu!qT<}CU&$b4Li<%x#jWZ(^WL>@jc@yaVTw1rmDkhwcI}v~MSccC zkth|K%(QQ1WSS3JZ%E_IjhNb-A>iN2YQ$DUoq7^}d6*g);L6TrO^?p>)R9jic-$jF zE2aV-W4XhEDl_feijNvYI0#EqKo*S8pZQ>B+H)&DWbA-@gqkiOL*E8hkj0EsiQtz* zan%MCRkA-7J#s@TqvB4lBA<>t_T)>?a>GBo^Uf&kHrS&oo`AjgwWrVFfp=5!FRKc7 zPpvARU3+$(UKt$2D}&`2`0{uDY51K|!zt{8_A>e$q5#pJN$MdP50QhORB`r8_t0K~ zHsaAFNCt|MWu%KTF-2nD?7xX$gtJhi0CIF%g&+iZN`5&kr_ynaDWotfPOhka5cH~# zvY|^xsDe6-k>s!z&n#}jOjvgV-^{2bMc6#*qDG|nW8C$UW-7K zAU9if%nX#1OF<C@^h+KW1Nzx@z&Y_bBoui_# z9*v?sRsy@-nC;9AZ6mxls5@&qH)GYTs2dSOsm0-=iU#Eq$Qi#6ULw_1pd~@-b?{FV z01Qkn1G({a6BW%tI+2C&_!Io@S^3RmM*zZ6$^pmRprIu&k9dL=7W&XnTA^i9#HhhzNffibA13sT;s!kUESfM`zeZ2Uk`S4MgwIsX?0*Qd>yD-!zS+>kYdE z=_B5C=)U|UjoN(EXrMg;f1Qmbt8m1pgGCAYTElXRb+J0`k)=5f@y=AbP}SR19={i> z4)nT29`AbP^08z9>gl*`Q!^tESB8EGHLn*E>1!S5s2r!h7RDp29y+s(3gp8hMG_7B zr3-UNmmFhZtLDt~A#&5p>yU%2;&RgB)E)FV!Zo*lq8Gq8)rK&GVgE65k^spGFuG{T z#1mZgLKulD(9m6`F{49B!Ep#N%FV3tA<))Y=46{>PCUMmv;_LcEg4>(*<>8Vci6R2 zVbmHm9mD%GO=|X~fps^q(jQBGZ8hVoStm(Ldf?<_05@7PgokM?NYb}w(5##9W-T7o zok;3VR8;LmSPi6GeCH8%dT!#{s*<6a&~v-fTY>>X4g7QAo@)TwHN1z1>GKs6kfxWv zfn}8 z{Of8bV&v&ts+|a#Ty!;XW|QJD(v)%AF%_HXv?5w*y8Ei zcqC$M?Q1I$1STG^qlqkLS#ZGPM>gx5w$NJ~l5Kv7gM60mSlGsE8i65|I8y(}O=zYK&$NLV*E%DQqC4RuWS-RW0 znGW)O%x(j{-fE|u8E7NqNnc zIx47YoQe-T9MV-0>ZNW`RGK2>O5;UeY5y?mWwV3GzJBncPVRSP)LO~=hCI%WIM|nj z8109Wbz^ML0S8}YryY)VK4b&GWwZUJBV{7`rBQLoiirw}ciL7sf})aFEkdod=@cA2&?*3z z+g#J>V#()zg?NeGf}VWwPt;Ry;Ojfy7gj`RXCcAo2~W9oCJ6XKztMP(Yj}zjJ)1a= z4q&PTDHNc9wm>GmVVe~7??q>9x1i_~iXszlLkt`E6~^!@EU?smWJ7Q0Qz__{5s@BN zg^xYsM#0Z?*2(lLPq+S2#tHpKQMuK-nW5S<82ROzb|A6OI>ud5)H78XxVTV*JnARr zM*74pk2=BfCPl;IjrS$=IEGL#*O!7i%UiUHb3NAb2w$-VB#t>CBg5lRR=USJw3i94 z;G(mpKI=UNp{!#n>KPD5Ldt^DDpG7XdaZvI+b@wr7epZ1<;uE3ilKAF)^@8og+OUL zgboDbKI>1^x7)MXQ02T>wl=JdYpV2|+GdxD#fzWUHxW`Ke zF!ar~!6F>Tm~J=^eFj4$wKtNJwXF?M+BG51@>2;VQkkL?nh{p9)5_U&1hP}&_%`wj zo=h`o1xH`BPZXTkqW&6yRp>c~juw?x=seG`Yl2A^@MoE6A0ulk^bF%uq4R=cg^Yg1 zj6^I83-th5U80H@V}(N{mQA+s@0n!;;4b8)=o&d$V5!5mVueU%<2&D*Hp9H!#Z{gswXtkk<&f$@<8hx)vN1rQqh3?o=R5(B1g|1ZOF3KUw;9Cg z9{cPu*c@qn{{3R;D)ku*h5!R`r2YK{HWj9NEv69POl&f&$te5D!@z1M+@5qqf*fK-rd)l>a8^m+Dl zkSmLv4@i%Q$En#$q-e5Q-bLY~;dhJy-pJW`=t!DZt$nJlUF!R$Y-2p0~GZ!wBV(&Oo!#3>2EkA+G`&kYx1O%A!b)?%04WBF-|5 z>P*BKMQ}ENj%E#oaT#TC-eWXlEQv!<6rmS-2&FMIk)U~UbK=gynk~7bmO2|2#CmL~ zY-BCWk#zt`CJvxektmFF(kR|R<3|V0h&K1O4L@4Ylr5&@uJLjZnbkpb`Xz`3U*}x) zG8OuK5wt(8cMyR4Hta%T zi1rnI#L{`9rt<~vNp2HIIfn#f_@g3CMMt+*S+Z6y#OGHlJ!pMG9Av9v{l~4?s_|&K zIu)=(zjG82FOt&>PSt!DVPq3`*)c zd>NH-a@iuOB*LL2!eO}tN*D`@e<3vC$|ImDOSlQBL-iNZ&wvck|B*(Cm;P2%EHyT%l8?Qm=;SUF>T0+nB2INg`^$# zu(U7s-oQ&f3#^WjvOv#7Qcb1blB)>8F+mVCQl*+((v0|>=VWCv3KBpwMrXLD6}_#( z1*t8A25Ynm7uRAvT1r8HBZwsDQH|tGCHA*Mh*reUo#LLQ`i7$CP=rmfM4xRLBi_j> zp~NPQ#OJNyP#6)r$|!McE*VO;pO5=CtD}1TnjYY&DF_8xAItil=8HUkIwZ` zkAxr}%2Wz)CKLCZCv3X;No9(X;aSgRoTbD*dn|3XsKQo2P|EY@Q6-;YSEtHIk8aw4 z!T*+COGGstndT$9AlE@fL8n7A*K<*8{Ug-z03S^u`vZ1O4tYJ&dC*~H#QYw2&wDM5 zgOqUvX)VuOuPbw;b3@ELA6M?SnvW|bv+gBiF2Y3{z3H^0%E@brw~+zeg&T2E1~nLN z1FNAFjhkU|om2dd(PMHM;k!GkOZN?`<&r)8ti< zCrr3a!ViGvL+g>d_WpG$oCR~0TlTobGRT31F*oki6px>I*NwZdquX|V9&_W)5@CIn zO^=eX&yV8#{4FQ8%gBFp-u7=Qn?P z0q!VX{mwh@$gs&Mb%3K%#y*^(VA7p9|r0t zeRAzU)!MhNTJ`F)djDO$p{b_p{%U4~#p>0+h45nZi$(?yp1KM#zA879gO(pytmE8Y zrwhyK?Aqz7bzwIE#PtcPRXj!dZp4D;yNwq3}e7cPl(e;YacJ zVt=D(pG{nkCTNimz4`5AowfB)o{edEkW|GgMkV*7-yRM~qN&%vk+X`h^uwiw887oo?P493TG-eEgH}aSXlLH0UiH zRSWl{*f^_sqH4%>kOElqfPTZKA&dX|nycZIg_y3vZ~K!yDCRX!QVqEd(i3YwgkPaV zaE8JOOTRo;>b>T9_)QhLN6`~`!67_wTTuSPY{jwgsPCBQO#zNdVKonbZBv zt5$tqK5Ry_kE8o@;AW#Vh(!NlPq6HWiB$7nu^`WxPTN>>W+X6-HD@NRV$HdUCb8zs zN?;Fb&TJaPnp00pSaasl4Az`R+Q6D~M*;&_bF#F4H77^YS92C8i|$J1+?6a^n#@_6 zEV?_Hb2rUh%~?)cS94ku7`mFXl2)$ftV&H>wPTo46IbgLHWOF+>n5(Kp^%!mq7&BC z#MS)@n~5v^-9}feFvX?x?F#GfwjB!V?=~}WrN7(E#FhTa#8r|X09_{APAmT*g^w%D z#Xs40Na1lxf5O65OfMsR`;o|Ka5 z8MyICVf_VVBsnG9Zcs*X<86Ggk>ts4l5O=Y)J+!U&5hGXGNxqPbcMyXw@p^~n9}F5 zP%jx!rbi(0#RSZm=_B8Tzz&+id~%ZH$tRL+Tw-VD$zB}1YJDU#Nw)EU2yCV)%*Q54 zo-82Qb_WY}lVdn|HTpdy3y+o?i){GQYw69^--Ua{3A+ zuMvF(GZQ;0nN^j{BAaX34afEiai{ir@(lDCPC~&geE8%u7i*L4rlBUy&5h_o_TX|(!L{8_TYRLS@rBi>0pcEe%6AtY{VM=Q9*49p$7 z);@d0#AwO=__BmwmUJQM1Y_uR)Uw^)Waxmy-ZX10kq1Qs`!Do_)w@lnT$??7b>mO2 z4sM%{ zmZ{ht_or1pSEWFw(G%ZFX_! zT~j0-mV%Tu0i?`OAJAiHdqh1Dy|G)9^^{1r#_S4>pW?`c7^6-dZ^5a#8j*lyUPkJm zvt}R}dXt>BtTpnxi7*RT%7UlpU~IAVrVqjt8jAcyV9AG;dcxtkmq>#=wu^byj zw65=^=?1Gm9gdcP?npI&Fo;vwjrH4zI>U(DP$i)!sikI5Len~%27J8~@lW7%iXB2t zDcuf}W~~Odf84gCQUFMbU9l?apJ{M$E@qMSJc|KStR&jvqv`jBuk#zKiR(C4YFtye zVY8hJwh{YddVq@JWf)V|3>vl4O)8Cg40M(f`oe0YjRM}G2yLh!g_r8CX4wE~P!QvO zKJ4^)$~eaZBOf@AV=)d`g{D!JNLE4c^*Fy0JIoID4vi=n(ng{^ALr-vLB83Xp0gY| zU~{_RP~o;#J#&JEVCFK^!ICx&LN8odY&u@(ZpUiJ3QsWu4ImWetb!qKSgXQ^BHyG$ zEhmb6pc2VxKqu*4v2PJb?U_P1h0UTY zl(Enwj=|(sBXux(i>rPP*DOhtJ7%C($+G===G^A|5*Pds0cu zCR{7+V|<-`3=@_^7RpScC*9n$E3O`;Udb&jR3%3?5DpJa;q#9EmDgea=IZ^Bp~kg5 zd*cA|B{w&YWJV7zEuXAC3z))#hfof%#&;m{@SsILlQm&wDf=iswQ0b<%x^@>OgxM= zTh51!{IR0a0%QCW`~yn}^Jk&PO?y$qOgNHMAEu~|4TCM+Z2K}zCmxgMnxg%%z zjp|~=w?(1Dk#IARV79e791}NZ0Smw&3^1q#XwbHS@$TkRhgcY&wNKJ??zqglBWf;CNxt5}ES}mI3V#Dh(ji{<`tWK+MM1>?(VnB-m1L5W@Vq9-xe2;F< z(yayP3C^O&&=(Hx#>CNo6lQ;;j#HpZM$pZuF)m>_nS75Dg48!U#9ol_(iQ8y=|DV^!cW$UxWk(LF^7L(3^<#n?!F8xi|7 z6`y_scT7ANWyZrv}>T%|#qZ z{|Z(4`^HmM9s`{Odo$CX z(X$1-RH(a9zH*$#;%{n_52Ozlv^uRsg}Ob*@B92Y0!QRFymK35O{W=w@T*9f5iG2V zoW-jJX8qT_TKLL4SMvP?ueIPQWP}9jj*lV%xaJ@UutVwi8#QSBwXUH2)lkvpGP?r2 zP+Ub+LC!g9+_b^wOt~oBSEO^ht^3@;hy)SPzQO-d&F_fdy=gK48vLQ6@;zkp5JChGNZKx zvZnF+svz4HTVfK=8puz_ zw$Kik`R^J3cM<#mpWfeih9`e?JeQL{I-a%3A05w}<&SQ$0`UHJmjdwqwp0Olf4f@& zcz;{20KC7oDgf_qD;0qEw^a(j`&+vL@cy<=0eFAwVu0S??q$II(QVX6_@ldD0r;ca ztN{GcJ){7j-mU;3+Mxg-+N}T}dQ<^Gv{wN@^n?O{Xukr0=qUvN(SQPg=#T<{XpjLy zbd-V1`J)p(*647M_LwK&bnxTo%i*h9A%eR-W?`20m{ZL53bVAwoMN_Dn58}D6tlfc z*=#-RF{haA6=v&Uk2%F`uP{q{%qeDjg<0BTPBGgHXw;9qVHTc5VA+>x@IBZo%+f+| zirHRamKK6j%=QYiv=F>orC=dgn5Bi_6tlg;EG-15nC%s2>!+_BfRSKBE}9c^+1#M2 zs`=;uj5{OPm7fl(?aJSXzrf%h!yT@!KEk;D$1#^i#DmoSHBI{HzGLzR+;`-OVYve# zhI#9eCx+z?gcz1P5Mr3uA9-R}?m&oPxdS1FuD|aBoFKJz^!A@&o$DkQ`!VL z@(@g)nt~K|x0Q#$Vhw^b4P_(9h3-cg7S|~B?qxVnzzg{&7#8e4Uj=@_rfi-)DC7r} zhn*;NA7WU1gzE}-)P>$bq%p>QHGhY-`RYl$JyQi>Haeu#ffr9k<+9)qGE^*2;&@Ic@yl!R6^9XuJicA^;!vJh zXW+~C5unBZpQhN`IHoK>-8>5oe};y67ZGlq2(=S*frn_};f<<>PjDzU#eYulzJ;9J zpD&>3;|HB#0d|ox>&*@OpApbkkWG=Ph~QjZJIX&NkT~yG60WJG=rwXN&@htWtUrM zc_n zaxRg%m}O@RxK94p(;YV^Z zXYu1Re~2XJWj>to<~(og*}M#o@0bx4u*LwRWz){`9M8Rt#02*ZYbw6}t{UdHUmZPX zKW5h0??mm<>ZkAx;}$JJAUu2;135w?aP+p;RaXOekFCf~znbGPaq9WKXUbf4^>Pli z(Q-m)TZdeVfyhH!m&HP-*Kr8txrvW2umET3YWzt7l7%-KD~aqyY%OL$`~y2U#xjsY zX`J8icCO$v>d~Zd8QnuB$@?P6gq}&S}fxwUDqZa zbrm`;C@nn9+5whV5+3)wOSr8g46+FUSM(YQ+7>YrBQ%Gv!++V>!RW={g^|}5c`fl_ zq@^|2ZMRhobY7VSe)rtATezO~!!NxTpj=O$px40H(^Cdg9ycI~v&h5YaqqR#ESv&w z`K%-1;W9dynfAlXn*ReQbvU$zr4&^8@cl?m>b4`ed?Ky($B*w&3J?!wlGN=$(vwdh zi62wb2Cz4V;6qGykQJ)`SD4dePD%24WC$@uaKgEw6IcKhBcs( zB_M~2Edt*3XE_2ya9F}W#%BnVm5!ERrQwk4-b-0l!(0rAIp*?Dh8#ICxFts_42<*R zDI*`t#mmXi zL36xyVZ^s5pk;rkuo11^n6x~k6)WgQ);_tt&xe@dek831($HM|99u&%ss}3|<4=Ki zoiT6ba>52<%()r>r@5>=^XPSbU6~~aJgrArF?{nRTow^V&V9BN1OpdTMtqfo`A9@q zMf_al3M}xVG$xBM>qLZvY{X(4?QxbdqWe99yikl#M1HiycJx*VAlRbBP7%z&isLr) zic3*wJH!|eX?;)uvS8(GlU)0pVe(#GeSF*~m8QW#9 zC1}-R1ZD{;B;OO`hDjrqX~!RMMqT08bs++*S%jaR)kaHBpp-OEut2axLgo#BZtSRA z&$NQSKw;1lLN7&ecN0p-|SM4P;AaJ;C)>T|cE+N>!mI|v0U zVrdN9-s@Qy;JJ{=pVmIgP$Xm#Pg*9&pvUkAfCv;@6k6~*3Ss}#NJ{b3V2fkRyB5dG zBT1?$4;vOzgc-8Dw>mz?mh;)m?KgLWU`FfL)f7LCH!uS{*!xr{=&jvHkI;X5p_A{T z3jPubmdcF|^0qb2+tzeucO#NB>z)r(apb91rt<*87~$Tm0GN;103&CWht?_8jO%UcRHE+N~TTUq>$UWQ1AaX*v57S@!Uft&)tb251DOncT znbY%1DeYu;vR+V=o9Ce@19Ql>DDQ04H>^}Z$&~=moY3Dh5vg;hGn>D76MgXWB;>_bGm^amB=`rR3LE|4Pu*Ex`0Nx^!^C^0 zqJ;aT2%oKGy1S+R0Eo4C4A+J#&nE|xW>b%tTYLqDr#0TNW@F$U9FB(qlL)b`~;n6 zqlVIH>yb{mkiXyJd@mRJoW%Or9V0XEID4e6=Dy`ty_-FVcG`2hu*(j9A~#hPb~PfO zKOH8ZgnhF0B{h@fv=#KY4Alz)Bh^?CFwJI1#rVKe0=`m}7mYhl2w)6&?37@&IbwaV zwTSl{+Nk{HwZ=|wm0J58C0^B708JUr(>&3`#e%5l;*6w zzlgLKr>Os@zzL>B*t1ICWd{c=YV;_Y#U=-nc5*OvQo}-KO0LAo4ed(6C!Na@;Ya18 z1|VuUj#45q)PTX)WRePI=_%zVHNc4%v^Vif0Waz#Oh&f6k`H=x%8y424eiB-LG9f^ zqINUvbZJKO8p_Ofw$Q+RJ&MvCSI}`Mesu(jU(C|5l!m#XnTG6p4C?UMpiyLMI|;i0 zjs%#h4m#4QNld_?C`2aN>N4XisK#ww1QE17LtQ!1^3vwc%1MQWjnXc)W0R?cru$8e z)Ze&Er89o6(6AY|(1oUl>29WrRLc{(-(^JT*&$cO(F&oA06xJ4!-Bt~e7Rgo&0o!Pa zvoK~>!(L0^p{{Ch;BEAyRfFI7kA+=X>N6 zJLsCt>(X>M;NVzs+rk_$kSYX`($fL!pb>}zmlD+hgIaqJF;B0`QM=2~@a`(DJD0A~ zdS+D`|Ed@k+-IE>BX$lM@3XG(EC*ztO&`anto&n>O+&~{Oc4KlCu{rD}39;^La_5*)T5n6#6!>Btv9k)+N)N0#?JjwNtc z^JrzpWg+E}Ew~D+w!Hnuw_`UJrww-}`MhT(Sh7gRL%G5Y?8gmZ6iN8ov?~ zH`nKTe2su!aM)wi5v8KdiA*h$%@HBSfkzAy2>ornBlH#YXA2hq*X1{)$ru@KHDc*} zn`PoH237pcl4G1ZQ}F;~%>D?z>)KJ5tkB?BD_;4HcB#QksI0!3~SojY)FML{~f`z zNm{WBGDiANr4`8vRrRkD@SIesgf*rre>&r+c6IuSfMwS}7ZG#8M*Kt!;W%k)Ur9(@K3{ ziKuA+e`tf*C8W~`(XcW#85;&5z3~kU<*K8StBw^K?uD3AXxa$r1otC+{z8%Dv_z6} zq3(Vjw&T@{L=6~sbSp%4+xfDE;sbXY5CIBxj~2B7Daj~Lq2Z|onCOMN{pIhq4#OIp zarGb@aX3EFFQ7_yJNhmivvj+o*+whNHpLRf(P_)XR&jJXO6@(fK6@PwM^vHtmvysL z`E!sV+$>f1d4vnS!~C1~v;JOw2d?!Ezgzhg&GvAl%m=30lw6v0`jBnmXi4X@vl22L zKlj0F48~0qE7aQYW`(F_*$*h-oR9sO0rOlApJDR5e&-JH2i#fB?-!3SAvTisWmf+%tv@guvS6 zwBXs2*lGu&~ zm$G7)zPLrJpa-?ZS~&p$=3S7JW~Lb=Oyk=OGpB+u4mB_U(Ux^UmUoYXt2HL-X}8hp zd5B5PkjBqDT&y0tRBGqHURLK^#1pZxo`P zW~wCYv=NIT&Qes?yU*ftz4Z$AgAf}`Ql$d6J?)=#tgo(1Gm z0Vg%wNfOdq0fFrf?CAPQHSrk%%UIhZbO)Q z&E-T0dc9~TUg@w1QB#bUdRkf}_>L^4w~?KgK|ll$w@zI~a2($h(6m~I#JQ_M0rDHu zbV|}hRP7jWh|tJ9OzUrY6>R2eslCvTyAArmAf>&_55~vg2cwzRW;Gu!>B>S+z;W@a zl_>?_%%9R-!5bsR5|R0(E3lrLd1+QO)hO0Q6f-1jR<<8dMQA%~fEf1#_hvRce59`OE45LQX&H>r2G&7r-$j%|DjcpsX z@w-BJ9HM(>c;ik7(SEt^8pW{`>$W%Lob_ z$cV@$7>{o%oBbhc-f1lBcKOn))yjegu4R^P$q(6}FOpF50A{2Eq-uShQi?#lLb^Ut z$iK#L6w4m@Q3O>+SR0tIN~fLXX!_2Q+%sK5NA-ixdu-`+xB!5`J1BVL7ZSHFZ@Jhm zhs=5tAElhhFHz@F>t7GQVCZCf-UgLl6r!$4I-Gtgs{2Nj0jO>gRHp~rMuMq@?o%#S zdi{B-5Srxa*(2v;kxqLvYbI2gaD3V_X4agCHE6xCRg1xj`8y_nxDyk=xRc83_wcL#pPC1vug$f*cDUx;rJvz zi~Yfh4@fA;Q9eM=?))BdZDp>COm`4gK9H!(z4DVs1;I7+mV#RP?ls(|TaR>B?==1t zdb;d;e>;9N&9A~JQKlIx8)pIPo^?#CMcuqb&Aw)vdWR!vKq$ZRD^~i`qA8SUOocC}=+aU)LK>{jDp_lO%!ftyC-x_8m(%24@HaN04&UqC% z{V*eS3Y7{yy4SMl==Umd=LOXXQjOE|Nk2Ep{s{H`7${4RsvxIP!z%O}3S*e*gnBR4 zJ!C5mRYW@8vt1ZssjUrX!O+lu|?(W33q3bs4zA?`1~M~fbth0#TQkInYqdjFlL z-=JCm*e=^Pg?+xWx*Y=^A1!v4h1{^H8hH-zn;i~)0Dxog0$oB#CRL;g4W**|?Gsr6 z{$YHxptW#22vI7(icIpaL--1647Wq{iN9q>s*Lj#nsEq)dTolD?6oPdsO`~WHMZEm z~tV+7=RJh@j81`*YD{(zN_N9cWeMk?)hjqA9Fp5@-cEw>IjdWPH5|K_TMY zZzwDN5x!b&>R^}^kv3VjqoCVSYiLaCc9Q!hzKPI{C&XU`xR&6JBSY|;6LbR~47njNCxL6n+B!ha5_ig2)rAnBMx!J3hnX*xSQDs znc*uHq3mSNPS%gZhD9d-rWRz*sE<&4<2k9uj0bifn~55UOU3Nr`>t>Flh<`nl;mMp5%iqL&ua1*F@mEC1k zi7o&QY9#IY8c6jvO5t$(BOo9vV!H5}oAi}l(39HZe&#?rh%9K4McN_MhIbE`SPa_n z9LUS@^w3{mT>E^iws7a7CSW7;#OIs zeP3oeBV$iasgRYf%2fy#dZ~a`$h)>&p5M`IFTde;7r)~7_K>c)vMjM2OG6ouOhZR` z0riC`h(MaM2$+6U8c^2*xgiCs_$TRC@`y@Su#u>A$g@^ooV9i&Yg8IUE>p%L7m;jh z3)4*0%z;`t#V>uB6ImeISUTZxCLER%9`XpT2P#E)Dp?6n>c}NsjF~m|lQSzqxV%VZ z7q1gSTc}gv-`mU{190goOi2V2D`JGptDfl&)EIk zNs!3^pAhu}hm?sH-&Mo<+c14++z z1AtN{=h@wWjvo~O(9NUw*KYVF#)z!(r|3b_c^U&@+k8H;CpR=cn8|;N-(d|Q zdH$83kw<<4@t23#&3FqLHab($y|G0h-f8JsbT+tupSaJKIZGo8LHSexOx;9Ymo(xn zga%j#CktD4SY8)AEX}xQn$HB?M01Rrxa`l9%JyRSsM7Y0WJAG%8$15KYdh!LI=+0+ zPLpE*V9}Y#kJ-;7O2Pzt&Z&>A2XW5$UpJUq<_o&^upvdYg1o4|g}mQA*pNy9DRi4a z?E`BLC3-oE@KCM4Bnp_!as=G zES1Zao~M9A+x}KUG+mf(e^Y#MHwk_%qY5dRroB;1uk9a(+aa!L2W&(8_Q*c7eM@j@ow!g@w$ zJLMVY@WUmkoo%gn|0qgd|2xQg<_ggKv=q?Hnk!g-1VD)jk`C>l@lpV?;@7@)Aq7w! z%|C_s#Q-YfPJ94{v?#Y9;PnwSVSj1k)nljgpvthv543&72C{rz2G4iWltvaRooBhhw2o~DRL*H{jnVYrW+d^5RsFnqQKjL3rQntJC@td>~7Nh zq*x0uV|P($>pW|)&vq_t5`{Pe8bEJ@hbc11nKIV}aG9X_iZn87zYw;yU4IMRzH3%D zE1X&!q{69ncjdRZy|Xdag*^Axg>LSw`P?Xai~=Lx123YPv>jiom`MvV!*pqV?6qfS zB{H`pTSdtLs)45Q19hAil3UyCF*Q4*xbcYYe$jpgF7rxN#xt zMFU@!L@lQgV(Wa`OrbcdPe_%9%};G>#A20nEE5qe?!~nK$7jn~6qOe^K9t1+6j;i# zy*v0~%uZ?FM>{-m1B)C-3V_U^m<}hwwx%MVr7)~aA?^xisD)9G}!Iy{4#^@ykfujaw|nVg9|NafD<>pGNUl4H-6ae zD1&cdumYJj2MO{8v6MjCk_HDwTi88BHCVEBRzY=H9xpKxtp+WEjHqiKRwEbBH{r|~ zDI5hFXB5A{=g3|eAw~M8usUCA=QyU3AyumvEK3fY33%&r#QzKTd!NlgPn}j@VPX^Ob8GPSN~B^p$QcoTRAZpuMcamxi86K*-MY;o)k*nF}hHO@IZ5c@Gk7e{YgiE)+Bj4Xp7 zDPbAu2V}tE_42&%=NjixAlI@AMx^(8GoToive_N;Nfb9>ZKyH!DbMxXI23WU&*nd$ zokdHI!-+dSzOK6vUKzDuRH9`=_|t}$&>SM&Vi+Rv)Cf}M#SYjtC@3Mt2of@C4l;UY zcO9ydlOnA*v*t;V46Cl(bL>V!sp0(mQq#bKQr!ipEuNy|@_CO#YQkDJ#?sb`&!Z?C z4GkC2!aXopCgzbka+j?qBN<3sZzpw!M2&R=e(hL?6>I2-FHjeTUPxL7k|TH@+G6iU zv2eB=rN?#{Ups|H+D_mPF`|hb*qiiBD7EZ|#cRzj_Mig&F+%^ozrPgpccc8j26}<* zoxr3V3w`qZuY{kl3Sgq&I-%~=aptT(ZFegT;P1#N9)jzW?;uDE59zH6*DanW!8(Rb zye5=8icFYlIv3HxMh27?2G*dWNqkZkCxZP?&>#U8sbunKbbLHdl zCLfQ}8*Yy34qQ#6HXjzPGpUWe)byv!biuw9tQNLE%{OG@TCbIDM1)e?h>K_&ZDFyk z`tVgaV;=`k6Z|R8ol?Aga%p3L(7m9vNk8y_1B=mcQ}K`G;lX#_S^NAu?N3WKZ~cRJ zt{T4(>v33bw|VP%$ydCpcw5y9+?L5y;EJnf-BP%!a9dUT(|X|6IzeUbRP&?FiCxB? z%i6Q`qaN3NdJ;@5(jH**0=%*-zKIuh3+5LZ&O?u^Psmnqhz6XEfyxNwpYLt~X4`AxxKU;}|=Dqf5V z$Z6wQQ!NoY3Oz=T>zRuOgG(nNiA*(Hixk}C9l_7DTK|JD@X>n!*ex|2PKaP&736zR zf;eHWwc=`Xu=Zu3IGV8uf97psaNedG@IUz3g66sV{@aACWK@$uEa5tPrXSt?qH@0o zSfhC_qCWwF4+!>ca?X|c6` z8){~nu*EikGOY&?MlOLM2xt|gAg_?+Xqo}+M7gn8d1L)XOpq&D-wObr-i+Xx2?+fy zFczGt9^Xy zZx!iyj0X6U52D1EX9)9?Q6pBq9M-3tEDx{-_$B11u$IXdWK9z{q*Z0`*{Wil$indn zpP7lemJDKmv~FPvBa3(S8U9m`C6L`zuy21EeS*Oxjm+1*rlWTnL)xPwI11j&U< z`pT&$dTQEGgnYVH*DbeJZ7!Ca!iObnMb;%Cta$B{P=R9nHWLx; zbdWpd8LY)vA8XO2jvn?KWYu6ks3@iHXvPMB2X~nG3&)FBLT-(2Sc5;gHB5rfLp;kO zFEB@!8vwQi;SCfg0%;iACwEwR(;6Velh+`N`_P2TMga76o{{1A5j*>eAFwmE@E?)( z^bm)G?$WYAp>}5}J#)?x&iHFyew&xSkL9fl7+g9bpp-x!M~;BLR47;z0q!Yp!%#)^ z7Km|(Cc>rQq$>ws>%*FZ8Xh4K`2Cs#_B0z3D~>UO99r)J#3tJta}>!!Zs@Y_r1$8q zF7}pSw|2p(D@Fz=24^fY;|Avn#Cdq+xEhTAojmNtkN8{csa-ZBs^C+P6ZRoaSWK}w zguSNAVc&4&ZIgUC%dGq1MLnoC>VLWlzwjz8cb2?J5aj9!?=Gwp)&YPFL6x5_)rpH> zuk;F?Snr*<$~v)rikgn9F5L+h;EfzaL-c3Gzh#^yC@Nm9?wy&4pUK|Knhj^J85P9< z`a#TxwpI>e#`u@I^!~f!Y&F^(S;DamZ$BWBq&c(hw_yRM{lIhpfn6C$C(>IZH?5y< zx9-TezM0r(j(QwtnPGj#<}qd__q*32f1$U_N{QOKjTJ@RwfvO77k@LIe}fxP^Q-93 zxq}SX0UaYAU-oqwd4QH)dMGRQ<99i#( zQ6pZ()P66a5cs`hLhE~A(NZ_)E52l;0pt+5(H6p=LLaW@<#Pft0&jA7r;x)()F4M0 zn&-iNW^khrHGvr8rh!cJwWdyn4J#64$_3{{*qGzblJc45NF>%ErA9kPH;02Dd0k4I zk@uELZ6Qr=Vc&SR0bBLWI$J`#2>=j?3P2oxnyQDeiLVu=oc3%a$G+!8v{OtnIWDF=YGNXn50nNU>gV~*r#n!HS$ zWQjK`k>mLPu=hUTRUOy8sDJ}LNse>&P83^C)I_;a`^hbhxrNs&D$;v|kq|b*M?!!0 ziG`3;Y|A{SQBujqHNwLB1KZ;xi(E^_c}5|5;E;UAC3(ihsUZRrNw!I>xa5WtlY}PZ z-eW^koKPEVYQ5iYt=UIMLfA=@w(slr_|Vz&f7Yy7vu4fAnl%WdHi%mEqNjkKH8y4* zJ?PU+r#j0u7$SFm=ZrOIa3ae82tdoYWoYDn)K1cs9`t#ydbnlTgPH9n6%oj80 z2}T#2B#EaQI*~f`-2HY~$$cH9+s$!-?_QEVzQIF5@}4gJ>ovUcet0S$;m>3EDceUE zZdA#QN+?-2AsKci`l_)S{NY#LB985dcI*guWWkkDp4bAvoYJMTZpO{}{eVCbI#Olgps5RQPx;Qh}D5X8xCFGdl z{*Noqcb4U1>PNHgis9$8Ag06-T?SU6D`k;i1qz^cgL=?ue5LFdKGd{Juy3|jV70qg zN=>6N6u)+!74V#$3`R{@Y5xSD1Q7*15(~IytZBYzUx`_bX%9suc55|qhO%4B$%Jt> zu16u>LwsolUl4-RVd05+tT{(&^!oMde8oOFb1N|lp#WEMwc0+5lz5G9g6~#5Bm_SM z@1&;NYEj@31v}FS)G2Pcux%Zra#)J*~3#8Oc=X5LBtRAm?*%}Eo(xN83>Se=}kQ%y4 zI1CTja3!n;OAc;#7~wJzH_Dp|`xUR42oRoJQ9L3FQk;KIz5mp^?2D-eCBlY|BJe7> zZRv^93-H(?5_g?L8%G!rle!UrZEAago7I9CwkfzcThpyp1kG-RdN^_g_`3~dCIlQ5 zhvkrW3X3IqZ>+Hj=uCiS#I`UO`9upDdDnOtPi+Z4IL#wp%#POqr`=DbW<&ew|7 zy<0(I+1@{+zOSk1LOGvoKk@x&*H(pSOcmd!qO-#Cm1vDcPgp=sk@#fXXSs4DdDbU{P8hBVAwt&1_Jr<87 zE`!}$z0Q4tFrDlI7W6gSE4ytEep%b(eAD7ZdRA%W8@9K=Ae~GvDKo;+Wa0}J`=3|L z$?k~lFkJH&`jENo3${15DP4FFhn>$`4ENhG&<{oh3O$a7FhLcD0W);X5^hyYoTG==Sf?0^-1YqF)2OICwsBhEeBu=p(6$-O3Y znkzqXc9O)!*)1Nsr77qZ7J}YzV(bM<3vu=jmPFE{P3R6T)Le$TOyf3rod+Q*-OfSX zy#gv~9S7o&ZLl;RigvC1D8%R$`KHQ&;c28WRk<)aEsi|cm=;Gq%u9=-z?Vy~rmE2I z?~;%rUoMxJs=0pu{Sq?I@9z`G0$)^XOjWVpzg0pO`EqyGRKfW42fr#J(lXHAlEtVEfwGclJl@T`K? zXKRjorkg>2Kj#u7d3>3W{N!?kB;^7V38SAgmPzur$`=7bR|(x07~aocKN)70BtiB2 zBlzXbD10EalTuiWT$sYwnmcmsRbbaD2fM*-R(s(ZQ>?BA#6AG?*+qP}UBoAKt>t#- z+jOcY7+8!`!wJmCTQ~d?9)1h$x5$3y;+y_l|C{4Lq^Jn1SlM|vH<{#0tct5572#M1{(CrA)BW)f}Z(Af@#!?8ihv35TP!4#o*UuFArjR;W zEN;cGo|8k)z`k<>&ZNMVyuBb+w+0KFw^LzscgF?ss;-ZC zP6_tPhxg=zGFy62Zq->cW4tM*@B}8J#G)6#cyiaIbiBm&0|f6^3^nxY7wcG?eGig@ zT+qV(H3EhA7!`3QDD6CTp;9+p40Dx?pHo26;Qp&lBq?1cw zHtoWM1(TABvtRpaa#0H=TC_uvGF^+dD_(;ZX-BDOrIblc4r}>rGAw#nb08=nyG7Zh zD1}LjG-;-kOiL6zXRjiBa*MKyvea&=k5}xR1AR&l6sV5E|EQ(N?*i)62*Vr=yG7cO z{Yp!+o%%fDkzNhl10a|MVOwP^?YS zxSnjkIuZv@(iU!)s7^n-1>t+y)2N8t1;A)!3Mf^!)KA=LyxUDdMOAmmj4;NXK<(2e z_hhostp!+9!Fc6@-`PgOL=J2v=pvk=hiDLT5yK4znQM@8w~iTax5`9r(v#}($gh5P z$MIDT84GI#&3Srfl@d91GYR&4;2 zd2AO~@WLL{ELS{?Fz#pUR^sF+)B{3XaGVI&j^f*f2?}a{tcD}U zmz`7tLzl?~AXSAJ#7e8%y%A^=+{Xk>bt`+s2btJHeaV)Z& zy_~;+0=p>GCMcOC>=WA>R-^6a^9`9o+Bzn3x^^)hlsu^CB`?NeaZ4M4W5;)J|RZ`&iwyS81#TugF$?c15fu8#L>abEilR><{=HOs)gPGo9B z-I?{U*qT2JV6cV}*44h%*CVgW=M^YB! zN{P~#geAjFR|Uuu^Y|hpAQ$PijYR~ES_06iuDzS)w4=MZn=ye4^yEa77>ijDMgeTI zNDu&78_c8`;FLSsci43tTnrq`n*^hI>02{!v+5jXB}jA7>|X0 zc80R!{n`#+4nM?18}J4LZ)#DN+`&nb@WgK0{eD>&jJB~zv2Je3&-Deaxf!$VX zaLd`|Acs|A*4=tuMSc$1f}4u*Vhs>|n50fyfl&>p1tlnC5>J7}E9%Y!+Fs~oXMe5? z4l4OL!Hv1ST8tc#+IzPN9;gag>{Slo$z>*l^%qJ3PD>sTB)hgKNh~@F5r|%yD`>)N z?nu&aISA)58c%nv7~uBXA<<8Wlq3O^lA>h-_8=CfPk~j{Ey$+zmRuAkcV++rO&8qF(!d||$-kseG? zNZxh~GjG-e#$!K|hu~U70r6t+`(!!*f`pzQ+dNS zyHB)J*NY81b)Off&d87S-Q0}<%~}~4_t-Uj_ZbOq*vqXQ?Mp0Pxs<~nZ31gnhT z%Mp#v_$quOkt>3MZd<^M1);4>Wdw2^)hF7g8@$fu0$n#rK7!9OY~7o6cNaE%vtU6Q zOwP_`-JVa_e6C%CIGb~Ac><`!s(^xWGoqdyfl|%_n_lbCvWq6cG*Nb5R@p31`_u_; z#7lX_7ndiyXB@QwGAx6zn|+x!?60ux%z`heVF3=j^S)3#HW!5+bPcO>6V_T3EXc02 zu_*g{2p1J7%R&PTTb2s?$xw_Xi$iF}uSXVK&q>Qv2R%as=%k?Pizva!fvb^hz8t-Y z7DwXWhbsChy#8mP2R~VWmOwp2+5_6?FNDia&c$!E=mmLJP1gM#pNQi^R1OCubATE^ zt1x0FZdlLE3Jck=qD&aOgXxHZV@-)t|BT8vI7**Q4!Kh@MjB3k8G;K3#2Zo3cIyq!6 z8Ulr=Lx%uZR*LevR7Yhh%@gF+2I}B;EJM;T3rbM6Aj~qeDU2`Rl)(^~G=$%l5oR+y zc8Ep8R0jELGruWJE>qqVrudG`Pe!z2UTNfa!~f6?Q&2atkEdPmp9h5j<^B%wCnVow z$KD!C`Xn1m(srAWuXOP0Z6aXdSiv-4zl$<84QMzvy}S1bkw9b~QiB(%cNR(%e*wXxRPbUF3~%p(*yY||BnkK{ zxA5Lb3DhZ3{7}tHHR6fvyE)iY_d*h(5jax$nO!ny9XzsccvJ!7>fGH)uvj-CGWLL- zOGMi7EcPkB#RDj&>}~^0=*}70~SezulTJVL`pV^j*pTrN=;Zy39e*Xw^$39r2RHhPXqS; zD58i|C|5L{J(hg|Np3%zh3K=o@wrPyL{Wd(Wx3KTz+0b+ehjB>+&|&7OQlHNWpCET z_F0ByOLcZ@xM(={snq8NIyFHtDsWCN#tL-P6r;2Rz;{=m%4pYDK%Hu83OX5BR3~n2 zL8Lv|cW!M}!sq_%XV!t-Ze4;TXpr}r3Ithw0nyrU871l(uEgEC1_?wt<1@|31MPvt z&Lz@H@75Z4usJQ4!2}K#h*hf?{u7l9CUJ!9S!f(TpYen_XOItsgZvV5F%Q`>7M(te zaNBGRvod#pEi1b3n+|L5jC>AvXS>DjPIkTNehkdSs*V5-yKCagK{gTxy2;XO(fS`o zyS6~be-g%Rur)=hISF-&BS<9fbnKv+@;SI7gIS+L~?Vp7Dtr}!Jz0> zfLA2~+%^NZqF3R>iTdomK!aQcy3O9puj3*B${RQi#${E^qdh;>zzM|!r1)E_mv<*J zI#&74*=_@=gy%Ie=39EY@9eUt{PzG1kFd}(8Q<&Xjds!j?n|G=2Y(($8^;L#;LNDG zZ;J`#xR!#HcmcHX=AJ(Xj8^Jd0jWndc%C9Pq|!frH6!}$^O=S!9waPWX5i=rq_&zvvmcokhE@mumjJ+eRc zlqDm(RpnS^FP7o%3`={RFHgWGc>=AgOdeae@5bf{RHY9@4h*{V}8RTU=H{wyz z_cA(aj@@er&Y~NSqDCP^lw|iE*=u81RBnore{7RDp7>2BC&qoMUQtp0a&L ze2d@W7aJY!PPO+B@^gs%3AqfFJKv?lv1k6?on$!taqMZocPIG}f1EqP za55Vd_kb1AYJt0-q7!28X+{7+BQpCn(7O}NfvL#;Mb}yOf5;eIMg@&@MGg9%j7r{$ z0>lN%$WPghkq%G>BKNXUB52szV*7|YtKzqPBRWTVS=kPG+1~z=?H-aOh|c+vc7xlm zla;f@4r8mb#G&9ktiyxp1Janx@T2vwM7ws$)Jh7&)QS<2@X%KnVUCAB@mgX!-;xD2 zp+7v2nC1`Z*=r(}<)?++F-4sYB3Z(Y7|*ML2aGCVhlxi`I*MdYGm*^+2}91M$7Ncm z)tzz>E8;F5bCn6q8ka)wgfbXBe>j&Ld-C+v*t+Ck*p8Us)mRW2P)MsnpCrJx(kNm+`>w>A-M6;zzh ztU;NO^4;B*8;TH2&}1QMx#6uCG)%>9YVU>`I5AEMH4lqo$InypI}-S9N1$5ruf*4t=XuW~?Za7}0>5iFxg6hij&!7U zg2y^+=i(l!Ov#Rz{)*P0k9G|R|04x~_DDojI|gras9p@EVKw)Jp(bj@&@)DD{7Iw` zO(kN{Bo@VDrvk`z;y|k7HRGp=h~va;xwI#8Er)$@8NnkHWZhKK0nS}!QAw9kf&RZ^ z8Icp{!UnUgFrzmELT9b93Qqzq6TL^J z9~!TB0MsO&M8+_9Ok#On#*P@sm4ci>y|c?<2zK>r@T9BdI+y!6^G&d&z%uODExiO5 zVxO|K05;Qb3e^r0emxtpB;~z|;A|2SzvTpM7TA*PnfaD8U>kVnA3}`P0!oM>ZCf4fzSZ|^xaB@ku z{dFCaq9*lqI|ji<)o<|*tHosNR%#UK`zeb@o{~WzbDpw8PjG7S2plSobtsA&9LC&z zGUhDm4x-!`FnJ7R#5m?WppOIb5CY=s5D?>VBVolk+~{+~ zj59}dO(~I=odfW+ctm@UJeq`XN+a%DF zf^iEK!QZv|HK^mfj+uZmsP0CxXaGo}yXHtp<#xuz=Y$Mejxh+mO!DkF>mjEE_em?DXpI}(c{ch0IqVKL1q z08LNtW@R|FcwXmer{nHOP6`%#aT=j@4`COdB8Rag**^sr2vgZvgo1^5xDP)B@L2LW!+`|Lq6Cz`i;|@=b6tTSHrEyU6(Mt75ixn) zT)!fM=9-_cGC#X98#%zx$c!bIz1NxOAJjSfI64AfyEX58;28V{5V=Hyy=CaeIKpQi zm85PA7T<=DKrXt=HS}09qsKJ5B80ej9zs4a_C7J8P>~PMx{&5VfY`=xbeb~*j&;K+!YH(VzCfBBQc8U_*8UgOe~&h zAn4DOIi@xY6Cj|Zcom-tcs=}vFtq6!J~HP70GmJLmwSGBgJ0f+n#$J@61{Hemfuc~ zMMQavBBHz%$$IYB>U$oMZw4JJnh{yI9@zqP^eUhkjPz``IV@5Jy3`-wrT0Pg2dL@M zduA3pQyq;@@F~qZG4jtbk zGl-6t+ki0%m_klRgehoJ`OO~t446MOImJ}}pF z^<8!8S}?rM!y#!^7RNIOGo&H#CQCz;0!C&ue=vJ|pnR4nbuflSzj5QVMN4^RBUkMc zMVgHfpnJlfDlMAuAcxRrWK||CxsgX-8Sjzg#ywN~oT0PhIYm$SQybG${Do8U_l2_4 zCpV5w^53^{XcGK{53IQ~-*k}icMsk5F3uO?A=DBd$%E{lzCZ!P9gK3 zEjbi@WM~re20qNek(}%v5d4JA7811Ym@TAYKOQn$!f+TrBHl0L z@f*f_+kjNNw;>jLpr9kF8 zPIVYzvCu{Hoy=;S@5tb%PI=CBQy#dOUoqEI=KAp_Q=4a}zmc34&?W0w<5x8M@!MA& z%Ce9I_-Ssj-u!vVT}^G_RC+Z=c{s;xItGLsOwW96Y}brdE?<`wLWo$#nXZvPr3LWP zjEGZ(sjESZPo&^mfgswQbZA&iCMbl;)K$7BV+HisUp-`MBh_{3zdScSt`!CdCO)g0 z2$Gpoa;WXHKN%9iqW%`MA#_XkUGHAq`j5AmJLcTd{n>Eq&&;yh&3}!7Q;mzv(sh1$%sj9IUVp_B^AP6=zplb;;xyrxFE;-^Qw`}5tzO-D6|Hba14ZjDeQwaA@7yj4Q%NKVZ43&(nKP!}C4g~}=b$YO_ul&M7hA*Tu83K*$^Wxh=b=!^@cPpn=8zty$_Lv1-!^|T(hcg%S?g4#KlBTZ z+;A{&X!-GUt^|j&C_{T)ljXuU~df;wvveN=KlH<(sXX0h77kH1FRxYMeBw z2J<4&JZC9zdg?h-dk)JHEd=5|7>;Q>o6P-&nQ5>p@>xXpvzU%63ap29Jd>$Irs$G` z?Q>RizJk(9-n!?%nA!_wF$jK-iGb$!nd*oi`NoQ+e%slSx1znJ)O#*tKff34tK;%7 zp1yz$$C7rZJ?ytfULJ=BLj((pjG#Ep_{zvjTxn2VdanA?@=`z_pN%AQzs)`_3H?dy zf2k+WSm2mNXOauf_=_A^_svewc7oIpLp@D@$!;8!|R{FQHz}a;=(OwkZyyA zX*szp$82ONCe()swqp-CHNQ{TFPRbjPvg6ojv*Ux*nUUt_XxfvQttZ4C$Xa=7WluF zIGHZa$Bn$j2>Gcx z%!~3!j#;nr0$<74uzdQ>1q{JU!I!=NBnG3eAbioTe*9wbRrc7zrQvAr?;+6Od94K- zU+W=WX0|p2Em;|~wc9%vuFC+L zJ=@9rwX&{${Z&8cfJbda$uoWdT6Tjr!@+ZzQ^#7|!R(Xl&1^T$uk{ucWaOoM24#xZ}k=8IDr z;+Rphc<%Vv!E~r_#+M&i9Cfc zIzGl&A`XR(b-%P>$*Hh4xYTE^NFm+5TM>y~#SoXoiI&3P{C5O=ie zaWtp%TqxSL6Alv662#-%8`JMVg?}Oi6JZwuv$ofmUuducqw{K>W&&_uCbZp zHnV3lpLvJOS2A;2An>jQCA#7{mAe+i0N?= z>(8|Tv(q2NV;~ zcb=co7FjzByGhTMOJ$u0SlP^IH)#)$+%{QYil!D+Tq-iE$5 zo<0X~CZ-^BaQ?G(^}ZOs^u_q&!p%x|4`ISS^=>+EogN?U!gb)fQ6MhV2zC9sKD({=2vE{xa`%b=)(T#9Fx>KeapXvuYRk z+KL!{>XzWAu>uF&f^(;bW17Sp{rK#BEaa6wbWRtl&QLR``gx&{uONr6Bs=d4}O$FJQ77VcL*Z7N`Q zeaOTSdl(|9Ups2z1+Z*uDhmDDF%vKHE03CrxnxLw?J>Xdc(R{utWILO!$YlvkOn+c zKRwUg*X~^S@s(|7An}f>pzDIpMi*Qgwmd)m7f-!~Aw6Ub^yqWvlwZ>KJ@?kQVos6J z1tg@)>dud!`q)LmA4hOfE?09KIF6iV#0Wi>Dp{5&S>E4E6rs@a^&kSz>({l_1A?PITZ z{1oADy#A99jeqQX=e|6|=IE71HC_uZN^V8TokO19Iqa*iOMi9E8+ekBRuEFZP+(Kf zVg#=YDx&o~7sESFBlzH5T2B8w7tVTc5V{J01Yyh4$indx7_xMDoYAlvA#QR^`*ztG zrl}yws!tFwq9?Tk56iffmG}40*_rXR9G81m^O3Ne07}^JPVuK;ADd~!&9M4X8u1yDZ z2TA80;KZ2hiAl>`@u7A}-3_B^*DI7g+A3!%)ZHhvwjSHcZnjc&+r%~*8i2k_>|`>v zsMM|4=GCo4Qgh4m!vl`g#M4jqqC9~0XaU0H1l8%yRY0hBif$L}p@BRN8w>n}>c>PY zr_Y#*oJWf^>;%$?pCbbCP`q&t%u_c`Je|Q}b%QFLSO^IqL^@(0#ZLazw)Lp$7d&l_ ztwhmP81N*gYr`EO7N9T?8UkXn>6FfC_W5!prmrY5{XU5a#Rn8CwD(02H^>~cEwlbh zs7Vi$gC`)wu&^GpeoG0;4c?Ca7Rv=3`)T%A4fcISdr=OScQP1Fu;u~WoqBoS$%Ka` z-VN@_e?{p_?$BGeWAk`lAq}A_`*my{?<<`9lbbAU?WX|(reY{*iy|l>ct`7W)Hv}MNpC20lO>EmE$xezh(OAsmi9$UL{LAI-Q-Qhn(wgnPbQ#EH5v=5 z_8#)Y?MkJVuAaKN3W;lad48(AWmP)NWwQ1%!E(!7c`*H+!Wo;p zZ$;y9_-$UmLMVttcdsbI40|vgDV%{-;pXmoB`(2NwDZb8!aP?V>Kyu)&U163o5Jvp z7j_@HDeM?4!0J?PMC3Jrs9U)QeIizYXz@c0#+RL>3-bqV=kw^*r`B@-PVEzEOPqKiLMviwkAa4t5>4~)6A&w3g9&ty<=|eQj6lYUC8pn+J0sBUAD56PCg782B}_k`nDEO^n#+6`I;cI3ene-zm=XgB z=(6esRgkJ6XjPff@fSPxvqP~~BY!fzYxq0Rj9*_&D4L$z8FB~O<&+a^)@}(IzzWAt z25ib=ibD>20?rE9)65eRaCYlg2ERlVs!BLbmM0pCD)f|~mG7(w-iX$^1@HjtGQt~+ z;USVJP&CHi06jS6OW;6}u+gkll~?E!2?*w0qs5~FQG!r=ocIYHh0ZMV6+^(KD%!q*@CSa8rP2lZC z&tB#m+$AYw9wVLL)}b^+UyVQ!q%Bu$DbZ7)Gx?9<-W!*Md^rYplGS~{mWFt$Gr9IH zZn6HV2HaYJM%O-@;CMj2yHg3!;k6}i=MIq`pp`5II!zU-esn+^0{bf(E>jpIks9jo7mVX$unpctL^MlEjl1LMd3Qr}SeX zq+G>YTD)-}H&NjwZ;_`p^d8rf7P23!y_#NyB=IC6v)b+!g2y`)8Bj0tU`6o)%hcKv z2P|I6XDd5QS%(-9aVz^!SMWwd<%9GnV#SQ9BdC^*&aAoZ8o)=@h!XG zi1AZdYA3_M2v?JpeOyP@KIoS{ZTJd>Te*Yf79pB>9`W&Fio5_}hl*Rdi{V(M9`?&d z0}fZa-IQVZdc;&>p^EL2$NaLR_T`?+WBOc=Uwd3%>hbZ^uRhXKdD5><`(>wFKlV<{ z0K7$SKn|W1q~YyN{XUu&y#5NvYsu9iMXN;#Z-G_22DcCcfi3g&l_jwl$O$BlX$1%D zYS-_d&?Y-!IHHCPfeTv*`aBp}r_3Hmj0i82bE@;j>0BGVA+Wna0CPbdj5~fMgas@MpUv;;U?-kXDQySe%w`L5%|_}&@;hy zCeVIYgU4-s-~|}|*(06_cQ9eG7AsbegV9~~GzLD#crd`rwt@?o+U*$gztu=Dc&Cw` z>^%p%Odok3yM{6@Y`}-04wC98T2f-U@Fy?-=$LK*UQs|^??`C$8Zlc4b}=jqi5Yi% z1W}iVJ|LPnk_62b7ODwyI`&hX#%|IJACY1z_lb1QR58Je!^g)nLZb-FRwi^rhPKwU zoys95vs)6CW1{gJjDX}ho&CkaHpIo*Ya)0+3P&hLtTRGhgtY?H1_M>xf2o2-Y^tF! zl9aNwy(qk!aR?97-9>j`cdL6deKmz4#@tUgOAdjA?dimuK2eQ;)7i3Q0q_;!A zk8Z|SFF5@>GEiLyPs2bd9b*g$1GNiC*&a}Ceh~xZ`WbDu^ZR55N^u);87=}omXY^l z6_mw5bv>>Ov{|}cIk7O%u+qUGrFd4R+$Y!_arbgeTP7q7RL5>c&Qb=dfhxZR+zBEh zXZ{HW%0$yKP|K5qRcjt;YKDPEBp)@2b`lB7a#&%M7Q&qxKfyp1un7ig=N4kTOt3>f12Tu%;HF>1`Qk+$Ma4W$(fd#G`*J2KwuZN&|<5UY*_{{mQFO zdN+q79hVbes@P7u=;lNmA`{nA)UB6WIxV1P-c+BEk}Rd5&l4Vr7Xp#of|0d)3<2!A zM|bHs$(E;hyG})?eKs;%bdu4&)kahRnU@&tPiT^9NBa(qn|`!Eh{&wbzDLr&!)V`b zD+@+@pZb5tqaDN&OcHxZkQ1X{S3f`-IK=-y+I0zIg&ybMceHap#b{q_9oDWZQ}G_->K-+-8y~u5bfKRr)d0lN1tMgM`^|$ zsOf_*cx(n?e!oN9^;3_KxTAoImt`2EXur%CeER+QOsCtN=ml6%MH*u>)Y+IUH7)UM7lv?O# zZMpW#Q*-+B@zq)&=CTSU4W$|-hDk&q-LF+=lMmhwQtBYLFF6>!|159hlIWmr0>&?! z53X#QFsM)|COH?W;onTjQ1lIcBoV=jy^eidQ6E&ahKexda(GlYx?e==%e+hOv~X6% zK8Egs7r^uViOE1rE8)_#!)**~LUO~_XE!S0=YpKn5jmlDQio+>!rsDLFb}ek7o@}PAoj# zZ2`5Do1ja#X-7}2Gr7!|Zk-tr!~zsJX^qAVXaG0rg2%nGH{2$#JA=$wOy?rW^HS7q zNr*#aG6X?;g;u9qX9ln%=k(MbwBcVv?G~&_)NUclP`d?S618icSYx6&vO8wVHk-Y6 zs?<&Rd4gKItkgBAT~_L1+3vwBBwk0cTEn}K*!W}Ce;nWOvCI}2>X~ZjJY(Gab|i(2;mE^r z*wTWtKaAWeoUNUK2dJ3>yJb*~7|XoC+R)8JD7dCDg5M>DVQ!5fe^Ocm;f0vPhi#~O z`8)4{W>|1i%UhVH_d)&+Y{f6PO?w`p17{_@+v&h@$-oYYdfN2tlBglm`HUoPk zYSi>RAyG$6?>>ndGXqac)KSwjBvHpq?_r5LZU#mr>V)YzB2mA|!AJ&n-WWB#NBKK& zOv(f7$E7^Leu56deo~?Uds?CZ`)P>+>}Mnju%DGEzWK*mds4EQq`i{rS;D}| zCsNzyGUenR;sDymgROV!CK5Y|ff+`5j5WE>7)>(gp;pj$Oggm#nU14`aeD+K>`jKo zgdnkU!bq}X=rl$TqPp!k36!Duj6<*`fSVvh1(d*48G$k6HCe+)UPKf#*C`n#7!0{s zuSm&pfRgG~!9ZW|LJUWQSba^&t55VbD9Eyzj1=LNRDy|P_F=qaY`7I2Lb_0)sa_Op zD(l!u_L3Sz?nawMU&JyGtOGNABzT?$*t7fN`s5_!>kKOr0W6Tz$!S$Or;cZ0g&`z5`og>VBT~6D#08_MIQUoZ3dFfwbRE9XfV|elpn9bH|?l z@pGvo_$4wwIpBmgqV=OdsH~x=xjVoC$cQ0CklXjj zKm_0Jm4R3`W_mdOm?szTFe%vGC=f`NKOln?99(*X41PeyCRlloj7?}@^m1%6zg>>@ z9)^My{PxXGr)P)?SJxcOC}}Xxl8!8M?x@Ku&@#N6#J4slHM{uA_d52m4tsLJ+%e*3 zb`9d_TLK@y752Lv-%~frqG==6;d1CAsx+Ci5f(l}J69EkgJTgE)5pRC07bQ$p<8X? zkKikG3tCI)D`%_Sg)lfEqM z8AhZQ+HaBl&c&BY?wEZam8cVL&)-oj4kd5rF4E8V?&=ljHdY`5Pa?$`P(}w`oLz49??>D`p64hr0j!4vlxIHdWTQU1d)FYSx zC2Bh+K#AJnQ!sk0^$#YfI9q2+Ucw?_urU}`nWmgTZd`fb??9R*$xAb)=QQ26DQD=m zO*t!Rv?=H4W>YTE!KRR%OH(+-NmIgf)S@X7i9%Bxi9%CyB??W+lPEMLU!s2Frr0U= z7oeMt14G>P!Ua5l)HWyCBkc?0CIWcMfy0P`+-WMp!lfvm#xY+wH$y($XJlRX>3x3f z(hHo8BO!pA5Pa+U^F5-(2E{{ZOv$zN8t-_DH%}(l4EoNgW4vW<3 zK*&@f4eO^=0zE+#A``v2OzM(?WmAoy?9qWGLz=a5I5Pu>vQVRa*T{}HMzz=^t@gXl ze%Iq`H{DLaDde2$!O=N*JvMJubb=EClbBR6ms!Dy`j@U%Sa(Fzv)c;vx|$f03dD+* z+f5!Kn`0BJ3`qSf;J73U78bA{3V-%__avUa9gzV~x2#z>`o>|70ZJk)lBtx7)3<{@ zd(gd$Zeivbp7j`l>WF}1*$cF~!FRY>y1{1ri9ZcHcPX__ z4X=M}{!0rV=56#1D^JbD#ZLbu+-A|k_truf1W#O1W{4 z@^KaS!nGxGd4B!h*!$*L_tV%iUM0=PyJ=ngS-s zz7ffrXu~mBjHrNZu@KBsT_Ft+(E9>SD)tfiEU)=B!R`y}uKHpJ@X5}pWaO=Bc zox`D$L+j6?wdYFSXupz|)f@UQm<4jF9)=`Z`!2u^#gv%`N~;WncfR0!eX^+om*Xf~@ng4?@SbZl^PTHh5|^M{o~4AkvG=U;7)D zf89HlUmEH>H)F%)6{MH7Fy`VN7|+=8Vf|HNrXv~pX98GXvcPGD@i^EMm}wUMGP&TF z{-T-YPZ3}fAinJc>>j|NMaBfdpS2dPwD)(>x3E8ue=fP`oS*MtyksY9J8@&lnN}Bt z`JrFJJcy}zH8i8nREGd@0M zFBMM=5w)qXDl7xV=e>!Q>CUe6v@J1}0Hr=Mm#OuU$y~BN;XqmSi9e{1rJ3vVSEidj zkNbbu7pzRz!(r=|`goCgf(7K8uM19Ag{Q9%2CXDMt3LX*aV#pkSui{EDx^n(P?twb zQ=$55+}>tKEI`r?t)^Em6h37(^FpEN$i*)%_Ex{P@fdQ=YFozce62%0i_8|bt@DkU zt+PAdnBC@>zQF%c_;Kf6t#FCeMye{+4GMFwuNhk*y6YBEC>e3eJ$zf|%}t6SU%dm{04(JpeV zklz@Q|8Rtd%bgjL75yia0(%FI;r3hWyI*R(A{p9-=D~VQJpCg)#R6t$&YhYGn+SEG z`9t9GSD6>k!1tI&m`3UJuL5DmXx!(}`j@A$zQ2*&0qgTOL|83q&+6dm;9aNL{x$Ia z@oxq1_@ja-%c`e^7x=0Ym>WYdh7|T!T_|}odjG>f*M_!*8^Y}^FwAgeGPFNH;3HCG z7&G{(%SOt=3=YuvA&4w}lM#905{SHI5@5(Dh+GlzH-ybIB|o8|p@`+bD#=6PT?91y}7b`NE?>Dt$&Nq8A`5rLvpO2gJ z6-D2eu{l0(b9{+k5%aNrtXQ-;?)eope#LUXB0gbSDI@PgTCQ~jk(Cj@97gMM>wSzB z+}$tFH5&@S8{fR==Jvl>*jUv5=kte3-rR5nnC0bW#axrS(a-JX80Elu#gxxgE;^4~ z^rv5zHv5fphs)=Ii_P^Li%Q;X&re2Vum|gf^0~7Pw$AkPz!BH|(r+v@8;Vg~V|&BG z#zm8>i(OP*OsiYM>i#OIE;d}g1l7g-#zm;^1GYL-9-CB8vDvU3_1x6HdSPR{y;PcY z73N=USv&?A%1i57g|{j3tq5Z zSPVJ!0=|WDvos7#hIkIdc3M+=BRlO{Y2An9zNohE?R&21OVjq;`(};L!XvtKXN^dI zjr?}~^~`o#$-jMnc|ldbet&_zsGOkur6!OMwEr(IZ#hT+B$h>wKl|cE?JEXJP97r% zfdnh3f_v8|abWtYVC(Viqqj>T{z8CR5$MD4_m=x!d^iTaAlp@|VIWvW|7yj2;0@NZ zyj+!K{_`UH#NZcP;$;?6zp;HLa+;TvIEn|^UdZgaKA2&gmDmdrRK(NsA;Z!LkC-bh zc^>$7&CHcpC`v$8`u!STEa+t!?5Io{daBa@^oW+x!)`^NU0FQHekRX6s!{$RqlSAf z(fw+CN^_EcnDYxkPLk3k=?X^+06}w-gqgD`$jPYT9tKZC*=m7GbEa+1KL~Pid|Yy{ zU?(5Znv?srtn)Oia&hfohu_peXve48lQ!RNyz%9`cb`|w*Gds0Z(@b;TBj_)(0grH zzSc<_&okQIo0K64%c$wSYu*~~{5uY1-1yr$8G%W;qZkLU^nPHXNnvb zO>Rb@2;Vv%)Ar>@1b7JF7btuaUpOsbA+n75FiaFs{(i3!?l5I7^S`tQAg1Ma9FVL; z$B(`s=opdSk@9LU1U)ezJmvsFU9GUHF|%Z7BT9itFfI)^^)8%{KA(Q(&GX~7k!`N| zAHT5JpLnQ?07pA5}^7F!V21vKM(FuD9)|57#oS-2lOSX#v`#TQke7kjS=ct`x! z{zVpkUq@ZP6a0P)>mG2k-ywe6rNq$h0Kb3n2KJC(+77QF5RdU}v}IXsCw+2#(0c+% z!J!m>>q%l<(M?_{M3a;Zr>+*B-YO_C*{g*CmLepUFAIkE7@3!Zz%mOwtQo`(q7oPf zs*JbwQXUY%k;;EMrv+p7zzGE1h@*3SpfSSVc9;t0YGS5eJTCBev`2DU-)C;fuTQV& z!j8=i1@-ANIAEkv*MI@9<~P%R4mzA<;g`*)ExwCJ*tG3`Xqxu3l*#Rv4Ige6Olm(v zwf$s{+I}ey?PnbS>69Dr$pZuvtEgU9VFs8;TR$6XDL*_e4ah8}w0$`Gro3_9Jn%Hi zGhZa`Afc9|qW6=_MSFMPci|U@=uQItIoJdFVAdW;3U5Z4%5y^KXURx7!gI_+EDh6j z?HO!|{JyC?EApuDlZVJAP42zEYiiHf7XL34VKy-WE&fP{JVTn(8i}Xx3yKeoNHkL9 z;5m!oO$2U+FJZnwXAYJ;-=1e4U?IB5pEDv@f}FNK?SE04heL!l=7sq$akF#7NBhhJ zCs|(TVIx5Kp)c^50V%E>A1gVw{)E}YT*GDKFjY8X!}}}ZYjcv7IC6eqAA-{#-vzNu z)63@Rd$e*Njm_Wz(T>r&d3N!)Wa6gwEc>G_;;+DNldQWM3+KOB^22*px35}QSJb{3 zBK!|G<_D?OWTO=!?A7X(g)x^xyYGR1U?bxs+hxO!4 z)T2T?RxWjmP)}Ym0%<|^PlI)rqwBl6V$--BwcXObW?`MKyE3)aTvS_)*5*lVpMNmZ zDwX5wYW%u5YWtwAP3_^7>*6b@Z@IZ4&(w#p6Z*FzuzVQwhhrPaJu5?4H=V(*H{6?~ z6K=?h*H@Vbj}Bs1C)^fF$_D$B3RG``=~#gKcu_2C8MoZ6^jhj`kt!v zUH?N714)<}asFh*EAjeLh2K+*C_h<%W6XsztwwvFoSl<{1N(DmqgiyH*+jy}YC`NG z`ui{7qzl5mn3;!_;Aq!hA~`IJM!Rl?B>N35>5wh{LXvWbpZ&({mfXmIyAG(7$LBj>DbM0$(PB7!h;E6vK!WFpQYYLojS4&VlYL^jBdi@q-8u{ZXH3E8>`RZvC%?oAxK3CFa72s%z<#qp#7$pfDKTh6n| z+j0Ry(AUvxo{9F{&9eaM4Fs2MM+dP(sBYU(5rV6B7hE*1=E(DC24rg0UG)`3tOTkN z3!{ETP=;{qGnO<9M>}BpGH|s1+tGUH2q%W4^|U;aibH>Zm#%(_@8Npjb?*s*OSKf? z7hPTA`z*pSPJ4xly9?nlLf4#o_kt3$MGt>}KH^JWh{_HxL|jZx`;kUA=oj^Iu!Q~Y zBBVL>?h*Mi(hP7Y$}C)jckUTW`&yWvTysDMB&^XdG*Y&mqJCy%gz1dT6^+cuui*JR zaNiM$lai&hU{K2Oi+_L((JHl&WT+42giVrd!-GrG730!IpbvfFsiv}M3KJtaNQW5O zE%UlhD%!eLXWQvkJr6GyPWDsrlW_XxI72wih?8C zNTm&asa3(RDuSrm2mMs?NZA zJprm`yt=R~uf4?7gv8H1&Kv0DGQg*#9ic#h-&eOv`vtVEn}&9b0{)Z30A~+PsCe zk!bHX0HfGbT~UOcQXFjDh2b^}Yy(Fce>vquBkz9U5nQ93)f0`grmj#}L*_(dZuaT~ zCmLrVR-r^_G0YP#aMCCjD4&apA>ZW%o8TLY%!)!J#(O0);Bll8+2V(#wKFyp&iVl$ zE&;^%SEJJ{{JGiiV+*l(I{0%fkE>35Qg@i}M-={usVklge{2f;5eRAuKV&;B+zSKv zV;T5$SA7HPH!EVgO2@l;cne_y{$dM%?j-mxWW$er(&cXt|JM65_@97(`DFNOE{4BG z;fJ&c_}54oI8my}z^_~N8(6@x8NPSc+_zmHy__6X~Dwg;d@yy(w=%<1VLZN3cYk zV}om$IW*GV$88))tzwEEcB5f9AFQf8f26!X8V(&LYI3}ou?p#z&r8?;Gq@OHibVLG zWBM>D!n`>q*f~;y>4U7DlU%U|<+8LE$&Va;7#V@x`xi5Nl5%);w9GXoA-jb6 z_zWQFgI@v^<=*OxT9%EsY2Le05D!L&hjOA<<8{VKI5r0nmUYCB^*Xphv}lG+I@O<= zk(C%7B=M1G5mvAcGbSYCL>i5scu#FAeACPSP<6wqy7MWnAaYt8(SlOSJ{yPN1%k`Y zAY|iV@pz>*3^~gXu4s|06mPJe!4lDD*Or~byo9we9x0>m0^VGKmYI*qCYT5ij~JFq z;Xoc%#Dj{N=nN@}1MRV3MZ54s4RSfy1d=BhIQNS_?M*Fkd0}u^%-^H<8^a%F>jbYU zE^CE@vwPJ#IH&;oFsTV(;{u{<-Oj@#D1^ZVo#S)QfN?gAcN}e)v#g0Ft-(*C8N~$^ zp;F2~e8$qDI4TuV8r2H7YvB|iHttY2Qo7ddY}}<`wDM>BM&7J(f;6?Pl z@dRp_fM471ODLOYO7kWnfiT3QCBl?S3#W`#;xkp6^;jtUrgZM4{3A1JK9v1l;?(z_ zG7kl@F#JP(nfC>llkWq4?UhS22xe zn*%9TGEG0;J5LvwYaMevDAd_QdWaUqooJ#e7A*oxSr$idti`MZpJ|#GO?(Q$UO_bR z$vAFcM~C0|T6Fj|u(q}{Fme+e{$*`+_(jzBR@8e#VE`zvMu%SoDF0JI`5wyKOlo72 zMEe2IWhbIVZ6^@kK{BKzKenaeIT){v4j=s*p83VK+34^E)Oix6 z{Q#D5U_CQB{4><~W_0*XgujIF|4s9xh)UF+k<2OF0*+o?iNaxP@b85)9~zwjGd$5+ zm@!R7YmPW6qnK3W4`?T?~$$MH;jOSI?<9Rid$FWN91tvML2 zMTvC>qcw-34Zn!iem`1wC|V@rjCOXx9ikpEdNr{M+q!sX+*=lpUX8z^Q5u=|u0tgL ziYnp~c^x9Ji(5VzY-tPqV%M8J6>q`MRt{Doi1@_!CUjP!0FGt{j@x1ol;PRTPK6(q zJQ}cqbv!bq1jiP*p3_9mbQ1@a_;Tsvu*IU+q20#BZ5*%STL9l$#z{)lllT-X1fSG$ z3*q@>rraXN2jzY$1|RJRYe~iMt+nN{v`iUWrK^j;X1!59kK)483A+(h93tLNp;l+B z(8)E=-|-%?gTe=Soaco&+=>>$_mqD8NuoT^rEEk-)a0KXVQrZorgaP3$H{J8va5^L~7yOz#P z%}m~83zR$VYm?MJxmQI`qbgxC4aF~FLG#P@aR_w(Ab z&KGRscyR+JrSRhwN4x$;66que0!F@SwvGevI1E5ez$7TEIL*qhU|={=jS+S}I(!~H0Yf$MNrJy! zh%Cdmls^fd1mD7==A+T!QKUZ;9X5nh*Z-^JvY9Xze6^Q+EQp7ZWTe7K3e|s8nc)*!OB(;nM?Uq5ej0g~>g3;$z;YVdonx4rL zJTaO?x}^~gkwPiq(mbw=N3xp)1_hDm(%}f}r*ONBLy!_JL)>M*F@yPkl)ki2v^n6no0o!+F1ptzSuqFV{`S!Lv1nklUhy;9%^B%%( zTYwZq=$E%^z)b}R4W{8lAv|BmuBiwCU(T*+9s|e(gPMvF@U@AWSa_y9Sr&;c?CGZD zMAIodM3yym?h7WUWyETqot?JY*=ci!LR*!K(i50^f1rN!7-v}$M!ZEy#%o0wTWIki zQw%4rigf^)DP`4<9u5#jLb72(OPjSduKhX{>_9RaC%y(hUNb5f#h*<2B)~EJao}*6 zWd_dH?E<2V9qV?2PUHyMY_ZBgWW!wvd*``&89Sj0J();guKs&P1tGHNAYj*7bbONN zAd{#@^M!TXr@iOeuXCT0hOiCm7S8c`iw66ByMe`-QwG+u8aM-Dx6!EW^`jtgwrhflFYU)okMT_jzzbt#| z@9PjmR5?*Q^~*k4^lJSSm8t-s=(<`zRt&gTP;#|?iXa|P7`Rt3c(r^os}QsCX;gCC z(B-#Wl{0(wul{n8F9DeaDf#L2Nw8+wcpe%LId?8JV+UVx<{yF=oqOjW(i@(CsPnvY z=ZEPI(H*+;eRR*Hd*+>U%-Hu{nSW^hi?6?G4rMRPr^$)LSx=k>_#vuc4KFEmxfKDIxr7WwhHrYh#g=b5S{etdzcs_^5*rfRv* zThxxk#Y<*>Jl~n67cZz>hbXElb3)#%!q&N3+?oTpYI>0l|;A7^&Wmw;HW5zRqkR zKK%H4lp`SQL7gZ{+)s$RLfrery${kRpmbmj3UpFais!9?m-6wx)8KlAvrfIr3jSv1l?Z1EqwW!UsQ zqv)g$Dl%t%yYR*~=oC!iCbFy@kdpI~TY)Lbt!Tzi>~^|Z_|wXtbqrgNpAKS)Ut)+~ zVuxR1hTkFkB}V8cHu#-{3Eg)JelcFL!+oqWk~$&xho&D-GMJ)W3xQX>^4i_UilSYA z2ruy34CFaCh48Zwe?*(S@#4sd>0!0H$jkeqj0FM{rRV|m!5s1#1RTZR>j)D=bii)n zvq-_YUqfKI5mxo&JvEwCOWei$h;_CrLSzEptVoebt4|TbnT*DUW&V;g-_r>AHUfT$ z?=UbU8`L3Y0z zr;t^^h#fMeVITKxg%mT4(sls(Wo;oeJR?+@FQCeNk(pCvDsc>2n}>svN;&Vu7iXaH z{FON2;E5qq)fQelVZlT~xf`BW9|WX+A-D62^m@mU5nvWef6p58NX?HQ}<`zsr-xKnT)S#;DP>WTNsuHpsHH(YJ0I6R&1KFS!K@3Kl*OH zNul{Jl$CL-e%3FanhOM&bHCcT@epx7n?KjL{m35T1>1m@dX5~K3V+aFCy;B_rQPeiTrZ{?OzCwETEF_Y(Op#ounv zQg$=1b{-gG`Pdm$7S}^?F~rMb7}M6HPj&TJUTnWkc?FX7SjKF>PI;VxLsEuozfO4# z11+nzU#Hw-pyk{4>&SEMkXQ8G_UOk)`ToXVC%}GsA4Hi!0BVNi*sOs6#(?cv0e`>% zN+>~=4={kDiVcVwDkmZE`UsTd@EzC=Ki{?okNaK~8eh9zbzBBmOtN-6!>OQCyMw7& z6jCL=SI3}T_$hlVebo=%fTs2y14Dkb`+TUCJq@i<1CwlZxW{+cUxD51BYt_rl#TjO z`oN3N(1I&F>em(U9p@GKeCN5m(7^T~R3Tslv3#B>I}J@y1KWrz7Wj3=oriP%@Ztmvqu-|r1m`Jgm&1QP@KZAmel)|`I8WV}%02NHtDmBQ_$j_3Eu;c8f*B~rB7m*B`2<7L zr=n~J-85O`)}n4SD~Ud6zhM5mzb6)fLCEPuq} zk5UfyZHo@$OIcD5Mz~unZz?gsLlQg>DIJM28ffPSx|i(jLU+omYiVnDX@Bht?N2Lq(M+D2s|+O&DU z-*cat`~j@?*WLHe=Y>z6d7k_C+;h)8_ug~QJqItp#*57q#KiNLQ9&5x@gpQ#FJgZO z$E5Un2wc;w2AelmhVWSAG)iALN`GyX!sxBJ9>NUv!XMGL$Pix6pjIq`%`f5|DD*TS zfU)K(#!`q{!H|=9`8bk7akUq(^KHP&k&-kUI)uO^VE71$6HNSaxq8bi7|`!V;j%3e zT~YsFbO&cQ?GyI&=ZNa_NL`2YFzz@XsM!NuUE0hv^9cd>Q)U`ouEHz@Ij`cNgq#PaA0cKa165GeYF1Oik&7hfcWy9n&c9Q`Fgp{3>mWCy-+I#h*ri!`ND4uo)ma;*=b zn0$B=C$I}pnx}EA9)y!tg46SxGqt@N&9>LxT*O^$JyIz1lL#ry%X+mNEii*%A4CNx z4*V8gJ$cXo{=}J(?pxtKRt5VZIlwDSu`zA7TFd0%8%$2Xz?zvn$H=^llh8;mnW0YH zd906s-kW;oFkc);9q+2I$y@|kz{B#gPbQ_csn$N3l-6QW^7mK`XJQ2*73r~R67y2z zxK&fDBE42kor02N4dx|(kEEzpj9VdbD<$r3wbngmUb@Vle7R)#Dm!x>BsSn!;K`hX zO5Zd}-@@0bQ+Pz;(h4g_&p~?X8JTr#c4*n`aEHwfAF*eh^WrNKyf=TYOdly`kIX}qSt)X#?}!d6Nm^usF5g0d(cK2 z4GI#8584R(nmtvc@kuS}N5>)WQ5$(cxcME8Jdbddui4nas1(e7$Hrz7TVJ!WMf9?} zh_wqLEJ92ITQ_m?05g1am0bl`IjV!TDA@d6VQcolHl0`2` z>~`Dpru3QlZM*S_^fuZo-8P-i#z8V4JOA4@B5GNJ8)`D55s5#dZZa}4%ZB}@ zv^NhJg#0)+gaheT0|$2GxbAJliEs(@4vrp<;30M*Nv~I=9VH*E zJZeQiNFI@bCZ@(8kDD5OW59(&ZW}-$GF0gCp=6Moo*F4p3@>GqK983gRKodDJ77Q~ z!|TvU`C5%MTmXdLL0XwH$uaN|dI4K+Op0gDn-tH7Bq*?57!sM1vr#x#m0`}p=E`)u zU5hF>_UL9c%;{LZD9j62XW)xneJ6s$D>L;M<3)+%7L-;FjhkaBXs|6FH)XctBq`IK z9Gi!q1KS5=;=RZtT3`6M1z>nY{z=VuUI?%!#qsi!i_MQKIX{|edFq{QeVW+kXRFmv zVRIX$zrFLY4l7@HJ)jhi>E?XYi0`?MPR9%C$DGI*a~!bDN88KuF?sY@kuz#vBC1z* zXPMSbs^K_HEUAWGSYtw-RVP)$N!X5JPN1+{WWurvm=BDVLsZz1=mT>;uvVUzLsP;F zW^sNxZq)&|pb`{^0`%Rrx{+e*+-d&gXQI6JW@A3XacnfypvFb=mTXG||%jOEZH779l+1#oR1r6XY zX4AuNn>&y>ky>`!3}Khe;W56&aos+fDNwu$7|eY(Kfp|Fw%YsvK$wj-FX%uz#D!3o z*=RF-LP~??W~0sE(Kt`VK8EfWjY37rht5 zi{6XPLb=66YZWm%e<2>runtG|@Dw?H4qvGn2Jr=`xUw&2E7s)U1G7Cn?or8&+K1A4 zT%yQfi(VC1^tKEe&6<47r)<>z3sGy7_8|M)=pivcE*n)Y_5X!}742l>cJ) z!UGy(o(LLjK4qhvsFkcXs<|nXZyVL`AuZSF+<}=7jPP8OwtRJ<-D!Rl>*2$Ez(zfF z=lw+e12*bq%XvQ5$Dtg1GL3TV$u!DC@&c5Xs|JuPR#%=?11x+9H&^aO1$D%HfE`hT zEg3}Z;YxG}Ng7pZ8omjLhH@tHUg^SvW1t-Cuds{=wnhVgGkb^4pzl&{pu%fVt}yq9 zt7WgRkHCUZ4E=di!ytruxR8w%E@`1KfX5~jZ-#EgV?B0g-HCz=Lt^Q@S5G(}L%}a2 zs$ocP;0;YDe?l<(r<`$=hMB=31O*v{xg{)Gbj1Xbk>%j%0-o*-PCn9CKz!%{4QvRS z^Y>;T;s(q}phC~SWgb6dlp)Csp_6Wt1eC5vjk>dkPZr?8s;8;KmtJn^=t!( zz`PkrvxV!-wL{5xEOAVu`B>dN)zC)HbO@YDHFR5bF4b@Z@*1PG52f7R#^}0qpgl3eUlKp4m&^gy1ZpUi{g3j!$TaQ2YFHDsrwRar{rsmK}8jl4FvPX+h!z&Q8>Pm3Wf#!+$b0M3coA2i!EO0zc= z)z}(V=C>QsSE##=SSOj0wr5NS;A z_06A=ci=aagGWukW&uy&Lzo+i^6%$IHioxEhDEIez&v21iPOo0M4_IgNW$OJm=y88 z`LK=aialD#v9QJ{mG-m$?Hv_036gF8GO!x@GMGL~F0KU#f?}#1YZPEG8n$h{!J!k%@vU&?I)9c{6Ynz8!5!?A|>LF>k&Vm68|m#k@I$FK`(TUw}tE zIFPTPIK&_gVntC`ts0)N>Q<#8)b<` zsap6B7g`ER3cXZ2E&3B~Wujke!&9Vu9|9bfo`Ld2sz!-g_!X#y4Bh1Ch;@fxb8ox_}__H zFdx@WD3a|t?M4ZhD=QaccIrm5Mor(M#MgrYOct>IQXo}S{+OPt0*|dgIkG)TXteCn zQaaC`?Z65W%C;r3BatKWi|s#nekA8N3*34Kg0mkjGQ&+}wbitjfg;9obU5C#WoN-5 z=ufBzd3bi`F?NWfWJ!`M-UhCS^B^>=pdNu}Y{5KzYY>t@NN(2DP;#>dGPSt11~RpN zYfU``7iUq51dB;KYC&J|U`LiMYEufI>ncJg@=r@XNzEPYCkHA5qQDq zv|e55GKL37!6g zefTJ6gcqSHwLA@8W~|_uvmK_oV%;=}A63pmvs{}JTj&FrhBgbbk$JaGnq_F@{H3B5#H@PDX3h>D?BkdIotg{k$A+d4QIDX4Mdmu-98VDhgPzR8@4V6}VLug)EGsqH-%xu8OLuYg)9z3an5?E3LpvRdlx%xLXy~aJx`c z%k4r@9k&Zb^`chVh(5M8Tjh?9R&G^t$5|`1svVsCEnA&+yS($McJQdql8Xdt2lZ*J z?R=^o-;j4c)sEfr&ZpWzj$~~Y_PB#*W7c*#A=^PZvxM4|9I;y4$vD-HCnTItwPU}$ z^Qm?mkas@Sjy8GcQ|&k;?|iBq-SW<-+HpkQ`Ly*$M0IzpN|)$ExoM5pJ7`3IiBY3o z)}#-eL$l=BK3MI@dMB$Zdh18h)u0-V&hI1F`T-N>yB(csrs?Rg%K8-S%a)x|A(vGa zS1>eN)~{e|wroJb+-%ty6-u+p232ULRW_tT=};1b!PzpW3T0YlE(QCuWo{M9w#rNu znroG%Dp;T`OH-kFR@qDigS2JoDzwll%TS?0t1MH&ByHI&6$)5o*$NhF%jT+3$STWG zp>nHio(ff4WqB&J!YW&+LMyGZLKV8(DqEsLHC9*z zo`>})3sk$L%jNo|+9h57=vE15XIpaVK<$z)fAk>ZrE^@&o;~zQH9Q2J@Ps}TJU6~CJR|L; zKOgPUXK>+%Y40nN`opw$kIr$i_GJ%+-db>7eVQ~vsJ}Abzdh_P z6$4RG57|3lY$fY4wmpa^2-1jJcp2Y;|C%pDW{l-u>A=4o=|I|$rLj{FbT>xnEq{Ri zTQ8+MFVuh5TIZOSV?5*Xfxz!7Rrf+(JAjF-h1{?NGG$kE?ia1d5Deq_hA?HNV9a42#D)!PomvCKiID^J*-3HK%ZLN61+>V1>m{-9L zl-&}W{ha~(qdSlxwG0}jFy*}o4A3ZwU%|2^f~E}@X*TE|>|BHvEpuCoT;86BO!c;469>8;icX0#||6P*_v66ccGRWaafhE+!uH9CzFcs@D7S<`ZB;eCPN`8z? z+K%;+B3m!yfJ5SA}6Cdw=ftv1=hy|*2iQCy@h;I=iG`n=1fcEWK`!C1|ugw ztj8MV*^H+us6{sTP^)8}Q#WcjD{DIo>fj@%)~dYQs;p53)jH7%Ci<5|qUz3q6-ZQV zRfep}avBJfk^)ThXd=;)&Vm3EEwL&WT9t*gf+&gRF;OIuD5tYv9umRTO14!wm!=*i zQ6>`w5{WW83o?-i?p9`6mFcR$)QM7=C@Yc3q#^D~mkg6sxMZPKsPW$8Y<{ih)4~$9 zcA*s+z>+}b(^BwSS9IRzu>u|8R}DknuB7h;nSa=KoPWNPYmbo8^XbA1&!*D1~D_U4q(R+Ya+7aCyrT>)yhum=7~{rY(hBy95?NhZihV{tVa= zwd7ig3Z~PEZOoUAA7{)r3s+VG$~Ut}`C(WzD7H88?kW!8YS#1d*w4Z0;Pr$IezaY{Cjsv7R!aJ2qV4!g2{h$zhg-N!JbM z6OhCN;`OM4NGdUl94{AR&2x1-0-)o4i%T1=OA1N3c^~zmmm?R^C*Rz^5-0)vy5Q&B z0qmppZK-&HXcOjxt!GxCaxBE+U!c9<;7~Ebd;{_8_*F_bs=fis->=syTDcBqTF+Tx zk=TmTd^Lqe{Zm2V`+L~h$Q3-8dX)j%h@{c{0zaNjL!~KQz8>~amv2AQ8r%LCqdHBh z-^s1BM%uVDt}6%__iaEFTwTDS92D1=rCYw^G@<gxoE2I0IrpBor&a}$XG6h-%Zdk=<@Y4!1A9&zN^*x3Au^x;KV0<#Rqq70mb;J$+v5k_~`0FA}bU?cuc6-RfL%jZs7a4*I^H88IJlHwvg zAG6PVC#`*)4dbyB=%q#AfF~`nAgQEV?2a&+Iagv_HMYrbC=W*so|Zfm&cH)<;H_$> z4)U}tw4=K1sF1RQJS~NY!j%LS<%_km4WOZr@9AGp%MygDoqSp?w1$niAMw|U(&jb3 zp5(8!lXk98;|RA6hi*n@9Qt>vg*21|nPV2osht&SZIcg=7?nm~s|_->96VxuoPjp5 zg#b34P}mV3F=eiHW(rKU5&VSTcQIM6jbU5SCp3iO3*OqfUw?^5cuV}jpT2~)w`iEF zH|9Hq)953p_KuKoT_4_|5nqlYH_^L+k`&%5a8Ls6U}MY2LCH-GJs@&Ye$3YfS+LaLxBR>p3!aJP2V#fcc zb3G)UefuiE5uPyNAJe?70D8XdLf;>AH(nul57}pv5OYlBWTts8# zdj?TFFUp!F3uD2)*e}qkW3frKmn6ViM;$>X2&0W5oIQDVt|Uk;e1dLbR37=Hz9HiA z?T*D?e=EkzQMe(cm11c&68awDr99?>nt@pYEjjj!e-HcRafCi+l%fm6IOg?KY-h|v zjM;UXRo{`PFXkbKuKjMQolnGmmKDd|8(S-DgLWs>({Vf)E?5ZP0W1_Xd2UCY<{fAa zK-H;uc!shGna9t{eXP7<7y5u5yF0$_*+fs?EpHOMJzkXz4&p85+szI(W4^~VCGF9a zG`fS=-E{HJ^_{}H=)(QGo82_w9P8MAvODH`NLR4yA{C4wf|expNCicpnmCB4Kk9EJ z36x;f6%B633=`iC!2|{qIm&YP+4*_t`Vvax#@ZBpi7_T?ckH%o8pEP4x@9+pMr;dL z@E?fSrqXt_6mQnv65FyJ`NkDUW*n1kgkCNZE$>gjPfT6~9s=eGnKemte3P*Ijm!3eQ_8Xp4z+0uF*$+v{(~+Iyw2*kdd* zw&ig=d!LIv#^0EL^ZIkK!!fA{KtQRW1Z4n(e`E@Rq(RpBTDp*SWO=GDYS^cv+t@=u z!S9grr@Q6d^P@gN7|!a|PQF?mB7_QcZ!Oh+IBS}3ByjZyE3#u+Kz zI;JKw_+gPv)ktnUmdk`({YO`HiuAviq(H;OH$3 z)Fb8fcQs$4;(j0M$E_f6GtpZEoA6wNKQR-M?}|75jAabnH6gwMZ!&7ZjN#mNkmM*M z)m|cCJVkwt98x9Z$bn)C`7+@22V?2*h<`@W!1cS-CQqnWm`<$m3nRFW`-^Nyk@PqY_E zinZ{GR_jr$c_=2ng2G;;HGSaW;r3XcrkviT3pL3g{RG6?bRDJ<22&KPL{-AW-Zh2h zkh>u7&HHRHH~^O-Es2`MEh@s%dD3I@Ojh5@izq3El!MpJGp*wiNfOPQa&J83=v^zIXm;0A=?yr6i z;BIlN{JcG8t!qzSt+TVBc7pQlhvrY-=FT=W&+_fZ0^NHb*D=P{N78 z?*G6+P?l@~;qIdp`)EtdV|cqaesr`w{(1N1b=}{G`p{3qaL)0&X#RxU0=E$*b#co( zA~8+B7Hcc+;YN8YlRez^P(4rX;dtj@)E@5YH_qF`4Pg%_yEk~l!0rt~l@pmPIk|OP z0E0DW;4%!`x214)2Hz8|IoK}6vA=v^3&1j>jdI-_QU7n@@rCZ5|=?NWns49lr)hpyWK!WLjLTMw(U$o+lu3>Q9AnNt`tWX zh5wiT1UrO#-Pkn_q9IsNdq7g#xmSiBbn4UGD{lf7&F1^t;5G;2AIkK&77^f_5jmuMr{L)dudBM@>k7a26%_MZ`)mlF(7z9`H&8y3 z+=i@NhEhX|D?+XeLrD`~PXK;|p{!8retGxEFASg~7!zGcluE#D>|gVrx^)!~x9Q(+ z9n!z|oBHQI~9SGoI?l=>1Vxn?;8@s9WxA?@o7_zseQjNW}^GFgj z(~k2Iq}h^D?v7bp3%a72fymZABX z_*;p|)5FWQo|Wh$&q|YzR6f%9n8^pI)Uz^!56Ll$Z`pjzsLv!Afp)F`3z(rid^vVj{O;<9b2Se2{QIb&t0V+77a5bqS^ za5ib&I8QkKsgK(nKSv;hu>)Vfh~`Ujtb(~PI)q;J#lhKg7tA!a?FOesTR|tXml#l@ zhT}La|J$2#z-I@I=2E8e#cgw6Z#xQb5fn10ewCsM(^cI}%l`y?qeGAr@B9tW5PSkW z%<}cc_wx$|97;^ho-E%kJq17$%l8e1%j|h+kiPl%wX|)T3?`!T;fZg*SEkdAll8R9 z9n+2Am1o!-W7|LSsWmWF&R9!fsct_aCL5Vj zrC4|xL_%10a_2Q2a&PWry_mwi6OFBPD3QBC-#%pdZ!yiOfX~Q*|F+r|kl~9^Czgk? zRGH_FVnGqjM!Nxn`1V7By!2>EwzV?{q0y!3mMmErJ&OTfZzaBVQc4%y#-lCk$+>v% z5|Q34e8+HC1a9_JUaBQ>x~P~Bv!Y9o3S&uK68$>++u9jGfF+w17S2s7og$i10Rv+f zBMOw9Bq3`O*9y_&6h`V(19^3pMG)rt)e%xmT@d)`6~+ya}s>^Tnen@En5 z7n6BK9}}jzk8_&x?Y8n~D&ON)KKfYh@^dofF3-0|`Sw`(z{U@){7h9q8dyAphuuQ4 zT)fL}rF{k4+;2~m8^E8b#4XlycG>OXJsbuk8?{R}iq-{W&um||*5KLgp^X8#4W4MV zj2mhS!#d>)_+!w=}D z%N@y|*)7@t(+?D-*((}&Y2d}Z@KZWk3@bckQwJb0AGVuHLoV`dqxd-DzN2B6@FF(~ zL7*qth?pYKuKsG~SE~LpZ$k%$zhk$7aC?HSh<{K+9ftUTmsRu)F55tg=gC2=YZ3Sp z8!f9*hNMbZOkplsQNhy8{Fx1HE{bmGV}RY14J4fiLge1}&qmRSClTs-QVWXACpBWj ztI;KdDNL|w`P6(;cSCqJF$N?`;Odk30ND!Ne^Wynu0XF#Kn2hdmX#crdVZio%y8>C zG(H>#a`+`nL0eW8rVu3rZ1N(OTu1Re1bJfY7jL|^ZW@ZY%r?H!&7~VQ>7K>a2z~Tc zy-Xx4yDy;M^V#kP7wQl8ia6jD(awD#9lBR%lZgBB>}=>w)@wdkXlG4T!k_HmhZ7~D z4|R#e)%`j*rN8F=cEV+?-GFkYRB?`vEm-+Y) zYsiEiho1igGPNMx+SZ8Zvw4&$QpZd!y&KzIZ~Ml3a|>!3)}iPtN5TC2G<)?p3gD8v z1=_y>w4(oaxOS@ASUV2bH=!(|SBrJBTKuULggtF7>LhfiRP;tlYH_ET>fD(C==hcAqej*Vf}KwgJO9A6x<7gDI1WX>);c`E(#*zZcQYvk zDN!jFJ;@yWN1}kK@ooH0DD`}uaH;38!WMg`xxRsoSDv11->=dA?Yu9~>l>(lOB8LV zDxjGmUG@KofWsT7sjss<32U!^neKc+1RH;_Qa5`W z2I`-q>Y+cwo1bb#KaZlC8iue2ho5DV%A8lDw@kn$%x)iO&G|9ZtF|0K{Ak1a67W|s znV%0ox5H?q^I>b@$DkTm3zY?H-uB3SVh>~6{}8~SV<>Xuu5N zjldwZr{@fx%a_|;M)PkR6tZP+ymhp^FwsAW`&~KSUAqB(!?pUJ7lli5(<`7Zbk*y( z+sO<$jdQ=~T$nGh(oK4WU^(W$efYlX~u5rNhS-cq`KDA5*>zRhWl0 z@45CAEW3>6zX!#fZ8x7rIVr}@cB+03djF^qQ0nL|a`aG4w&x)5nJEUbJbss4 zNo;Ind2){l6H~j5P9BGs+E)5J;*|rFm9}yyh$iCO`j9hut#0!JviiZ@G8t`V?N{}% zB_U*O#Y4eSZOwU{JzSEZFqYr1 z^%i&|>GdxPJQc}>i0G51a`^#&&7h29Sa&$F?&vp~KQ804Y0znG{Vo>`Z9{Vox5p>u zMN?Y<4V2RcvHGxr1E;6jcc)~20)u+U#R)sM&{cye23Hn0F2V9*G>?fbyb(9>VKByw zmDQhJgfq#RN<7sdsUBCc<{WNHV~;kXByfUQvxnc&W)BCa+AbCR6^f2-HqlCWln1nJ z(PkHAP^wi*57%js36q${&`_JPaQ#^xqb5KuNrG$&$d3rfKm#oqv-H?Pv&PZ!=U~P) zyPz4XdjGP&#j;@;8Kn-xnTsDcj}&GOYJB?o=N$76Hq@vWHP_7Qf;uY1zEI)TJ@Hp* zmj8_2{Nc_HijRC}&M~Z6al>YtEzSBQx6kNpP=-CUGV{I~NlIrZ=GSWVJCAZ6tiwCdg=8;@mH5gs%19`I z;fTYL{6avl|MAqPtK@P#{S4?d*!bo?&P#)B@;%y14eAX*gK{_@(zkO`f;$mTsS z1NF%l+92M0ObS{>@Q|0O5fH9H?f2OTOxT$fU5^KCyLVIx2~W@srre6hR1ix+I-(mc zjfSv8w-?PLD$Ee@Bkkpj_9jHD%w`yfXVXP6n5cI#01exfn?(RIES2jzZN!GhAv+}A zbEI-0P*3F7yRBBbmcl_x)On?n6UM0Mn*5u4>LDA{;M@kpvh|fRO|kPe@ZmFrEM-38olHNEIUqX<{S+#uH#90mc(x zBmu?~U?c&?6LQ4=7mOriTY)?=o&X~WFrHAT7USa267m3;7?Muv+T4c33nR zvSzwbI!QEuu4`XEWj}}@rQ#rN(I;+2>GX00CU!KeLTA(AVSSOyD#TGVN`kvF9Hdx< zaBTwm;WwCpIjJ^nn%R{Sx^kL%M^|3zgcRKLVjKu!)`*9l(eS|BRr|Whv3Lc|t9-6q zyuwV5{f?g~F9-%z8B8-uUp20KW!-1&26V4kCaP%VzcjA<#kvJJ{|BS2gnZQFgQhTS z0-&htUSfplLwmc|q>EuUCO@mx9Sma>6S9X^(N; zq#VT2lybQQq1wqzNhVx?P}2wKJO%7QAK0;{z(hZ%CgehZrwE;a1sUR(I?-Y#ksN8A z1vAOT{6oflkD{OHC+$-jxcCF>#=_-yCE>a(0oP?oxFm@6Q-rPc$KcMn^c8wHN7ski zaRg>pn?z#)tDhib>qM7yultDZD*?`&lxd@AbSfe_mY>$UI5k0yvi>cKP3XPNX#;*D zLcoVc5IQ7)qrn=X00a7C5G~`9@`X^Fj2sysMX`Km?*zO2b$|Wx=^RZtR3J?Y?cfvb zc^pnQxU4B^X}Uy62yW)`BLi2u5Ix=UxdUo`8a{e(-4|lX%hYt`bF1Q60ZCBcv=+II zpDw+^T9&3t(yY?VIc>25tO#f26r>tE+pI;|RHIs!s!Ci|sTsqvu4?9-!?A))+~bH< zrGv9Zizei@QX6(-#_1^udqa&*EEaR;wniLQX$BHGp7TnY`d{JX4>pW+hEF8ro8!`_ zkEE}v+`kW+`d>==?jp)bubNm3K&-N#k}g|!4{f}}rX(r!w`<2zsBeTqFFtZ$4UuCW zcQdqpCCKg?yB77rIwL-fVv~=FbQM>@zJb79DR-q5J-rW}ma(j0a|ZZ0!^fbsF8;6me;i)f_SR))e1F|U9z$d1LSFws z(fjB7=!<_G-p_)yJoupO2gRGSuxQzbxvc_cQ}=M!WU^kXzy*~wR9XNo428cGC^Ena zRXhVzEUINhl380k%Jmdvb`{J5dq|N9WRxnT-~;_t@n#udmM`hQiZ@>$^9^CKkZ0ad zVEBsqj~gc1HhMlm{CDdR+|m}gv}+RI+RRH#(DHYgScmeh2cgM)c6Ds;@T7&nc*XE1 z8<7a#x;}GwQXzs-Wix`4RWjZW&30FGp=)^Yxuc(GA6ul(4@(m6=#p$l(eM?7N8uWR z6DI>kbm8@mrRPq3qSGoIs^q+gM*->pyFooB%Z!9!yxmd zE$|Hl;5}51)ArCisQ(O1U?%H7`~mAW@C|nP%GUzm3&ji`w4AUmt3N zX1q4?{~LZ7*Zq64+N0-Hd*SiDxnEBB7C^tZ2GR^Tp*U-7dlce&b+20nK6L7!Jq0&0 zkt4JvSnPHjg_0aib=-zjz%dSC^3)4ixYX(Ewfx7a*16ctoh+8>(Q$1To*A~|A=i2QyKpNJ za{)8 z;Sm=iKcY^w|I*1Dp{H<+j%p*@$19L8qREXM9GhaBH6Z8LAA|i7HIHV{a#fwv+Tc`m zE^C8J<+(xKxGmDuVS<2{!qrG*5J|zyuNE^T(*p5rBejfJAvu$}jHzs+UJBnC*_p@T+q>-t)d-LaB0H=nUH459p0@kAE$cD{a(^&t=Po3wiD@oiVW?UoN?+f&hMS6TjhyojB3 z|3k|6kj1-*o^Mja5+%HW_uww5Qd+(}cwN^_`C5?*Z8fD%t^TKo@bDX!)&aDYW6Hq_ zJU`;G5q`fXx+#aZX_|ugAX*zfraeAT2ML|zp)&)*}`x4U|mx$uik5biIfVa=Dg3p-60T1t*s zekq@q>1|demc^OmPJLn*I4oS+_jvSHCi2|AThY0L?e7%*Y0{bL1i!WpCZ=AQXwF&muV=36g=R1>GQeQIc!hk1I-f}X%|iJV zH6s>GbK0jVN057EWWL~=9&wR(JLi;Dz2IoK*z+R_PAm>M7bR6D7lX`153C)f@djM^ zB+YPTi(%aVdkpgVTnqKoAAs#E1?TmJPF%Kc`7t@`VX$4ySfkjkf6#k0scJH#{TE=m zMe)DKT}<(X;|ABI&QL!xr{FdPrUf`~?eFrPVgNPN57&MS4A$X0rA*7i`xelc2Sdbq zE{{AHAT;?-k?%sUA#zj|()!a?jdig8giH8W^3Eqx;08~gTdIKGD{LO+!TM7nL~(>2 zAUO(!@AjaA;BisO?_~w>I$Pu(h6Zi$4A{~zCfXnbrtANpb(4e8;69}MM-T;;3RV9D zU~ERqrx6WhSSVc~B7fMA`yWq$B^oWi#S}1#*J36g!*Ou}HsPY3dU4W2 zSR&i9D0ay5RIo`N3TF<2=1O2?&`9kzOfYTJNPW*pefJKdrtb&V&I4@6wa>KiXGU9B zOD|sd&wA!0-oB6mZf@LnEk=Tt2P0`8!f^=(bH=kKkZELIZ)9$OSf~h>X70?!I>wms zoN@UHBXe~gjuDK^H;fst8JGXw==`@Yewhutw!ig=*J;%jd5-9 zfKj!R6&Tm*pUk4bs1kqN$O=ann)&o@2C?jmmHU0J1dMAhA7Ap0?F=6Q_s4Dg9$R=H zgQRfSm3~v8CDf?O2KxI0OmS_od0y!~jL@aqC^%tbVRO4+6*U0~8tSpAxVAXmF0QE~ zZ)pM*Ee~S1ZDd9TJ()+1j-BkZ6eDn~={=)%B@E{qoky1%Rk%mPw;#*f&H*B`<0z7K z7}rLb13RjjQ3=QSvyoYBf})dgD1JZFK>u6>fgHVvA_mVZDxoL=%c^S-Ujx!$RX=>Mmm z|4RDlbT)N4K}9wZVPU+5CzeL0^o-q?=-X(j=pr`t4Q1hoa1&@3okb%q)M!*MJB~#k z`Zto21Ki|d<9+l%*Tl_netg_GPY%SUcKD1p3vI|E|8yQqO??idnJ4dswO=l*%_%fm zA~L3~)x0JQp7V3zHUDOGzO;uUcgCw6Qkm^i>%mrx*Vj(ND@Pcd3HCZ(GA=)A%y?dg zc)QWrz6V}r@OSu+NPc>@B;mmMK{5fj0iZt|Jbi_cDT58SRycw%2<0c#jdhI!ylU$o zWtdgHfFVYTx0|A#1HQC7jgBr1!nUSCqxQ@E?Ciu?9pD?7bZ0k)D;AegJzTr7 zfHnf~CM(rpz+N4d7=@A-bCxqYx>M#Og&wc19Itj?I$nsT?l?VJ$yik_Wbe?itirsX zGw0{3qcZ;1GTh`5ZAeZuA8vm6t4}QHm{|Vnr<98SvMFU;$$#Z3Wqj%Xn^TG|?t@P$ zwi-^8YJ{=l_xOiA~u5pPNrA zKJ0t~Ro0RDq~b51PdHvb=zQ|+#iQpF%JYM6ebf!bUT8RA-;|2;ZrEC+!-yaZmLf2< zq9aX?K#;y@IaKjrez-K1w&qizIuTHdOxPNU8`UU!sk;b5Nouf_xQbP&3o3cN@&Bx1 zjMLGJTQAc))QlqBbitM3LT9l;&iDyL+i_K@r&1jhV~#XhJKmq{1>gm+*d}rhG;++I zJhWhXTxWK9&B#@dXnF(Ah(|4Uc?(^UOGFw+oq~+AAu&CTIMRYa7B5UJ75J*;tFt4u?64n6%;&L z{!?*Z<;0Zb>_Zx@gTb0&9LL795txM|*ofPhu>XQCH6Mt|$7#kIr?GdnQ(bQC^}um( z$0O&dh#)=a8BL;g>C#llqKiP#kFcJ60zhhB>O!*?!8W!_!mpKZp=HUz~VwWBmLDOxw-^h78c`&@Hg zJ@@+bm(IPSebnGo;oLFD%T?!&TG`W|uhi0WoSi2vSC8N?-zg^XuhJvw2AC~(yfeMW z+p|7(jnn(i+G+oc?JDj)58;`jZC4MWI#yFE_{~=9-P2dQ9WPCf3{_b*ZdN7?2M7ne zv{x@@f-_)Yx>UV4br4Wj5$adZz0%Z^qTT~%g9Q8>1bk-z^GoU~ZhBJAotQp=f#-Nb z4Nh-3;~iXY0GzAVUSgkQP+m63bc6-IS~s{HxUH9_!fv@KGCk&aZ~E(S-&$2^h1>~n z2+wkGXXsAgkaPxEXerJx0QrmF7uQci`7f931HqO?kZ2fU3otK zRC`m(1f?&hK<9HR_~8^5;3;$q$6|j$JXhob7U=1stG;rR)BEz85bjl}o=<|wZUXCm zW7VLVpccbg`rzhYqDhTl6$qpHEX+S`{soi04wIilpJZh3W$Boo7>@=^=BilV>+P?% zQ%OZgW3f}cJ~b9`MDLv-RPp?|7>VIr4_MXogHRKoxuGC4$G?QKV|{Rv!pxW-ieU7} z)pBgprS&-YSxZHLgHtXHbEwoI!Ikf#1dZ@C~y5o&^LcrdudfA z0FuP0<8s!q&ON#a%uU(ABV@$h?)AT3(}Pq;yxnUiuYbjRV*QC^tz+R20&otXFmFX{>iy;cJ9t(Q3U{-U^651N~>1(YVbpiqQ=SPHhVbzchl%;?%%sC4SEND%g+dd6L z&O+SLz{1C>NrR*2s@B2`;m8_35bYW+nr?Q%==P?mfaUPoNnnAe&8aU$rl?D&-t4TM zBt=09P|e0X-g3BZy0CfTeGP!(A^PbX)Xh%yr>XCbFqqe+F_?>PW`c7^9nV|Y=Z^ie zz=^|4uFG80Uy_xXyXhWxO8sXxO>Vl!m9pmJ>QaD=@~pyCWUivJVzIljGcN-ToCy+i z5lMvR&(sO(kZS8vrPiu*$ELqjiPBo(63Q+p+1npzeRlqO5;wVkS={R&kbWpg&Q!r! zgv}3)*$fq&YZYcHKQv}%sUS3Fvz0%Oy0yWDG@BooSJ9D|P{$#Eq&`)AA><~pilOMz z?07C-dmDzRdMElqjE9+y_%$O*W{gfUsx?XdEZFUczXi#^UF5Ivnk;fmdira#aL~)H z;n_qX2$g4y!z1fIuCF=YuMTCc35}Y;%d^&$tHsq>H&=7Uw`Xv+r)k5Al={>=jIZX0 zj4!o85|mWhD~Ya7!v(<75y+N|gUrT9(?%3=1&SyrA60~)B)T*+J|j^;VSMV?0?x}{ zeSZF&_=$II)D*^_$LlE6Aiv7TA-Jmq+^Mtn(o%H~{K&hiAmN%X2S4*o1UoFGneS!- zpibqvyq%G=)*91VLv}KH%EzVR39$cYVda{S{HBf8WFN3T%%?aPa!SV0{lT<$JQm-9 z5RQdZpxi1lRiN4`N>zas%dMg`y^1IesrBV*Q*~A#lvPunRfhyKt<~w)h78=gX?lLx z3gh+x(;xwp*=c53tFx!RYHgTng>zKEtg8GHxYeZ=7G;GdcO#l4M(s-gkL(BL;^rxh z4DIb$uENlH#KJ!~gt;hNy=3ftnY)3>3ie1A&%)9Y%WKtR?8Tb99oqrqImmc>JJ7}5 z!Fk((Bnn641Hc^A_@M4XAk@^)OI6s6EOU8_LXmrc9B)y1WGyGZqmjSC++a=Qtcl>9UL*2xRTEP8guLhKJ(zcE%DrpK>!(1c%9?R{5`QME zKY5oqBQq7OJFf|0W-*Ygu-tod`KC!$sS}pBs3QS(hb<92jKG2$wkoziAfRZuf-AVC zp{(c8cI=4Gz%co?PVmFTeTQ;YR-Sc`H=%JfS?g3co_45v#E;8+wYh2$daE2pnX=}G zvi`t5RmG0RFtK8~J;2P?vNjVR|ZoH43yG~~@ZunEKZ z3EHCIgyLY<{Bqrjf!ax^6gIWsAHLuFr`j7vT2UbNW&NRUqz!;sx53|+bu&EbqAtPH z=^scNCV}0fs7q-R?Kz|QTdXY{QX9(I=6lLDT3NZ24CGq%NCL}ib->cJ4QtRo)WG#@ z!yVWS67HpdTg?Yn`_+3ko~8g#i=0&E&kcnl2D{haziEoXt*8Feud1cx>SgsOX`R0x zty@HpreOGcnB1+C*E`i9(w@U!4-)h%x#I|)B4J!^MdIpuPz3pE9!R2TaDuVzS5%t8 zZWJ4rHBM^=oGNn@Ih%teMdiZfptma2Fwue$OgENS_*NU72>Mbzn2%Eeg&#rWf#YM4 z_H5|NY0^$Imn8pa>d)hjAUE4+;Zi@h#%Z*CkC^nC%S!_=<;{DDadQ9=Ep-#O;cD&? z=DWf40U#HtH;1wc!CFFDb>&%bfe7OnHmaAg0oN5m27cj1Ayg-kBANG*2<#7@M z0wGHg5cv9F$lut@As;;T2u4ZN*UQ?msgM8N?{H2H?o&T`(=_T}$}&|L{(i{pQ{)Zm z%QenQa6V)`laXim=;WA476sm$^3870(Yz~2+ z3!g_|^MIoVOh34EbGM#A7VaFtH#(xIW=sGCBP;EImI0_s;AngTR2f8tm}(`|b_k0u zaUR}8)h{es#d$8R&kI(jZAFL(txH+=gk95@5iRdMwqN?1M9_!86B zN_nf(?Jp^7b%y=La>PDQ*iU^DT0MUU`igj^whVNb%FjU^0IyKS2}4(iDoh^#shIDS zSTBzB#e6K`6@F10gj$RqUmP2&1-Q5o*EWo`opVvdVW}NPCT!GrZ0ylmkeF~vLzvtH z%)|a<6hbeIPbz2hXJYcGTcO@+8LqpOUCYJF>5OP$TKp-P8pg^A25F;<3*+C%7gh+! zgD@ZtlE0;Qqq>FBzC>0oQkXR}UZ2d`)Lt0>>iJnq(1)IdJCj+tJ~@3&v@j>0KPKxO zoi$ly!AewJNkyimq@xlSi)mp;;siS-GDC^BdNm*>xSj)8~uW54fr zMV+k#VR-TR@je%o9P-(#~&v2h?PFrU_w2HX4>gl%1GYaE~bA_NeVp*S|G_tC!-0`W}5gza7iymO90S_qZ5 zbn3g$hoiqq(SjOC-`cn&b~bg^K!+`^CR83oLH2r9*B5o>C7s45YCZ76*jicNeuVv* z`qD<#mv+JWR5vuxvbt%{1*&mqZKB4T$JLnV3H{6ZbN+n&^G4M_@BI3?YKLX%8$jR7 zOQLi0I59a+K$boXQlG&AJu@3loXfM}^x3HvX1lEUuBJ|CI=h!eUK`_Jcs#ub{n`Ge z8@?drt8?wYcB1qyh~ioPOkKXClWwA=0(zAe#lHD&==MWNWLe~8yO#0eNz->AJ{L~A zcxh$KW)CRR@gIErUJ!mTGob`fU;Lksup(TH)F&jm{82W={^3IL@c$&i7lRM{kY!hm z&C4D>uYB*=G|BeAPx&G*P#8qBA*Q=D!6`KTAU_vZJ{Lq#tKEP@-J0!0SK#yoqAtj2 z19J2hbVE;G&757Tav)+ZpY60lPW-s6kW1Chc0(p6T>lL0EI_Q9!%aApP7+dgE`v3? zjx6W7$Rfz8g?r*rNy?{de>a1~e4j-2FE7DLXn=NyK=)3khV_vrCGU&ePg#`nZZXpWXbj+F+`0XBrOx#NU-si?I zbp%5r>1U5kANh0XCyq_;yKs5}Gu8|1b6oi|b?Ba=$<9v8-{9y20Zu8hbbP7>(nwW;h?7A4qyk-=b-L z0sJ)eXYf4Ggha+^%~54^`VQgn8oH@gv%PO4x=f<+ zoTI?>CAvnuSm$6ELzs+X8k|MJLOiC&{2c2>3*x?eTowe)_n0a_r^=dGX%*!RFRo3V z(ved1KhO)rp2_~i=}ievZ-&vL|kAexLl&WAYPbeP?e@yW_n|_msP2Qm09i5PtKH< zS0hv9tdZU3nkW#AG^2SDiJ#no`l5Lzb`S5qQ6bN=w%xR>KiYTw3Uc zK@RthrB*|qY7m_otaxx|br~?!M%k z^QSrJkrR4Q!G0>&z&>O#l%de=DNuj$r^UO*rRocSr=yckQ5S_B{f|rlZ561*oaB@W zl^YnrK{rGtXWW4>U}5s~SFr7*E=vUAm}FmBUK;i9i$8?}dR5VeZpY4l|5X10qj?vI zmh#z17R@=zT}C)k9ebTo1rPB&BSd{L9Oa+BOEt`t@*ajLwxw-DCJg`M4kT|tPEo!C z)-o(A4`B@lLk(7y$!N_A^Vq24pCJ7IoE<>17@YUt)= zQV<4c7HM%=EI(v5^xz-_`dKY^v&MX!OIUIpPtC7ZMb%ljz3H+l+}2KB%j*&&sd#}i z-j=hG$x2_X0~dx=7;@t0Rj4|v0!QIEBjTZu_!s5Dk6gRc=KqzHly%P7dqN#la#;|g z&e;3Bq~rB8Z7Um6WXOznh7j-wy}`QF+a*+2vZCb(vYzD4$9Pc=h$_l7MJg(J!Ia$U z713*&E0F<2I=~ae83Bl^OXAWHhvnHTQSluL_mX{qsP--q>9s0O##hoHBPJr>e$a5A zWcd{YL0Ty+j$69$s_~u#zaYV990;OF9ZNn5ehR@{|6$Pxrx;dbyV1;}7Ba#Pm6BWN zHkxPSTjtxK3E?&R4>Epia>N4$;FV^g*#*^7;2o@IbW<%*Zb7--)caj_-_Sk4$)<4*9pg zeZLNQ3b7aOkVlx;Kc+*T8r>m3xKM}eWZ>8iDUx!|?~q)n{rnEOM$#ucq++Ok0ea-9 zH#oTNM(Ghu58%UNdZb%=FNQZblSfsP!yy34Ad#*@){2(rFS zF4(1Q{u$oTVOj9&7VOCiwi$ahr@=+Vy_yyH4>&rEz2dyp(T*dcJ>-MgR`7?o@!jmu zgC=(=2Fy7kUqmICi@S2gBuAaDTiggbKBn6Sb^5%is2h6`jkW z!3hbg0L(8}%pD_q~uO9 zr}}?JvIz+AwQ6SOL8{=^VkQ2ix$=6H<@k32XTEPt@0i+cv={^!3r?PBKaSdWvxnA> zst#5bfaNxZLKeTWmNU&+q zaS(^uSrrEyKmr&NF}7c=tImpGEr!*BP>dCPh&^sSNynS<*!66H<~oDwZJ?3x9+c2C zSjn_7mGGUnCFc8x!CL0+y3at_Yq%Ch#`h@z@zT^DqXh}B{#aCP{4AVC!s zkQXDM(h8SA~5I3IxV)Ca#K$2HSL~^dWIbnz3v5_($a>Kd z|JF%t91v_Yw8dY@9zjK~?Fw5@flo-x8K@SwWC+P5C&xz-qtMaB zi0=3g-;u(${ZAX?T z=qO&c__|~#SiVNx3m7j#d!I$z1O!5ZUity!gp?_99?6#&CBnImjN@G0!nu0vq4Iv5 zix=&{xgJ=JUvyfV(ef4HT&Ct+vxMT(WRgwiJ2=;T=XtDa5~IM%ZPvA4SXY~{t^<1g zW3`+?KX9VJ9KoTdmwp(!7^HfAht&zVTYNSIPRjp~a4D`A!5p>=r{da*T*89{N;1R{5n5gnFf4_V^T9+&u~qLz8=BS;Nc+9f{-O{m=gn5 zn7Q=%M+H{RyL!R9^n!Sbgvwxbqw-O_>$kIHu)YoYePAVqgdUHTcvB6+#a<>Cn?cqk zl^9#+BhGlBNU@cKK983PpZfvh zy5leXUUP{@qzKK+gpont4U0!GFqpK#E~jH;Es$X5-H#Mu=Yts;Ed80efH6K9%}lU# za4b&t_6om0i!p*4rWDj@4tESOnvZeKfK}soK6VuCCLha&r~!BzHkv*~frQP+wt`=^ zj_|SL#}EZRCRhXmC+dnylg#}|Mt8?w|D6K3jxTO{pJx_F}at+)q{U9x6ElY?|~^(6qp-wMm`GNE0>~Pt`mB>kfuQ`7Xl3t zy|{4RzM`Zw8gZ9IBQCgBw<^H5aSx}PS8*!(xLK??WmO!3Oo2P8n>g=4VK3rpZsUrm z`3W!ukS7;v(6K75&MK+4d_RQFxrG%QoT0$rxPM>N_d^I}wiiV??>}L|mP`fog>W)M zKkiYzMv3}=7;ocA4(#XoP^k+JE1J*#U%Z_Se3aFd|7S7-3@|Vgi4Zi^Y)xBgY>g5% zacCvLBp?PR2}z)kKzD6R?aT6#;YCTrCaHON7;E=0t#-9_TX*Yr-Im>LE#0*?0XGRR z3qh?-gy2iv_Mu_hs4PU}Z~otN@AJ%C62Nsoe_AIq&vReSJ@?*o?>*;t&Xsl%O>Pm^ zqUACR^kKF{!B&^7$uC`H#I{Ihl|M>HKCEWM)~U@-|9yHMI@AR;2tJWVgUGnzzm2D#r33WlxO z7}PK+9o=>_VD^qp1#myq(eP2De7jNJA|YJM+l=y^a*2Jc`l-sQi1p!@MOlWv!A+<~ z&e>PO>`=Wg&^k$4gfc4Zw|z($bS>v#g*snyA_fZyJIYeo!D3Q23Omlv8j~HbsaK=2 zqmx&ovI7Vz>{tprwhKFISDS z^^36Myk^I0n;p%K;@T$H;51w-!5(Ab^VEl{RQXVCjn&31g54MeJaWVXV=3QQa?2Q! zjLka*DOk2P7b`Sckh-S=KM}<2cBKtbhxu?D zc*dth7qX195%J5V3dtVcj)gQHQznl%X!tw7%0V;Z$Vb&fF0h#@0#7 z7G`h-4}+_w%6Lqsh4GlAGL%}zP!XLBwR5&=2V~!TJ{EqEytWt|im3}7(VjdNslCf> z)IDz0J!sTDWYj%u)IDnDsVu(Yy7M@wFg3r<#P13}$0~q{;kkjUitBvQap05(GSy61 z=L&21B#lFGJh#0u;EK*`J+@_9bK~Q3w@&rLldB(%{#k6Ed*G_(Ukb6V#%ZQfF-=wn zNjwsG2k2t<{+4!y7^G7-)5PYE0_%RXyZS4xopX$X~w;@!4%061UqqdUTTzjB=1LhwUL#wc_8RlO2tAr-VlU(z zUHv3;5~bzzs;XGI=tI#SK3T~CoN$V(gU1Kj{vK{LH~L)~=Gxr&hO6Pv-V%^UkIlC(X2b(daU8o>cB*#rG^>6`2`^AGdz$dc;sGoaRVRciNrCQ zM4%z(4I$I?OTW~G3J)x5IeSb!?AJiKjC{l?!mIX9nGthIpa=UK?18`WLq+hD5d4_> z)*}R$_n^EAh0kM>JEPqiEEa-Qp9j5a6ir^Ok zJP*v0l@V~O!O~;DAgU2ta^692kI>7-18muuUMwE6a#&_5ll3$w0=%P`%VMIa9%MyA z30v0_Qqwa!F4WlGa6MDhb#k@ZzljXUvl!AYW3J%6q?PJx8P06>I!Ct!>2xSoSbb8V z2A)qdyD|9C#&xu@=Rav>Wx;4d07i|RqqMXaH} z7ZNa%`PKjV;_EC8y6XiMf&Lr!+B!jC6|v~YLbO2=UP_bNN%#3xG^guEtVEvWMmY7%hzHm(6oq4*~%w`2M>n*565dKpm;QUiXDZEp*_BDJU6+fw*1*DFBt z<>o%yo}I5L9?AWilDL1h=03LEnPMh|{QzKAh@nCCfgQ4PY4pYPKIs3U8X6bw1846l ze@E+&hjk%ijjs#!N4d)C`yckl)hX1E*&l!ahulq57P8~_$z9jWUlrGXZDdsTzdZ2m z5kE~^hkxGl_vT>8J+pDTStNb{M<#c7hKJqJ$;v%X*=h4|2RwH6m1$Uc&%We&QykuQ z)UiNwo#rVcDz^L>VjPb>SpcLDzyU|+`y=9@!&GSP??;XGGno#G4t8oF)z%V%yu-)6gEf)n`dqc3fDc5!HCpdK2 z@3EcFRkrh6<;^x7LD+t08`3KxVn!FQyu}>C zuQKq(S-*x(HWzWm9Wn<&Rer>r#CEfI#c~YAb%Vt#R@C^Ql9WCo4#q+2KR~71D~{iV4~<#)Fn%~8 zrKE~At&3)1AqthP5?72hp`~lIT~<7GmM%AIR^`{OYTX~rYkfYN%W@s6UDHIc2=hI$ zpaqSajZI$V)+-LLA>-2qeoMC)n{iFu5{%+3yJeE3D#F)rz$f1d08{XED)06Srb0Q& zju&hNBb|ky#)>en#$MC?uGwd#1>T3{*t`y;o~-%xtkaIWo?7~8W|%k);%_V-w)hL% zk9EJQ@hD(1S+Q!BuhmfKmB7yZ^jzS9Z}O>@u{OS>En}~LUy1(F1*xoJYQJ}BOPOpL zQkRB@RO!yrkg}MC-fjG3%zv+Ezxje^fBtXeJYMSebg;X)8g$KS1+H1GHp6v3R?6F> zuM(!h>uiKlhqJ~jNZTdKL@6iNKA-{udK1v590ni@&kco6&n~V{=qbDtR6{ z()g7sS}-%|D&Z@13pl6Ii{nrsAyVQSnS<2ib2c(I@ux)Sq>k3#eJ^?0hp}oS)8r#c z0s_8exI5=;pbN6ZD##F?&C!4GxZ&uB%@(-}Xe&8|*O;OcNJI74_n6qF#i1 z-ObgV2z3BJjYs`Bd`p0luVc zGoO2G49V~*SRm5TKHo6DE_t&#Qc8YR)smPuNn3uTWU+a~d_lY&!V2;K=>(LAH?|sh49jY`!{(OM>SfeSjx8d)5J~kjh#Dkxk7|w6I zQgG0*CCjafpQCbOUhcGh^9&XMG1JSW#mkN>87fd5y-2cI*16M6GLFpC09*6$IUZ(P z|0xe|@f64gcByyT0om3P;`xA@cm>H5kEm4lqO|d>^GzcP{F~-txoq=1*qf>#rTsM0 z+)-qGTS^Oh@Hwa(0x>o1pxgy!ci_`8K687c|wS^_@%^Q&F5|`J7I1+f+ zzoPexh@x=X3!cN%e!~X1xqTrmJB6BUC);i7TQqQ`Y-@bKm_$}{=}C3cgxHdjx}0DFhd;5bb?%j{K~ z6rhkxgR$yY1;ZB$w@1YD;yIN0O9_oeFd;&t`OK;4@{!lht0gMVPz`|~1p041CKier z&Cku!z)0XTOYNjVf)Rd7;9#hbj z_y2-YyP3L>kBT!+tq5A=w)F+xkBYt=+h2F>_Ivy;Tpq_gDHlaZ3~H+e@5XSCJbPR{ z!#lONR_@u#Y}8@N`S!VXKI)ze%o=Ri(tHnhk3I03^w(k^@vNE3=5T+>U|+R(SysQ= zUozBJ_eQ_lvpCpSXZ3}9)Xi1nC_rMJ2g~0GT>p+-OpO0)kXsI}x8w(ZUzESxUv^Ob z_64r*l)rK;1QZ=uXx*rdjhi#idQaoE(K}{ z^-47NRlgRhPHmbNxc=y-3BN*1>l(ORpXH_ouJ3Ukajqki(X?*UM0q-OcKbkp9LoD~ z^Els6M%-LU!MEOSz*{m@(8hn?<#by zI7CNjXJlgDXV$^}%&*|rn)x{^^qYojNNnFerf%=V*Htc&Nl7MawBSd0r|&bvy&l(p zZmjy`E8jC1;x+y=&n>uzD7oF-CU=ltQ3V7(`6s!<&UyYHiUz6d%1MYa+cS7vJ@wSH|eY8R&UJsN)O{m%FpuWOpv8ZvS#n+ z8C=gqi&+gVncy+QhYaRTX-;SODYsGnBv2cp{0U+&?0gp11s)Lpee(#b(p|v{>$~4# zB9%X`w$Nz3nr5@|Iut)xhCajljyD9sEC?}K%(GD2{j5u(szH$qWU0kt)XvemN}+sqYMfMrwh zQ?;qsBegX?!*PrX@u-uker+_;meWOxfp6V_Y^6~T{8E}zCTB^v{EgRa}c!C)XF0zu2F_&m~!&DxL`U<)(sFHQ8`cxsJD+1QRGHt6U{ubGe+H z`fi(UQNxz^islqsviNXccSo$p@E4g&*6HumY}`@gHJ7aAoBjHZB94=%^&5W){tYxC zv^XoUvrHA?f0C9{mC>&((+C`v*@ zft|IqKJY*>pT$kf12KuR0W(^jhZ!-yumt5`h1LF$y$3oyd;duZvIfr|OlFo34%iIh z+wBMpc*D2L6jLA>KK*}V4UBNOb0o0y&lPh{zJ_)Lewj7P&@WUEhK@E}!4exf%Ao+2 zz5hOAlK3-Y=nD^`-K<%KLW!5ti^RUdJuF^ZfDGXcx^0$=AOpd3MW8`Y9iBYg1vOGUq$ajoU9^Ej7rN+?v2r*@L2_n$! zB)w14W6kxe?7Q;hr+QmywC2hWg^#Ue_-Lpl(97tat}xq6%KoJw;;he1L>a4H1kRbN zAi$N1jzLqEFOZ{~NCf^_VZKa-~}ePn!jDSmp-i$1d6%d7gPNeiS25m>6O+4Z$pJLrXhD@ z^2|DRQn)_IXi?@=QIn!yUgVFPyt_#iv6hk3E+rO|N7e>*&NzV-CCXEeP0C~Qp!V^x zpUI=xFWdNh`)_g3LBVdGZEfH35o!d6s)pMsVu9_9Ee={8%Kp47A*oC5MxN9e4vZHx9a@pTiGGTykzuE;$Y@sEllWUj;3|jy6+MYx}*oi2S`d zwAmMEIW>)f?2f?WD$Z<0w)K0o#$=nTvNaL2f=S6r2oVoTwSm@kaOJ{PbKtWc*TuZ^ zd(~05nT@^RFEQ8Nj7c~YJsoIy31AaU!OO^Bn?}N&I`1!4-Yxr0+@H!1AzjAd0yE%~ z#rGZjCxJqWK4vo=`(kq!pHEQO^F!B2#b zB~P)I6I&!`mXWxu&|FHQrG>_>Q}V7hFYuFEUBSchn=nH^Da%9dE#=G7Tw~Xxx}e9^ zOLCS`ykIPMJ)vIgdWt`>T|yl4>`DGucl?lYlxPK(8EchS5kY4b@;nxkS~#vh1ONua zswMU`J8|{S*e-dC7h0P&f#(9 zKD%1K%%}XbI{uD4Oa1x4x(PC!CAdI^s=?PSADCH8mQI_bnEGMt`l*!Dx<{4sj8o25 zDW|+egKsqIe7iWcwIO(8tq-0TaTCT#*Q|cD`MInJ*Q>>1b0L6+J^?oLah0q`CA*4b zq+zZUJ}HHAabB)f{3{;H9o}~u3)do_nK@=jA^jJm5VIuDRM<{qR~A1(GVYoqk9Ya_ zG+K-Jgoxczx~|pw&ToYj1vdovLnDyT%OshpN?=`(aw`DyS;HBkRe*i$RlpDYyO6|T zL)!2}Y*#LM9UQ-nFgIh87o2&8wNK~W)lSuwR#@e2F{`&r_E@WnywtQxYSKtoN)lr6 zrK_~=*Lhp#Qg6hXD0!23CwDX?XqMJelH^r1FScoZm3k|z|G+Az8Two4rEY7R{_|`7 z=d1EZIC|PFDS)E|lwuOta91H;9x--ZC%@IIY_!gj?`lyNmb42IHb)mK1W_AYVUXtX zg0=`pAxp}ERw=i#2Cn?0TXA&PtMz0TXZoxBn2 z?S15}ZqdE5Jr;8bLE2FaWwohES44vvwQkaRV{@gyt)ENY@`rWaM`ODRbWhqeOQR`C z+N)@O$E9fwN-FCf@@kHL^KU7o+qzc&Y1V&oB%7-}!ml`2r5q@fVikq8Hif$-mDRUbQ>YlrSbLDC z39d?BN|L;at5@p0t?ML}^%!}Rxf&)OK4XPf&Na_3{tYpV|7?A^D~7;f`u;*8)ls?V9pF zLcylJatID8DbQ)&hz9Nk0TNgc>1i${ zMUiHdF1J3*3lgBDthJN;rA~s7GfG#f_Urez*zcFh``940o4W+;Zj`PuZZHT;IG@Yr zvc=Z;4X8kS;frOtRLBB5R(Xghqp^*$sVEmPP)^JnN2%W<&MAEQjQeC$~@FLKi6&C zlVdCf_-Ede6U(}vawZ!$@*P4+HrJS+CG_406|q8PPDHifm;H>D6wA?D%32^uwdQ#o-GA2prA z`ynBnLg8c@g?SgGFc%87z3~$0JlT9fI{jUugydl2@R4W>>bu&pepo=gM3=z%<1+rY z0dUK!q0Rp2B=a1;J12Kb&M=WA82&Gs^LX3C;XJmq03e~YeGd!#(gJhP+Qc^r^agU^ zh=4!2mqI~6P4;Isk`-Mz$}Y6r=|E8YMvJS@6A>H&GB`wz7kt7vYzPgL%<`uYdZ-Az zic+F&}SCB^@;J>&DMcI(bsN040q4oJd%Y2$Gjcb}B;ff}pRXd5NluJCw zTRtXV7*2xDO7E-}Z7!1%AQK1E6Pn8F;X$5fS!d$WpB|20ZtSgDX5lkCPs-;l9y&Ir{&w{kR? zpdxy_$nFMt`;01&v=?nomfaluXYL!lIcATV#0tOdGzf(x4Ql7AQ1nY8Hc}?>S1HME z4x9YNId+SZnlsr1ahfqoTh!7nM7NC62XdZC>w~wqk505A`F-`lFDNPA2WnQ+0~@nx znzrmu>=Mbs#hXg%G&i17$7hZY_yp7vphE_+`3|y34s+q5lTrT42xZO&9+2rTCs~|l zIW0EfJAwbN*m-dMP`Kj7S?0zZ9B;!tMr2!8cu=a6kj7ibiHYpV;_^Wx8sDAs5;pRz zXUm9;WX^fks6Nyg9&j0rZ*UZDX~r4&L9_%QD6=0}D$>hk-h*jYfBg$~a=Z!hWJtE;ZvY=XeP zZB4UNkavux098&jMP`k(CK#&q1zK-n$6y=L@$f?~~ge-~81U{#6GdGbr_wFq%xAmPh`=xRO2 zJdU(}zD*KSCjr_!2+)qLa%r%5Dg86Z6xrT5Ad|$b%GSY<8Jh0U-azYiicS%#F9l*> z<{9KM!{Tf7hlRq?HxfDKZ&VX6WgjuHjbijF{gP@Y%YId2GGmbzdn!-#bQT2oR$p@$ z!9-pZVn?h15F1|s5+XB7Eu*afV~ETblSF398ZbWx{da<5Fw(N~S*+v_D}euNY>_6I z6YxTVvE^Fym-vVE?W0%b(W!L|e2t3-Kc(A<*&z8B$u%+$TdIP0*-qnvC>>f#&LaC}H%G zZXQM2TmkX`hN!WAgBt4!#x3F|d8}J^D?(%|c?n}3gl&i$ymx*dn>u$~@4R13^zVRK zU9|Nh&G%%x8n32oTbo*A?WAlmR-AF>(8lc6;jLHMjX>Y%CE9kw#Q1~)Xuf4YY|&^% zP=c^;!fe5MK@fw5SzYtd7b4~(*`mUHtrsk)4mz&2w1R2?&ma!F^VWj;LGxuiP5LUp zaK>lWYD}4c0a-N-o~J|0HX}#?`o{%H`&OYn6&+|J%dUpA4nA@ed|5gg^6RRkB%BX4 zI_Ul;-)ix^MrrB24_0I-e3`z%4d9cCXS^`iSeVD19{(!gkd4T7TQ>2pypFzBTwfq*3Iu&oQ(#VrSU*x}o^a9>NSfS+ zk2>pb6NnO@u_V`=QEA<)l5Ta9=0;xwdXbFo(*75~R&WIc!&9sM3Gjdr9}VIKh2GuP z(6lmX27HLJK#!Q+^b=}b&}lC;O%EMp^J_aV%sytL{{DcrF3AVv^k$m{?5;KH3d~6p zWrW^s%9PIV-$yYS8lw0o9#DD{^*7DnF+x$!wI(R&OjUf>wv(%g8fJrD66l(-@FMq} z5~Kq0zuz&JZbSGqKFC3=<#*#Q0qT7vVixF)zqC1VBt&KZ6+iFbXOj<-cS~oK|x9%q*q}x&}7Zi|{}rIc|9k0Sw_0>k6j; zwoT_1$D)sm(|!1HLUfaPoNVz5N{n(EVihmV3bf3Ca2#p5pV|7yf1q}35NIH7_lsIq z!m*lMeZnb>SU@%E>Jv_95zc7YW`(*3f-7his(VkikRZokz&8%!*DFGQmwXyEd7?gD zGRp*nj^8H1SGdVw3t3hfSO$N&TvEp5lNr@~gp`E_#+8K^6zIE{RIazhW z?IOQ63p=yaL0%0F(3znFjd!x_gN=|Y~#nDTZog%}`D3j#Tv zo1|{+R^054Scjwp$&Z?W;A7wyo}+-6_iJR#Fbd4ZQ^VnQ@Q_E;?33Lt5KlH(ftE+H z2#d}1A^tKr%`N!BuQ8)N+Go^II^TRYeZP#z7>l(C7rIUE1Yy7`y4vd0}@@%5r6QXqQ5jzFgn>ub+2aVX{>UC7n=c+a@#Bduf`ym+GgzMEIzHE~2c@!IcS;RP0qp7UpgL%MSRS{^OAkJe%E;j<@2#;l_NZqK zrOLD(9B{k103%1dQZ`KQQf4BVmR-s|B;N+I^iycVk|8QF`RBo1xV&ZB(a=$a3=VG> zWN;hk;+-hPh?64%TZclP0{^-NM(em zTQx;q`KMOBoY;hM5IEIl%yS{O;wUxz1Kd{nJW4l zYtbOLkxU2;ZS>$Agf7TD;5lM8K4qxb9oo2Jc1`OHbt%;L0b%=Y*#1+Owq>}hcccAe zii)qd%9BZ)9|>vjBy&^(Cst(1jun4dIcJMnNM4=~9SOAFa-K1R)Vz)9fP4>aVIM2; zE1Mf1CjQTvcX(U=Q(TxXH^aNtc<}@Cz6q7y#{tBZ&j(ndNW+}_bX-|9KswqA4 zPFRZ-*ZR7=t3DMgf3k1Y55a7$ zc!B=_AqKAkAo7@qB7vXqy9Qv2;88MFwo;%@D+F4if(P;%pv(2+a*hgSFjKH%nY&i0 zyqV>_fv?}q;@;fYF8G!ycT81j%DItA=_&#b6!Df;#9BU8Ko-Hjqh<}Ciaj(%e#dr> zlRrAxpq7&2(^mLxRw(c^LEc5In(qt`b7crX4}7jvSVFT^^!(iA0V!^I71ZA| zF7&8q6|_Z}nt*93Jg4%Z=b;iKJQT=UP?^JcsDcvmuo_GxD4L*(<;3&HgcSaAzeiBX z559xlSq@X>&W~U@(9>=Ki3;0r-wO}U#V8#Lw+D9S3#qm3f$KXq+`yiDVy*SLuO`Mx zprxPFH2M#o_5G32lRQ=$DQyJ+zUvp$Bbj)x&-ZQi5N(wTXi)W^Mex~bew7ulM&ed;!bxzFtZFAW-Fsl#Y;msEmEJiR?-oA~1v>& zru^np0G1yz4@Imm{Wnz12WH~`d0$Q*Hq!^Kp_hk;TiP3LC=Es1xBXf-;EAs!8XyDz zA^n@2Nz_B+eC|F`04a#&o+j1!wP|^m+j+x7NqOx}lZ&&E8{L6#h<*p_ zatQzMqOxGa|C8JPm;ZifRYm%$#Of(@<^6zlw})b zIp)$rovcWb{YxTQVc#D+%Zf-?Xp~)NloiCO&URIRuD6j@Dt4Wrf^_P0j2eoD8n^bI zK#5Ns`b`6)j8?TVZB<)U4Q`qrj2LQ2>g|9HnNk3Lg)8a|Z+BZY&#M6jzZYjS3We89 z%kRUIzWHFpeSNF)yfKx$Z+bAW>`yWZ!GTHT6T(Qky_YP=3C3U^ef_$+09 zPbXJk5U+;~_H&}Vs_v#}U2bP^%L%TWGdE|W&=eZw7#)a-UbMJ2r!fMJADi8dj^fkBR)7KB+L(_&EEwtTvm*z0EY1Y-|8nrplc5TeR z3FjiYb3oIVi>sg_jSnajuSiJGCWi0wsXZRVnwTWO*%%SGS-2@p?-W!1j?vQ=3pu+c<-urOhZ3j1%iy-8j zXT7wGg@9u+RzYroV|)Ky$V}D&O1ERG6YV(KmCIFhbGbp|LyBxN5-#1j9=S|n5!}qm zfW-(Pnm8zNC@Yl6<~NAy3$rY$_4UddK6)Pa2vE}2F*t2P?V zTk&{^vDqSobT-f*4yw|4@j6az{Gc>mMj4Ht!GN%Dps=n|^rbaFo8}kT%|Au+XEM}i zK8Tb{Eyk5Q%UU^ z9MPU}AFw^u$C=JCZzXCG35&w`iDb>gOxac9rT1K2C9)YHik@Gl0O)qB;S@g!C5rNu z`zXP4vf&J$W-OT%SpXJ;=H}e|y|JuH!}~3U2Bs`_ z4$5Hz@kWSXLpy)Y?i5IN`Ke;OULKeU2?OJ8|5Bo5X0?gMQX-(BXqWEmVZ_<7U8w z=0dS9+<>@?`^%OU>fwd7VR&s)WihV`shZ&>n{l-g0DapmWFfD#I*{upvkZpRb>?2B zZ|s_zr8rHXM((`cE0I}V<$_0CH+urDS4$t1V+X==1!H#piBnH=;~p1gjmBqO4eS>8 zR9NTG@6Zog_*lmWSop#?Y}r`X?D);Vdcz_5R3LS}E>{5#G8p3GH{0mwZh&U@yDSTO1oSUGNdI*r`I9jE|;}Rh#>!n`mVT0ePLfL;eQXu9OmfDEjQXf%q-{s?DEvMe~TWR({^P>8CK1pHu#3kfAvD2l8o*mpAiyV~&@R zpJ=n4UuQih6$76b8s6xgiy}}mO(j!O7L^`7uFkMuGRXSdFi~Y%C|C1t_DUqivoDeR znzPnnl8CO@uC>DY83j7GsU3oUvtV)xJ@)*Q=W70?a7fxOmtz;$H292Ltob9z_F1*^ zjN2a48SKz+-T8C3%a~w&7iv^nK&8AZ9hTPofL)HmG|A7s032jOngyMbDUAg+HV@#y z*_W(Cq`2lRD*Ewe@GiGVg8J1?acaCx#2MZfTJPWZ5tHS^yBmS>XWQ8-G+b#7Q+;Ti ze-dG#5bKG zU7nz-N=Y)iy|~1W)N>x5De;dN7e!ZKmwLvg!g-|%G)r>LioC=l=+Td4%yD74T)4eH zk8x#_t=<=?`PE3UB!V_F!Cn`(q)ZH-bw-|zoM07Q?w$0S^#j^0v)t_Ha>)aSg7o%L zAL6T~YrM+!6>`CA8PPJ3zqxP3Kp}m9*tdf_0y=h-2m7_XIUf>h^I%Rayq2uRMsKzPhl72pRs+yBnr$_J-cvGzFJ*^CeG5{j?WW zN>)#&d34%uuqY;%q1zWK$=fjgMlMJtnof1_i|xBT9Bc-ETZS9^%g4)6V~B_?u|nuew!W5 zTe5;1fL`j9>FLwkXLh7=O!r5`dKy5P8YHRWQr-VHUiK3a>q&NzBAqAN|_NXvRxn6%z8{+*^N7KpG+5+95gAZJMwh(Ac` z?*elXSv>YSDea|5uB8f^6}b}IGlLnKj|rl{V6UDkJk)qBG#621a{dayZa(r=1GdaWO9FTUNdL33Fe|g)BOli<&mVuL67b) zR9LoyMj3A#QC60C<127_d1l)FC_Vp(^c|^B z7`$gn=emLQmIh5W%t>L9lvh%e zwtxEm-;LZqJuS~h?w?{6#Bj-tRd5e!9OrKG$ktV+sn)3<$Vri&#j2$8By>68D~0t4gB?;>F~;5ThWy+Z*}=dQLd0yu%ONmYwCIpGc z00W7~9cH#g-uh65H#$Y4h(@@#Udeod7lbN&@(>N0^AMPF{JSg#>R%FnML;?H&2%~Z z9cBM2cw13H2}rtNdlM6VBt1xLFIRXc$jOXOh5+Y@5axV$NQu*v*4|Oe|6uKHZl7h# ztMvX%pRcLA_d|!$e{-hoclVbB`#Me>=-YeZVBh`|dyjXY=;%AA2c70`zpH;HXW;%7 zoP>k@E0UDd&imtTmEt(XsYJ(1Qqwt1B7L8AJW8d#`}=41wI5$38ICW~seLZHytzE` z^}CKwJkddoygt#P*ZB1Lk$(P6*9S;>=y0q3v@*loE2sC(%=Y*Kq3etPwAYLO-QVT1 z=MiU)hFQ(;3~!!NVYTXW4M*|mvGO7N+7axbVgRtV{vNA{qvO@|JNa+C4A*m(?4B+{@?!RsPs$uTtUhbBU@T*f7QOq;hx07 zP}k4WL@-vrSe?^W%=mk((&{}&d(@C7p2QA5A|*$xWLDB@p!YWy(O*KAy+n+z-{f$b z81L+#U4uzAr0?e_N$mw2#*3$$Z=adD{cZ&B42hk&Gl+TVVUOtag%jcykpBL%nv$L@N^a!Q{ zyjN@d_37epgnUd-u;)iV`q88$iqqHc(Z*Xcf8X!>boGlFg&*f8kt^_sC`82_Ao&RF%g|tlzZJl0dsSav$bJE~rhF&9dmw3F(s-39i*lZo zAj7f+2r7tVR$>H?!Vgq{?h@H=Cbwfb0+#UjdE#2+TQ(XY{ zr4=z*6)~A21}57%f2?!LK+?Hot$XorM;k>#Kj&M9tQf+PJ6*0rj21bWFZ3t2y*BlS z4kh7jo?!?TVpf@qPan5peyTg^-^)d@~zV+)i=kkkNMmD>I`tB zj6w$bHQf!B+qpC@ddIT@1r&AEpIO&`+YR#dbUWuSW`SI8A3l!*gw$Z2>>D0NiPbRG zny6DfNveTf>)mguJP*pR0|G6OKGyx4R$p>W|E_b5TGPGpn!cw~*)@GLy{3c}l=|2V ziPvoH>|vZRn+os>HJXB5rCEfqa&?tvi}}rH50PxBX^|QX#Kn6 z2cwN7-Cs^VXTXfGUypL$5&IGLacwfvW^vm@zQ9+ISV~oqx#*nq<0hpO)5{`q6h5#9Tfdr`*VSL-G ztfd$#sB?8}iPyTD(WklYFpIyseLEa@-63G!C)bkDNg}^LoBX?bIKH=&G~n%0esclj z^T~aZj>ltvnj+!S*ZY*hZ2C$H5E&n5Da&oj+hs-8-=uAmrB{<|~dY2d#MmW<6&PwhnJA!^7-MLkYXyP}jZw zZR3ig{*B{A-V>gRYM{ObAl+|i-T#`7PK`e!xNZKC)wAQG>3=WZ6ySW_BB_Zce$(mnbpDO#vo2^TOdF#hUPxZ`^9Mwj4X~? z)#nwC!0EKn+w;RjdmeS#^ZnN@r9EId7w4IoHLQaF^h3Kng`>15kM>-LBnAm;PK%ht z9XSZPcK>S5OP;{V3PWjVoS1XVZs@OXh6kcGx)> zVa?0(_j2u~2m*$PWLmvam6%|K`Ud?K0+h(0AfJ=JYDXOx?VZ`@4z#?$P*M$KstTTd zpZu=I(326poc2;veum7%y5J+8!D%lK6gHo8M`xVbCkFJ2VCuVUywj(eH>siZAsJeP zR;de;o2y)RiXXwf*pUKXJEy7)a@`<>cRdn%Iq;3&^JtqdG#I$QpKtjD(=I9Sy!UZZ z?TJ2o#lwHJ4yaF5_zYt$BoVSyTv5+$aBzln@wb5w(n&VT9Sg zH*C95qpp`mUG{$3v#m%()`lAe>k@Crz1M8>azE0C@*A^3tSI^y<++OovNZ$E!;=q! z(NE$J^LsUuub`fbba5=vVpoZu_!LzA7tX4Bq{bZdMEwkxJadBi^0c~Oa)+9i3)4ey zMg1Yg_&|$X=5QvQ=Wh5?qIVlsOGk*n(jDln9*>Xd7IIgN5Y$1k8QhAvFWerQrI+Ax588{Aoll| z%t@9vg6~+Kgztb~HL>+WTwGJ(jg|V+QD5g{YJVVGYT2{ra)=qBpF+e~)o0nm+KJvp z{h695X^thY{qi{dx6l(8S%H2LS=P{*MB(2}D?DAFMgAoHonsG)YPv_a9DMOGwK&LK z$3=|D1RUG zfd1KVMvMbm*^eml5t%Mo{r^Wt?Vr9Lrt24lo|O7W;2VS<6#beWz9@S5n@o@3HynC+ zl|~P56ZCYX@`w7_QMqF%V?bIZfqOuZwUtCPxTmvZ5kTY*1n^-{BLK5woTCXiSdkTD z<64qyl;nXO0?%WVEHf`(E}kG2cXX93K+w73=S_B>jXs1ZR--q~Bg4<*>OEz3SE)** zKnvz!qiKQl=f`CGG-CffivBq4=Wdqh<#;;^P;}ysu;3rtyzJfmF6W`A+!qNv8x9hq zxmX?F?ZdzDi+nT(fRg0yWT8x|l=tHAQpQ;fhiHF?C3M_a`uX~vR* zSj}}tjRU%dr_e&{KrffX>a80%G-7+K|Z=H zn7x05ED(tx^azo*e*B{P#?k0;+8+SdbSdo*qju5vO`@X!+0!(Ka9v#b+9Dp~&99E1 zGd|EFc>d5Uo3fkV9S+>z!jCFz4LS*DL_`@-Rgqe&TxsoM-^umr-oi z`}*h z?5HATIH>WE7+)jp?Z^J=0%Pn*=ZG;T^t_+(q2k3Qtw)kSK~L&0=8vSsXkLl$eXpvb`9oUZNBpI5ufqQb zSm*eMg$Swr_@ArtApg%tFR#y%%A;h0(2LRAyw%6H@y2$5^TbJ;g;!^)MN0FJ0!g0j3-`xFngQCEC3G@RCti2&Z7b-vpE~*mq_a|?DJEVX7wWWc`{QU_!3p$ zDEN}(8k4^Z1?j)_s+J%z9WNsMV}Bm^H;~_n`8>j2zYVpgtofq?q1NpkAzlv2VP=aQ zJ32MAU4C~b{Z{pZGk;SVva&veV#T$^fqN(eO^WzCbn-fOFNx+H+Ow)N;q1oqJDj~ zu)fhLtN-~g>vc7FweisBay3ldQOlAVf12^U{O`>3+h5OJz5T%Q#0y0h{7_rW$qH5B zQQIgsO)=Sh$^q(P_Ee5rDgLkW+n3}Y#K<6`e8l=U4$jJI_RsRACaco=dv%odr+G|3 zkJ#_x(}3T4zuhH1^ov|^|M}QDca8O}je0nrA>vDS{t&H>TYEakyKM{#<2kZA^`^U(8_Jioh zi9gV${E1U0$SC963=K@lGjHEKvSuXl#T6d4ln@nFpNS)V7*uf#rA z`>co^tCqATp<%uGhj&bG%etX-Y4ofyKWKeg(h>BNbRv$d>qkwOWla|vqUX)=JA7@6 z{niA2ilvi)vpX`{=1-|_xXgNcgWJ_P-~UPL59-U5>sG71ZoJPg#vggF*1vSTNYd}I z@js6WFD{DzNjjg5=%4sRw8!`*&})wu(T}H3fK-;!Ug=VR<2;_tz` z==EOXlN`SB-gZvrcD3P0^7ooON6(1@p*R|BT)_R!gt9)sVWNLQvCnfvIXi#S>@;tX z*hyUk$0Bn0I=NU-uKrNbyUe#@^Q4@kLuLK}e_MUfsLyJv&o=6F+Uj#d`y4#{97HR4cnRQQeGwf*q{jLj0t>Vcf>}u{n}Nvd z+8XO65s0NZvn5bas7suW^R;dS-~;d({V3ac1c=73AB}S!aTmLt@h!9tltS?A)nbi1 z^3{~>p|rpGR1qseBUjrSb?4DmZ7RMxx}oN-Z0je@P%%hmjK52nrE;4^_P>d7lN&fZ zv4uGmvGQ)~51hzj<)eN% zTzHW@W)*#f&0BK`&<|b#TbRG!Rnxy>c`cYkbqPrSiZ#lKV=eax^k?+f9pcQ^U;QEn zw*Knl^56}5PFW*+Wc7#N=sS4q?fzr> z$r}S1{V(^2Up)5arU_I~{i38e)py|7Tm3JSVn2kDqOU)E{McKYCJF&iJutmLd^)+5 z(^5*`{<7xI><|t3S_S;-KgR!hG4>oQA#~c-@yEwS7-Vv)rcIn2S1cEAlr^yD_@cG_ zE7n5T@y{Ki@)N(M^b@a(cMP5}ws%Z_MTA3Yd+(bZ(ht1yhyFfwNH3ZFd~rqYrdwy9 z5x#qwqQ^hGd>)*KkmH{fz90YW+P(w5xY_WZ`rDsP6F37e4~8moH*~M=?@iJ+9^$;S zK9}LNtnZKgC6Tx?Al#1|^YQlMTO+)?IZ<2he!H^X1Jor|99R8yVlO>*VjrjDcuj|> zh=C*WN6E_oGry7{F}bJG`Zj(PTD`2^b5drf7l$~%2|Y=g7E}_wOga z?7tosw-x(*m%jd`+Y2Q9P;&Ya_JM>xU+5vdS-Km2Y}bMtb}wfDpo#uzSLsic3H~+? zocy#tb{qm~cAN9OtGgBy?3TzF&aptvFH{5Kp z?EyemT4?yB5Xe=k+7x~3K&}*`;5RO_KA69koFTI*H@_P+Kq+DGB`dbgN^3!*63i4Q zI$BI_>=8EQnNwE^YG?ZDHn-hm8I_5D^q1=LAZMgnH1m_R1NeeFX?LSPza!KYm?vRv zBb))NwEyQ-xPzY1U#Ii+rTi{WU&Ql@=V(=>_1HJbR~m$0`CZLTS=`eXT)APYIaRD2 zmt~`-m7vL`&@K1av~)Z*TMoqNvetzS;0$sboV=m3b93POh!0 zu@<)o8~jyP;WovOl^X09phG&nx46W@6W`2JB;}>}C&=&Q`I?fL^ujC~@AkqyssfF7 zb5^TTPV|Xr9v3hFQuWvGM?aqIZ;{>}e$xbN7v5UR4_Sk2F7AztMZ%7>pn5UhDRy~b z@%{=EP$^;_?iydnIoW#sw@ON-b)e7__D8IDwXRx$%n0POkKxzmCR|VQS}i@U(-TjR zV0zYJ_8!EBeNIFM7ywBu@6~xK@N^~^tNVfS$q^-vhNKzEy*F5K*W_1^FW|oP~y%20=QY2PMQrXt3cj zWBnX+eSvv1sEu6DEvt?7b0w;YM4LE@ZKb53dG8`4E-9sTntuG_w2A?}DS;W~Ul-ws z_y^2g0rL_{<&pSL;P6QfHNp=p>cOP=U-COE={M&a5zvC@6PVK{kUGWgjUUCb)PP|= zG8}p#I#V!zE^|}v@YL9R1(d44X5UKbN*F_)B1t^ExoH8m;z;1>eGzLa=1I+V%tqK3 zOBWWq8#2wqZP`{I8@hVJ^+@)5M6ddyTW_y7pr=sxTxN0s&1GhXrzf;`<2VH~lupU1 zm5_26x`v0N<5nv`B2R<*DFG%@M-1&(x3Dauz_}>K1V-NaJohPfP7Vj76O_k2yTx_@ zc@ee6z`U+KSvK1W|6??&67B)^yR|9>v$JCcm%o%0@~vqa z3lIPzUu-=gquVkAPwyR=ECUf;-SeP5C__#C&T!COwmABX;1*Gt1#uc*=uJKZ>-y-$ z9CmW00%W7Qn{CD5h{^9Fo5npW*2q|?d-gGU zUpTb+FCx~KO{}ScM2F+cI1IypE1K_R+4GtcSbw`JB2`v3i$AYe_|%)Ja@o`DsQt{_ zIl*OIZEViswz}r`1a8^}8K2)J*ASU+DNr6s( zAgSputCj>J$W0IJRAq#=_&3gzFq(zG0Cm~=5X;B*Ed<#NTHjeikWJpsl0dzM{|XrO zZg#G4<4UL?F6d;+lFgZ-bM~2mZvHzF3!krwSf3bFL6T*zPPh8e1_<59>XXu=Pt{=* zZ;~ch-{xIJIH6!4wo0-TjV|CRAHrhfD`rJhv!QOC}jD*e@Zs`Qlw zY;tZxzk5M_kTHK26%yYRz)SkeY9B=#uOiqw80jPo>gd1^r8F?($iI zB>Jx#lm6TPD~W!wh@=y3kYIG>i!EPEe4j$j-L`>CU$Y|C&UI>BtX*}8P2;__Zr9u@ z6=j?izA;PU?N)eiJVs>fpj_jK=O5@*ud&59kXuyeZCrldfPrD z6Po{@tRSNlcdNNq=FW|x)xEK(K9lm%+~R443|{XIm3pJ&h}bePl}DlRQEsCo0D27} z?L;{q@k;55l2sL4s*W4dt$Y-%meL^gZqcc6Pi3gq3leZM0a<+~4+@QU^GLvfn{hDo zos?jx#x3&g$MW*#3)25hlE9ofkRRmkcJ$L5Xz*C%2Za1mpR|SB#f<3&QP;<80I^{{ zElmFOs0|oL17HWF&ww4bzz(RNE*%QTB3|j0mi201i1GtJIrJk%t2;}AZZ?O><}9_p zPug#duy@FO0Dpr0pB-&^u%Z$$pSWnQLUm!SmQ#rwIurZn9cl4qca%&?5g}@0?2Fh1 z{EV@wte{WH7qNo=K#f*^+=-C-Q?q={$+FeSvQB3aGfTpI@LOL|qdxU8WzUa`v{%VD zvB#`N_bSIB;>I^z%gzvcBOh>6K7Fx;{?FqeTOT?AmO@~4EJX?>%<{qQa!xdr{O_m8 zJK7U3e;67+aQO%SX4LX?q5peH<)d2}L4OeXvmt+qkbfib_bVmJxUI%WZN=xDT4X$k z@f8=${Jofb*$m(o`iUCz1|vEs))}s4=Bf-#GiY8IFXGe7skIW9M$3g-51qbR6!dB| z65o+BSnU4#fc7S&Ia|7`Mc}GX>L6T+&rIk#e2T%dB8v!S=eRga``C}O#Yhgjp2$(2 z4OW3IioY_yyW<_Nw9VS1+ojIdl3$OnOSiwgpY&`NW9uVh=nsegQR@$X4@yw$4;}TP z6Cn8xb_NXS#HkMb=G-h%-gM0csDTbAIoL7$XYwf(&}4Ib#45Q<<;J&Da!J z>i%L#$dnzAfCq1RWT&6coW#f}<7Ucg1#R+^^&Wj$c8E*VBn|>2ut?J^4uU z7KN(c?`?ipoxhSH2n;;`9Je0YBk`}x-BM4;^n%)`!dJ?AS8$@jcuKgx+e(w!jmqpvOTZr_6A z0{C%1`0YZq3Q(VSn}>oMK9o>( zuTgslRNad{M|pJ;lsuvAp1ohx-Jyf46J=t;m}1lirIZZw=FagPj*_cXd_hMO@t#>Q zDJ!F4+{q*7Qn1mIK=@4(3Gd*m871DN{a!_PALBjz(7QFp+}2>|UB~9ovsi{#F#la; za|pc|z51?U&%5aPtXd3M9OU^{&Np>bl|5baaGaxMl2;7b9CG2PWZJ;lNtV{(hP8WS zAt)}%5|cu4Y1W2o&G7@Xup#{(b_qA8n&Vfqu*eZu%9RTEslJ z(`X8A2$rx(ddnOk>fo z%3jgqmoC_TPbOv*wm}V(*3Ol)SLAl7-_M2W*3Adu-DC+^#`Vhg_y%ThgZuU^{PLRb z3`~-p$`&7Bgbmy8&2*U?y&PaR6YD}^Arvo{v(Ogrz~thq*!h2&oqrR}_ZDYIuawl3 z&^h3*wVr!YdgnYtR?c*UWA+;5r;AtkqFYp|30w_b;v4un*G3>#PARZEJ%Y!u> z99`Dw9>^?3fq!236m2kroN=P;<9z&W_^kemi*1m@Wu%k6jFo|gR_EAJqe!S{r{}Ao zMC*qXHqfAIxGY}7^Q>Bk9J_*Pc8OQ%ie^ZWu-Sai+~f^y@djFNNAeC7=(m@XI9~7V zLaCIKsMe)wT~7sVtJzsH1ka)}5(aXon(W3_GWZyGa`~5dS_AyD!+ALJfC)XeJYX|E zFQ_iA2f7nCF}_wt6?H*OjQaxfdc|nCe>lH8ba1mRBpY~-|7y!)aJ@D*Cp&4juaK-u zvO-5TUZtk`{ePfI)iTvL=&3$W=J+Hv$NwmE{GAKW@dF|hxX22{V5g_tUsb`G&(mFD z4XYLOeNB4im(27=pBk8b=Go{~f~O_^!RvGgrXlN5tp}4hujYFVufyErMMKX|nfj-# z#R#?LPkmYG2iK(P2MZFfQuTw98mVC2LV|>TP@ayhAH198=tBCz0R)2*y0SA()ervH zsU-d2FL5>#y8kUXeL_DdO|UleE?qxp@~p;clqPBY;N8guBxI?x{xF_!!8;WU^)!6C z*%Bo20ggHb$FTi!(kqaY9=aJ$dZ=r_Z*jpLj(DQ7y~|ahPI}XON1pU_J<0q(xJdZl z0NJj;xs%T%{p{|bJRO_==VKFTV68XGa7?Dy$NJwtUMShh~`$- zh|>L__3r~r9@(a%R1b1ji(8p4{4Q&!NYO?dF#TM0Ss9w=$H~0>d<`mn;`1N0VvQC#?pRN#m+Yu?##<$%pi{vQyHuS%Y zy}R6ECwG*}wSTydMONxhAvsp{Vm{w9hZ8=t$B`aiI~=E?_?PG@Srr}ROhulW|O6S*7><(l{n|21JUD2Rz#iu{poYwr~XIwlVN|~AJJd*R}ZPH7~Vnn z5ly4zUsx+e>TGYy6+mK5t^y{-{BzX>t8UN!O77~MZq{adj&cj9pHEqtX1xtq>1wk! zEB|QoAGqG4HmJYIJHNd1^p|G$hll$1E7V~hwSph)RguAQ4`udp^qOuCn&Z{Ck2&8O z&J0|MtXB_%p>^o6c{5l2ZWuHNLs5UAwUSkFB?oI~Kaq5P-MCx{q)R^U&cBrO^p8|g z>XkK`GwBWH)X0a@wHyGme!=y?rC$4Zmq-9H`$j?vZ`gKIkKUirFdQZNZaEaY3mWUMzx<+Fxw1-ZVztDjEM>nsb&P_0zOWWtXSCbQlBLe$5$Dz z+&73F^c~AZ@N9Z$I+Y_AL0$BSt9)140!EbrP67WoVr*Vu-GXw$KI@WS2Atw)`VQ`& zKP5Z3hHYuPx4-7G-o4$Vc5nM%9Uf-uwoE2nX|S>i(P-^zXIct=jI-8FDQ7L8i-X&X zqiyPZYR%FIH(%tA`s0#;%Dn6M&%3TN0HJ)$@|gI8TT{D2jE zK%ZyT8B&EIIQAWsQ{_QxW6~RS8=|&h2&1PaAJVOBWr?#dgW#42z$^_(*E}Tphn`pg z+hs3oZ*@bx8_JS?;_VIe=7mkO<=G55BKRxvd)c=iwn_(q@F=~6FsIXbe0}_}4Ai6z z`koL>QO9xoM1qxa9RKYJ)ypfDw$?d-zctzcJiQ-lu|r2$+QvM7kJ;IJe5>s^YGUw1 z_R(S{27~uF zFbuZ!sB>g0;i=rqy_5f24i;Q_9mZAkg)TtBG;AoR8 zO)GL(GT05Hhx1>k^z8G5{Yq8x2&X1Jjp(rmx4#{>I-f~Q=SxTzBTIg1!PvU_kKFHsx(Vx4 zcBq?M^e-LiMtsG@OR6#~J*W*)3CW`6D)fr-vz8l@pC2*bdQ#_GQ_wRMI+r@%dMahU zbuzIuQq`Vs`teWxA9Zg7A60ej4Nu5GMi`kvA_j~aYt(3{MnWrb{749s1YyD?VJ4xN zgtpeET&oC~5QK!_WH5)r@zP%7MJpEj(AKuxmOi4ziZ%fx2_QsEH6wFEwA3CSAjW*sBo2{W zsvnpu`tC~^=<<9T_!Krf#GsNt!Tq_=LV9^W!QkbumuUAukOAoP{kgmPJ>(_rIQ%6v z3>CSyd{)7w!xZ)L`XT2UmWGv+ah_7$bdP(`yEsL=67qe6VE>*x&n%6H&H#m8L7qH| zk_W@$yd+qsv>*of96Q9TC5a>u$0-X^GtazkPEu20Qp3ZOP1W&mZ3$HzH~P5_6b*v& z%z+O>yse_QJPXv@1`SI>Tn~pyM1I~FrnyCby`^k$SC63@1UH;?oFyP-0WZsUp$vT& zRL|=y#XPsz zafzt}d~?Oy^xP8PM zw3c`w0E2;&ut>dazwb0SSTOe+z`=((o;591o;wYOFsmlRrCCSyM91z`w$*0Gp_+@* z4_ywOp%cBMGg}bd9=T?~^Chw*Ku0?)GRV3o!x|$o@c$VgBAFwl*dA|1RPnLDCmG)H zs~|m;=Vv6Yd9MXe{xjLt-^(f=Sat8}(OKmuYRb8gJU3&;y7?K|vhb}Ht@t>bVjY&n zNt2u4nlo0*66|7IqtAIpFUcqOP{A{?)26{<;&U<^Kn?1c*%x0VD-roSh0g zv{I-{I@)~qLl3x0u7mRbs;bo%>dH4^uN^L+n5V6CfBF7YUrK%Pzm3CZ_4BK2+>2lJ zCSc+S7O0(sM}rUxr{L9u-&Fc#7(fjBrVzvKyO0>R;Z8#J{lbZGA2>l6cAK-0-4)-q zMi;{_6qy*)2Q`;?6Jl5%<;hvoAO%UlI%^}9;Nij{%TVVCJQ7}Cx_+O0E0*^iOTcz9 zJzIspQT(GNk)&Au5#=WS(Q;%UtR?jO=GWOcSsw~zuS**UhNeclHlL{KDq*9=ZoLr{G;LTfFGF7cl-iT zM0EYI`@tPUy+S{%@|OR0{jjtP>4&9}epnjx!_vZ9Txri&83!_FQ+lz!L<1Gu1mSlWpCVQC}jhcSPO^gZW+gK6k{&OFOL zU2#L7s36Vp22^dpBf16A)?lWMZi1P!)G9w@mzKf>bE0&&5eGe1FEqZ6k1^ig|Bd{o zrU8YwKLM~&_D>>(-R}kNv^wDrn zs)KNZ+~m=DeiY0&gN(2P6-ARR{*@8zGz`!pkZ5vY!ZAdw^S`S4~n?XFok8F+4pyCp_Vz5-HACz|s zAM|t3#o7my?HSZ+=FplC>9X3E*C4t$Alj-m+8U7R2O-wY)5P~h#&=x7`=t7QIj>73 z2uEd(W0k?hq*iAuuoq*>?n zolIO8BzjC_X$--4wvNaT$W01Se4NB2pWx7f*vlx9rL$Z`qz=8=P}c`9)72o% z2AD9TaljDvOVD70(}@Pt1Pyjf^7Tp1{x;_^romoZuG3&|i%dEV)*;?Tq``6_3klDG z0esM4F|+kA&DXncf6@8+zh6CjzV_l9&)01b^hVEDKx97me62@@FF9XH{{!MM=WFnb?XO!z3$gp_1(c26UjtJ432oHW;qx`1O~Nvz>eWbBehSWDu-I4q%hEOo!EMkx7S@ zL%fX$s~7$_GOXyDMZk)-kM{RXeYW6wn?3V2pl>=!@>1Xi!;Wwjg&7bLUxYZyt;?@A@an)8FLj zk-hUviUB%Osn%wNE3q{m#D@*;#c_(ONBfj4EnrOM?2$VmK3FzdLBU<|o_@J-mYk)O zA1+bKd+^^;{MS`d>{jyTy5)TPP$yy#G?6e29+m6!{R8p7mHxW^xTTB07yy;svn*b5 z_CWNaxQ;>*K>H1JXKXL}j{5iDlH8*WLGhQ_i()8m;|59~LFO#iVV^6y4%yR{-dRkC zXpd%LAr1_@Yvvmuu>~^0sH4PRah(x3KcZL4mDN#)>Uz@ay5j2)$fW-A4YIQf0J6P0 zH(_WVEQ%8rP>s+;TKzutlPdfMDn$RNpP8>$Xw&ribdr+jN!@q_7Vfc{>u{7X#-!Z_ zl}D^9Ar7GZN2*p(OH^vvzn(q2@lD_9^fq_^vO~{*IFU~D7==He5P@U@6Mgiv4E45>XAhC)x zyYp+J*}eH-VWCFU+fJ3nBe(1erIRNKenrB(`9;t=fk`_nYS|tAzEn}9VfAcN^%Z1X zxfqKD7IQScpW(PaK3^=J-X{9;J#Km%R8)93<>*c0tupgt* zf9W~xd3;k`8;Z2QLeQa;7+WRh25qUx97SSaS}M$^j$D4^)E^bs)@!=8<;aqG($Nhv zej=0c2nQsWZ@^2jRdMctY6cEwgg>&-Lz8psI#BA2qCp1ib@rHUPp?jpd{5v0RUZ18d;B8HK@^c_-W=#7oW{ zva3t>?2&ir8*@*?jMcWrDYVCP=C)eim zqeV(dTruT(7dsE>XrY8&-^6R1b2CPYG46SlRKDAsbh$KT`MR*t%lBZ{XZcPra#38{ zq164Gg@DMwx_Dsh+gDd-DEFqC8e8hq;0fLHwC&XvxS>d1Y)T(k9LJ-22n{r>j5F28 z<*tjreYNkfT&LGp*8|e7&AEdblbkJbm+aahdmdNJg#3CPT83Ze+h6s2Q~>T&o=_q z|K(MTUAI+-k$P{5zwT*MO`5Q3M}OT=fZJFMrAp{g$k1|25@zlBbC6joZ;_olWmhsR z#Xyt7W)dLY2wjv7RH<&x4qJA;Zzv4+E89yD?)c_XI^CcTVQc&^>a}Mp`jy^BJ%a>7 z$sKZ+8Xc_WhDEEG>>{*FVigl6S#Rwt+?wp#AnfPSAj?nudtiWto-K}Ray}=!o-;bQ z9v%GqD@F%FdU`e&ORhw+*sxk|1GG*{UVFeB9st?L zqnY$J^jvmsB|s>1hKeAeS6o}r*7JXeZfnN~ZGDs5q6S=#V{GX7$(%%J_kw8iE+TFV|nNFMoo79f;lYxLnsn zJ<5iAEa@95(l>z0c4B^#i@{;@qI&ID$jO_-zh^FX12~_~9=Dc&n;bBdfkVpsaWG@s zAl0>}NIO1_Q*nY3cP{hwh1&f$iStz42MUJcPoVe|!W_;d*_{C_f1BhA5w<)aIXk7g z7VV)cQ9Q={;eZyvH^qq%_AjxyA~QU?u$#15B6AeB#6hv#e5(8MlZva0>>rR}nEivR z1&fI=WsiI4P3+e6>T7TRYW3B(XZRA=<;LA!TW!9*%y&_nvkNgGO}%iX*o9vc?Z+Ib7=P4$bAwsf@QY406_RwQS;>}p>Z2U-G{WEQYUHXLJlx&#)P zp58AzL+N|7-OBBmV6x201bI=5%_KY9hnyi$v+&T-Pz%&z&PGk^Ou#PK^_~9ORFhnr z!gvGjtx$(!pauqb&=bY+3Xek3Sfjc=j4_f09>Dqm8aNCz@Z#@jd5FD0$N(6lkSJH| zA0U+y+C6))d*<;f;uYP66?=m#R2m-yq6qI^T9AjaeD91oN51lJSI^ zoCjpr0dxc0BvR=@KX#%Y75Xym#5uZS_&SZO(F9bF?L;KEzCOQ;yBOAI)C&B7uIV5o z!za6V;PYI!Z@D4XzGcZ}bR4sN%ljMAf7rLcGAzoz1#f5Bx7_fh>|3}7wr@EMjtP28 zeLl;+1-T;jEuYm=#`D;>pa=hgeM{Q9g0v=gTGJey4Q$_%b`JX%d>>}tlE(HeX|Qie zGik?GpqsjV3$#7iz9mh!Z%NbbThfGmOB(E3P~wt@jS}m;+=U8A&B(qbP5Vz&M)oae z0ro9=3-3`2!oCF!2>X^aXn(+vCd~)?mNXN|hDWe(p=QaxMQ`^uqn=2!WZ#metwBOl zC5Q;vw_HZ}>kHbq81@{Ph>`3$4uE}!X(nyhk?lE{q_oJLP^x{eAAa-d$79oBYiiqY zX~uCHvxc#Lf%~DF<8QTM??8X5SkpM4h%LeE8~WZP@2bQg{>S896TfB1TZZZPV^>A% z_uC`-{dQfy-#&tVKi2&D^!rB>EoaA7VYq(36=F9-zuyl1etWEb|LY`~w}^5v`u$%g zmfsi9?~jJ34Kw*a2G8A#40z%^L_DgGSl)Iq>0zBq-p04SFefRD--=MdX-MewyBJLd z*;-5^Y1#w|hamSO5P~Jkt=n7RcSL)Ou7&bjpuJ#lF&%18WNv;u&;%afH}O{i&DqTzE!=!G&ImriiR8!qw<_ z8}NsQAPhpI#x&VvjA`KpeRbU^7bH67n3vm^g0EnAOk9@m5}ngH2s;`(z)%$CB-g;4 z+Y{GT8y#L#CZI&#g+Z$cAy+a2SUT?+`5gdwmX41rji6JDhP1`%5mjGy>9i^t;D zyqV0{4r&XC+QWsPk2a)}J|XVOyGmoZL*Na81!Q>BZ*$YVdptM-;4?)DY6-q5y$*g& z$2UP09BVBG5e*U|x*od-hzN;UV}x#W9&0zE2UMOy=3;)L8AwgcKyrr}NCxX@5`1~X zQdhyH9{i)b!ZLtzVlI(8S^1EE|0r$Jf56_kr7eNH%ED>0Z#4Tjq9~(?SUBqBZc8Xsh$5BJ_@*Q>?8k8cZtH*0)tni)S$XqqjX&iI5HzhIYb+S7 zh0I{RjiPCdg}1`=4ry#w-YnluV6fU=-R*U2Q)kj)z}_btzlXByGC^fdTDd62Mg?C8V6~c;g!Ym7Lfc-!URySSGOF;V3?CEe$|B3-; zB>E2yX_>L<lP+tpE}wSkw>SUYNW4f5sWP&ifan`rm2OG@IBPx03! zf>+MIE5`%z;BfKpCWW-#F?7z9Lz@-0032@(8|q?kZZL>C`H$n?_CaSZF%H&1xTF=% zcbAqFX`c(Yfl;y$Ls#G-+O2)Uc8rKt=z~fE@#dK|@4X$|d1yQ^YaT171#B$HjDWH| z_DZr@E}UC2g6mJ{+$YjGCSOePbw$8bWH)Dazzz<@^boR?D||k%eGs{5WGI!JsMg}JG>1PG~ zV82Js&vMpXX4fxysZ52Pg(Arv;<5?yJbMoAzuM=JYyi>0g#9vUQdGTMyFt^@Ovdt|2k~n?mRoY<mON@?yyCUe zHA~w`1O>Ti!FEoEsweqqxe=I$E+S3~MXA{B|7(BzXXL-1U9}jR5KtZ&rae2M3=|9P z9o^sYpRpxT&H3%I$jlpxwesg#74xg7Xs<(f(b$3&=C;lHOgkx5`Oqs|V4*%D722CB z+)Wj;6>pN{O;HMyr9vYpr8iCTW+;VIC3p!>0uGk|OnQQg?8$_G@tPqaF2a2IVv73- z#)MQcOTsQ=z8S(4XuDLmL;KOi_#VU6*slEs-xSx5BJIy? zXvkcRZQ!kH4~WdMT#ePb8pk$>nSID`P2G-YJ&uODWZYVI;d4d~v z+2Tal(-zx@-lPjXq1^+6gtWWp9!;E_tAH9YhyHpn7V1-ESO2Q<;7P*quj4cr3#XZ} z;EmEmzEK7{6Z|BYt}yR$E1Z>l@0VC&U?#vl6)xnkcLhJej?fZO5<9-R0DyGw-&zd+ zCcmH}nQsblK`G`M(MM%Zi|l(2^oJL`cDr^7ss-CF>e9b7MXXy0PH=D#+v!utm{a2e&~j&uR-@jwNG}|BewrDn03dt{}~wWu_GV3 zeW2puAV>k%v1%jr0qPE3?M9;yF2JBjtD!!)K)O4V6wh|B3a!Ks0t~)l^kKRIgXMZ3 zL?o*(*Z$Mt_J8?99R{NPlhENCvHhQrT^r3oxcyU;1q_C@zou_J_CRC_0M8K!yz;W7 zQYirT%8;Ck&?k~ojYmh@>PZMkermb>RBoav1u@wYULAwtnJsd=7MtFkzSx@nZKycYnRSaM{mP(@2FtMn!1Lji6+ys zoBhs)u@q+V$j8f$BkV`lk&o11jVsWxYBu$A?9!R)=;u&I9le=KjzyMW?-7bPLPeHV zt{LKLO8t9J2KV{j80zZqw~Y;StX5Z1>1A(Gl?`=|nyN z$O!u{vxYx~$Ad`MtkTgs@&!Mv;86t9@vD0xq$5{=^)Jr`p$}H<7b*X@-6NG}Z(U!g z{C^EEA7ifv4rs()Zx>Orl{FW160efQDZg{=7AoeO%I=>a0<{q->BWj6zM-(^H*LQ|?#yW({KMK_eQl7Wxa zk%S0j^X!f}cAIhnW2CGl$coACKLGWnNNz`$?|N{ri2qfJ%ye56^}@sXYq42zsd41m z>s;V#N#%B}?Yn3dT;+N8tO9%8t#A@f!)4m{V09w=V3cZ&u@z7cv?a0MP-zw_h3uUx z{mE*s6wZ&UEi?eGxx~`&aHM8-zJ6nDEewW9vER@Ex>I7v%5hXm+{1;HVHFjzQS`rn zZ{+@Wh4%Vu$p0nzr{c0j^HT~>UiIe1iQ1(&P(T)A(cC$LAtEBW!n>Bx_y+S2h6`?j zotUKbpP~oh89n!#Z+GPqTfEoZ!f{+LXUXO}6&J!QoR)!VTx%WFGkBtJAQn8l zpI)GklK4UsKHqo(QDPGRJsb6(G$r)y@5d18(brYRFQIY(}y?a{A-jkk0h z296!-$BiU?E$fw#L7O=NTmrYL3_M|YTKYtaSb=pheUWPWsMv;0O%`0tPvSY@z)!4env6*NE>Yd;T{Zs6>QfdY@6^fe5 z^>&6{$9~^Ycru6orKafrdaQ*_HkU#1?Z-vbe%@E=GDOMo&ZsuvyT99Y$tQKs{jO+Qc+O z*es6`?~#Qdyx+j0*0)@YK4~0*oEV(gz~RGM=#zpFH1tU!%h&Zu@nqg)2R*8=w+x3WAaS=KKmHiO?6ZHmMKJ9KJmb z`cw4h&i{e_Tz;PZ9L*W2KY|})b?mODeT0CLwH76R0P)hU06wtp6pLuyARC+x!e~v{ znix4305G7US9#Qq>DP7;)zg?XQX_`3@ri4;jSx@Su+mPtoI}`t%^dlp6I`5I6V*6)7WUV zp4nX-qWX}>I6A3(SCz_lDqGldMpo_Fj^ipFMMTPvA#IwO@|fUr@{jFwg17V$E2!;ET2w@*k z+G*GZXO$ zzW4$KO32(UC7GKW#4Mz~7(Z-?4?_H={dyY{yx&JRh~S-Jjc-Gqtg;!<>o{l3j6Y(f zoy%E^;?U{cUyDpSy}Mbw>85$c-G>4P5!mGX%><%ZS(^zA{?!AJ=x)*J-+9}W(!Q(- z(B&xYk@Js1B0p~rjO9uXlSIB@^$8OKh|k-ieGNK1^n1s*0n>1G1oB8|5(7U*_z}bI z7cIbzbfqf8JR1iP=C=qN3aoCDv|!yqIHSEiKr4ovYLov*a$4U3AAS@j^_4$_BTb7n zMCpXAlb|F{3kg-Qn3BY3+JmR5o+MgRhF^Ivslr2J>!uYSHCP#}ERV`kK47|g#R zUIm&GHjij;P2W!3lA}my#;-}BpFsjx{VkB1m%~;BC=en@r~tJ6J_&^IsrPIfT2BSX zwP&_qZ`gj=a;j@wt6RGl*JtEJ$pmSJzr_p*Ws8)TG~=1WC}V4Uib!a3jHg#@)+?sq z3i_vy#^`M&U#P8tzO&ns>yx1#aG{c~j!+WYibAY-X$dvNS# zRV3TiuDpo+AV{x}3`E)lEkXP(!{4^ReEg;V_1b$aIy&DVb3S?P0Sp|Mo!jNYZvsv4J2^`2DAkpR5}-ZT=yrLXG3rhvuVo}`95+}OpCT)l!WkaDj=?aIj( zdAS!GPQN7x_#wKgmx}8`7-Hs^mhe za{I#gA^vv=uG-tRE?lJKP1GluJ3(#$6{<%`UKoFb{=qw;Tl+0#3Ti5 ztLydddi@&lH>)B1O%_;bcv_yVMqQ8OZ{5%7H9h_Kvnflp)@Y5r%_8215}T`k^=>J#?rcfF$`rIw8tS+;+;U^j&NU zDUeOiVt>z%qfvMow1Y#8K*>JriqT(a6jURPf@+Hz#r-^r&Yxlw3QdIEOx>3CsWIa@ zOQAuvKw#&*AdI->3RqwG|)BvqkJYkO#jtQB8U1nH9z%cl= z=Ecq-L#(iTbL~RC^RU7J?f^%sh`|idV3mg!4Gvz zuxS;I$vh)uj%U<}0WfOB05YMPAZ?;Chs>jZ4KWD9i5L)%ffOCoO*>JUc-n&}+QJ9$ z>)3Bwy989l2H9(otrO*Xt73+U2#km=N@B5Uep^X=Td~d#r5;kOht!!C#he}brbW5E zO`T~EebcTcgsxWeE%>D7)BDIs6*y{tcSm=x8ldro_Nf6HerO-Q?otCZ%Fuz}zV1E1 zJ_nATP>L|t2sFpuXU$>~`Th$zknqybv z6y;umQu%?RU`Mf9ei%%{18R8)Z;I!bT7Ik;56j9hU@UU*FdRas&!S)ki;CK28=2FNMjl{^rw4<+%~3ckdYBMEsNa(98Dk zwuI9dNSTkHZ2#XIBu*8S7LOB*o*}s6@TS<-lJX90*s$1>)LB`U_(N(OW-8d) zy^qedUyE@bbme6dwy1?FBsVK()#qH{{hx^Gn?`w7l zCF#EQ9DbwjYu-|2#b#wWRr^d-waXmJ4x8w7bT%aEx+w#)lIbX&w^BD!(SPqiq` zl#~2D@f}N1p2n-zJRR4yBQ^=QKcJbc`czvZRMAbDN64qRuK*`Xi`u*uIfJcg^A`Hu zr#4f`-Z4<-BZa<};O;1zcUG|VXs`Otk5BHIwerrR$JH)=njto+!X=B90FBD6%$;n} zzE)&1<*(UIH!nwhs!uvj_zo0tkOFzAUD}V}g!svq?#0Jbr={kNX!uBg{)R5@zZCj~ zn|Kg47sy2Y){4%bi3bUiA6apv;_vZf!e8F%bng<12Ki3iHS!Y;=*RbtP-hj+;}#Qy zE969rw%Co&V#59%`eKUWre@8UJ0(Dm?l%5e4_=M-kH7Nzt*i)pV|gBG7sR$i^uWmE zDuXCjWx?bYX?v4-a-ZPIeT?QGlUr;|E-wR2u6yVW>{h+)CWtnbW@@`Qs_ivso3+dk z*CX?FIKa_v4YwUe&n%B%BM_Dv`%+3B`)rMmU}?b0g#1xBoJqDu#AS!;J_OxE%o!kM ztK*FAvEAqyIP=L~Hbb~p1rQPVDuvvl!|;THvf$wg5sC#*x9AK6;{)^0^9*M$OGw(# zkKz$&1Hb9ghGIk7aGfDu@J%r43(FhOVX)kfzl?qvf`?1xp?L4YwZyG#c%mQ1n2uje+RVWiXH%zuX9%i42HqS#bizYlR~jJlTZ2g#F5(TPNA~ z0-57r1_A*bwipo5b+Xr84DoA!kv8Fde1mHSa&y&RT;!HuEZ~H7O$tdvpx@eGtbGVe zT}J2qvIi#J8Bk)v{f(LUAeUHjGtrSlz;31HX7eXNq_uaAm`ff z$cO?#Dlj*XaI%PRPhob>HI$4u(tE&@O?e*ysL?RW!n0bhghDSJ}Hez2E1_Szc1kIM;w z%=^KG{ZLNtwRYTyrMi9unOESvx9)_a-fVmH5Y`m5b?(fn>$my;Kx@&NLS737d38NQ zpoO$5h4fnB@Q9>wct|2?9Dc2!|1Kb}5Sv0P_VqBj7tVuTiOJZDJj7#jAz3)Vi zE7us;$Wpkg4d+UeATJCH8ix4JO^gZrbhGhk#hKhD9w!l;W04!aJ5NW_` z3P6mof!aw@Og{hC|087~O785fAHnwLptth3qb$)|4T9SpMM3g@0EU6H9w^E)zhC&& zAn^gzPC%#~EDNuzhfS(19d?|uHTF|Iot+bvak#|91mNWN#;UmWQdAxe&11lvP|M2f4)fJ6k* zk>qTM+XkYCaY+02QZ!SaWI_pVqC69!-V^8e{s@9ko?XC`KY`q~!5I#e%vTgZqx=%R zUya{Gw;TTO9C}B$i1Goo9Q;yxrNTvad79)o?D!bdiBoO^6!rogwReH>ve*>gqIkx^ zTXPbNEjsaJjqgNc_Cwlzeo*tCcGcOUINNbe_(<(q4j+mwD%Qhc)X(5{7~sB4&<_B7 z+6LnhnDV@_`aZS=9zws5m!VS)FTX{dw>4(rCG#ZQn)`NWZysk$q88)!qfzB@V|jsVTDrf}@49_?`U;oJZ% z4O>l2bwxw8dUCGibxgJP|y z$^>_FzH2FY#Z=x=;1ZlrW@X3o1h>FpsWz1}RbO1H%&}@e?$)`sw0oeb4(bIDQ;2%O z?|7n*#Mw-GZSqIwpHbfdTi_DfK_1o-_vIM6Nn9TYnJa*+)N7-?6PNrup&>szKT72FLOnX8+!~6wOKg4DNyQ8700@3sJ#c zCE8tR0d+U5M(R-nhXUTj{=*uC@&_146dwx953Wx!&A3vzX$3CPSKtz?K(YKfvHX@n zaZ80)FRX;Bk`1FJYjEsRdK=1B+PzWm<3of1rQ~| zAK79eYl5EXM`X=UFb|kopaj{asW}C*=U8ZFfdD-K0^kxy0cMK3LKmmu+S`3vE$>lt znGX@%ck}}_$ozz_s_)2$m_P8-ML;m+U242Kx@8hB)|Y;4GKK2kKx|NTb^=Vqq)-=R zR0caL)CFnkf*C+XYEd?(Fy!iiL@&2sVd&x|Fpb3Xf)t@Q#AFfZ34b5F;Rj_uT?f6; zdB0bAp7=;0M96pdkrwc1fu#qtUgrDaTB3@jx`AGcxWCm6(h9nC005B*4**fCv&N&Mi6O7Yt6Ot z8*hB@GN5Nmf5#DA23HHcqvH1g_sqocbx@mw{*ZqR`1*t${hc^6R>GM8Y-`ML%Ybkh zWNb9g3S*<*Z=)@=lp{}yZtDf~@r$-~^bbZ`Xa*Kb$1t1H43Y?X`p8EDw3_qGL_*1v z;NYdn*^l5RGnAZuZV&28Q{@%?QeCIK;shuJdBuP{8xe9tAW?Cl?JR=4uvn_=(k?0| ziV*bqpx-&LC>u_y9XuDdM}7?O2;K=;%2oGtV&c0y&WiT{`%7%62!9RMs8~`Z5FSDA z#fu~EBel5`kEO)p>cRtd_of~ARr6*X{a6j!*1kZ(LT0C_qc@_gqcF|3_8ENize;m% zvaS6AOd zoIh1OH&g3i6se<^pe8c&h5fD_J8HCGt5KJFQ`FH_oJz-b%_V{bDj?Krrrnp>(WHEW zoQ&U}Ant1V6SPB4?Sl9NUT#Wo-pVYCf-W|Qy5^0@4EM{D@;E&dSk|p=NA{*b552Bs z_H6TJJWI;c^yM+CjNAgr@A&`R59cuT3+1wYY11l7EIT1>YT$|SX0|vubLSzBzs5;A@V(>a`>psTfT(^ng0;$wvOat_chHNxD zz8xz|3JjnKfz-CP6aZ>GVhhZ}ub4n$b|2)Vv*uLZYwUGo?VJBPApJ%^m)h9saf`{B1_~TbW)rE1oE` z;;hQtDO06DAL^Hs6Zi`ngP1eycqCAb4{mKyCmL5M3p7|LkM<#bd4ka(cT%1^sz5YR zB+s2vr9DtXD}`yE`AJpE+@z_}x+a=@g@7e9@I`)QmSrxwaQ`{GP|IDocsYsaqq}hV z&rp$o3-41fUs#x}8*t$W|4|gikY`pg zFKngBwP0Fk{VL5{&_48K@K@SI&yLPz9Y*mq!0ZEB%(Y^zOs_<^-d zl>_vwP|+eqp&SBzZ)(S*mR~A%7SH0j^uwo$^D@k@1vQr=SckCPBxo@DN#E>mJ+oA+?FrB66 zMKHVw9FNib+X7x-6}hllu2|v-Vq+mNH{x;=*^Pj;5;vf|f%`Og|M8osIS9JswIma8 zD}_s>yj1N!mZE~kp2L@PUer)aWW*;H^r_YcM$JDYVkJO()8Lm2%`*N31eb)afTgw=tE-0f}jjO z^IAQDyPDt&t49p^{J6>0^AI#$ViGjuH3Fbqt}kz&)e*>BM$3Bd%d)D^TG~w1e=9j7h@A3gjF44)b#T$ zW*fdl^G4|z@Dcja?3ox%fO5bki@X%BA;UR9I9k1d`>kePx@0Ik`kj!2V(LVqIQYl!ElbNg(5AH@f=81+(KAE(q z_F;sGSv-JzKu%UT@6q1puBVWbFLG@d)h8OSp%Zci7Av%1&Ez`_-td2mLh__y?K5z9 za32^^Pb3!jV!ptR!Tv#|cc9;4|79<{f)x_+U$!*Hf7wspMq(Fc0zv63F?e}Ag=WbE z^EuBL}ZLAGMOrwP>4F>b9$^b{TVQjX1LjEk(h^T6X-9R384 zUhL#A1&<5(5BU*JpfBb>MBKkfoh0l(gf!BkHB8Wdnoha`;t<Y7!Cqu;{`nFzDZ!KDxw3?uWX|kd(|LZbc}N z)$I{#ff6fmOPgxZw)PeLk;>b%Dxo7!Y7;`fDbKUW)5o>GZ;pRq2{OQU!vdZjP*1kT z-y)kZyQC1(W;`kdpye#ko3*q5hS9R8U7A3Ovd#@8=(uw^#()~DC!3?I922P$-m$)L zl^dzb-WsDyOLUdb1KHDhM=y^88eg=R&ry{dsY+)%E9p7g9r@7E`xMI{^N8~AjfCzC zeuoP4lND=2kz4=tQ(Wn!^7UDjDYmtIJ!Azy|1N|lXd#zwCOB|&Zk1eHh^Klvu0`-- z|0C1|F&>c39xzrZ;t5=kCO0Y8Cc1abwmd1ho(7S4M&A7-@Q`zdbS27|HQLU_E>qembX0^|BU355N(wK=y_D{TntL5gs{=C2KHP8tWkHt^St z_?menzH8#gQaJ(#YO?d`C&AP@vosNH9jqCjITZi-#C=j7$!B`4$Qhh?N-9T~=YAP( z^Iba}Z*k6622(Uo_ztr z)_@+!-+&)w=X!M8?`*MSQ>=I7Kp1B99sJwopzAHTq6b;$<~BpNGqT}_o z?`g*x+Elhj(s+w+rJz)7k>z)`+jAt>*4bDBkNlmeHfJC1K6lA={d6gSx3In8Tft6{ z$b;HjKSU!>b}iF>FNZWwud z!)Wsbg?UGj_A9+Q<7SwF3tgCwX8H)dh@y|yL0JJb-QLk`o#(H&nwp$gnt=q=52sbO z#+Ce(f~SY@Bv&Luv$XL(`Y2QyOKpvLKst-#7LQVt>FBxk^`GGM<;SY*tBHZ|=_IJrypnBvRltQJ@|L^x8FKhCXskKYlE7m2tIcMzwpm+T3uplOxqWAPQWM0^m#; ztb6XvX0HF@aH;>0!o8m%N_|vGrLy%>0y?Wu3b)&p0y^zld8E`RQR-zbRiKv=125wq zJ{vA|eWcWZbquKhO3AFW44RkwH=SGhez;8Tqv8H-6lIF2r7e!pFRldv3nEvv)OQt)DL!22hCsNbn?#|%R#2fQNUCp4eme@ewSx~EfH=_@E_9kk zcS!rcT<9@kVi+^E1%-;WC&PvQ5Gk}r6skiZ1U53d*1%n>3Kw`RQh?{{kTw?uaLC3$ z;T~>gTDa7X2P7*XztNqL+0T#2sM8)lb*F{1sl+}JDH z&3Fu(+Flm?Fe-ml0Xo4N-Z;B{?mTzC=~4Oq{_(lq1`@jMLz;tk(Ido7|0t_k+ik}KU!})0Fnn2@ZX!GTfn|R ztrDB9fUB6Ew*-!?_gEk<{ly?wj;-+w2m+}t)H~Lo&mc%G1UvI3`3b6Cx_w>iy9r>Q z3c<$w$tmgcCnrPYJ{ijFnJs}g5qH8!N|vpUgiD3Lh6`kq`unb>ow|HxZc>H}XHFqy z7KAj;10eQsm%o2Q!)hpCT@6=^tc{c>l&ToQ@%ywxE)3EG0dH5sYAA%<2IUCW44H&0 ze*!;UZ#*&in-6*#`RtBby3Y*oeFmx{{qlh0w6D3Lu2UBlk)TLsI7;j7bOu-3WTQ>T z{kS|P%8${#t| zbN51=Yggt%1*U`krN2?E*Xasr{c}+%4J^kP*cHZbeBt|=KGF+~?=xFq1tu6GfOJf! z^Y9vHh?X8Pp(W>G2Ux|Id~OK8IWWk>e+cg=4l_Xt{r82xF|UvQ82-c(IfenKaztsc zMj6~hJ#rb!-)1VnQ459z$_!6ea?CNGKp4ipbNRVnygcW3pEJMQ8dE*S^RK;wzM<$4 zadLJtak`;Yn}q*3m?b%SF7`r0j$EJ^9^Hqxrwkjfl4FnQ5Y_KWR1w0_qB^Z?IGQ9Z z@ygf|ugo5?T_S)hIa6cGD>)g%-lq?HPa5`~9P>`?1EkRNFNm3PQVg09G2mvkkRp;R zLc@=I(9TBg#4wkPmqK_woK;y=i&2l9pW8=cFju5;!_J=!Od%O4g4^39xhPq3C(A`} zdz-SZD7DF*x^DKhO$FCR8Ws-A4ND)R@!*2Zp^8zn=7Q$^D5%MF^Bc80MC@!&){3+uZU_ z-CP%%MkXXcX$oIGJ4kN|M!cSu;4V@Si)bi4d?UU@xmtgCN=)i_^_^)gq(MRCHq^(Q zR#f{Hs`gu~i}fu#itmY-RXscvl|{cN2V0KzsYgf|>fuY&5LYG~2~dSc;99}e8GJ3| zBBuq;mXNCpzvTnatU3Uf4F_Q_x|=>jHwxc9J?sHtZ`Yyjy=+$sFAA!sKS(YWc6S{> zW_GLq3vT*7u6i_#wZqVNSh*&+AEpc;*MK*;TlS@*;nLtPxh{ztr;bqD&VFv}-H=aH z?a;gmIS0BwQm+hILZj5HR1=I@J3=r_uC#<8KuQk5K)KRh85$p=#WN=4+7S{fXiP=O z)e_=$v@I@tRT1m+EPON^KgGUdzKe+A`w;|NDOvRvAIUMap8|2>(03fI9*`nfTnDQV~29nTPT?91we;LhNKYHwlVM z+FVg_R-L7K0_sX-es{a(ySi`QoRw z>?R(mJ4q@sZ;Ojw^Rej}XQcx^WI>AiUiC#@tpP$4B8DPPp?H61cF@j}jBWNWYr(T8jCxc! zC@9nLP4a^Lvy-;;Lsq#aO~PN1v7a*zAfvJr%aC|3%g&M2!ddN{b(C6m-n=QCH;wbg z1D%anFZkig@_+bS7CGLsJ(5f0K=~k|gE??eDdu8j)?WLDvwDdxjx?H?@P`IgZYKm< zcLLA&TP*S?tgls+DnD$OS0|JD!o!LDFen!%$;HVKT(3wYh#*TbRz@n+VGDf+bw|Dy z5#j{!Uenh=(Dft&U%JTEds67SQeaF707LhYmx0iIz>OQQfv;9Q+c0h&Y3Auj6AlVs zn4WJZe=&q>=}Aq1`xd-)(gS5UKyO`8=InYM>YTt)Kqg(}=tcDmMB|}{6V)S?YA_RM zwSvM&2;54WT7|GUSzvSU#RzXhgI9E+iyhXm>l(FuBUOA5jf7r@S^ZzAy-u_T_cq=8 zROd!vY~M}JWwelQ01hDR!4HpqRAW=S7OKHYXv4LzJ)If{OpejyFfvD5Irh-E0EaG& zXg`k#1MPlYjfdraE-~rgZ>E*H*8@j5bk=CWJ#?s)_p43~o@<=_a3Hg<`_Is0r7rdZ zucD)>vqxZ5wR|&BF0vB71*o1wWz;?@!&w4-+TCa!*wwWG2pukZ&<;ZqAMoOAM^Buc z>;UI4LAZM84_z1BkJ&?s`|Pm!fC|W_IJOlI=7Ll34o;!eL4u39O!-z%DS%2 z=n#O^1%CwG$LH`ndJzt4oQWUTT#d{Jkd?d@xca+WS)nj#VdV~7VoU;Rx+5H#xP_HE zD5Fst)|Q;(3h9%FSeso)J=4Lc=OClt1+SoSn~J1|M`K~mm= z+Jil?l*TiZn?tT`fT?Jz69*fbgrNnFIKoRpB^leprD2t|1mC3vrgkm8K~*CSgY0n* z7^kmo-CY3T4)C4@7(;cCwkPW=b0V!tHKcC`o!A(IEthA(-YUnAw0G~r0gOJP_s(rt zHFWZ#n>gtCum_z$1OS#rgmh4y3|RsZkrWXsfgLZ=g27tG`fTULj78DO@E|C6V|R6R z9}2$Sy_W$Eza|H}_fP^hSeL%R4#4e-lSq{gB2sj^fxTJ01~*fCu;~7RCHUddPnSD_ zTJZHFpQ`HeTd+2KGM+Bl6VYRWo2Y5Rr zbfU-)CByH8R6YP3vK!@`q|7(r@6A8MR9yq)YWANQwd!O4iey5VIS4C`qc$nu@%hrX z^r&S(yiV9N$}3VK7hMj1FC2T+nth{xPisF57u>j+{xfmD8*{-7_J0B)TFv7{<=kjCpTHc3O$LF@Sf5VDW@)0``N9e8eT>6>%PU{DwW-8O0sVXq= zk5J_~GvLV!O7);zQJgCep~3|P+%h$oZO0saTmyaK-bB@TEV%#3C+d-!1j1&=P|G{2 z^DvO;X!x4xy+n2P>8i+h?;|}vyneELEDBK#@6Z?hu!b}^J59phFWL!F#Xr}H{YO4R zClI;lU+u*I3P?aYlmHCpIZP7EyAiM( zvX0>X;651t`G11tBEPM^ll~Tz#)H9qUwFnY1gs17bKj8@=;wj+^^<%7p`W-1$!|-Z z!6VMWh^{^mNK5y}SmD5x3ufR{olOfI7<@LZZ!G2RzW_Yl$3QFK1O)Qh&FA!PxZ~l| zBXsd|m*bvVlo2`|eUR8>vKq=f$<%2a9+lETePaPoq~?I5?3=u!zDavE^vwdCx8z?B z@T6Zj&g-DtbQ!2Lrdy-8!vCtGSSV1m8*-dlHZ%Qm`R#vU=y&^eRj6It;uz@(ssUKr z9yn=h{4PL@BNGBb-5H5Zo{vvJ=>E@v+@P;&g6kg~c}y8ep0eyiB@Ze}?<@?&>SZBKP@^aKFA3Z#JncNh(X0%D}EE zOAX(xr|Ng>v|;yM#9xu~)97y9FZl0S<#e~+jljO7OV+M^b`9kxnu9!<;Q;>x$iRXLhljz+CXQ}C(+iMkaz>i z(f#Q?3?~>yUq~V@()GUdzKF|RG@fwnl}-5zho+A!u5AZQyyU$xcvYWUjyj;JiE ztDOHVcufVo(nSBJh1+L%O_f%rodvJ);XLPr7hUe)RT<2hYqY-sGX9koQw^LEEHe|m zMNfycOW}UUvCQo2uRyE<=$nS4*7JRW;xSqpR#LzqJ)W2Ed+-vVJ%h%LSYF>AA(j`^ zS@P`c=XSI_#|mW^57aV5%r1LVo}E-Bs-t3dZ2-6;F}oh2w+qGW`s%x%8=MWX0MDN9 zG`Vc5faiO^GUhW3&zSk%6;40jd=sz#?D?jzG4t&cl?|V7z%zP2To~lA`2Zn(Nc+oY z=bjHL57!5f_7GnV`@7Vwc`&b1zL}gNV2i@V-%O!u#qlzra;3-1AU!w)uC6f?vd9sB zX^eOoNc*DkG9U^-lpT0Mk>}nrgFVf&c4mS+0qsH{Fdob_|Edgl#6(Il#YLR`Cv%k%Gtc(0y-Pis3&~u}z$7_#@OuC!&72<6J ziuCSBr;E%dXH82S#UXw^xs#zO<^a-J2EK)*F}Tu%H!#*4`%05Pa-16OhhoEny1zV- zp;7+w{C!rji|XxFEm+o7&*j#>l|C)0c=Y6 z4c7;@B|NZ(a7|urd5QimK=Ko+a=*3^Rnox7=NGJ=;gtfb12O;PS~Eh6$>31WPU4!~ zJkX-5r^EH9gzJ9-{BE~q55KSFcawO>1hhjRR=b57bPZ_7Z^v&wU8xZTo$l7FVa+Vs zDCRbdmL3nc^kSI-v#DJ3&%*D?{QgAv-OBH4^>?8?w1ePyNPCFd&EAUL;nQ}~@6%DJ^G!|#vt`}ml4vqih79W-goNbn{Bpl~NYz)(~#AqtqDej}Z;Pt-Ykbi)rh{8pX72L8UZpt;V#a5~biyBu5gMw&En1tkV=` zLobgsX5msq%pw5JKsG=fna-kcyKQHv-JJ0I@A&XO3mkMX-Druy4N^`B(XDq#M5VX9n|fmj&43T9#z=r&A55$N;_$*%qNWhOajYy8C+ zIxw(S(@LwhTr=M8l@R~RHK;x75fBs*`s2L}q4j8{Y6h&-z1o_X^yfwT{YWPpi>k+| zCaql0)2?NH6A7ZKCXqFVvN{LdRgIi=S~#mElC@l9ofK(xTuiHXi1d>)(5qEkP4E2Z zUSA>7-=g#-oW4IM{U6u?nD-Yc{a#N0RZO}n(x0I80#27>(q9qjHI$yg>Hij!zDcBe zkq+?&IQTl&o{GF+>PNpN=r2iJO{!i^yY@>!!>uKT-=E@lbNGE7-o+lVYBy1HF01zb ztI;8M2YQOLf1>H65UIS?p{7T*pVlJs=lw##5RU&8_WHv z4!@s)@*?6~gx^2FyV1YL^w#pYHC^dYZ%tQR)ZcXF$Eq%FZBn?lr@8hq;rI3YezMpY z+p4J6QuNlkzE03^>-}%%8ux@t+#OZoLyU#s_cJb0rkB{xC4Lw#k%SWZQhtRB9MWo# zziKOI`!_wCSn8WZx|h^g5CLbxLpK^z@kYa*;lX(yKXrTuk~MBK_pokbWBi+9ep)=Or0(@UJopU}Llk<-v=X$CsmDAV6q(3jx z@28sTETHVH#1*lv-An|{N?Q6=DJT}Rq()x6K)>1 zaadeSy}@CjPRB2t`sF9n@W5)H!(>ef*#lV}PK)gkWE`rpbDP^l9s1P1SIMCH5o*w< zc0)}1E|IHd{z7-jH$aNPd8F`XXK%lfFZwKSb%87=KLqMv-1b>Bl%d zFDAWKq~Aj6J)C}>p5Bl7JgAjX`ax|1(hXq-6hX!P_I&hw3$P*izwY{I7(Ks8*Ajxt zj%Tyt+^l+@b3@cz42>M}ItD4SP!{s+01FTjSWgeMS4iMi-mkqpfX+lN6nviAg#rZmV48w0N1Pb)Fa>|9 z=x_p|ad_v$#|38MGl0gz)z0^Q@EC5}gxutBim0;L%iy(t$y6(dEHQy@DgMnba z!DoQ;2go{4Aa&gwyWH6{j~$wV%>Y_FzLYFgZumRy$_cnCmy(_2<@A;hVLaWOS%(D9 z3sYln(Tije^^g3EM*PhVyGPp%{|w?@UHvJjMBK#Jl}@d!XmU^G@VRE(aB=B7fnfk+ z2rKHvou6aN4oH{aS}xTL&kuMP$vG)-i=HgZqB&shTZHDw;iL^WM8Jk+!@NL7&i^wb z|Iw$;&JPqNyn@3Fs3KMC5LJRhI733@FicenJK(}!i(HtpEv^X^oj0Xv<^<_VslseH z1-CcB;UR)Nk~|G=O>vFKRhi8Jr9)9X8tNaG4%kR3#(0!@$=Yb@;JlO~?M~4_!z=sE z;?)SCX*Titi|Oy=aDSZ_=r0spV!MphfL?29xc};1$GthW^9K$_bv~M&1jkmu>q*hY z|2}vvh;9<&UDId5j$uCoFbN@#=cm1jGVnPR;~)D0JTz5p2fZUM>D|m5L4>?Ni3eFA zl!R|!fCsgfn?M(g&W0DS7&IX20xl>be`ANvj}Eq;Af(jTQhlr5h5c(u-M(xHy~+L| z9-HYAezfU_eRyZB=OxFAXaru7H<9vkCiwdf=e=0uoz@b@I6U9rM=)Etl=#Xe%+bz} z=S?S+;u?hOg!J%gI^m|(^gBYJw3_<=6Cw=l1N#Aiv^R zu)e#Xf(8lnwQ{almcUEOoMi0*swg+bt^FKTlxWZ5uc)F@=Mb`Ea#RqOHS_v5f}h_F ztg?89wtOQ)0396c4%ii968;UGJFoT~ z;^~Da0jEVi)TO7RTfJL-4!JTG`uz<3{(E%j-+;0u+BNt~(4S{%%O@V3>G*@_XVhJf zy4)5=y3xm-#t1MT=Eu`l?Ph#6`0;-NO#;k$0sibiu*-(=XET^zj_)J#%VC<*+Gg7M z|FQS(@ljUS;`dB4Nd_30K?jT#C2CY?utuYmoB<7&ToecikqHzC^i*w{(iUL`P`Lz$ zSca!#{hgLyZ?%xj`FL8=c7~NuQ*em$yoy|{E39!8+PXN+NffFqC+yxc* zRa?+sn_0*Ij#TEgXX_eu-_)0AJ5uR2ZywZH@5nD_yImTq>+Vy@snat^=Sfv>@tQK~ zuW!qDUR`%Sl}HA?zRdZJFe&Q~tH&hkT(8<8S?A`d55Xi(&p0y0^2Df{fTJA9`~1G} z5(#s7wn0RQ8RIM=TAj;SG=UfqCBBxGZ-cAag5peKvV`rH#HLc@U*XE!5=`|HgOf@nsfUn>PrHgs zdsmOYQy6y4-Ceg0$SPzS&V3D-^#ZnhjVp) zEGN<5n*6Z$c464l*k7_bSH49%lDl{Hr!Z^@-JE9&j4<%+c$P8l?(o7q^7O8J+iY;9 zL2EaAbq23w_)&PFS57^vC+HN1PH|W%4*kxl-#M*!1bw0gmkmFw3&D|b#<=f?d61VJ zQ+Vk~y!7aP+@bs7zTIG%ht~JmR!=cpw});{YX}%=h<9lX@s5UgM?>_*W%Y%2csH;e z{gOV>xHr+bSHHw5FfS2_nPAGua&QVlJQ%WhY8f)?TJ<^p`qg#(W$rAUPj1ajdNXjz zK8gq~wPfr6BEPI9tFC=3MU+a!$8bNQw4GKrrd>G>wx{r>rHXw-UccYm#1zC`gK|gkjz9irls9*X}`AeO?X^vGRi|uQMX(cf!Sk!FZF(eNrn%ao{Orj<8#lZTd z{eP;ye%jg6UdWcNv2L6q-E`;Fu{z-e(`ZRvm1uy?^zA217x-Hkzpzug_u13Gw5F#g zO_%w+I&Fvs{x-)4<=8wOk!m0I=?k4vbYquERI1dyjy2qS`s=4p8cIJM=R*K&DqO-v zx=amY6mv}lW37>i%4s+1H|eGsbD|ljD2h_D&WCP?R@oH26ncF8a)0+goCJ3uil|o zTvKy!ER4UUi&fc>+KKAdH>QM@!2-!dvm^v&`zKGw zbg9(kFKFR}?qTUiE^m^)`m1ZzllN+J6>(gJiyz&sq54zmJbC@4&eV`d`4hKS`7dv; zzjcN=A17}AxqAlo_mh>sV^I0QF zV|uYO*c+K{ule5NI6kl){fIqcQE}iqo`9Iyp~@a!;o-=ZE98XGPXr$k0h2bk)QR4u zI8{!iCszym!zuQIQ>I(Sfz<94TAwf$O#BvnNZ$|SH?Hwv^?VubZ(EyW8a)3`VP;gitE z-~3+F$Mii=>id5a-VrB*H%=hVq-4RI6b-L7mXIwMV-z-Ai=Zr&lyzy^t3P3W_b1H%I`Nsr<8A-_L;OX`>AN;Kt07Ol3o~HJ z(*x&n%Q2#bV@*3c6xuv9H;n!W`V?j3a6Ad;!(smfDaM9elo4tqaCD#ACY4(LAcvt% z@o2M_XuT?eC&ERdB!URwX3R2k!?p6f2z%r7W>P4|_zmM*-TjC7JugsG{x7giLB{sc`u{PcqKCK5y@s$U|Wu*%rptOJk5i$RP=p(eP-x5=jTl-qx&nS<0F)5`VABf{Wly0kuNle^fcy3HV;K+??FM)@Vsqk9f z-OoF*St9za5HfV(-$p0?E$j<<+#j9)-Ok+|Z(~yuExn(&7_yW;$ZLWWqoa=z-C>9t z3_PKUc}AzZ6jQqWmIYnuonn7PHc{n|mhPc^=}Y|4?jED1`=p*m6#pY@*?Xd;PYb;h zORmP(T8O#48(S|7gNEr&6o#uNeT?1=*%kWnPUjxo3A0PO5AQ_hAEu*w_-MmO4L{)S z#|R1v&FKWlrDwoR=Uh9ZL~>7d@+v-a=0ys{HDFujtvO>TT;q ziu_sU%P8?I)}jOHkCu3fjx_#GlRS*p3f-FzGF(Cn#p$*&Z4#P==E{c@REdRgNv@^? z*eXH?c1CR&zEY78229L$$r{DPu({(e+HiJf$D4A^TY8VY$2#_YdEe2&`tQQo!Cx>V ze6u5F+SBS_Gh%QFO~8wRxwJbvpTVkDY*~5Ng+UJ<4_-aE@|;ff`oWdC7(Wp&W2Lq- z)Ls@ta`71(o{C!=y17u~lF|*#_$HNM2zA>k$e_ zx0i@MUSb>sUTwrjhlN(`f&@O(f!S`yJ7ToSqug<0gv)nVI?cET6HCM#SYjq=Mu$l= zl+d~!`O`~9yCBxkwR>J`MQqYy8lSqqUIE-g^+(=E|4e@#v?u$Mn(*}Vgm7BIKMaF| z7It5h@a6Y4+)F23eA4_2j#gF~a={SB zW61W$(SEMk{x!dq_TPfm4ADMzM7cw@f4VO98%q5{?O#s&g=YK3uj~E~-Tvj_1%*Sl zpQ%e-@-f;sC{5!U^Rq8~?@iT{3iiCu~2C(9+`X)B0ob z_a4pP7pmcmSUP`y77_#D?_J>%OvGH_X&8vP;$@x`e?N4Ju>ob{2{jFc!rwn~GnJYR=F;Y2F8F)w!&1!*T)%RU&8ej6)!=aL^9~~K6kEgadS~%^ zm;3gIATyy>5Bg#fqOqYcc%`}w5Wqwh6l*n?>+b2mAWNvst9$09XR+(#ypr~Nrh%W+ z+6jaRRfpDESk|ufIxig9nVIUSriHtnC{&Twn_X8aL$If?qYt z@HN+mF@W6B7japO*2lj+ijIhSn;VVz+*~0)YrK#jN^>^TjVp-5VinK&Tp zkNXG-`EX22wTNFj!6v~HE;cR z^`*x{NbY4nT})v!9zq6@S3YmWLl_(JYU%ns^?w+s#2UF4`=Clk!A`u^XSmX_=!6?$@dIfakfvvHzrk{v z%D5-t{oA5b!21R*p9J1-AxIkn-X8@%F}&fQr@?FLBeZ7+P^nkXzb4UK!etA>WsAdQ z%fe;#;j-o7GUD^B4VSGmJwJwT7l(ygZWk6fd^`FY- zh#w(hSYj6TuClnIMW0KHAA!rr)t4BDUU=^*nY@DIz7JQOUi2catfvz^^lO}w%vcP` z_z{bfuTtyj6z#cr`dC?CYiwBV zdmj1(<@F*v!gLt*)Je8L7S|22Od?aD)dNNnM#kZLKGFi1I3as5$~SiN3QB zer}0TmM;_;f0^SfwyanE_pgL4s=P_qvV5V-8}fuL%#+P%hOlKgvx=P$LEeyOv4oAf z#5hU_L9WcV=x?aiA?+t0ggt`u_9tc#%av1;qGq-PlDO6hwBy_mCKE~ zdemx?`4TIjMt$GP_fdGm^2CPLZ@q;M*U&{_eq#<@4d~R-@j-q1daapi< z4t<)xjgQN^MOyb;{u14=I?os$o_|0B`~Fnk#Epj}x2WEV#VWo*&%nKU@!Dj->%9g6 z=kaNJ02Ck&1QFLplEHV24%K^DLiP3#;oY$L${YblTS=Zx`(H0f7`KiytZ)Iri>>$= z*6&k<5AYFjxJgvy*(HraHpGR-j6#{h&lnpDjk?Rh@qd3w_SHLZl*)|7D+*-_S4?6G z*G>vAy*zwtq0ynIZ`CAgKfP2JSV94rzFCFsGbYh>N)tyHz6S3#%H8y&tch9cj9b^H z&tmEZw=ZPQ;?eM`b%SR|uNRBC5>;y*DH+A;Oyh-uz4)iCHLfD=ZPSav%h;ZL$K0Eh z0AL1BOGB0KSdF~CRE{Yq^~yf;L8j{@1Ryijo z>uWdn&eOiChth2NISN*(IjFwFVR_lP==SNvS7N3*K0w7gt$Z4J`Iz+OLiBe|P0wt} zTqmVx|NQ(M-DD!c$DE&?WdEn;=j&z#L(I=tG$bdOpOwo8&Ck@HA?9bxp?`LMj`V(< z`PrMyb<+8<&ePaS7a7^Gd1!XSsQvWIx-$s{aw7*%1;h+SS(li>erI@mcqu+DoQu+8 zCnfeTZ7Cu4*Xi}m|A*@{{Rs*Qp5Z0rY0V_WSO^N?$}#UywaEF54{=P&p{MgbtRAB0 zT(*lqaF{=ogy+R7){jT!4AbNXPl zuQO(?jm+;OEaokR3k=lA}XJvPNRL=_JXRYeBA%F|2YTg%v3$hi7GbT|D>sP&AsgxRiH7wst6 z8|@G$;5CK8bBlJtFN|k@b8KlVnZ)v2OO}GY(_P9y* zSFMFF9keO?Rn`V`#lx0D_Qg%SJ~AhEfYhBO{1X0J;fRZlQsZ-5JJhvc{gzTWDvR;U zT@c8+&95#BjA>jS#}7Efa)eguGsSPXT~FEi48Y&xfhiH9wTn7&+aE()dqPkUrU~~>a>rD@B2;J{D1Tc?}l6-SJZ#Z_12%y7k+Wb z`ri8DKUyC_2DoSRv!sZG%~`q3i$@WJ6^&~zum!KiX$5vJ!VjPy%xGSqk+4@sigqG? zKuE2fe<8;}(#^qNybO4$M2ZJmj2RPL>ySOK;E&sX@xTM0KKd?ez}QjS zqQ(;lY)7Cb)Z+AG$5C`d`&-2jd%|BqT{IRQqssBtiqYE4vCdGNc#)E~Ge`9@3djzm z%H3v_-MUI@%-rGYUHNz&YURUf)S(-6Cy}n|<>C|j-Rn`w@!yZO&zmts7+rO*--osyU9Bny| zZV~^ha<#kf8N5P2i~qA-(QV@>rCo$>%a^BJ+GFXqN%D+qP3<8~+<)Sc+VZTaBVdQ+ zMmQp6)dMdbhtTcc*CfA;y8NQJ+y|E)*w-W#%B^te;eAa~As53ES!NDpQ~Ka#d7-s1 zbEZ#vAX7`gchd6>_`Bp%4gW%9P=(pZ)F?~HB>$TP1Qk8SJp zd%1(!cAb9hk-^%w*m}(+)HY1g<6J6>-_&gmdBy=HNW9HuzJDXS%_~ph8%{<*ZjH5* z(asN}+onj0xY5?5@MY%B3_nv9iOeJk1H#SoBBc-WQ(7mEiNML5J$P;*%xUF)+{rwh zw;2=EODG#HeK^`AIXb$drB6hgq$Be9bhJtOL74eJ?7Lwzer0d?M)Ze=q3yVAz2V!P z&+of|>#@5!_w2g?KY#mfI2>K_OYQs9bo;FxdUk%e?-2nux=rBfe0E<%cXju^h;;L{ z)_oD_r1l1?W6nr-qfOEVQ{Bo%+p~0>TXH&`)r|FscKy1Nd$dE`psh5AU}ZzJ^scC$ zck%kUFOn}+av}H2sGe`rA?xcO;D_+4eLEvfdhlo`fllUdMw?~}gnCHV5IkLevFmGe zj=9j(?KGp-<3kdeA~egNXH3V*MqW9wA8vaBd4O4itaW{@nO6svuxofx$4I^$k$f5b zGZ@+t{{3WW2fy%wt-^u4Bs@o1AxIi-!pxUxZp#D=89raT`}Q1}YtGMQa>dEIFTOU{ zvj42|>gWGvvEf-lKmSagD^S>qE_qmPMCESq33(w)_Gx`F8eOtkR5N5He?wl#_2{>S z$l4UCJwY^+M`)EqNo@fCRwBX*_aChTaDDQJu<%f_OlYL4%}BvGMZ$FO2Ycf zr^m#7TOSzGxSqY^-w^`T8Fy@2H&FwR5Gb12Zh{RLRYa}8_(yl?GeOtRm8g1P9%WY}G zJ&WE$LB*U$^o=H~TRu-Mh&<(9OZ1J5KhO~O5&7EGEnn=?%JSo76H@mPL#Gg624Al= zSif-}ae-8tb|3LsDox%;pxVw!W^^p0{s~(UC0#J4k>STpW)L9o0%dQV} z$m0pudf48=7$wi}s4n3ax!fTO+N&tFVhE2rv=z9?cZL^WIqeP4G#ufX-{eYk<{ikp zW%Tu~H2_|{oeI2tMe)cg7l+-FyztE5A4ZFPJk6;b0dbUWhgv8N2 zGuG{vd~*e$`G-aN-z%oP9AgOaI0He9*3_K=`|L7qB|0<`iowddDP7&Z;&pd=0(kZF zge$$V)7Y}ZSc~DyYlD{q64i=Jg0kGAIQ;N+zsKq*a&5vMD^!fBP6_g}P%r+0V~%j( zHb=Pt__99k%LtekBi0b;`O58luNKaKiHeje<5(5UxZ2Mda&vPqOS`r-$`|7bi5ti~ zzp-XPSe%Lxxv>`p=zL?R7F|T9X#M}%D!+PQrA!<_-$dxUb}@!p#8aQU9A@bJgk&Tk z9P_L7{W32nGh&9CT!h&3dnQkt6v#4p=^AI_Er<#2CG^VO^sJyp`C_iNIn6W}wF?N@ z{sjb#bC(qyYB^5O^x)L=aO!t}N*^zr`rHL-Hs*iSt*?fT>EwzbJNa`;_lJTL5K#Qt*EtZ1di6yN`KX&t z`l(oVGB%r<>D?4wq#uejSZDkfADoq6LP*(h9`3MM4u^KP7gyNop$HtwzA>aM|7(>CJ*yT7gM0()&ZxTmfB0=t^Sp^EgXT7Qk2 zCeN$LzK4ixenh;BasoUcFk)w0}l7s!m_YX>8D$XP(tIK^xC z!)Q+~c>?W1R4H@h{HCQZx-L<-%w)tF?*0l&ue~FDvS?xMhrh&&yEeAkgUb1CeDKF! zdzPwqqkH)y-O#S+kMyzQNB7XI`qs|l@o=5jJdo@web|_K&v|Z7gB^~AF|2~$HJ;mN zo>N5Y^XYxJOEExEU+U5aS8go1{LVEvoX+W6v^4yG<*uzw-@Dn1p2hjQy7D@62vfU} zK2L=XqRLF4&PJrVf!g8-M zeUg?Ch@vy0Y|OL&Bsu$w5MWZ{Egb*fvE%^3g-QBQ>1~^Kf!v{;hAaij@!2?#KBun7 zN|dklP%UI(BGrEJ^?1B^LoO9Fb`=xYc||g_{2pEEivXZo_>I*G_vSl=wFQYCXnCL7qt2B)IFqMR;_wo+es7nZAv*c8%T zJ=I$3Gn?0urNR*DVTc>2!~U8Z{tw7#D=toL_aDTZ?K5xFru^Jt;k_;Rg2fWU^j8#y zD<{S0^tzjH7)fB4{csOu`NYMZ9NCZu#0mT6968Pg-ExEt4yzS`)I>29?|*cmp5UL1 zI|QSVS$R~MFi2&&?fO1hz+!7T$MkRD_qI*HK&zdkiEAo??=a$B*zNLlY(HP6s-Tq{ z2UJ!{VY>noW$bN#Bf<4JD%rwx$%98c;K)O6Inlg)>kqGd#S~4{LU`bbi<*%a%Q8tW zDZRhsSt2Zp8x6%X37X4z0@#< z#6LFI)eKpD&>vBu98%(&AVobNdsUtGw5%#bG<_2FiNyqW3tNeX42!G+7Rl@?H%V#h zYJ&Q~?*-~{s)j?AwXOsSf~PUk-|WRHV{@=Afla2+EO=w_`o2}4Cw=JwjqH9_l(lH0 zOaY6=XD*sc-qaITA@imni5@AYfULLI=HXZefy88l;Jh;Y4CcQXsxGnt@U&!>q)88VdVO)Ck9Hds1Iey z%ZhU974-?$lXHmmgr7`vJtBim@d`f%M|JR-&?f(bbM}N}aDSOOIHyxAlTyuKFaSaGrr6!rfGPJpR-ezsym>6me z^+5P#JW}<4M%%;N*LdiDwR(Q8#*v{bZw(EuoEFUaO5w)|ekWlX!alirE4NdLJ&^MK zeh-nK^_SW(t@e`j%kPuv&kvRVRP||n+#&5Lzp5o}1MMm8jIeg9ks6jH27R3V&2be- zlO9wiz{zCSbR8Ogg6*QD13y6I!2ua0UBf$+KJ-(cmkAzZADBM=D6dti-_5q!7Mkr2 zTF(RQq0ROZ{uKt{>$*87L8%p+G)nbbQt9>$k|6b?-&Wz!dOc?%S~5;6u6C`@GOlsP z94zf&I*P!7#86BAAxW+nLO&*p>15W>d-e>ZFS>opo*F+Y__05^J@x+rJsKzvJK=hc z&pOHAf3R`z;9KiCX%BJ&`}I?#|Ks)<(=HukpW#dN;bYrpe2R`Gmz&0)6R+Q2b4$U3 z7-SD!z!u+|Axlr@zw{qnZ*$G+2iW7HO?XoJAo$MmPgwk<`XWyP&&lk8($|lyU@QML zGaLl#$Hn@olJ!#}>u0B^*Ae+an3{x3n|=*S`BTx;Q>D`;?^f4V<{ zp?)Xv`5&{7Ix)SS%MFS}Z`15GxK=JWDSanVhRlO%{26{&V~25RxKKpos0re};hA8} za^7OiuO43m4e-mth2i0;G?_^0?P|@68@u3K`Q`L&7o_2PQiLFeI9U zPMa~01vRhbjV!}a%!Oe;o)mbiB`U{sjj(8PxPkN3Q(AnXybEiG{hBW6hdcm2>C{?t)y4`hd$@sKEfq-cL2K5j4j7vSUO zTT=M=N#?&`r(i&qak(87Xm&&{xBCtS|C*X(ncw*mpT{FI16RR^#_ob=(8Y##U~@dm zrQ4|Q9X)Qe8HdpN{F_Wg%lgkXHE-}oytgrUcSc-~8HaqkgSeA$YW<6`#+l)Bs4hI( z_+!BfqE~Uy_u={xmK}QLGqK^u^99dPQN*@(iL{Y5X5h!Lf zxYFhiA#dcbGg9HY5kVVNGDkGN!o^GHfgL>}8@=$Y98L!L$*x)|1d00*WE{4|;sZ1Lw)q`)g~|S3oVMzThbLni0oC zb;J^DU23%Qx6b@B$6MxWC}U!y+)@Jx=2~kE4FC6xQJj=GE`v*@SZwHAts+s-xeM8gEMe zen%ivik7i87@De(H|27fpj_amA=Lj8IICk%uj9;bt;TfYh6~Lnba8n;r#^cKln(- z_z~;3=^y)@`rul@_6B{GXte!}AMf}h7?WtJXp zTq~$|A4$b49PXy?^MGOml-l=UV043RUagv0&8t^nhN;Y01-c&g9a{By-|;m|G_CN> z?+R8JJGA-7RA%vN_t&>dC3Ct8o@av5kyt&^c;SV2pQ5ivf<@Gd#2dw9uf^83}Lk6P97(K5FLI`>;u(-`Scg z+J~EBxoa%RL&Rpxw7ZPlaH;ZjxHm7M5c)7P;Dt+c4uo)7IA~+g=JW)!j1|rpyq8(l zc#hJUEi%kZy1VHWFp+z&xTRZyTkUIPw9!QN?O1h#vD0^K%~GT1L?c@$Eq{WMt$!hH zWEYwvJC6!{F9b{b&)Lhs^|2>xbb7TJ;ma6S##6#S;1GP48F68(Vb{cikZ0yo;E2%~ z8>e-Y$T#{<-vS{W9Vyb$;ah?k)lb183p6IE=|=gvq%TfP!RIiq66EIoA@r5Q7WFtF z)I{=;nG)cgG){q}+TeeS=Va)0Z;-%pk;<;44a=T`>pZ_))%!Frc*Pz;xG#xhn= z2n(-K401~iJNDyxVRqmMYq&X7!SSq#ag*5x3R+0#UM%#8&`%M=>Ad2U&}Fe2V0jGO z=eXu;U6my}S}S*7F7@2qVBRQ+L2K5Muneo7Cno(ph$-Pb<%Hc@#d*0(;(7Sw5|$G$ z)@pKk(MD#t)_hv>CXKSC$T7$vc0JE>6#<9IW@60mi@5Gw6!C5sk0jWyg|qeP!h@Vu zINW)<{#WH{wak$j&x}XBIoPj>BIeszj!>t4gTuPyOls{7!H?8)nIrm;C*jZf^CQl0 zoOHe>F7H2I0_}VcfVt+ulvmADlfnLk<-CSRblkbjET|m3(a8yMuRGE%Q&s7ysf|pT z*YZx5F|*iVEH0LNueO=Ri|v`j1Yxw`!SMrMN~{D-An?PHJWgx2)i(bvZumXzGta01ZtQ7YI!>;6H0*yeO!dDkzo0Bn zmL_|juh$(3=`QEByh9h=xpvf^Ya+AHaH>ypmLRl2WO2?ov61M+VST2FI{$Hno=BAc zGKo1FMqLtB>Dg!0FX|6(9`H_5-x??I^6Xx&{_`n;HxGF0O?aIrJjntt6B#;SssvPV zGFA)UlUs53mRneZHJX1H>gLaj%y9vHH9(J37kt1g;xR07MP^SP$G98UW#$<(Gsm&S zsISVfgByMGs+7cgl#GirC8s#sH%7=!HkMbr-J2!wI<{l%FVkF+g_=_=)12Zuj{~n4 zL87O^GR`q0>9kHU1d#VP8To1j-K$k45xZf&Mou%8gF4I zs5Y)OcZ{>G9m6xw-j^;>?R1mn92XmArDIrDmNe>e8PV>CB zQ|df1{XVn4KRpotFiSjmFCq(Ne zM9T}Kb%oLL$HZ&Wo~syhGJ@%YyA#aqf2>peg1 zK1qzJ*uZm)gWBX18#swdf&FwEWM*|&6O>0xzz93q+AX>gGTDQz7ua9iNQ${@z z;y=ZqS&xp;&F7VG*Wcozl3N2T{d!_@EX?my+(|H;K-zvBTGWW*|Pb9xc ztOvaaB;3>hk;T{>5-4WYKO~BQTyp849XO~znve-Me-AV{T;`FB2ampZ;IEVCwvq{U z*>P%JZPO;&5kk7uRdfve1Nj>APKbNu72&dq_A-8BuZ6sGaF@@&UU!-PveCjK`bu3yv!zK4 z4Zz^YT310!sQJOjO;v2ZhNCcSH|7$b>|vv}FH-hI*uNbeMN}1=O;i%W>l5m!@wljv z2Y;UQ(;Jb+v^P@O7cCzbt?P)E=SSG7j=r;HZ zG^Ra*AAG{w92#35>}fXzshdKa;q^aMoJwRW4VnrM=1( z)#?&yk2qUt^t5u4x=7k9DG#y0PF<+s#fuA8sCsEIwr4vS0#qam&JqID_7Bo!XMje+>=@ z1`#=b)t1lYLiV;SI5XAtJ!u*sGW_*P z8UB?^lQWor*RbO8dDJkg`BwMlhw1fTd@pmgm4NS+knz#>aNRh3?hyBozSdo?_LyUV zk@T%$u(>}<`wQEqgVJS%Z6y=!^tCRa?n>sLC4C);pp`33f)1XZX{UQpe?_>iKI&hv zr7kz>U)G+@f{wGd<5E+e(tDeVpLk7OvbPq034aP%n-2?A>a2!|&-qY}pPEWC9@Qc< z>X$C@$5ZLB*5DqO_O2=w<$fq7tT5L@dioEM=MPLb*`Xm8jNmV~cC432N8Hs`EAEQ5S%YjY$bN59<_2)aeiSzek!=lqcsI3JwcpY)iUkjz}= z6xH{nP@a-%^~iv=KCP{u7vdDT8O=@-<7dBUrV}QVxp|^I#GRg3?$ed zWpg}feS_D-I~+1o>%pquBU>o^^jQVRao98>;R`r)t4Uc^5Y!o*R6P+c1-*NsZUg?T@1ZsNym3F;SO z5F|0aN5L$4cegoMjBoOUajN#85TqP1z607%w2T8EJ-)?eD<>S^6OG?ZkN_FKMdyFA z@e_Hk;HGhKQ(P1n6W*0iAI=IDkCX#0hwy>X?hb8b@56bKc!v9E_1K7OBex6H>VR(B zp#`mhl}I^6FfhuLY*w=>jB}XpG_M*Ca&xkAF_|9cDAq8rtNGd!X)t11J;O7%Wx)F zzXbp#7!HRtPkl;6TZoD&@b})X$1+Q3Y|A5-^ zp~E=T^4|E&L#9gTUf;_Jx%KazjB2$rvnQEfGMi=sz8-uv?&TIr>ye5}$J2|NeQoaP zZB?p<(X)It?yz2!BwtyJfiK`0h)>-@V%}@C@M(a8e=#Cn?uyy7RoV;qGDkAjZ9Af$nD`5<@*yb7-shuKR_x2TpgX8`1;6K9?fg7az# z^2rjFlHajPLII7;D)lZ`5wa^|!Y33>+7LxFoElfY!MK* zuDZoEZ}h#sdW^AK>&xGrr|Xr))E@Xk|HZUfmIuzkvW;T}I7sY;oStuVAU$?-zB>vE zhhmMsCB4CeLJ_V3Kp+4{SwMfu3BA*p*^$``I!kozZ1ZwgpJfh**CXpVtX(f$dL64r z!@olAduMqIW+mkL{sdaoM&{g^c}QKzfK-W}sb2{VpVQHg0VwU##{xO(%-N?tdK3m= zPFJ;h_#A*Ly(h^5a8RCem-?lf3QF&(QrjeXmWNeo;#l}{r%}T-i8DN#Tjd&0SR}(U zE3-s6k7m$1w^N;O&MCu+1-R6KFE$i|Y8_|=QH@o{F6y|{ssj$hO)c>mF1?G( zfov$n(O2j7qe(;J*Oj2FwPFRoww`@b{7SALaGxbq1ftZKYv_{q8O#k1@nqpKK0u#F z?=PLKtVHdleM`d&Okcnz#xma`UY^f#y0o^S?I}T6@H5@N>_?et{$6v^{ma@1_?L~d zBUv=-Wy|I4f#fTbW{Ey=yR8F_2CQU>n}a1^xFXLCT#OEuj%}PCe-rqmzqRezQU>3p z1VTF&xDbQ(e3mu+dtMzAA_LR*5v#L|*xIu=tAhGPyB38j^TIgpsKiCN$Jc^?Ud?}a zBmQy1e_Sdzdwvf)fG*%I+QmJcc$^@daFX$`+Q+z!%a+>2aBGSs_Y;7@Sc`zamowh! z+`m7szLW+JbfzqSkyzALdMJD$bEV7v6yb;1^s?y)wHqg!&b>lqgxYzFnwQEkv&@+U z!yG*R0*^`Wg?<}n;89|SkdwC6(^}rn$;9VEU@c^=rpjMkt?~rGr49`sx*{#bS@%rwmE03t}>o0*eNpKcVtx~1>RZUMmjkUtqtSl23x@k91VrPv685ll!AlO za1Z{p&03O>nhzS@gX#(vO5*(Wi%;qKIq~_+y#Gdu!r8?WUgK2d7GsA=GNnK-FuXW;}%DyR;eK%E^bV~htU}ZPy%F;SKbY;_}vX@eoNypXa z2UeD^D@z-cp)32_7%KC;k?f`nj`9wy>=l}9&-SDSZs^MHm&)o>l}Q2BK0P^R4A>iF z(Wsr{S9<|*p@l8FLXkZ*r)BufP#Nw{%W%g~88)P4_{vZjmZW8vJt%`Yv2T&5Jv$9c zO#scrojNfMk96XaZda~_@uEbFVt&(+;Av_vc72$8a8a*r<}v0paGQ+F1xIN8VN~RM z@VU&|sCX)L>`eF9cXDnBHRp9V%lQGOr1mKrm%!+CoK0Px8ueBwLxzdqY2CMz1ODiU zV}U)ep2V|Il51Jt>r^{t;_>-WO!t55sJOt^NQ>h*r&y_|2V4-Rr28qyv=-$Kb=ld& zPup7B+mNxP)U{>F$hOi^wgs`_Ma^55xY|lb+Df?f5B8u7-jG=hGvKPKjRy}m*p!D> zLQm%dioJ1>{>usoqDI0ZH#aBDk@AcO1YA+LyDYlqPy$xc9Zgf#lC=l`sp+LBg^49{$ zXmx|Mv%;xH%Xe=q%P1u9DPxm+Bd3+$NxyIOwXC@sHff8!sJSFIBXon8VB$X+2?&un z-fDOi^}9LzsA8V!EO8?mY_)M_dz1(8vP0{MSUyU<2*~X=WREuFrw~!V@T+dhN!#1x zdoR$Lz%A)sAm@R2ZT<4|-5%pue2ha^7Ch7cWMiq@<5xS})YF!`aMSTFey@A44%tkr z&K)1))Ol}C=Ka~gyornhEB(reG7hZqxF!$!T>Owp7yY{z()} zrQLm!v~O8yVo7W^oG?J{|65K+6%N@D(dJV{vyP)pf!+&RFUHRJX(r4+rKF*1IWm#Q%&r> z^-5*}Cu{N01@b7!+NZun%C6RX<%J1d2iP3<2uM8CByQBfB|34|AnxuYetCZUAN!Ip-A#1x5i2UlSL)NxXYP(WuvoO(V zlz5r4;jDJ$Uj_gP9Av_cY&j=$#Jq|eoa777C!OViBy3XS)i$F&kX_O^*%th;puHxt zz+R(nMI^H@EE2pCHMLdh7<&TBX^Z68lfn~5LLFEtEz!-a?URYeA&e@_tygge8V`wY zVoyBiBHz=ge3RHvg#lJ|yGf}$M^2Ybv?es24v4pSmomMh54xa3N=LpSD9 z_XOGYU!hcL)jUYGiB;2?MlERLZfWC%x%h+?3@tjs*`O`>RliL87LM`yEoV3VdKMv6 z|C8hCLSI{8xF22kGX>lpzj0h0HH2dG+oru=yPUU|CD z!O}?J)+~_~w5y8*UbUS1wo9rBdbOabs^ujOe?}UXX%&*7(2W$Qht0CQ?M4fGT}!*I zgf?5-?fOgO76K}Kx|)`}`h^SvQ_`L-c>*v&nyw`#%-+iJ(!xrUOr?>f^h9j*N}awc z2imv8e9mB&>}CLyL@-jhp$cyh_3_{hF@r5}Idjq)uOj zgFH6wyVL3%Yt8@6ZGHADXbGEvi97yW*5NDhK9}A<}c+gz@k~=C(?0|BF@!NRE=m3D&1Q zm{b1}{fP~yjG8a`QLAvOEd%l+{XAx@nG|!!S36*yoN*j7ein)VA^od)2xP~tZD5J1 zJ%Y_;6TmUIF}X@T=n@#(aZil~*`&~o6U0%HPWiv7l+cZnaQtkcYeRs+>!fBv82u`!z$g$eRn`uZ7LdTpd-{d*$(5djK#~Y`RO{(}TRV3Iq=u&8d)3zFa zvCfFjaoh>T+c=KX@ic*WwS?#p$=}~+9*5{28v)n};*`z_x53w0obcCpJP{{+x8{VO z1@w^0>;41Z!U=EsNI1q-=Q3>Y!Ha#g0qGpueTQ^W^T8SJ&71f^h9oa6dgT9uys&0h zO@7#>70OS-51Z_;zaf`--p>eC!3wu$(F=>af+)m4tNy?nz8>XrVd28M*7&8WRr1Kg zGx|ZVg@#;pFI5P7hV5#TuEk;-WEY`=^=3U^HS0<79PvW6QnL4Zvm)(HJczW|6~Da6 zb-^h1AEzodt*EU6$>gy5vADV>PDOrq?5CK_@Z%AyLia9iSS$iN~_O=VGlfzk& z2ItI^!TAy!LlVyE{o&j$+tFY+x0!=T*9O43q+6R(2*^vir^{M1AFoV2B8oJoO$k>| zNm71&StW|f=jX^izXwo_a-?G}RAf*f>BokcoP&Cr+K?DZPK%t5=_%)E^5p$c1EVE; z#bJIiA;XN}V(AhXJ;fN)Yrb;pSBB1ECiD%RU{NT%6{fs*HVkhVA1RH(buou+rTadk zDq(dZrTc^rdv3lUIDvaZPmfc+OqNJRphjJgfM-HFJT+>hysxV52bJkzt#P9r zlendn?!J*f>BSoC0}H&zAf#5878o~VEG@n&aAAYpZ%(Fg>GAolm;=Ocg|CVs8i-6CX2) zOoo1DxAs)XNiH`2UP&_?P4YXQB|oa-V+zU1XlBIsnzapI+0p77S#gou6U_LawR;5Q zToVzD8bUX8*=iedK;X#w{U7{dX)#>i73h`n;2h|Oi zc7X|z(k?`!`x)Sk?WG5%OOe0T3CZ=u=j=CnaL!%Y)dnJtl#HzDZ_~Km@2s_K(RLRd zRbQkQ3R)uvbU?-u*RWy#DB#T(@aCt$yC4A`V9^4#S-=3A1y5CYzM^z|KG%DmWUHL2 zXtMqXFS6z${o17t!SF=liUEmM!Hc^nc-?@sL_wV>1lsIPOK6#c@%-gQqflXXCXSrK z+?y|rVesgZqD2qWSP7g5A11d+07B~t&1xJAT&(xGDK*f?6?VTGD^iAqTt+a}qXE~? zL36#{?AprFek4?Sv_sN?laS?T4S=v24Vd03%vO(sH|9D(k*gs?p^w(f<}+uqBaZ)ox{+ zH!$N;ov~w3#)-*{^qD=bUFX|BD4#u+1TCgVL3xoiFsU zP1p6~w7NzNU6=Za1}&|x=13&rL9H+ePdh7T2zZo`Pd4UM_XgDQskAymC#d6>1BWPn z#olTmDJeqHaHg8J4&jWQCE9>^1Ma2!628sW((}tO^_bM?KB+~fu{Aw|iL^_)ho5E@ zr$n_nvQeo#%eTp(`naxldN2wlyY;L!3e_!>U8g z!>W{Vo9v(q*+NT_98r4umy_v7JBG|~Eg2RrBz{!0`|cK_Me;}yq*P?vFo0x(c8u7*(^u@V|w^?6eJBcz6I*l!cx zL~ZWhlzQw;k=5FWiQtggR1f5u)gxY;buH-r*wmAhrK-^S{TLWS*pn1lmC#aagQvbk znwUp6O#e!|VEjyQeWjlDr*rx-o}(L??9%L>rTb%VwU>4cYGk1_;-!%;-AHacxZfoE zN)&PuNLc}l>#<#?0aWJQ-xqC8ot8HI5f2#@-V%_SM|6ubT zGnt+EzWgsTPlf!xuS#aD@*9%#<{WkTW^z8S7X2Ujy*5uh%Q=>ZGgyMLu*Q?%hb)6464Y<6kOjeg_)+&} z7c=0GU!lufaUe-W;(NjBGinLnOSy#$<6|zT=i%&7r6V58^{e9|Wa-|^z~VkRm{Y3` zH1Pt7m3py}X>bYJG|Cun;3u+@LvZJ$GOIA|uHrG$N%!VI0JY!{VI;Cb$1{STxz{m4 zb2?7E@yGX)TTsTm`Tvqn{PWMCJ*mRdc9B=tF*}m)xILK9^ySr}NLRu;gfBJfbn+*v z%$7Iw>WDOz8N5&me*iL2DDzyip}^=w=4AWUAhU8}XS0a#A~y`3<);|_g%G;rVkv4? ze}?uYws+)6S*n#xPz6(OTpa)usHMxcefC){&wUk4QG3>G{=PNZ#Zwhe-_uXm@${m8 zTELT!r~4N3(4Mu7zfXTkGGEVAtEUUhM&>S*EfCNaQ1?A+D)L1?OUlf%JtL)ha=Mu_qsCa z%$xHGtbSJW(Y@#IG@ib|hVl+>ONJXQ3%|{F;mB{j*v7`z1QFBvW<~zQc(ADnS?tyY{I@SnmE5{}JdU~xt^o~39w%d2SZfP*9djjKkv^|qNL2pZU zM(E8<5n4|C;HUBYZF!qUc#QVPBr(+OSn1+MFmq=-zv=SE5uSK{q>$u0`F@z6ch2O` z3O?WFbN&lkv$y1IEwgVaw{I=WpxQ0v8KJh!P`j>tPqOmJHMV44oy#6-%Luh+2nat* z=DO3&6Kb;yfbDik-I7eDjxzwn!KaV55A!*|XFs2Pd|u*X%)DbiQHS>Pt@u`aAK?1{ z-#vWy@O_x?!+iJh-OD$zgudAqaYX34Ewd-HH!&J<>GWhiGx;pw1M5?_bXV)0(&O}J zv-j2;VC**{3`ZhwByee8WLqo&z*d}%jX_G5Ak8F$1&F9_|D~< z<#h*9qQ2?n+rzh{yZCnT?d03Zw}Wp-|G6>8Xb<(h#AiRBUa31VH)WZOK-ygB-}E+Z z9_Sl+%y<3f&Qu#q{(a2Dp4ffUeNZ<2bl}H?MA_BZ`-9iJe(7C+PV^ zmgYZ7&;JA&wCMR|_Y9=x(YpuJ^Y8J_q~`_w=y{5o+o&;9%#AI~hx zh7NcMu`(_{dR;zfnD4SSNv~{@F0}b{n*Xh)~&o+iIr z)H&qp?ApA`-E^03ryp0*{g4!I?(FO6{4X0T`bWVt_KjS@P#-7EX=mSSo%&tI@s`$T zhqcbx%3TIqHp2Ta6*X^#p&?4!*i9b_fGgqtP3|MB`$2{QDO2b}$I45Oc81=_47dtj z*pegLS+_lS#Mp^S7tXQx^6>Qx`!K{ckp00gsCc-U?_J4914~=^Yu#Z>EaWRh&QKe_ z!dc5f>mEkbi>Ww1u=0~(`)-wS;mt17nJI2ZDe zVYFtp`dU}Ikwy2ZRr;;pdKbYAqY$A~j+Dj0t#;=gP@i9~An^jycOhX@at@)b2Hk7Q% zW!B_R*0fpI#C4gd@F5}?SeenwO~r)`uDar^%V{AnvPQXA(l`zOJeH$&Gf3e=^|byH z(O=A4qu#rT4C+DNwcEI)W{?thZu&HFG5t+JJQIkij5Fd!3W#K>gf~^{5`LjVgKR0e zvzZTqVBoW7#7_RYg2M#Wj=q7opyucsxFI9;==J&DO!MiD%s(z7DZPruV43!kNnKiLt<2Cz@jyj)Weri>WIv7gd>M}Zf}zI z>|B_Ko3#7hYx=^G{gMeWPekB?A?J;TMQje@W2zW8z42(6>vnS7R*qZ$f z8S4MATo@nL+X9Nv@O6AyLh5)B`AnJ|NRB3^5_WovoO&{s#JL3MpLv^PE^4lle_C9O z?Ie@!H(xwTY8n(HG!%LLp)i|T(!G_u|Ll2Y!FNx5p6Q9e6Ap2naqu4A*>;C{p4rUL zJH7lV;d4EoqrPEG6UOp4}BzfllvGdF~zDXL*?GpQyM!hx_*C&(z-JksdOj=og!nKw|AO<3G-g0|m~TE0 z^VunH{uSpL_4O5O1QpADBe#Es9fv#56$q=EDpnvQYkm+q+yTEMyKb}Wx^vjkhyk~L zKRPEtClbnS&B#Y{)urbpuQ2UUH?AINL=>^&x}_()($tg{kBgFkBXuH_XT>D&M3&H zDU8CSUltaDn?3at0b2UpC4FAV`=R+4qrSM>4 z3ZP?R9LtzItnuJAw)Av7u*oF+kKn=2#wW*QwSZua$+?0Dql{5oOEE=oKiVA6j*ZYa zyf;k+lA-Y7XJ67|#mM%RVH6(PK!nU)CE@J&D1p?-4h)-|8_3~B?4d2V z;&H_>1?qY~w9_t1TCLhVC-wuTw?wA5;fqW!N;ocP8>}+=4=Hdi037BuHVNiT>3eAN z^r7`BI^+P7j!20U$P#L7l@hbY6vv~Y7k?-9#EQfnxOTi zs>#V0PIz2Et$Cj+*D!a=cvVfU~?YKYMM$9 zC3MeyEi1=Q)eYuuTIPyxjK{?hZ>tm-y8>cwxE9&Jy$+f3(bH zE1_AhU;Xw0G+Wg?4)6>0HN2dx8BDI7mkBoCb-hKb!t3{Ow|JrSjP1DSNI2N#)7WHs zg4I}q5lZpoK%HSbQsN2){B#4?n&5QNi_>v`tFsu62aO8Xk(N;4lH&Q!;OHuKtw1Ko zbB{!!8T1}$$q2YPRa!~G%weVbXjM7sPFZX-p>2a*y1Mw~5Bk+jx{=nz!TmHc zadfq_CkK$y?o(&ehgN`JD`P5Tc#HIG#~0|?k>Vf=-mgYVORfR$crSAKs?i)#$>vYZKG#xQ1L9UvG@}Wgu&k~mg>~3shxH9mB zK5_(xajqW~`luu*F>kZv!ELO^hv4z4G_rAZR_rN$hTb{OH94Lq;F1;!@$jpAN%pHR zfhh|YZo1L+0jDjTbscGS1V)5*IU;4K)-H`*44$$u^J-NMDvKfzaK*D$aQYJ(A3KL7 zi**bMZY6ICwf;6cs36~mb2*9 zt&8(rM|ZIOAzj;wBY68|y8|mj${x7Pd>ec>q#P?xt2I7q`HM62b@}pSz8*VkDe)=- zqY@=bC=oogs8#a5WehLc{Z#z;@eP^BLI>kT&kK=y!)CW{3UnFQ_EG=&m&F2Swsd=V zbn%!G+qpC{J-(&Ku8-H@q% zF4p8vyLdVTA7>sdIh5Zk8PVlEf4=MJ3+L|~@eG%-yh$xUFAGrgqCYQE6!akz->i6@Y@ScTRP9a|i9tsD&_fijv?Z9p@R#^$1#6he#@&6lMf7o~(le#i|@DuWy04Msc5P#-R2_{s!Hoc*#*Ci;EF^m)3Ti{G= z#z(%6l^I4arW1WAwm*QP(8LW6`&D$T;C1`@zSvwGd90j^ZNmt^+7H_)*tkBlzL%1L zVFX-gJsRvS_)v=bRW7&3(81TJnDRm3&WRG$xGZ-Tz!Pgpz^7X_0^v4IW4Yr=s~gvvaUrBedjT8Z(V(jvQxNk_ z>ET$6<-Ajt^LLh-q{0antjAMHbdgbWS#?fUph+-}3GH?ZDfnSjw>fluq;U7&> zk|qqPk`~0LLQ>Mmf|F$xYm+kdQYN*ekYD&MVF&^cEQI7dnlT)edKQ!W7mobcLH3l; zt~l>QE%vS1I;qX>zR!`r>OG?chYJa{Cb7C@mZn(<`J^|2klz%~(rvL%Yn*up&IKg9 zM^Y^$dksmgM$g}nky+3igx+X$Rb$jDGFwA1L$d6|XPoeQ;NBSJPbFSxCdSSZT)%KT z^JMG{XP^HJ1#lmap^LfqB=<13yxRLift#UwC{zy*#qH3=ct)@u?u$*3-$A!ued}SH z&0gwSIC{RP2txQDFs>9_$YzPLD*h>4v9b5t1E<~Z42=4@4I&rJX)ntah6xO#Tkz)( z-KUZ4b|5-`2XiCH<7VxSQr|m0(KBEatzl3?2}*2UVsd$+Y7P31QC~~uR9EAdg6y&B zdieqOae*5Rm_JLuz3_l0VIdWvqt^d1JCZ6^v+)&4k|#1t$B|99r`mz3Oy0C`S0l z!Xx~Z;Q>gLp|YqUcA8N(4s;q(rJnzPXnPyLsEVuMf0NxL3rV;E5{wF(N~l!CMgvL= zksu_5Di|8bhw@<`Ax%@%6m|hyNH%U(v$<}ytv+g%R*OE`x9wB=R1~p+gg`=2Y51rn z0vZ(cu8SH4Nr1?{zcY6?AAq)f{_h{n-h1cc%$YN1&YU?ja|VyE?$XiBNjT`Oc9aU4 z>>og;MeHAz5oXXpm}K18U-AeO^+3F++KzYyf+6NdWNa!lKEj7dXqcNSL-bj_Ca_=4 zwT>_l!TVUM(T5&Muc7Rff`-NXCH^&1hYG|(J$KfHz`Xp;lXTnFs_y0{X%^-?Ym);iU4ikF?~1&^T)Kpl2a0ulIf-VEQEGkmUe> z29P+B?811`JomgJfVulClrx!}1BAt`=R`~90SVqv36lHrk0$?Md+Hy~N-?m%v-x9%q^?^UN?fbY%-jP{}r=dB4 zVlUZpJIn39b)CnaWl*5AEMYSuGbl&g35(sq2yxt!{Wv>BkAr-d+G5xlOj^kFqVFsr zfJn~1uQ;#W-N0qSs@=@c5pKg?J>}3L5iz(W_z)uX$FpU&8Lb;xafdOVPAv`*@|Jst z>3O*C#iv}zem0`teimc6){{zf zNO*Hn^~rJqg>iGy_yW3EGEx&Z_StsGhzzk)SzE+oZ5K|CWRK?e39g(`-kH9)xVxJj zjw`P7&75Y>66w8qq)~?Up>L<-j&5PC2(6V_#LH+-B?Jv=u}A~ZG`Q>tSiOsmXDmAI z_#>kJxY;qpI|il6vT>)3yOVDRv4R3KL4xq?n{qoA-f$a&dU!+JNvsOz+ zPLK8=LxvzedSFKLXydw`%+r-O4nY1|K^`|2qm2LFLW#XNb}DO$5zO5!_P|B?#wk>! z!N}taBa*G7rybLRPEn*_M({V$ga0loD9Bip;Q-#J!p$C{l z=s8{PO+wm;%2Bc(y37g#h*0@`y@;nc1{;+Z`JfIYN%UDrsD9|qTEmovt!Kh|@y}zB zCi(PnlANJiL9CQvB-}6iu~;w|i;;qbunKxg2!(_}DBy?Xn-CZ=2mum@i}#$r_%|^C z4YHzZD z3w96;{tF>@9u|=3Z^Gd$7Lep&EWU;XzCRcXvkmNm!q^ZBXSadvyh7$cvIGoi6l01yX z*R;WIQQprM26FgE=mNMoyPaoYnfd5Bp zv1q?mQw6AuMMt!nRlJDWaF+_`)M}Qg7p^m0$cvt{-{;=-5-{YNlQ^oL`{(k7~SiPDDD37ylF4XT%}FU`d&3<(Wj2@_;Nn0vtR$3BSm{@O;CHO zH4qEDW}C*heOBhl%Fo>mkyDC;&bKi!pnI*dMM*jN%AHrxdC;5FP>v6r;fkeFq`YlJ z@NDE6NV#TgAqqt?(yxmMjlSmYjA4T@n`MlUpSQGM|>pFe4XGqMA#p~K4O1W@kLA!U(cHe_80&AC4)!tfFd!cvE-+Xlv;GMIx_QJ{q zQf2(Ee&(HXq;B~2`y4a1mu6GzyV@-VL|?8p=}q?gJB4PUL;1n`@AvW+b!RlfTbnTm{$`J`@y)Jg~m+9l+^ z2J%pSbk3VtP%yo8IkWZqwi7;40OlJny)6mR^DJ9k{7h)cH#VrCw$Rrjd<9=>;LE7v zmDx2c(IUlIy!T14p3~sHPqZdIXS-K{)^q;mRUq}8o!e*?0TRq#2OL(RqptKqISh%l&tkfbVR15EKB*6O{sYi<{+w~p% z$fz>ySIL_cTKipjQz{!@!o$5w7R&yk8=FM2G=7o~Voc!9s|-H>5Rop)JYf1HXQ+38 zFH;5;#Vqj3Zvt|M0LivB8PVUADodrZHyNF`d;>xkkjmW-NFIX-Dl`8b3l!QOcJ4Fwh!rJoQ1{hfB|(Nn@6tjjZ^ROU-V5PkP4h-RP_ zBLvPRdV?ZJ#w~I4j7Ncg6;{((utJbNSCEjoLFbj&_#FQtW{u}S zQ?n2%Ifpf)Flso8*7+BI7Uu)pPabjb6?&k?vuk;4=ax^hOD+X z)+)Pt)J%Azd{{{)XIaKJs;Rf}t2tu6cSdjOoWKNxuqMM54mx56h1=9%i!poX?DM>K zby%9MqQ>6{@}`9w)W|WjMwQ1=uJKzlz4&VgH!ggE-E63_qytPw6$fICpGqR8==~N^ zBY3mIDFkKt*F(9m56Y}_pv)_fWyJ=$WpAlIhJIqvtxILbNoTinU4(vTuYN~wu=6&t zOB9#fZ%Yk!4t^+3n#XqDqNVy|$;gTp2Jj7kjb+oI;t047uM=Y<0U1X>VqH;;8q;k{ z^(jWeJUGAEtBlfO;U8#?`WT^s#hK}+Of<2u;MDw|=X&vf zm82dR*LQZfuvd;Z&9$fRHvV%94QVN4fC{csjsox+ zqBQQyOxR)8a37sgfMPTjx$GRV>Bk${It8i3A*EWsb1!S>yyn4230G=~Z%%tUce8fl z;sdP#@?u`pb4GhPVVDDLy4=f-vpU$Lb{>0|9rxbZv-D}zL^$ohUGjjyB2oTFF@$?xP@kWai7&!Wl(fW^5f}NDs zkDhsr1J~oNmY$%$6PVyW6U3PC=Nt$zCtLwN(J^f|2(E2fE)!ooK*HEzOER&ooAyyJBv zq-^KY-C1NuS_LtYn!x^97VomI|4lBjK>hn=aSs0c#c@CGQg1xuwv;Rp$@IaZc}?7k zn*N6ItIc?lIi#fYQ9f(rbGLlT&S6<-abRr6y4G(6Gmc}oUexq-B3oCQHYLli^4r1L z#kUg)W+;S_s9JGhG`Bp@*j_f`a zbfj@MBi(%}!f~nlRHP%x7|oPCw7_#zY~`$2jB+3TDj+_&ym=Mu0a1G`O{L^%vAOBB zm}@NFc-eEQ*NWcgjjv$%G&QOXX_G;g^YPHhZB-ze3vmA!*=K2Q8B}tEp8*L`v!|+D(;$wNPZ$jYOwJ83#qp*ZZ${Zn$$Tg zt8=0WV7SLjNB<8R-zM><_^v-~_D0jw3XOXOx={{tvab-Vws}dd-s6my%y(&*xFup57s6(PoWuTzesYsMa?AK~K-f!mDB?<0BQGbEq290U8T zGK!`Gd36+}c4S>^%ybVxk|iD?(0GXtS5T&CHgv==C0Lb5Iv6j23D_P1oIQm35wiBy zN-g|1gEO8T6nDuw6{q?aB~)Hk=yN7;z$JKXNLTx8k4RG4un{{eb6vvP_gP$8)Knn< z3@5V>a~pzM?YEbqyIRr@6ciZ^ufey@xVmVva%xBn>F*IY^(yDpIEKeue?_^L@4Mec zSUJ0+x>X^vHOyZ7+Xo2@>^4gnNeN{SU5$fN@1i*4r-wN)+~gM?5qavHnpStI^8$BI zw&t!T%ys%I$J+eD@@%AiAX9l+97W{HGS}%Pjyq(CBF@biatU#f&Ux;hgq1hfu))Q3 z`by^vGd{s>xblr3_Te*$IISiNTGbrTY9{jx=EV6{N0>+%MyDL?FDcPHKPHB5L6;VZ zt4;{`C7VfK6nSwrr}SvwwThzY2dM6JUv4(k7?of47?mu?%&L-b%`W3nqVGNz)tZvL z_%=!L@W2!~W{Pl21P~NvOJGOF3^Ai@0kZ{`9E{IoM;jx(%#E|?4qPW{YmTvaaV}Q; z>O2yaJq6t08x1jFGR@4RAJoD1P`=R%E0yTE^8wr+Jm2_01*5@~&d00w&5Vut+(W3h1a1vc-*}641(qv} zSA`b=o+ZH356J2UfZhtje=-DrQ1E=iV+NOb7ti-DzS+At-@7=kZ&|haD4N`Wb3F`r zYC|2~eiOZ(rPAz-@%0ON@p{A&k8zZ~=&RjMF0W6VKlzkM^gSj*GU_JU#p^3psmu5k zLHG<|?s@@b(P|!*4}7>GA9-`#FvD5ks*JEW=ah+~T3L=aI<<8(=yy<_k+`o_~^Y<=r3VqZ%QA_A<9b zE3$YaLWzn(rPedXBST!s^`UwQrxmffN6OhEo{zKh3rVYR`O6yuRSZ?0aYVDDZlkmS zTd1N7g6h?HeyakPu~8buF{<1a+$gOPRNqFO8EY2ZPt}9hgsE!RJwl5>T%r8)H`56R z*cA#>p=e>iaxv zC?y{}Bwd}^UEo(%EyHK0N>y2n@7t{J1Xt#TG8nBHW*a%9^ci`9ec|96IRNW!h|2eR z90KqldxO~B>sihR9PxihtNZ#9^U1k%Pcf0qCp~#Qebb|V+z@STE{_HTmlX$no+{nec86516 zcU+~bmT9MN)Bbd=C~M99mh3hh=vls5X0K4&)l8oZ{@YcQvnL9 zi1iNM)IvF*t|>Qbo-}z`6EQ>cd|SOmF3~)rdDDB!o}rbBj0?ZwqaBIGSTDxr2`yd) zPpkQyFIQ%!Rzr(Wh{!iGTDHgli3dBCit8<|HHP)7CX?RE*G?-PGDF4UYENZzH^)_< zk%6zWb%r^v8oz6Y{ypg@N=h=%IEP3vo@#)*>!p~es+mOy3F%=eTU(F+XjM)L9BD9)cP?ib8FU2a^rg(yV zXKT>IOqpc%@qFWSk95dn+F+rtGLQYilY`fV%bd-WO{oAEheEh9?dU0mhtvKoMFw^W zsA6ZhTi9wGRk=m*kT!GLO?=pppDxQyVJq(1KlMpfCP7h!MXOUq`Ey*79-PHK8lxaU z;x0-(!GzXvv|qWFJ#s~5?bOKlE@_Z>OaNqu_5Z;xToTw+#d% z6VQlB!v1w2%ieZC+cOQ*x2BK0~y_cNV)Esf7{vOJ5Qw9%9klo>L_@)QNji)Mq(X9_S&@|hIbjh?0uSjSsM z^3ND8Aem6VAT0k3>X+PxDhD;?nqS%N+{B*ITFMCVelh70R`-yCLXN2vj(5@SjEh=L&)er2#m&BAyohe3UAF%Q>YTJ)Ey*0jbTJdOLgJ4`(WaM-! zLGg>M{`7y&q#dN_t()bWo;EC{u@46+XTvN6MtKEl}nz5{4*q1CYu5Gm|pr zAy=VHX~qpIwPUXlbJ8SD3#6H(aw^iaA_+<}GNH|-ec%StCLW54uzOn3rogyG(Q4pk z@-S`2gQlX*1+Se;o3!)O<_{l+X|uNBT-uyIhc-VWy`oKD*I?uI^KSU;2gI1f5UbS> z<+R+o1UW9}9EQ_DR!7;e?Ow*cIEBW0y8!Yp?bkNjsL-$>OE?oyjoy&n3N=_j(;jK0 z8=I}vC9QM>lMREMa)nLLNizQaK^PX5@r?WAWvg5D-K25EVrpoPp6Ht!(cP|}OqX*D?o&fnMY*;W z1+1=ZX|=z=B7y%C@gI2+ z+{X@8oR4gCpA@P!3e_6ZYKNiyIj~w_5DD3p zmB4xr`YZ{NPGW^{rrB9VHw}n3U7T#Foh|FnHS(L}lHW0F<#+NL`OR|hTlzrKQ|b}_ zuzHNCP>-nP>M^cNJti*Y@#KS1kMYoREE^tBPuqrvc;W|o!;|u@oTIcvwX@hQZc3h( z$PEwh)e`06cL;7lTB2%HY-lE7dQy4%W@NhQMaA@4vZ0McoR~XsC%u?DD)%L22IHgY zyu_U)2`t!tab+rL$^`ZxHzT3d8ZJ(i(;Hv@L_}$v$dKpn<*?HS7%W>XA_vtONeXaB3Q7tWfXSnO#oY1#_bevk*~HIZk{(IF)daN{&YrX*Di^&b;sjUtR&j59w~a-H>>z#R9^6iO8Vb&quYlj7^(r1&~$rM&FH3Jv?&1# z^{&83mghJr&q|!JQN-sBLAi@B&QkSJSV1~5D=r)JU6k-#K zrq3R)M>zxXh>IVuXD7c#ki`t9NE3{C@Ai#8-+Bdi?Da1Yt*mQ*CA z99=9fG%mdx4pL|1XUxO#8|4IcAbdFf2}$?QPRDDWpFlmXayg^b{E)Z0 z3({M?8V`#5DHUFm^pmzRX7L{@3Y^IF-Eo9h{o@3<4qOLp;126|pud zBAULGHg>6SA)o&dvQdWHNO646ZCD-Ga=^kdE-%lK#Fyq-MYP)|>FxgVzIU5{ao>A{ z|NFdGt2ELF{WJQaBK_C#?mBe^zPp=jX3Ofg^?KI}E}8FWzU027wr&;Zk0wM{4gTYX zq1u*RzM1wmlL7nzJ2Gga)Xd+>tAPFXV6YM=U>k`J(ZIivcU}2=8VFE9`L%*F+p)lP zs?0Idb!xdI*>!5Q=DC+}vpyEy{3|8dwB^oGvqFIh;Gvq!Urdxqodo~QeeWs$Y~Jf) zre5p3gex~DIg{~^8p;cWLU~7%W@@@hagCYcSs`<S{Ij(6QY;1=@pO(DJ4X zb{o?j3Gh!be|8+Udmdm4>Drdk;t_5nm&Ig#lWy6*`r`yE=L+6>@y6x#Jjri{YG}=u zM*%IW`J#olHt^Z4q(U0x(>#w6IH0M#eD+5>hxSM5s!Jf~ol(e!U=@sGCQJa%0L~N) zw3$BIzJ|n=N92e zV?}_y8L|t~=aGtI9v7Lw$^mm*H3M~A+M>jp6cQL&H>0;!C2UdYB+$%gV&(rX1?kql zZotBLrfhH6?U-a%PdHfH$|8)^!IAj*gl&6z22|o>X8qoc!YuxGd40O8ufF-l2Vzb4 zi=ofmQ=vV0oU*l=gZ#RCmT3>Xq{4rw{Tx#%F3f-A_=&scF2{Ix&)u5m*TkJrt>iM% z1__Jf2cg!W?tuk*dLPcl*b}my3qgoM&CHhKh%41SA~H*H2!gFwNzdqh!uYodftAe3 zHj6-FT67iA-Dq(Ms=otO-KrPZ+)Z}>Za$pDU8jENyu)NCzAb^G48>ZN(dOtmmZmr*fdPOcsabc!zA$*NbFeA5ZoB)Xd@-<5q}r1}6j# zPm>r?{Uyk^gSK&91s{=uU~4dF0> z#V_kAv=$jO&-Vp#lkG%fiX+)1hun}Bt!og+n?xS&)%vF z)?(&dIlQ|aC*Iv}_g5~|3m&3P-qw4Zk!;B|X}2zJ03TfBRU!iL76*X&z%?;H1 za*j=C&J7_39lR>Dccbc|{{dsg=(}ARX^x_x>77vhHbg6J9ZMb`DSxo8o1CLu~1UeZZftU9|-P1 z)%)&xHSXyOB}Wk`6zGbs+RM@TEWmN-)aQ8$`1v>CwcJQLMpG%P-`ETl?6^3%QKEe& zDqMK~B1N!j;@lD#?N+WSgRr4$5tc5bBE~Nzg;^=f+N(mCI>za${RiNia+5LaTzo~A zU?>eLozU?AfeJmV!b)_d1k}aUsu3H*YLf1InAEybkke1pwfUPN(s|TeQ(X3~GbBD@ zt}~2yGd5xP{@8@+{QZ`{0DmX=oBEH~ggc2-GwFi5MG2yt*X7!$R%kWRw6p6pFW7-B zQ&&3dQwton^u72+-Fu_!^h(DN*J%l8C7^Y>XRqe@J?T7n>TTuuKTEIQ=9n|}M~;b8 z?{<7=s?#xU>KezlrWQNKT>rM?Lf7dZITBr`?{;Xe(@w`w*XcD5_Nx>-7n3QOOo?Pl zAX6Nf>|_d;Lf$cwH(BzIl)S?vFPKQ02IaCfbEoqrrkDE8^f`$L7&zORnVysQe2R0D zK1^<(jpO#&z~sz5&Qv^X=nE1zi5f6=XLrZNtz+A|JG$S#cyIUHnY+yEUgLo}B)#?e zChcJ^zmW;iKcBX3?Sd=BL&pEl)3$|s;h29qZ999CR(-UM`u5Mru=beSJk0r6`T?J> zmZd81#yt7;{84__->iOLP`^vn?@aZ3JHN&~7^>aAr+6i084oP-R~TFN^i9cr4r@4#z90OXGqNZDwHet8@WQ}eQ?Br