/* * 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. */ // Revision by Ubit 2011/12 using System; using System.Collections.Generic; using System.Reflection; using OpenMetaverse; using OpenSim.Framework; using OpenSim.Region.PhysicsModules.SharedBase; using log4net; namespace OpenSim.Region.PhysicsModule.ubOde { /// /// Various properties that ODE uses for AMotors but isn't exposed in ODE.NET so we must define them ourselves. /// public enum dParam:int { LowStop = 0, HiStop = 1, Vel = 2, FMax = 3, FudgeFactor = 4, Bounce = 5, CFM = 6, StopERP = 7, StopCFM = 8, LoStop2 = 256, HiStop2 = 257, Vel2 = 258, FMax2 = 259, StopERP2 = 7 + 256, StopCFM2 = 8 + 256, LoStop3 = 512, HiStop3 = 513, Vel3 = 514, FMax3 = 515, StopERP3 = 7 + 512, StopCFM3 = 8 + 512 } public class OdeCharacter:PhysicsActor { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private Vector3 _position; private Vector3 _zeroPosition; private Vector3 _velocity; private Vector3 _target_velocity; private Vector3 _acceleration; private Vector3 m_rotationalVelocity; private Vector3 m_size; private Vector3 m_collideNormal; private Vector3 m_lastFallVel; private Quaternion m_orientation; private Quaternion m_orientation2D; private float m_mass = 80f; public float m_density = 60f; private bool m_pidControllerActive = true; const float basePID_D = 0.55f; // scaled for unit mass unit time (2200 /(50*80)) const float basePID_P = 0.225f; // scaled for unit mass unit time (900 /(50*80)) public float PID_D; public float PID_P; private float timeStep; private float invtimeStep; private float m_feetOffset = 0; private float feetOff = 0; private float boneOff = 0; private float AvaAvaSizeXsq = 0.3f; private float AvaAvaSizeYsq = 0.2f; public float walkDivisor = 1.3f; public float runDivisor = 0.8f; private bool m_flying = false; private bool m_iscolliding = false; private bool m_iscollidingGround = false; private bool m_iscollidingObj = false; private bool m_alwaysRun = false; private bool _zeroFlag = false; private bool m_haveLastFallVel = false; private 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 float MinimumGroundFlightOffset = 3f; private float m_buoyancy = 0f; private bool m_freemove = false; // private string m_name = String.Empty; // other filter control int m_colliderfilter = 0; int m_colliderGroundfilter = 0; int m_colliderObjectfilter = 0; // Default we're a Character private CollisionCategories m_collisionCategories = (CollisionCategories.Character); // Default, Collide with Other Geometries, spaces, bodies and characters. private CollisionCategories m_collisionFlags = (CollisionCategories.Character | CollisionCategories.Geom | CollisionCategories.VolumeDtc ); // we do land collisions not ode | CollisionCategories.Land); public IntPtr Body = IntPtr.Zero; private ODEScene m_parent_scene; private IntPtr capsule = IntPtr.Zero; public IntPtr collider = IntPtr.Zero; public IntPtr Amotor = IntPtr.Zero; internal SafeNativeMethods.Mass ShellMass; public int m_eventsubscription = 0; private int m_cureventsubscription = 0; private CollisionEventUpdate CollisionEventsThisFrame = new CollisionEventUpdate(); private bool SentEmptyCollisionsEvent; // unique UUID of this character object public UUID m_uuid; public bool bad = false; float mu; // HoverHeight control private float m_PIDHoverHeight; private float m_PIDHoverTau; private bool m_useHoverPID; private PIDHoverType m_PIDHoverType; private float m_targetHoverHeight; public OdeCharacter(uint localID,String avName,ODEScene parent_scene,Vector3 pos,Vector3 pSize,float pfeetOffset,float density,float walk_divisor,float rundivisor) { m_uuid = UUID.Random(); m_localID = localID; m_parent_scene = parent_scene; timeStep = parent_scene.ODE_STEPSIZE; invtimeStep = 1 / timeStep; if(pos.IsFinite()) { if(pos.Z > 99999f) { pos.Z = parent_scene.GetTerrainHeightAtXY(127,127) + 5; } if(pos.Z < -100f) // shouldn't this be 0 ? { pos.Z = parent_scene.GetTerrainHeightAtXY(127,127) + 5; } _position = pos; } else { _position = new Vector3(((float)m_parent_scene.WorldExtents.X * 0.5f),((float)m_parent_scene.WorldExtents.Y * 0.5f),parent_scene.GetTerrainHeightAtXY(128f,128f) + 10f); m_log.Warn("[PHYSICS]: Got NaN Position on Character Create"); } m_size.X = pSize.X; m_size.Y = pSize.Y; m_size.Z = pSize.Z; if(m_size.X <0.01f) m_size.X = 0.01f; if(m_size.Y <0.01f) m_size.Y = 0.01f; if(m_size.Z <0.01f) m_size.Z = 0.01f; m_feetOffset = pfeetOffset; m_orientation = Quaternion.Identity; m_orientation2D = Quaternion.Identity; m_density = density; // force lower density for testing m_density = 3.0f; mu = m_parent_scene.AvatarFriction; walkDivisor = walk_divisor; runDivisor = rundivisor; m_mass = m_density * m_size.X * m_size.Y * m_size.Z; ; // sure we have a default PID_D = basePID_D * m_mass * invtimeStep; PID_P = basePID_P * m_mass * invtimeStep; m_isPhysical = false; // current status: no ODE information exists Name = avName; AddChange(changes.Add,null); } public override int PhysicsActorType { get { return (int)ActorTypes.Agent; } set { return; } } public override void getContactData(ref ContactData cdata) { cdata.mu = mu; cdata.bounce = 0; cdata.softcolide = false; } public override bool Building { get; set; } /// /// If this is set, the avatar will move faster /// public override bool SetAlwaysRun { get { return m_alwaysRun; } set { m_alwaysRun = value; } } public override uint LocalID { get { return m_localID; } set { m_localID = value; } } public override PhysicsActor ParentActor { get { return (PhysicsActor)this; } } public override bool Grabbed { set { return; } } public override bool Selected { set { return; } } public override float Buoyancy { get { return m_buoyancy; } set { m_buoyancy = value; } } public override bool FloatOnWater { set { return; } } public override bool IsPhysical { get { return m_isPhysical; } set { return; } } public override bool ThrottleUpdates { get { return false; } set { return; } } public override bool Flying { get { return m_flying; } set { m_flying = value; // m_log.DebugFormat("[PHYSICS]: Set OdeCharacter Flying to {0}", flying); } } /// /// Returns if the avatar is colliding in general. /// This includes the ground and objects and avatar. /// public override bool IsColliding { get { return (m_iscolliding || m_iscollidingGround); } set { if(value) { m_colliderfilter += 3; if(m_colliderfilter > 3) m_colliderfilter = 3; } else { m_colliderfilter--; if(m_colliderfilter < 0) m_colliderfilter = 0; } if(m_colliderfilter == 0) m_iscolliding = false; else { m_pidControllerActive = true; m_iscolliding = true; m_freemove = false; } } } /// /// Returns if an avatar is colliding with the ground /// public override bool CollidingGround { get { return m_iscollidingGround; } set { /* we now control this if (value) { m_colliderGroundfilter += 2; if (m_colliderGroundfilter > 2) m_colliderGroundfilter = 2; } else { m_colliderGroundfilter--; if (m_colliderGroundfilter < 0) m_colliderGroundfilter = 0; } if (m_colliderGroundfilter == 0) m_iscollidingGround = false; else m_iscollidingGround = true; */ } } /// /// Returns if the avatar is colliding with an object /// public override bool CollidingObj { get { return m_iscollidingObj; } set { // Ubit filter this also if(value) { m_colliderObjectfilter += 2; if(m_colliderObjectfilter > 2) m_colliderObjectfilter = 2; } else { m_colliderObjectfilter--; if(m_colliderObjectfilter < 0) m_colliderObjectfilter = 0; } if(m_colliderObjectfilter == 0) m_iscollidingObj = false; else m_iscollidingObj = true; // m_iscollidingObj = value; if(m_iscollidingObj) m_pidControllerActive = false; else m_pidControllerActive = true; } } /// /// turn the PID controller on or off. /// The PID Controller will turn on all by itself in many situations /// /// public void SetPidStatus(bool status) { m_pidControllerActive = status; } public override bool Stopped { get { return _zeroFlag; } } /// /// This 'puts' an avatar somewhere in the physics space. /// Not really a good choice unless you 'know' it's a good /// spot otherwise you're likely to orbit the avatar. /// public override Vector3 Position { get { return _position; } set { if(value.IsFinite()) { if(value.Z > 9999999f) { value.Z = m_parent_scene.GetTerrainHeightAtXY(127,127) + 5; } if(value.Z < -100f) { value.Z = m_parent_scene.GetTerrainHeightAtXY(127,127) + 5; } AddChange(changes.Position,value); } else { m_log.Warn("[PHYSICS]: Got a NaN Position from Scene on a Character"); } } } public override Vector3 RotationalVelocity { get { return m_rotationalVelocity; } set { m_rotationalVelocity = value; } } /// /// This property sets the height of the avatar only. We use the height to make sure the avatar stands up straight /// and use it to offset landings properly /// public override Vector3 Size { get { return m_size; } set { if(value.IsFinite()) { if(value.X <0.01f) value.X = 0.01f; if(value.Y <0.01f) value.Y = 0.01f; if(value.Z <0.01f) value.Z = 0.01f; AddChange(changes.Size,value); } else { m_log.Warn("[PHYSICS]: Got a NaN Size from Scene on a Character"); } } } public override void setAvatarSize(Vector3 size,float feetOffset) { if(size.IsFinite()) { if(size.X < 0.01f) size.X = 0.01f; if(size.Y < 0.01f) size.Y = 0.01f; if(size.Z < 0.01f) size.Z = 0.01f; strAvatarSize st = new strAvatarSize(); st.size = size; st.offset = feetOffset; AddChange(changes.AvatarSize,st); } else { m_log.Warn("[PHYSICS]: Got a NaN AvatarSize from Scene on a Character"); } } /// /// This creates the Avatar's physical Surrogate at the position supplied /// /// /// /// // /// /// Uses the capped cyllinder volume formula to calculate the avatar's mass. /// This may be used in calculations in the scene/scenepresence /// public override float Mass { get { return m_mass; } } public override void link(PhysicsActor obj) { } public override void delink() { } public override void LockAngularMotion(byte axislocks) { } public override Vector3 Force { get { return _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 CenterOfMass { get { Vector3 pos = _position; return pos; } } public override Vector3 GeometricCenter { get { Vector3 pos = _position; return pos; } } public override PrimitiveBaseShape Shape { set { return; } } public override Vector3 rootVelocity { get { return _velocity; } } public override Vector3 Velocity { get { return _velocity; } set { if(value.IsFinite()) { AddChange(changes.Velocity,value); } else { m_log.Warn("[PHYSICS]: Got a NaN velocity from Scene in a Character"); } } } public override Vector3 TargetVelocity { get { return m_targetVelocity; } set { if(value.IsFinite()) { AddChange(changes.TargetVelocity,value); } else { m_log.Warn("[PHYSICS]: Got a NaN velocity from Scene in a Character"); } } } public override Vector3 Torque { get { return Vector3.Zero; } set { return; } } public override float CollisionScore { get { return 0f; } set { } } public override bool Kinematic { get { return false; } set { } } public override Quaternion Orientation { get { return m_orientation; } set { // fakeori = value; // givefakeori++; value.Normalize(); AddChange(changes.Orientation,value); } } public override Vector3 Acceleration { get { return _acceleration; } set { } } public void SetAcceleration(Vector3 accel) { m_pidControllerActive = true; _acceleration = accel; } /// /// Adds the force supplied to the Target Velocity /// The PID controller takes this target velocity and tries to make it a reality /// /// public override void AddForce(Vector3 force,bool pushforce) { if(force.IsFinite()) { if(pushforce) { AddChange(changes.Force,force * m_density / (m_parent_scene.ODE_STEPSIZE * 28f)); } else { AddChange(changes.TargetVelocity,force); } } else { m_log.Warn("[PHYSICS]: Got a NaN force applied to a Character"); } //m_lastUpdateSent = false; } public override void AddAngularForce(Vector3 force,bool pushforce) { } public override void SetMomentum(Vector3 momentum) { if(momentum.IsFinite()) AddChange(changes.Momentum,momentum); } private void AvatarGeomAndBodyCreation(float npositionX,float npositionY,float npositionZ) { // sizes one day should came from visual parameters float sx = m_size.X; float sy = m_size.Y; float sz = m_size.Z; float bot = -sz * 0.5f + m_feetOffset; boneOff = bot + 0.3f; float feetsz = sz * 0.45f; if(feetsz > 0.6f) feetsz = 0.6f; feetOff = bot + feetsz; AvaAvaSizeXsq = 0.4f * sx; AvaAvaSizeXsq *= AvaAvaSizeXsq; AvaAvaSizeYsq = 0.5f * sy; AvaAvaSizeYsq *= AvaAvaSizeYsq; m_parent_scene.waitForSpaceUnlock(m_parent_scene.CharsSpace); collider = SafeNativeMethods.SimpleSpaceCreate(m_parent_scene.CharsSpace); SafeNativeMethods.SpaceSetSublevel(collider,3); SafeNativeMethods.SpaceSetCleanup(collider,false); SafeNativeMethods.GeomSetCategoryBits(collider,(uint)m_collisionCategories); SafeNativeMethods.GeomSetCollideBits(collider,(uint)m_collisionFlags); float r = m_size.X; if(m_size.Y > r) r = m_size.Y; float l = m_size.Z - r; r *= 0.5f; capsule = SafeNativeMethods.CreateCapsule(collider,r,l); m_mass = m_density * m_size.X * m_size.Y * m_size.Z; // update mass SafeNativeMethods.MassSetBoxTotal(out ShellMass,m_mass,m_size.X,m_size.Y,m_size.Z); PID_D = basePID_D * m_mass / m_parent_scene.ODE_STEPSIZE; PID_P = basePID_P * m_mass / m_parent_scene.ODE_STEPSIZE; Body = SafeNativeMethods.BodyCreate(m_parent_scene.world); _zeroFlag = false; m_pidControllerActive = true; m_freemove = false; _velocity = Vector3.Zero; SafeNativeMethods.BodySetAutoDisableFlag(Body,false); SafeNativeMethods.BodySetPosition(Body,npositionX,npositionY,npositionZ); _position.X = npositionX; _position.Y = npositionY; _position.Z = npositionZ; SafeNativeMethods.BodySetMass(Body,ref ShellMass); SafeNativeMethods.GeomSetBody(capsule,Body); // The purpose of the AMotor here is to keep the avatar's physical // surrogate from rotating while moving Amotor = SafeNativeMethods.JointCreateAMotor(m_parent_scene.world,IntPtr.Zero); SafeNativeMethods.JointAttach(Amotor,Body,IntPtr.Zero); SafeNativeMethods.JointSetAMotorMode(Amotor,0); SafeNativeMethods.JointSetAMotorNumAxes(Amotor,3); SafeNativeMethods.JointSetAMotorAxis(Amotor,0,0,1,0,0); SafeNativeMethods.JointSetAMotorAxis(Amotor,1,0,0,1,0); SafeNativeMethods.JointSetAMotorAxis(Amotor,2,0,0,0,1); SafeNativeMethods.JointSetAMotorAngle(Amotor,0,0); SafeNativeMethods.JointSetAMotorAngle(Amotor,1,0); SafeNativeMethods.JointSetAMotorAngle(Amotor,2,0); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.StopCFM,0f); // make it HARD SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.StopCFM2,0f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.StopCFM3,0f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.StopERP,0.8f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.StopERP2,0.8f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.StopERP3,0.8f); // These lowstops and high stops are effectively (no wiggle room) SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.LowStop,-1e-5f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.HiStop,1e-5f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.LoStop2,-1e-5f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.HiStop2,1e-5f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.LoStop3,-1e-5f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.HiStop3,1e-5f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)SafeNativeMethods.JointParam.Vel,0); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)SafeNativeMethods.JointParam.Vel2,0); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)SafeNativeMethods.JointParam.Vel3,0); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.FMax,5e8f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.FMax2,5e8f); SafeNativeMethods.JointSetAMotorParam(Amotor,(int)dParam.FMax3,5e8f); } /// /// Destroys the avatar body and geom private void AvatarGeomAndBodyDestroy() { // Kill the Amotor if(Amotor != IntPtr.Zero) { SafeNativeMethods.JointDestroy(Amotor); Amotor = IntPtr.Zero; } if(Body != IntPtr.Zero) { //kill the body SafeNativeMethods.BodyDestroy(Body); Body = IntPtr.Zero; } //kill the Geoms if(capsule != IntPtr.Zero) { m_parent_scene.actor_name_map.Remove(capsule); m_parent_scene.waitForSpaceUnlock(collider); SafeNativeMethods.GeomDestroy(capsule); capsule = IntPtr.Zero; } if(collider != IntPtr.Zero) { SafeNativeMethods.SpaceDestroy(collider); collider = IntPtr.Zero; } } //in place 2D rotation around Z assuming rot is normalised and is a rotation around Z public void RotateXYonZ(ref float x,ref float y,ref Quaternion rot) { float sin = 2.0f * rot.Z * rot.W; float cos = rot.W * rot.W - rot.Z * rot.Z; float tx = x; x = tx * cos - y * sin; y = tx * sin + y * cos; } public void RotateXYonZ(ref float x,ref float y,ref float sin,ref float cos) { float tx = x; x = tx * cos - y * sin; y = tx * sin + y * cos; } public void invRotateXYonZ(ref float x,ref float y,ref float sin,ref float cos) { float tx = x; x = tx * cos + y * sin; y = -tx * sin + y * cos; } public void invRotateXYonZ(ref float x,ref float y,ref Quaternion rot) { float sin = -2.0f * rot.Z * rot.W; float cos = rot.W * rot.W - rot.Z * rot.Z; float tx = x; x = tx * cos - y * sin; y = tx * sin + y * cos; } internal bool Collide(IntPtr me,IntPtr other,bool reverse,ref SafeNativeMethods.ContactGeom contact, ref SafeNativeMethods.ContactGeom altContact,ref bool useAltcontact,ref bool feetcollision) { feetcollision = false; useAltcontact = false; if(me == capsule) { Vector3 offset; float h = contact.pos.Z - _position.Z; offset.Z = h - feetOff; offset.X = contact.pos.X - _position.X; offset.Y = contact.pos.Y - _position.Y; SafeNativeMethods.GeomClassID gtype = SafeNativeMethods.GeomGetClass(other); if(gtype == SafeNativeMethods.GeomClassID.CapsuleClass) { Vector3 roff = offset * Quaternion.Inverse(m_orientation2D); float r = roff.X *roff.X / AvaAvaSizeXsq; r += (roff.Y * roff.Y) / AvaAvaSizeYsq; if(r > 1.0f) return false; float dp = 1.0f -(float)Math.Sqrt((double)r); if(dp > 0.05f) dp = 0.05f; contact.depth = dp; if(offset.Z < 0) { feetcollision = true; if(h < boneOff) { m_collideNormal.X = contact.normal.X; m_collideNormal.Y = contact.normal.Y; m_collideNormal.Z = contact.normal.Z; IsColliding = true; } } return true; } if(gtype == SafeNativeMethods.GeomClassID.SphereClass && SafeNativeMethods.GeomGetBody(other) != IntPtr.Zero) { if(SafeNativeMethods.GeomSphereGetRadius(other) < 0.5) return true; } if(offset.Z > 0 || contact.normal.Z > 0.35f) { if(offset.Z <= 0) { feetcollision = true; if(h < boneOff) { m_collideNormal.X = contact.normal.X; m_collideNormal.Y = contact.normal.Y; m_collideNormal.Z = contact.normal.Z; IsColliding = true; } } return true; } if(m_flying) return true; feetcollision = true; if(h < boneOff) { m_collideNormal.X = contact.normal.X; m_collideNormal.Y = contact.normal.Y; m_collideNormal.Z = contact.normal.Z; IsColliding = true; } altContact = contact; useAltcontact = true; offset.Z -= 0.2f; offset.Normalize(); float tdp = contact.depth; float t = offset.X; t = Math.Abs(t); if(t > 1e-6) { tdp /= t; tdp *= contact.normal.X; } else tdp *= 10; if(tdp > 0.25f) tdp = 0.25f; altContact.depth = tdp; if(reverse) { altContact.normal.X = offset.X; altContact.normal.Y = offset.Y; altContact.normal.Z = offset.Z; } else { altContact.normal.X = -offset.X; altContact.normal.Y = -offset.Y; altContact.normal.Z = -offset.Z; } return true; } return false; } /// /// Called from Simulate /// This is the avatar's movement control + PID Controller /// /// public void Move(List defects) { if(Body == IntPtr.Zero) return; SafeNativeMethods.Vector3 dtmp = SafeNativeMethods.BodyGetPosition(Body); Vector3 localpos = new Vector3(dtmp.X,dtmp.Y,dtmp.Z); // the Amotor still lets avatar rotation to drift during colisions // so force it back to identity SafeNativeMethods.Quaternion qtmp; qtmp.W = m_orientation2D.W; qtmp.X = m_orientation2D.X; qtmp.Y = m_orientation2D.Y; qtmp.Z = m_orientation2D.Z; SafeNativeMethods.BodySetQuaternion(Body,ref qtmp); if(m_pidControllerActive == false) { _zeroPosition = localpos; } if(!localpos.IsFinite()) { m_log.Warn("[PHYSICS]: Avatar Position is non-finite!"); defects.Add(this); // _parent_scene.RemoveCharacter(this); // destroy avatar capsule and related ODE data AvatarGeomAndBodyDestroy(); return; } // check outbounds forcing to be in world bool fixbody = false; if(localpos.X < 0.0f) { fixbody = true; localpos.X = 0.1f; } else if(localpos.X > m_parent_scene.WorldExtents.X - 0.1f) { fixbody = true; localpos.X = m_parent_scene.WorldExtents.X - 0.1f; } if(localpos.Y < 0.0f) { fixbody = true; localpos.Y = 0.1f; } else if(localpos.Y > m_parent_scene.WorldExtents.Y - 0.1) { fixbody = true; localpos.Y = m_parent_scene.WorldExtents.Y - 0.1f; } if(fixbody) { m_freemove = false; SafeNativeMethods.BodySetPosition(Body,localpos.X,localpos.Y,localpos.Z); } float breakfactor; Vector3 vec = Vector3.Zero; dtmp = SafeNativeMethods.BodyGetLinearVel(Body); Vector3 vel = new Vector3(dtmp.X,dtmp.Y,dtmp.Z); float velLengthSquared = vel.LengthSquared(); Vector3 ctz = _target_velocity; float movementdivisor = 1f; //Ubit change divisions into multiplications below if(!m_alwaysRun) movementdivisor = 1 / walkDivisor; else movementdivisor = 1 / runDivisor; ctz.X *= movementdivisor; ctz.Y *= movementdivisor; //****************************************** // colide with land SafeNativeMethods.AABB aabb; // d.GeomGetAABB(feetbox, out aabb); SafeNativeMethods.GeomGetAABB(capsule,out aabb); float chrminZ = aabb.MinZ; // move up a bit Vector3 posch = localpos; float ftmp; if(m_flying) { ftmp = timeStep; posch.X += vel.X * ftmp; posch.Y += vel.Y * ftmp; } float terrainheight = m_parent_scene.GetTerrainHeightAtXY(posch.X,posch.Y); if(chrminZ < terrainheight) { if(ctz.Z < 0) ctz.Z = 0; if(!m_haveLastFallVel) { m_lastFallVel = vel; m_haveLastFallVel = true; } Vector3 n = m_parent_scene.GetTerrainNormalAtXY(posch.X,posch.Y); float depth = terrainheight - chrminZ; vec.Z = depth * PID_P * 50; if(!m_flying) { vec.Z += -vel.Z * PID_D; if(n.Z < 0.4f) { vec.X = depth * PID_P * 50 - vel.X * PID_D; vec.X *= n.X; vec.Y = depth * PID_P * 50 - vel.Y * PID_D; vec.Y *= n.Y; vec.Z *= n.Z; if(n.Z < 0.1f) { // cancel the slope pose n.X = 0f; n.Y = 0f; n.Z = 1.0f; } } } if(depth < 0.2f) { m_colliderGroundfilter++; if(m_colliderGroundfilter > 2) { m_iscolliding = true; m_colliderfilter = 2; if(m_colliderGroundfilter > 10) { m_colliderGroundfilter = 10; m_freemove = false; } m_collideNormal.X = n.X; m_collideNormal.Y = n.Y; m_collideNormal.Z = n.Z; m_iscollidingGround = true; ContactPoint contact = new ContactPoint(); contact.PenetrationDepth = depth; contact.Position.X = localpos.X; contact.Position.Y = localpos.Y; contact.Position.Z = terrainheight; contact.SurfaceNormal.X = -n.X; contact.SurfaceNormal.Y = -n.Y; contact.SurfaceNormal.Z = -n.Z; contact.RelativeSpeed = Vector3.Dot(m_lastFallVel,n); contact.CharacterFeet = true; AddCollisionEvent(0,contact); m_lastFallVel = vel; // vec.Z *= 0.5f; } } else { m_colliderGroundfilter -= 5; if(m_colliderGroundfilter <= 0) { m_colliderGroundfilter = 0; m_iscollidingGround = false; } } } else { m_haveLastFallVel = false; m_colliderGroundfilter -= 5; if(m_colliderGroundfilter <= 0) { m_colliderGroundfilter = 0; m_iscollidingGround = false; } } bool hoverPIDActive = false; if(m_useHoverPID && m_PIDHoverTau != 0 && m_PIDHoverHeight != 0) { hoverPIDActive = true; switch(m_PIDHoverType) { case PIDHoverType.Ground: m_targetHoverHeight = terrainheight + m_PIDHoverHeight; break; case PIDHoverType.GroundAndWater: float waterHeight = m_parent_scene.GetWaterLevel(); if(terrainheight > waterHeight) m_targetHoverHeight = terrainheight + m_PIDHoverHeight; else m_targetHoverHeight = waterHeight + m_PIDHoverHeight; break; } // end switch (m_PIDHoverType) // don't go underground if(m_targetHoverHeight > terrainheight + 0.5f * (aabb.MaxZ - aabb.MinZ)) { float fz = (m_targetHoverHeight - localpos.Z); // if error is zero, use position control; otherwise, velocity control if(Math.Abs(fz) < 0.01f) { ctz.Z = 0; } else { _zeroFlag = false; fz /= m_PIDHoverTau; float tmp = Math.Abs(fz); if(tmp > 50) fz = 50 * Math.Sign(fz); else if(tmp < 0.1) fz = 0.1f * Math.Sign(fz); ctz.Z = fz; } } } //****************************************** if(!m_iscolliding) m_collideNormal.Z = 0; bool tviszero = (ctz.X == 0.0f && ctz.Y == 0.0f && ctz.Z == 0.0f); if(!tviszero) { m_freemove = false; // movement relative to surface if moving on it // dont disturbe vertical movement, ie jumps if(m_iscolliding && !m_flying && ctz.Z == 0 && m_collideNormal.Z > 0.2f && m_collideNormal.Z < 0.94f) { float p = ctz.X * m_collideNormal.X + ctz.Y * m_collideNormal.Y; ctz.X *= (float)Math.Sqrt(1 - m_collideNormal.X * m_collideNormal.X); ctz.Y *= (float)Math.Sqrt(1 - m_collideNormal.Y * m_collideNormal.Y); ctz.Z -= p; if(ctz.Z < 0) ctz.Z *= 2; } } if(!m_freemove) { // if velocity is zero, use position control; otherwise, velocity control if(tviszero && m_iscolliding && !m_flying) { // keep track of where we stopped. No more slippin' & slidin' if(!_zeroFlag) { _zeroFlag = true; _zeroPosition = localpos; } 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 vec.X = -vel.X * PID_D * 2f + (_zeroPosition.X - localpos.X) * (PID_P * 5); vec.Y = -vel.Y * PID_D * 2f + (_zeroPosition.Y - localpos.Y) * (PID_P * 5); if(vel.Z > 0) vec.Z += -vel.Z * PID_D + (_zeroPosition.Z - localpos.Z) * PID_P; else vec.Z += (-vel.Z * PID_D + (_zeroPosition.Z - localpos.Z) * PID_P) * 0.2f; /* if (flying) { vec.Z += -vel.Z * PID_D + (_zeroPosition.Z - localpos.Z) * PID_P; } */ } //PidStatus = true; } else { m_pidControllerActive = true; _zeroFlag = false; if(m_iscolliding) { if(!m_flying) { // we are on a surface if(ctz.Z > 0f) { // moving up or JUMPING vec.Z += (ctz.Z - vel.Z) * PID_D * 2f; vec.X += (ctz.X - vel.X) * (PID_D); vec.Y += (ctz.Y - vel.Y) * (PID_D); } else { // we are moving down on a surface if(ctz.Z == 0) { if(vel.Z > 0) vec.Z -= vel.Z * PID_D * 2f; vec.X += (ctz.X - vel.X) * (PID_D); vec.Y += (ctz.Y - vel.Y) * (PID_D); } // intencionally going down else { if(ctz.Z < vel.Z) vec.Z += (ctz.Z - vel.Z) * PID_D; else { } if(Math.Abs(ctz.X) > Math.Abs(vel.X)) vec.X += (ctz.X - vel.X) * (PID_D); if(Math.Abs(ctz.Y) > Math.Abs(vel.Y)) vec.Y += (ctz.Y - vel.Y) * (PID_D); } } // We're standing on something } else { // We're flying and colliding with something vec.X += (ctz.X - vel.X) * (PID_D * 0.0625f); vec.Y += (ctz.Y - vel.Y) * (PID_D * 0.0625f); vec.Z += (ctz.Z - vel.Z) * (PID_D * 0.0625f); } } else // ie not colliding { if(m_flying || hoverPIDActive) //(!m_iscolliding && flying) { // we're in mid air suspended vec.X += (ctz.X - vel.X) * (PID_D); vec.Y += (ctz.Y - vel.Y) * (PID_D); vec.Z += (ctz.Z - vel.Z) * (PID_D); } else { // 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); vec.X += (ctz.X - vel.X) * PID_D * 0.833f; vec.Y += (ctz.Y - vel.Y) * PID_D * 0.833f; // hack for breaking on fall if(ctz.Z == -9999f) vec.Z += -vel.Z * PID_D - m_parent_scene.gravityz * m_mass; } } } if(velLengthSquared > 2500.0f) // 50m/s apply breaks { breakfactor = 0.16f * m_mass; vec.X -= breakfactor * vel.X; vec.Y -= breakfactor * vel.Y; vec.Z -= breakfactor * vel.Z; } } else { breakfactor = m_mass; vec.X -= breakfactor * vel.X; vec.Y -= breakfactor * vel.Y; if(m_flying) vec.Z -= 0.5f * breakfactor * vel.Z; else vec.Z -= .16f* m_mass * vel.Z; } if(m_flying || hoverPIDActive) { vec.Z -= m_parent_scene.gravityz * m_mass; if(!hoverPIDActive) { //Added for auto fly height. Kitto Flora float target_altitude = terrainheight + MinimumGroundFlightOffset; if(localpos.Z < target_altitude) { vec.Z += (target_altitude - localpos.Z) * PID_P * 5.0f; } // end add Kitto Flora } } if(vec.IsFinite()) { if(vec.X != 0 || vec.Y !=0 || vec.Z !=0) SafeNativeMethods.BodyAddForce(Body,vec.X,vec.Y,vec.Z); } else { m_log.Warn("[PHYSICS]: Got a NaN force vector in Move()"); m_log.Warn("[PHYSICS]: Avatar Position is non-finite!"); defects.Add(this); // _parent_scene.RemoveCharacter(this); // destroy avatar capsule and related ODE data AvatarGeomAndBodyDestroy(); return; } // update our local ideia of position velocity and aceleration // _position = localpos; _position = localpos; if(_zeroFlag) { _velocity = Vector3.Zero; _acceleration = Vector3.Zero; m_rotationalVelocity = Vector3.Zero; } else { Vector3 a = _velocity; // previus velocity SetSmooth(ref _velocity,ref vel,2); a = (_velocity - a) * invtimeStep; SetSmooth(ref _acceleration,ref a,2); dtmp = SafeNativeMethods.BodyGetAngularVel(Body); m_rotationalVelocity.X = 0f; m_rotationalVelocity.Y = 0f; m_rotationalVelocity.Z = dtmp.Z; Math.Round(m_rotationalVelocity.Z,3); } } public void round(ref Vector3 v,int digits) { v.X = (float)Math.Round(v.X,digits); v.Y = (float)Math.Round(v.Y,digits); v.Z = (float)Math.Round(v.Z,digits); } public void SetSmooth(ref Vector3 dst,ref Vector3 value) { dst.X = 0.1f * dst.X + 0.9f * value.X; dst.Y = 0.1f * dst.Y + 0.9f * value.Y; dst.Z = 0.1f * dst.Z + 0.9f * value.Z; } public void SetSmooth(ref Vector3 dst,ref Vector3 value,int rounddigits) { dst.X = 0.4f * dst.X + 0.6f * value.X; dst.X = (float)Math.Round(dst.X,rounddigits); dst.Y = 0.4f * dst.Y + 0.6f * value.Y; dst.Y = (float)Math.Round(dst.Y,rounddigits); dst.Z = 0.4f * dst.Z + 0.6f * value.Z; dst.Z = (float)Math.Round(dst.Z,rounddigits); } /// /// Updates the reported position and velocity. /// Used to copy variables from unmanaged space at heartbeat rate and also trigger scene updates acording /// also outbounds checking /// copy and outbounds now done in move(..) at ode rate /// /// public void UpdatePositionAndVelocity() { return; // if (Body == IntPtr.Zero) // return; } /// /// Cleanup the things we use in the scene. /// public void Destroy() { AddChange(changes.Remove,null); } public override void CrossingFailure() { } public override Vector3 PIDTarget { set { return; } } public override bool PIDActive { get { return m_pidControllerActive; } set { return; } } public override float PIDTau { set { return; } } public override float PIDHoverHeight { set { AddChange(changes.PIDHoverHeight,value); } } public override bool PIDHoverActive { get { return m_useHoverPID; } set { AddChange(changes.PIDHoverActive,value); } } public override PIDHoverType PIDHoverType { set { AddChange(changes.PIDHoverType,value); } } public override float PIDHoverTau { set { float tmp = 0; if(value > 0) { float mint = (0.05f > timeStep ? 0.05f : timeStep); if(value < mint) tmp = mint; else tmp = value; } AddChange(changes.PIDHoverTau,tmp); } } public override 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 SubscribeEvents(int ms) { m_eventsubscription = ms; m_cureventsubscription = 0; CollisionEventsThisFrame.Clear(); SentEmptyCollisionsEvent = false; } public override void UnSubscribeEvents() { m_eventsubscription = 0; m_parent_scene.RemoveCollisionEventReporting(this); lock(CollisionEventsThisFrame) CollisionEventsThisFrame.Clear(); } public override void AddCollisionEvent(uint CollidedWith,ContactPoint contact) { lock(CollisionEventsThisFrame) CollisionEventsThisFrame.AddCollider(CollidedWith,contact); m_parent_scene.AddCollisionEventReporting(this); } public void SendCollisions(int timestep) { if(m_cureventsubscription < 50000) m_cureventsubscription += timestep; if(m_cureventsubscription < m_eventsubscription) return; lock(CollisionEventsThisFrame) { int ncolisions = CollisionEventsThisFrame.m_objCollisionList.Count; if(!SentEmptyCollisionsEvent || ncolisions > 0) { base.SendCollisionUpdate(CollisionEventsThisFrame); m_cureventsubscription = 0; if(ncolisions == 0) { SentEmptyCollisionsEvent = true; // _parent_scene.RemoveCollisionEventReporting(this); } else { SentEmptyCollisionsEvent = false; CollisionEventsThisFrame.Clear(); } } } } public override bool SubscribedEvents() { if(m_eventsubscription > 0) return true; return false; } private void changePhysicsStatus(bool NewStatus) { if(NewStatus != m_isPhysical) { if(NewStatus) { AvatarGeomAndBodyDestroy(); AvatarGeomAndBodyCreation(_position.X,_position.Y,_position.Z); m_parent_scene.actor_name_map[collider] = (PhysicsActor)this; m_parent_scene.actor_name_map[capsule] = (PhysicsActor)this; m_parent_scene.AddCharacter(this); } else { m_parent_scene.RemoveCollisionEventReporting(this); m_parent_scene.RemoveCharacter(this); // destroy avatar capsule and related ODE data AvatarGeomAndBodyDestroy(); } m_freemove = false; m_isPhysical = NewStatus; } } private void changeAdd() { changePhysicsStatus(true); } private void changeRemove() { changePhysicsStatus(false); } private void changeShape(PrimitiveBaseShape arg) { } private void changeAvatarSize(strAvatarSize st) { m_feetOffset = st.offset; changeSize(st.size); } private void changeSize(Vector3 pSize) { if(pSize.IsFinite()) { // for now only look to Z changes since viewers also don't change X and Y if(pSize.Z != m_size.Z) { AvatarGeomAndBodyDestroy(); float oldsz = m_size.Z; m_size = pSize; AvatarGeomAndBodyCreation(_position.X,_position.Y, _position.Z + (m_size.Z - oldsz) * 0.5f); // Velocity = Vector3.Zero; m_targetVelocity = Vector3.Zero; m_parent_scene.actor_name_map[collider] = (PhysicsActor)this; m_parent_scene.actor_name_map[capsule] = (PhysicsActor)this; } m_freemove = false; m_pidControllerActive = true; } else { m_log.Warn("[PHYSICS]: Got a NaN Size from Scene on a Character"); } } private void changePosition(Vector3 newPos) { if(Body != IntPtr.Zero) SafeNativeMethods.BodySetPosition(Body,newPos.X,newPos.Y,newPos.Z); _position = newPos; m_freemove = false; m_pidControllerActive = true; } private void changeOrientation(Quaternion newOri) { if(m_orientation != newOri) { m_orientation = newOri; // keep a copy for core use // but only use rotations around Z m_orientation2D.W = newOri.W; m_orientation2D.Z = newOri.Z; float t = m_orientation2D.W * m_orientation2D.W + m_orientation2D.Z * m_orientation2D.Z; if(t > 0) { t = 1.0f / (float)Math.Sqrt(t); m_orientation2D.W *= t; m_orientation2D.Z *= t; } else { m_orientation2D.W = 1.0f; m_orientation2D.Z = 0f; } m_orientation2D.Y = 0f; m_orientation2D.X = 0f; SafeNativeMethods.Quaternion myrot = new SafeNativeMethods.Quaternion(); myrot.X = m_orientation2D.X; myrot.Y = m_orientation2D.Y; myrot.Z = m_orientation2D.Z; myrot.W = m_orientation2D.W; SafeNativeMethods.BodySetQuaternion(Body,ref myrot); } } private void changeVelocity(Vector3 newVel) { _velocity = newVel; setFreeMove(); if(Body != IntPtr.Zero) SafeNativeMethods.BodySetLinearVel(Body,newVel.X,newVel.Y,newVel.Z); } private void changeTargetVelocity(Vector3 newVel) { m_pidControllerActive = true; m_freemove = false; _target_velocity = newVel; } private void changeSetTorque(Vector3 newTorque) { } private void changeAddForce(Vector3 newForce) { } private void changeAddAngularForce(Vector3 arg) { } private void changeAngularLock(byte arg) { } private void changeFloatOnWater(bool arg) { } private void changeVolumedetetion(bool arg) { } private void changeSelectedStatus(bool arg) { } private void changeDisable(bool arg) { } private void changeBuilding(bool arg) { } private void setFreeMove() { m_pidControllerActive = true; _zeroFlag = false; _target_velocity = Vector3.Zero; m_freemove = true; m_colliderfilter = -1; m_colliderObjectfilter = -1; m_colliderGroundfilter = -1; m_iscolliding = false; m_iscollidingGround = false; m_iscollidingObj = false; CollisionEventsThisFrame.Clear(); } private void changeForce(Vector3 newForce) { setFreeMove(); if(Body != IntPtr.Zero) { if(newForce.X != 0f || newForce.Y != 0f || newForce.Z != 0) SafeNativeMethods.BodyAddForce(Body,newForce.X,newForce.Y,newForce.Z); } } // for now momentum is actually velocity private void changeMomentum(Vector3 newmomentum) { _velocity = newmomentum; setFreeMove(); if(Body != IntPtr.Zero) SafeNativeMethods.BodySetLinearVel(Body,newmomentum.X,newmomentum.Y,newmomentum.Z); } private void changePIDHoverHeight(float val) { m_PIDHoverHeight = val; if(val == 0) m_useHoverPID = false; } private void changePIDHoverType(PIDHoverType type) { m_PIDHoverType = type; } private void changePIDHoverTau(float tau) { m_PIDHoverTau = tau; } private void changePIDHoverActive(bool active) { m_useHoverPID = active; } private void donullchange() { } public bool DoAChange(changes what,object arg) { if(collider == IntPtr.Zero && what != changes.Add && what != changes.Remove) { return false; } // nasty switch switch(what) { case changes.Add: changeAdd(); break; case changes.Remove: changeRemove(); break; case changes.Position: changePosition((Vector3)arg); break; case changes.Orientation: changeOrientation((Quaternion)arg); break; case changes.PosOffset: donullchange(); break; case changes.OriOffset: donullchange(); break; case changes.Velocity: changeVelocity((Vector3)arg); break; case changes.TargetVelocity: changeTargetVelocity((Vector3)arg); break; // case changes.Acceleration: // changeacceleration((Vector3)arg); // break; // case changes.AngVelocity: // changeangvelocity((Vector3)arg); // break; case changes.Force: changeForce((Vector3)arg); break; case changes.Torque: changeSetTorque((Vector3)arg); break; case changes.AddForce: changeAddForce((Vector3)arg); break; case changes.AddAngForce: changeAddAngularForce((Vector3)arg); break; case changes.AngLock: changeAngularLock((byte)arg); break; case changes.Size: changeSize((Vector3)arg); break; case changes.AvatarSize: changeAvatarSize((strAvatarSize)arg); break; case changes.Momentum: changeMomentum((Vector3)arg); break; case changes.PIDHoverHeight: changePIDHoverHeight((float)arg); break; case changes.PIDHoverType: changePIDHoverType((PIDHoverType)arg); break; case changes.PIDHoverTau: changePIDHoverTau((float)arg); break; case changes.PIDHoverActive: changePIDHoverActive((bool)arg); break; /* not in use for now case changes.Shape: changeShape((PrimitiveBaseShape)arg); break; case changes.CollidesWater: changeFloatOnWater((bool)arg); break; case changes.VolumeDtc: changeVolumedetetion((bool)arg); break; case changes.Physical: changePhysicsStatus((bool)arg); break; case changes.Selected: changeSelectedStatus((bool)arg); break; case changes.disabled: changeDisable((bool)arg); break; case changes.building: changeBuilding((bool)arg); break; */ case changes.Null: donullchange(); break; default: donullchange(); break; } return false; } public void AddChange(changes what,object arg) { m_parent_scene.AddChange((PhysicsActor)this,what,arg); } private struct strAvatarSize { public Vector3 size; public float offset; } } }