/* * 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. */ using System; using System.Reflection; using BulletDotNET; using OpenMetaverse; using OpenSim.Framework; using OpenSim.Region.Physics.Manager; using log4net; namespace OpenSim.Region.Physics.BulletDotNETPlugin { public class BulletDotNETCharacter : PhysicsActor { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); public btRigidBody Body; public btCollisionShape Shell; public btVector3 tempVector1; public btVector3 tempVector2; public btVector3 tempVector3; public btVector3 tempVector4; public btVector3 tempVector5RayCast; public btVector3 tempVector6RayCast; public btVector3 tempVector7RayCast; public btQuaternion tempQuat1; public btTransform tempTrans1; public ClosestNotMeRayResultCallback ClosestCastResult; private btTransform m_bodyTransform; private btVector3 m_bodyPosition; private btVector3 m_CapsuleOrientationAxis; private btQuaternion m_bodyOrientation; private btDefaultMotionState m_bodyMotionState; private btGeneric6DofConstraint m_aMotor; // private Vector3 m_movementComparision; private Vector3 m_position; private Vector3 m_zeroPosition; private bool m_zeroFlag = false; private bool m_lastUpdateSent = false; private Vector3 m_velocity; private Vector3 m_target_velocity; private Vector3 m_acceleration; private Vector3 m_rotationalVelocity; private bool m_pidControllerActive = true; public float PID_D = 80.0f; public float PID_P = 90.0f; public float CAPSULE_RADIUS = 0.37f; public float CAPSULE_LENGTH = 2.140599f; public float heightFudgeFactor = 0.52f; public float walkDivisor = 1.3f; public float runDivisor = 0.8f; private float m_mass = 80f; public float m_density = 60f; private bool m_flying = false; private bool m_iscolliding = false; private bool m_iscollidingGround = false; private bool m_wascolliding = false; private bool m_wascollidingGround = false; private bool m_iscollidingObj = false; private bool m_alwaysRun = false; private bool m_hackSentFall = false; private bool m_hackSentFly = false; public uint m_localID = 0; public bool m_returnCollisions = false; // taints and their non-tainted counterparts public bool m_isPhysical = false; // the current physical status public bool m_tainted_isPhysical = false; // set when the physical status is tainted (false=not existing in physics engine, true=existing) private float m_tainted_CAPSULE_LENGTH; // set when the capsule length changes. private bool m_taintRemove = false; // private bool m_taintedPosition = false; // private Vector3 m_taintedPosition_value; private Vector3 m_taintedForce; private float m_buoyancy = 0f; // private CollisionLocker ode; // private string m_name = String.Empty; private bool[] m_colliderarr = new bool[11]; private bool[] m_colliderGroundarr = new bool[11]; private BulletDotNETScene m_parent_scene; public int m_eventsubscription = 0; private CollisionEventUpdate CollisionEventsThisFrame = null; private int m_requestedUpdateFrequency = 0; public BulletDotNETCharacter(string avName, BulletDotNETScene parent_scene, Vector3 pos, Vector3 size, float pid_d, float pid_p, float capsule_radius, float tensor, float density, float height_fudge_factor, float walk_divisor, float rundivisor) { m_position = pos; m_zeroPosition = pos; m_parent_scene = parent_scene; PID_D = pid_d; PID_P = pid_p; CAPSULE_RADIUS = capsule_radius; m_density = density; heightFudgeFactor = height_fudge_factor; walkDivisor = walk_divisor; runDivisor = rundivisor; for (int i = 0; i < 11; i++) { m_colliderarr[i] = false; } for (int i = 0; i < 11; i++) { m_colliderGroundarr[i] = false; } CAPSULE_LENGTH = (size.Z * 1.15f) - CAPSULE_RADIUS * 2.0f; m_tainted_CAPSULE_LENGTH = CAPSULE_LENGTH; m_isPhysical = false; // current status: no ODE information exists m_tainted_isPhysical = true; // new tainted status: need to create ODE information m_parent_scene.AddPhysicsActorTaint(this); // m_name = avName; tempVector1 = new btVector3(0, 0, 0); tempVector2 = new btVector3(0, 0, 0); tempVector3 = new btVector3(0, 0, 0); tempVector4 = new btVector3(0, 0, 0); tempVector5RayCast = new btVector3(0, 0, 0); tempVector6RayCast = new btVector3(0, 0, 0); tempVector7RayCast = new btVector3(0, 0, 0); tempQuat1 = new btQuaternion(0, 0, 0, 1); tempTrans1 = new btTransform(tempQuat1, tempVector1); // m_movementComparision = new PhysicsVector(0, 0, 0); m_CapsuleOrientationAxis = new btVector3(1, 0, 1); } /// <summary> /// This creates the Avatar's physical Surrogate at the position supplied /// </summary> /// <param name="npositionX"></param> /// <param name="npositionY"></param> /// <param name="npositionZ"></param> // WARNING: This MUST NOT be called outside of ProcessTaints, else we can have unsynchronized access // to ODE internals. ProcessTaints is called from within thread-locked Simulate(), so it is the only // place that is safe to call this routine AvatarGeomAndBodyCreation. private void AvatarGeomAndBodyCreation(float npositionX, float npositionY, float npositionZ) { if (CAPSULE_LENGTH <= 0) { m_log.Warn("[PHYSICS]: The capsule size you specified in opensim.ini is invalid! Setting it to the smallest possible size!"); CAPSULE_LENGTH = 0.01f; } if (CAPSULE_RADIUS <= 0) { m_log.Warn("[PHYSICS]: The capsule size you specified in opensim.ini is invalid! Setting it to the smallest possible size!"); CAPSULE_RADIUS = 0.01f; } Shell = new btCapsuleShape(CAPSULE_RADIUS, CAPSULE_LENGTH); if (m_bodyPosition == null) m_bodyPosition = new btVector3(npositionX, npositionY, npositionZ); m_bodyPosition.setValue(npositionX, npositionY, npositionZ); if (m_bodyOrientation == null) m_bodyOrientation = new btQuaternion(m_CapsuleOrientationAxis, (Utils.DEG_TO_RAD * 90)); if (m_bodyTransform == null) m_bodyTransform = new btTransform(m_bodyOrientation, m_bodyPosition); else { m_bodyTransform.Dispose(); m_bodyTransform = new btTransform(m_bodyOrientation, m_bodyPosition); } if (m_bodyMotionState == null) m_bodyMotionState = new btDefaultMotionState(m_bodyTransform); else m_bodyMotionState.setWorldTransform(m_bodyTransform); m_mass = Mass; Body = new btRigidBody(m_mass, m_bodyMotionState, Shell); // this is used for self identification. User localID instead of body handle Body.setUserPointer(new IntPtr((int)m_localID)); if (ClosestCastResult != null) ClosestCastResult.Dispose(); ClosestCastResult = new ClosestNotMeRayResultCallback(Body); m_parent_scene.AddRigidBody(Body); Body.setActivationState(4); if (m_aMotor != null) { if (m_aMotor.Handle != IntPtr.Zero) { m_parent_scene.getBulletWorld().removeConstraint(m_aMotor); m_aMotor.Dispose(); } m_aMotor = null; } m_aMotor = new btGeneric6DofConstraint(Body, m_parent_scene.TerrainBody, m_parent_scene.TransZero, m_parent_scene.TransZero, false); m_aMotor.setAngularLowerLimit(m_parent_scene.VectorZero); m_aMotor.setAngularUpperLimit(m_parent_scene.VectorZero); } public void Remove() { m_taintRemove = true; } public override bool Stopped { get { return m_zeroFlag; } } public override Vector3 Size { get { return new Vector3(CAPSULE_RADIUS * 2, CAPSULE_RADIUS * 2, CAPSULE_LENGTH); } set { m_pidControllerActive = true; Vector3 SetSize = value; m_tainted_CAPSULE_LENGTH = (SetSize.Z * 1.15f) - CAPSULE_RADIUS * 2.0f; //m_log.Info("[SIZE]: " + CAPSULE_LENGTH.ToString()); Velocity = Vector3.Zero; m_parent_scene.AddPhysicsActorTaint(this); } } /// <summary> /// turn the PID controller on or off. /// The PID Controller will turn on all by itself in many situations /// </summary> /// <param name="status"></param> public void SetPidStatus(bool status) { m_pidControllerActive = status; } public override PrimitiveBaseShape Shape { set { return; } } public override uint LocalID { set { m_localID = value; } } public override bool Grabbed { set { return; } } public override bool Selected { set { return; } } public override void CrossingFailure() { } public override void link(PhysicsActor obj) { } public override void delink() { } public override void LockAngularMotion(Vector3 axis) { } public override Vector3 Position { get { return m_position; } set { // m_taintedPosition_value = value; m_position = value; // m_taintedPosition = true; } } public override float Mass { get { float AVvolume = (float)(Math.PI * Math.Pow(CAPSULE_RADIUS, 2) * CAPSULE_LENGTH); return m_density * AVvolume; } } public override Vector3 Force { get { return m_target_velocity; } set { return; } } public override int VehicleType { get { return 0; } set { return; } } public override void VehicleFloatParam(int param, float value) { } public override void VehicleVectorParam(int param, Vector3 value) { } public override void VehicleRotationParam(int param, Quaternion rotation) { } public override void VehicleFlags(int param, bool remove) { } public override void SetVolumeDetect(int param) { } public override Vector3 GeometricCenter { get { return Vector3.Zero; } } public override Vector3 CenterOfMass { get { return Vector3.Zero; } } public override Vector3 Velocity { get { if (m_zeroFlag) return Vector3.Zero; m_lastUpdateSent = false; return m_velocity; } set { m_pidControllerActive = true; m_target_velocity = value; } } public override Vector3 Torque { get { return Vector3.Zero; } set { return; } } public override float CollisionScore { get { return 0f; } set { } } public override Vector3 Acceleration { get { return m_acceleration; } } public override Quaternion Orientation { get { return Quaternion.Identity; } set { } } public override int PhysicsActorType { get { return (int)ActorTypes.Agent; } set { return; } } public override bool IsPhysical { get { return false; } set { return; } } public override bool Flying { get { return m_flying; } set { m_flying = value; } } public override bool SetAlwaysRun { get { return m_alwaysRun; } set { m_alwaysRun = value; } } public override bool ThrottleUpdates { get { return false; } set { return; } } /// <summary> /// Returns if the avatar is colliding in general. /// This includes the ground and objects and avatar. /// </summary> public override bool IsColliding { get { return m_iscolliding; } set { int i; int truecount = 0; int falsecount = 0; if (m_colliderarr.Length >= 10) { for (i = 0; i < 10; i++) { m_colliderarr[i] = m_colliderarr[i + 1]; } } m_colliderarr[10] = value; for (i = 0; i < 11; i++) { if (m_colliderarr[i]) { truecount++; } else { falsecount++; } } // Equal truecounts and false counts means we're colliding with something. m_log.DebugFormat("[PHYSICS]: TrueCount:{0}, FalseCount:{1}",truecount,falsecount); if (falsecount > 1.2 * truecount) { m_iscolliding = false; } else { m_iscolliding = true; } if (m_wascolliding != m_iscolliding) { //base.SendCollisionUpdate(new CollisionEventUpdate()); } m_wascolliding = m_iscolliding; } } /// <summary> /// Returns if an avatar is colliding with the ground /// </summary> public override bool CollidingGround { get { return m_iscollidingGround; } set { // Collisions against the ground are not really reliable // So, to get a consistant value we have to average the current result over time // Currently we use 1 second = 10 calls to this. int i; int truecount = 0; int falsecount = 0; if (m_colliderGroundarr.Length >= 10) { for (i = 0; i < 10; i++) { m_colliderGroundarr[i] = m_colliderGroundarr[i + 1]; } } m_colliderGroundarr[10] = value; for (i = 0; i < 11; i++) { if (m_colliderGroundarr[i]) { truecount++; } else { falsecount++; } } // Equal truecounts and false counts means we're colliding with something. if (falsecount > 1.2 * truecount) { m_iscollidingGround = false; } else { m_iscollidingGround = true; } if (m_wascollidingGround != m_iscollidingGround) { //base.SendCollisionUpdate(new CollisionEventUpdate()); } m_wascollidingGround = m_iscollidingGround; } } /// <summary> /// Returns if the avatar is colliding with an object /// </summary> public override bool CollidingObj { get { return m_iscollidingObj; } set { m_iscollidingObj = value; if (value) m_pidControllerActive = false; else m_pidControllerActive = true; } } public override bool FloatOnWater { set { return; } } public override Vector3 RotationalVelocity { get { return m_rotationalVelocity; } set { m_rotationalVelocity = value; } } public override bool Kinematic { get { return false; } set { } } public override float Buoyancy { get { return m_buoyancy; } set { m_buoyancy = value; } } public override Vector3 PIDTarget { set { return; } } public override bool PIDActive { set { return; } } public override float PIDTau { set { return; } } public override bool PIDHoverActive { set { return; } } public override float PIDHoverHeight { set { return; } } public override PIDHoverType PIDHoverType { set { return; } } public override float PIDHoverTau { set { return; } } public override Quaternion APIDTarget { set { return; } } public override bool APIDActive { set { return; } } public override float APIDStrength { set { return; } } public override float APIDDamping { set { return; } } /// <summary> /// Adds the force supplied to the Target Velocity /// The PID controller takes this target velocity and tries to make it a reality /// </summary> /// <param name="force"></param> /// <param name="pushforce">Is this a push by a script?</param> public override void AddForce(Vector3 force, bool pushforce) { if (pushforce) { m_pidControllerActive = false; force *= 100f; doForce(force, false); //System.Console.WriteLine("Push!"); //_target_velocity.X += force.X; // _target_velocity.Y += force.Y; //_target_velocity.Z += force.Z; } else { m_pidControllerActive = true; m_target_velocity.X += force.X; m_target_velocity.Y += force.Y; m_target_velocity.Z += force.Z; } //m_lastUpdateSent = false; } public void doForce(Vector3 force, bool now) { tempVector3.setValue(force.X, force.Y, force.Z); if (now) { Body.applyCentralForce(tempVector3); } else { m_taintedForce += force; m_parent_scene.AddPhysicsActorTaint(this); } } public void doImpulse(Vector3 force, bool now) { tempVector3.setValue(force.X, force.Y, force.Z); if (now) { Body.applyCentralImpulse(tempVector3); } else { m_taintedForce += force; m_parent_scene.AddPhysicsActorTaint(this); } } public override void AddAngularForce(Vector3 force, bool pushforce) { } public override void SetMomentum(Vector3 momentum) { } public override void SubscribeEvents(int ms) { m_eventsubscription = ms; m_requestedUpdateFrequency = ms; m_parent_scene.addCollisionEventReporting(this); } public override void UnSubscribeEvents() { m_parent_scene.remCollisionEventReporting(this); m_eventsubscription = 0; m_requestedUpdateFrequency = 0; } public override bool SubscribedEvents() { if (m_eventsubscription > 0) return true; return false; } public void AddCollision(uint collideWith, ContactPoint contact) { if (CollisionEventsThisFrame == null) { CollisionEventsThisFrame = new CollisionEventUpdate(); } CollisionEventsThisFrame.addCollider(collideWith, contact); } public void SendCollisions() { if (m_eventsubscription >= m_requestedUpdateFrequency) { if (CollisionEventsThisFrame != null) { base.SendCollisionUpdate(CollisionEventsThisFrame); } CollisionEventsThisFrame = new CollisionEventUpdate(); m_eventsubscription = 0; } return; } internal void Dispose() { if (Body.isInWorld()) m_parent_scene.removeFromWorld(Body); if (m_aMotor.Handle != IntPtr.Zero) m_parent_scene.getBulletWorld().removeConstraint(m_aMotor); m_aMotor.Dispose(); m_aMotor = null; ClosestCastResult.Dispose(); ClosestCastResult = null; Body.Dispose(); Body = null; Shell.Dispose(); Shell = null; tempQuat1.Dispose(); tempTrans1.Dispose(); tempVector1.Dispose(); tempVector2.Dispose(); tempVector3.Dispose(); tempVector4.Dispose(); tempVector5RayCast.Dispose(); tempVector6RayCast.Dispose(); } public void ProcessTaints(float timestep) { if (m_tainted_isPhysical != m_isPhysical) { if (m_tainted_isPhysical) { // Create avatar capsule and related ODE data if (!(Shell == null && Body == null)) { m_log.Warn("[PHYSICS]: re-creating the following avatar ODE data, even though it already exists - " + (Shell != null ? "Shell " : "") + (Body != null ? "Body " : "")); } AvatarGeomAndBodyCreation(m_position.X, m_position.Y, m_position.Z); } else { // destroy avatar capsule and related ODE data Dispose(); tempVector1 = new btVector3(0, 0, 0); tempVector2 = new btVector3(0, 0, 0); tempVector3 = new btVector3(0, 0, 0); tempVector4 = new btVector3(0, 0, 0); tempVector5RayCast = new btVector3(0, 0, 0); tempVector6RayCast = new btVector3(0, 0, 0); tempVector7RayCast = new btVector3(0, 0, 0); tempQuat1 = new btQuaternion(0, 0, 0, 1); tempTrans1 = new btTransform(tempQuat1, tempVector1); // m_movementComparision = new PhysicsVector(0, 0, 0); m_CapsuleOrientationAxis = new btVector3(1, 0, 1); } m_isPhysical = m_tainted_isPhysical; } if (m_tainted_CAPSULE_LENGTH != CAPSULE_LENGTH) { if (Body != null) { m_pidControllerActive = true; // no lock needed on _parent_scene.OdeLock because we are called from within the thread lock in OdePlugin's simulate() //d.JointDestroy(Amotor); float prevCapsule = CAPSULE_LENGTH; CAPSULE_LENGTH = m_tainted_CAPSULE_LENGTH; //m_log.Info("[SIZE]: " + CAPSULE_LENGTH.ToString()); Dispose(); tempVector1 = new btVector3(0, 0, 0); tempVector2 = new btVector3(0, 0, 0); tempVector3 = new btVector3(0, 0, 0); tempVector4 = new btVector3(0, 0, 0); tempVector5RayCast = new btVector3(0, 0, 0); tempVector6RayCast = new btVector3(0, 0, 0); tempVector7RayCast = new btVector3(0, 0, 0); tempQuat1 = new btQuaternion(0, 0, 0, 1); tempTrans1 = new btTransform(tempQuat1, tempVector1); // m_movementComparision = new PhysicsVector(0, 0, 0); m_CapsuleOrientationAxis = new btVector3(1, 0, 1); AvatarGeomAndBodyCreation(m_position.X, m_position.Y, m_position.Z + (Math.Abs(CAPSULE_LENGTH - prevCapsule) * 2)); Velocity = Vector3.Zero; } else { m_log.Warn("[PHYSICS]: trying to change capsule size, but the following ODE data is missing - " + (Shell == null ? "Shell " : "") + (Body == null ? "Body " : "")); } } if (m_taintRemove) { Dispose(); } } /// <summary> /// Called from Simulate /// This is the avatar's movement control + PID Controller /// </summary> /// <param name="timeStep"></param> public void Move(float timeStep) { // no lock; for now it's only called from within Simulate() // If the PID Controller isn't active then we set our force // calculating base velocity to the current position if (Body == null) return; tempTrans1.Dispose(); tempTrans1 = Body.getInterpolationWorldTransform(); tempVector1.Dispose(); tempVector1 = tempTrans1.getOrigin(); tempVector2.Dispose(); tempVector2 = Body.getInterpolationLinearVelocity(); if (m_pidControllerActive == false) { m_zeroPosition.X = tempVector1.getX(); m_zeroPosition.Y = tempVector1.getY(); m_zeroPosition.Z = tempVector1.getZ(); } //PidStatus = true; Vector3 vec = Vector3.Zero; Vector3 vel = new Vector3(tempVector2.getX(), tempVector2.getY(), tempVector2.getZ()); float movementdivisor = 1f; if (!m_alwaysRun) { movementdivisor = walkDivisor; } else { movementdivisor = runDivisor; } // if velocity is zero, use position control; otherwise, velocity control if (m_target_velocity.X == 0.0f && m_target_velocity.Y == 0.0f && m_target_velocity.Z == 0.0f && m_iscolliding) { // keep track of where we stopped. No more slippin' & slidin' if (!m_zeroFlag) { m_zeroFlag = true; m_zeroPosition.X = tempVector1.getX(); m_zeroPosition.Y = tempVector1.getY(); m_zeroPosition.Z = tempVector1.getZ(); } if (m_pidControllerActive) { // We only want to deactivate the PID Controller if we think we want to have our surrogate // react to the physics scene by moving it's position. // Avatar to Avatar collisions // Prim to avatar collisions Vector3 pos = new Vector3(tempVector1.getX(), tempVector1.getY(), tempVector1.getZ()); vec.X = (m_target_velocity.X - vel.X) * (PID_D) + (m_zeroPosition.X - pos.X) * (PID_P * 2); vec.Y = (m_target_velocity.Y - vel.Y) * (PID_D) + (m_zeroPosition.Y - pos.Y) * (PID_P * 2); if (m_flying) { vec.Z = (m_target_velocity.Z - vel.Z) * (PID_D) + (m_zeroPosition.Z - pos.Z) * PID_P; } } //PidStatus = true; } else { m_pidControllerActive = true; m_zeroFlag = false; if (m_iscolliding && !m_flying) { // We're standing on something vec.X = ((m_target_velocity.X / movementdivisor) - vel.X) * (PID_D); vec.Y = ((m_target_velocity.Y / movementdivisor) - vel.Y) * (PID_D); } else if (m_iscolliding && m_flying) { // We're flying and colliding with something vec.X = ((m_target_velocity.X / movementdivisor) - vel.X) * (PID_D / 16); vec.Y = ((m_target_velocity.Y / movementdivisor) - vel.Y) * (PID_D / 16); } else if (!m_iscolliding && m_flying) { // we're in mid air suspended vec.X = ((m_target_velocity.X / movementdivisor) - vel.X) * (PID_D / 6); vec.Y = ((m_target_velocity.Y / movementdivisor) - vel.Y) * (PID_D / 6); // We don't want linear velocity to cause our avatar to bounce, so we check target Z and actual velocity X, Y // rebound preventing if (m_target_velocity.Z < 0.025f && m_velocity.X < 0.25f && m_velocity.Y < 0.25f) m_zeroFlag = true; } if (m_iscolliding && !m_flying && m_target_velocity.Z > 0.0f) { // We're colliding with something and we're not flying but we're moving // This means we're walking or running. Vector3 pos = new Vector3(tempVector1.getX(), tempVector1.getY(), tempVector1.getZ()); vec.Z = (m_target_velocity.Z - vel.Z) * PID_D + (m_zeroPosition.Z - pos.Z) * PID_P; if (m_target_velocity.X > 0) { vec.X = ((m_target_velocity.X - vel.X) / 1.2f) * PID_D; } if (m_target_velocity.Y > 0) { vec.Y = ((m_target_velocity.Y - vel.Y) / 1.2f) * PID_D; } } else if (!m_iscolliding && !m_flying) { // we're not colliding and we're not flying so that means we're falling! // m_iscolliding includes collisions with the ground. // d.Vector3 pos = d.BodyGetPosition(Body); if (m_target_velocity.X > 0) { vec.X = ((m_target_velocity.X - vel.X) / 1.2f) * PID_D; } if (m_target_velocity.Y > 0) { vec.Y = ((m_target_velocity.Y - vel.Y) / 1.2f) * PID_D; } } if (m_flying) { vec.Z = (m_target_velocity.Z - vel.Z) * (PID_D); } } if (m_flying) { // Slight PID correction vec.Z += (((-1 * m_parent_scene.gravityz) * m_mass) * 0.06f); //auto fly height. Kitto Flora //d.Vector3 pos = d.BodyGetPosition(Body); float target_altitude = m_parent_scene.GetTerrainHeightAtXY(m_position.X, m_position.Y) + 5.0f; if (m_position.Z < target_altitude) { vec.Z += (target_altitude - m_position.Z) * PID_P * 5.0f; } } if (Body != null && (((m_target_velocity.X > 0.2f || m_target_velocity.X < -0.2f) || (m_target_velocity.Y > 0.2f || m_target_velocity.Y < -0.2f)))) { Body.setFriction(0.001f); //m_log.DebugFormat("[PHYSICS]: Avatar force applied: {0}, Target:{1}", vec.ToString(), m_target_velocity.ToString()); } if (Body != null) { int activationstate = Body.getActivationState(); if (activationstate == 0) { Body.forceActivationState(1); } } doImpulse(vec, true); } /// <summary> /// Updates the reported position and velocity. This essentially sends the data up to ScenePresence. /// </summary> public void UpdatePositionAndVelocity() { if (Body == null) return; //int val = Environment.TickCount; CheckIfStandingOnObject(); //m_log.DebugFormat("time:{0}", Environment.TickCount - val); //IsColliding = Body.checkCollideWith(m_parent_scene.TerrainBody); tempTrans1.Dispose(); tempTrans1 = Body.getInterpolationWorldTransform(); tempVector1.Dispose(); tempVector1 = tempTrans1.getOrigin(); tempVector2.Dispose(); tempVector2 = Body.getInterpolationLinearVelocity(); // no lock; called from Simulate() -- if you call this from elsewhere, gotta lock or do Monitor.Enter/Exit! Vector3 vec = new Vector3(tempVector1.getX(), tempVector1.getY(), tempVector1.getZ()); // kluge to keep things in bounds. ODE lets dead avatars drift away (they should be removed!) if (vec.X < -10.0f) vec.X = 0.0f; if (vec.Y < -10.0f) vec.Y = 0.0f; if (vec.X > (int)Constants.RegionSize + 10.2f) vec.X = (int)Constants.RegionSize + 10.2f; if (vec.Y > (int)Constants.RegionSize + 10.2f) vec.Y = (int)Constants.RegionSize + 10.2f; m_position.X = vec.X; m_position.Y = vec.Y; m_position.Z = vec.Z; // Did we move last? = zeroflag // This helps keep us from sliding all over if (m_zeroFlag) { m_velocity.X = 0.0f; m_velocity.Y = 0.0f; m_velocity.Z = 0.0f; // Did we send out the 'stopped' message? if (!m_lastUpdateSent) { m_lastUpdateSent = true; //base.RequestPhysicsterseUpdate(); } } else { m_lastUpdateSent = false; vec = new Vector3(tempVector2.getX(), tempVector2.getY(), tempVector2.getZ()); m_velocity.X = (vec.X); m_velocity.Y = (vec.Y); m_velocity.Z = (vec.Z); //m_log.Debug(m_target_velocity); if (m_velocity.Z < -6 && !m_hackSentFall) { m_hackSentFall = true; m_pidControllerActive = false; } else if (m_flying && !m_hackSentFly) { //m_hackSentFly = true; //base.SendCollisionUpdate(new CollisionEventUpdate()); } else { m_hackSentFly = false; m_hackSentFall = false; } } if (Body != null) { if (Body.getFriction() < 0.9f) Body.setFriction(0.9f); } //if (Body != null) // Body.clearForces(); } public void CheckIfStandingOnObject() { float capsuleHalfHeight = ((CAPSULE_LENGTH + 2*CAPSULE_RADIUS)*0.5f); tempVector5RayCast.setValue(m_position.X, m_position.Y, m_position.Z); tempVector6RayCast.setValue(m_position.X, m_position.Y, m_position.Z - 1 * capsuleHalfHeight * 1.1f); ClosestCastResult.Dispose(); ClosestCastResult = new ClosestNotMeRayResultCallback(Body); try { m_parent_scene.getBulletWorld().rayTest(tempVector5RayCast, tempVector6RayCast, ClosestCastResult); } catch (AccessViolationException) { m_log.Debug("BAD!"); } if (ClosestCastResult.hasHit()) { if (tempVector7RayCast != null) tempVector7RayCast.Dispose(); //tempVector7RayCast = ClosestCastResult.getHitPointWorld(); /*if (tempVector7RayCast == null) // null == no result also { CollidingObj = false; IsColliding = false; CollidingGround = false; return; } float zVal = tempVector7RayCast.getZ(); if (zVal != 0) m_log.Debug("[PHYSICS]: HAAAA"); if (zVal < m_position.Z && zVal > ((CAPSULE_LENGTH + 2 * CAPSULE_RADIUS) *0.5f)) { CollidingObj = true; IsColliding = true; } else { CollidingObj = false; IsColliding = false; CollidingGround = false; }*/ //height+2*radius = capsule full length //CollidingObj = true; //IsColliding = true; m_iscolliding = true; } else { //CollidingObj = false; //IsColliding = false; //CollidingGround = false; m_iscolliding = false; } } } }