diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSCharacter.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSCharacter.cs new file mode 100644 index 0000000000..4c4e95048f --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSCharacter.cs @@ -0,0 +1,814 @@ +/* + * 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.Reflection; +using log4net; +using OMV = OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Physics.Manager; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public sealed class BSCharacter : BSPhysObject +{ + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[BULLETS CHAR]"; + + // private bool _stopped; + private OMV.Vector3 _size; + private bool _grabbed; + 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; + private int _physicsActorType; + private bool _isPhysical; + 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 OMV.Vector3 _appliedVelocity; // the last velocity applied to the avatar + private float _currentFriction; // the friction currently being used (changed by setVelocity). + + private BSVMotor _velocityMotor; + + 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") + { + _physicsActorType = (int)ActorTypes.Agent; + _position = pos; + + // Old versions of ScenePresence passed only the height. If width and/or depth are zero, + // replace with the default values. + _size = size; + if (_size.X == 0f) _size.X = BSParam.AvatarCapsuleDepth; + if (_size.Y == 0f) _size.Y = BSParam.AvatarCapsuleWidth; + + // A motor to control the acceleration and deceleration of the avatar movement. + // _velocityMotor = new BSVMotor("BSCharacter.Velocity", 3f, 5f, BSMotor.InfiniteVector, 1f); + // _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. + + _flying = isFlying; + _orientation = OMV.Quaternion.Identity; + _velocity = OMV.Vector3.Zero; + _appliedVelocity = OMV.Vector3.Zero; + _buoyancy = ComputeBuoyancyFromFlying(isFlying); + _currentFriction = BSParam.AvatarStandingFriction; + _avatarDensity = BSParam.AvatarDensity; + + // The dimensions of the avatar capsule are kept in the scale. + // Physics creates a unit capsule which is scaled by the physics engine. + ComputeAvatarScale(_size); + // set _avatarVolume and _mass based on capsule size, _density and Scale + ComputeAvatarVolumeAndMass(); + DetailLog("{0},BSCharacter.create,call,size={1},scale={2},density={3},volume={4},mass={5}", + LocalID, _size, Scale, _avatarDensity, _avatarVolume, RawMass); + + // do actual creation in taint time + PhysicsScene.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); + + SetPhysicalProperties(); + }); + return; + } + + // called when this character is being destroyed and the resources should be released + public override void Destroy() + { + base.Destroy(); + + DetailLog("{0},BSCharacter.Destroy", LocalID); + PhysicsScene.TaintedObject("BSCharacter.destroy", delegate() + { + PhysicsScene.Shapes.DereferenceBody(PhysBody, true, null); + PhysBody.Clear(); + PhysicsScene.Shapes.DereferenceShape(PhysShape, true, null); + PhysShape.Clear(); + }); + } + + private void SetPhysicalProperties() + { + BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, PhysBody.ptr); + + ZeroMotion(true); + ForcePosition = _position; + // Set the velocity and compute the proper friction + ForceVelocity = _velocity; + // Setting the current and target in the motor will cause it to start computing any deceleration. + _velocityMotor.Reset(); + _velocityMotor.SetCurrent(_velocity); + _velocityMotor.SetTarget(_velocity); + _velocityMotor.Enabled = false; + + // 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; + + BulletSimAPI.SetRestitution2(PhysBody.ptr, BSParam.AvatarRestitution); + BulletSimAPI.SetMargin2(PhysShape.ptr, PhysicsScene.Params.collisionMargin); + BulletSimAPI.SetLocalScaling2(PhysShape.ptr, Scale); + BulletSimAPI.SetContactProcessingThreshold2(PhysBody.ptr, BSParam.ContactProcessingThreshold); + if (BSParam.CcdMotionThreshold > 0f) + { + BulletSimAPI.SetCcdMotionThreshold2(PhysBody.ptr, BSParam.CcdMotionThreshold); + BulletSimAPI.SetCcdSweptSphereRadius2(PhysBody.ptr, BSParam.CcdSweptSphereRadius); + } + + UpdatePhysicalMassProperties(RawMass); + + // Make so capsule does not fall over + BulletSimAPI.SetAngularFactorV2(PhysBody.ptr, OMV.Vector3.Zero); + + BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, CollisionFlags.CF_CHARACTER_OBJECT); + + BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, PhysBody.ptr, _position, _orientation); + + // BulletSimAPI.ForceActivationState2(BSBody.ptr, ActivationState.ACTIVE_TAG); + BulletSimAPI.ForceActivationState2(PhysBody.ptr, ActivationState.DISABLE_DEACTIVATION); + BulletSimAPI.UpdateSingleAabb2(PhysicsScene.World.ptr, PhysBody.ptr); + + // Do this after the object has been added to the world + PhysBody.collisionType = CollisionType.Avatar; + PhysBody.ApplyCollisionMask(); + } + + public override void RequestPhysicsterseUpdate() + { + base.RequestPhysicsterseUpdate(); + } + // No one calls this method so I don't know what it could possibly mean + public override bool Stopped { get { return false; } } + + public override OMV.Vector3 Size { + get + { + // Avatar capsule size is kept in the scale parameter. + return _size; + } + + set { + // When an avatar's size is set, only the height is changed. + _size = value; + // Old versions of ScenePresence passed only the height. If width and/or depth are zero, + // replace with the default values. + if (_size.X == 0f) _size.X = BSParam.AvatarCapsuleDepth; + if (_size.Y == 0f) _size.Y = BSParam.AvatarCapsuleWidth; + + ComputeAvatarScale(_size); + ComputeAvatarVolumeAndMass(); + DetailLog("{0},BSCharacter.setSize,call,size={1},scale={2},density={3},volume={4},mass={5}", + LocalID, _size, Scale, _avatarDensity, _avatarVolume, RawMass); + + PhysicsScene.TaintedObject("BSCharacter.setSize", delegate() + { + if (PhysBody.HasPhysicalBody && PhysShape.HasPhysicalShape) + { + BulletSimAPI.SetLocalScaling2(PhysShape.ptr, Scale); + UpdatePhysicalMassProperties(RawMass); + // Make sure this change appears as a property update event + BulletSimAPI.PushUpdate2(PhysBody.ptr); + } + }); + + } + } + + public override PrimitiveBaseShape Shape + { + 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; } + } + public override bool Selected { + set { _selected = value; } + } + public override void CrossingFailure() { return; } + public override void link(PhysicsActor obj) { return; } + public override void delink() { return; } + + // Set motion values to zero. + // Do it to the properties so the values get set in the physics engine. + // Push the setting of the values to the viewer. + // Called at taint time! + public override void ZeroMotion(bool inTaintTime) + { + _velocity = OMV.Vector3.Zero; + _velocityMotor.Zero(); + _acceleration = OMV.Vector3.Zero; + _rotationalVelocity = OMV.Vector3.Zero; + + // Zero some other properties directly into the physics engine + PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate() + { + if (PhysBody.HasPhysicalBody) + BulletSimAPI.ClearAllForces2(PhysBody.ptr); + }); + } + public override void ZeroAngularMotion(bool inTaintTime) + { + _rotationalVelocity = OMV.Vector3.Zero; + + PhysicsScene.TaintedObject(inTaintTime, "BSCharacter.ZeroMotion", delegate() + { + if (PhysBody.HasPhysicalBody) + { + BulletSimAPI.SetInterpolationAngularVelocity2(PhysBody.ptr, OMV.Vector3.Zero); + BulletSimAPI.SetAngularVelocity2(PhysBody.ptr, OMV.Vector3.Zero); + // The next also get rid of applied linear force but the linear velocity is untouched. + BulletSimAPI.ClearForces2(PhysBody.ptr); + } + }); + } + + + public override void LockAngularMotion(OMV.Vector3 axis) { return; } + + public override OMV.Vector3 RawPosition + { + get { return _position; } + set { _position = value; } + } + public override OMV.Vector3 Position { + get { + // Don't refetch the position because this function is called a zillion times + // _position = BulletSimAPI.GetObjectPosition2(Scene.World.ptr, LocalID); + return _position; + } + set { + _position = value; + PositionSanityCheck(); + + PhysicsScene.TaintedObject("BSCharacter.setPosition", delegate() + { + DetailLog("{0},BSCharacter.SetPosition,taint,pos={1},orient={2}", LocalID, _position, _orientation); + if (PhysBody.HasPhysicalBody) + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + }); + } + } + public override OMV.Vector3 ForcePosition { + get { + _position = BulletSimAPI.GetPosition2(PhysBody.ptr); + return _position; + } + set { + _position = value; + PositionSanityCheck(); + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + } + } + + + // Check that the current position is sane and, if not, modify the position to make it so. + // Check for being below terrain or on water. + // Returns 'true' of the position was made sane by some action. + private bool PositionSanityCheck() + { + bool ret = false; + + // TODO: check for out of bounds + if (!PhysicsScene.TerrainManager.IsWithinKnownTerrain(_position)) + { + // 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; + } + + // If below the ground, move the avatar up + float terrainHeight = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(_position); + if (Position.Z < terrainHeight) + { + DetailLog("{0},BSCharacter.PositionAdjustUnderGround,call,pos={1},terrain={2}", LocalID, _position, terrainHeight); + _position.Z = terrainHeight + 2.0f; + ret = true; + } + if ((CurrentCollisionFlags & CollisionFlags.BS_FLOATS_ON_WATER) != 0) + { + float waterHeight = PhysicsScene.TerrainManager.GetWaterLevelAtXYZ(_position); + if (Position.Z < waterHeight) + { + _position.Z = waterHeight; + ret = true; + } + } + + return ret; + } + + // A version of the sanity check that also makes sure a new position value is + // pushed back to the physics engine. This routine would be used by anyone + // who is not already pushing the value. + private bool PositionSanityCheck(bool inTaintTime) + { + bool ret = false; + if (PositionSanityCheck()) + { + // 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() + { + DetailLog("{0},BSCharacter.PositionSanityCheck,taint,pos={1},orient={2}", LocalID, _position, _orientation); + if (PhysBody.HasPhysicalBody) + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + }); + ret = true; + } + return ret; + } + + 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 { + get {return _mass; } + } + public override void UpdatePhysicalMassProperties(float physMass) + { + OMV.Vector3 localInertia = BulletSimAPI.CalculateLocalInertia2(PhysShape.ptr, physMass); + BulletSimAPI.SetMassProps2(PhysBody.ptr, physMass, localInertia); + } + + public override OMV.Vector3 Force { + get { return _force; } + set { + _force = value; + // m_log.DebugFormat("{0}: Force = {1}", LogHeader, _force); + PhysicsScene.TaintedObject("BSCharacter.SetForce", delegate() + { + DetailLog("{0},BSCharacter.setForce,taint,force={1}", LocalID, _force); + if (PhysBody.HasPhysicalBody) + BulletSimAPI.SetObjectForce2(PhysBody.ptr, _force); + }); + } + } + + public bool TouchingGround() + { + bool ret = BulletSimAPI.RayCastGround(PhysicsScene.World.ptr,_position,_size.Z * 0.55f, PhysBody.ptr); + return ret; + } + // Avatars don't do vehicles + public override int VehicleType { get { return (int)Vehicle.TYPE_NONE; } set { return; } } + public override void VehicleFloatParam(int param, float value) { } + public override void VehicleVectorParam(int param, OMV.Vector3 value) {} + public override void VehicleRotationParam(int param, OMV.Quaternion rotation) { } + public override void VehicleFlags(int param, bool remove) { } + + // Allows the detection of collisions with inherently non-physical prims. see llVolumeDetect for more + public override void SetVolumeDetect(int param) { return; } + + public override OMV.Vector3 GeometricCenter { get { return OMV.Vector3.Zero; } } + public override OMV.Vector3 CenterOfMass { get { return OMV.Vector3.Zero; } } + + // Sets the target in the motor. This starts the changing of the avatar's velocity. + public override OMV.Vector3 TargetVelocity + { + get + { + return _velocityMotor.TargetValue; + } + set + { + DetailLog("{0},BSCharacter.setTargetVelocity,call,vel={1}", LocalID, value); + + if (!_flying) + if ((value.Z >= 0.0001f) || (value.Z <= -0.0001f) || _velocity.Z < -0.0001f) + if (!TouchingGround()) + value.Z = _velocity.Z; + if (_setAlwaysRun) + value *= 1.3f; + + OMV.Vector3 targetVel = value; + + PhysicsScene.TaintedObject("BSCharacter.setTargetVelocity", delegate() + { + + _velocityMotor.Reset(); + _velocityMotor.SetTarget(targetVel); + _velocityMotor.SetCurrent(_velocity); + _velocityMotor.Enabled = true; + + // Make sure a property update happens next step so the motor gets incorporated. + BulletSimAPI.PushUpdate2(PhysBody.ptr); + }); + } + } + // Directly setting velocity means this is what the user really wants now. + public override OMV.Vector3 Velocity { + get { return _velocity; } + set { + _velocity = value; + // m_log.DebugFormat("{0}: set velocity = {1}", LogHeader, _velocity); + PhysicsScene.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; + + DetailLog("{0},BSCharacter.setVelocity,taint,vel={1}", LocalID, _velocity); + ForceVelocity = _velocity; + }); + } + } + public override OMV.Vector3 ForceVelocity { + get { return _velocity; } + set { + PhysicsScene.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) + BulletSimAPI.SetFriction2(PhysBody.ptr, _currentFriction); + } + } + else + { + if (_currentFriction != BSParam.AvatarFriction) + { + _currentFriction = BSParam.AvatarFriction; + if (PhysBody.HasPhysicalBody) + BulletSimAPI.SetFriction2(PhysBody.ptr, _currentFriction); + } + } + // Remember the set velocity so we can suppress the reduction by friction, ... + _appliedVelocity = value; + + BulletSimAPI.SetLinearVelocity2(PhysBody.ptr, _velocity); + BulletSimAPI.Activate2(PhysBody.ptr, true); + } + } + public override OMV.Vector3 Torque { + get { return _torque; } + set { _torque = value; + } + } + public override float CollisionScore { + get { return _collisionScore; } + set { _collisionScore = value; + } + } + public override OMV.Vector3 Acceleration { + get { return _acceleration; } + set { _acceleration = value; } + } + public override OMV.Quaternion RawOrientation + { + get { return _orientation; } + set { _orientation = value; } + } + public override OMV.Quaternion Orientation { + get { return _orientation; } + set { + _orientation = value; + // m_log.DebugFormat("{0}: set orientation to {1}", LogHeader, _orientation); + PhysicsScene.TaintedObject("BSCharacter.setOrientation", delegate() + { + if (PhysBody.HasPhysicalBody) + { + // _position = BulletSimAPI.GetPosition2(BSBody.ptr); + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + } + }); + } + } + // Go directly to Bullet to get/set the value. + public override OMV.Quaternion ForceOrientation + { + get + { + _orientation = BulletSimAPI.GetOrientation2(PhysBody.ptr); + return _orientation; + } + set + { + _orientation = value; + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + } + } + public override int PhysicsActorType { + get { return _physicsActorType; } + set { _physicsActorType = value; + } + } + public override bool IsPhysical { + get { return _isPhysical; } + set { _isPhysical = value; + } + } + public override bool IsSolid { + get { return true; } + } + public override bool IsStatic { + get { return false; } + } + public override bool Flying { + get { return _flying; } + set { + _flying = value; + + // simulate flying by changing the effect of gravity + Buoyancy = ComputeBuoyancyFromFlying(_flying); + } + } + // Flying is implimented by changing the avatar's buoyancy. + // Would this be done better with a vehicle type? + private float ComputeBuoyancyFromFlying(bool ifFlying) { + return ifFlying ? 1f : 0f; + } + public override bool + SetAlwaysRun { + get { return _setAlwaysRun; } + set { _setAlwaysRun = value; } + } + public override bool ThrottleUpdates { + get { return _throttleUpdates; } + set { _throttleUpdates = value; } + } + public override bool IsColliding { + get { return (CollidingStep == PhysicsScene.SimulationStep); } + set { _isColliding = value; } + } + public override bool CollidingGround { + get { return (CollidingGroundStep == PhysicsScene.SimulationStep); } + set { CollidingGround = value; } + } + public override bool CollidingObj { + get { return _collidingObj; } + set { _collidingObj = value; } + } + public override bool FloatOnWater { + set { + _floatOnWater = value; + PhysicsScene.TaintedObject("BSCharacter.setFloatOnWater", delegate() + { + if (PhysBody.HasPhysicalBody) + { + if (_floatOnWater) + CurrentCollisionFlags = BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_FLOATS_ON_WATER); + else + CurrentCollisionFlags = BulletSimAPI.RemoveFromCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_FLOATS_ON_WATER); + } + }); + } + } + public override OMV.Vector3 RotationalVelocity { + get { return _rotationalVelocity; } + set { _rotationalVelocity = value; } + } + public override OMV.Vector3 ForceRotationalVelocity { + get { return _rotationalVelocity; } + set { _rotationalVelocity = value; } + } + public override bool Kinematic { + get { return _kinematic; } + set { _kinematic = value; } + } + // neg=fall quickly, 0=1g, 1=0g, pos=float up + public override float Buoyancy { + get { return _buoyancy; } + set { _buoyancy = value; + PhysicsScene.TaintedObject("BSCharacter.setBuoyancy", delegate() + { + DetailLog("{0},BSCharacter.setBuoyancy,taint,buoy={1}", LocalID, _buoyancy); + ForceBuoyancy = _buoyancy; + }); + } + } + public override float ForceBuoyancy { + get { return _buoyancy; } + set { + PhysicsScene.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); + if (PhysBody.HasPhysicalBody) + BulletSimAPI.SetGravity2(PhysBody.ptr, new OMV.Vector3(0f, 0f, grav)); + } + } + + // Used for MoveTo + public override OMV.Vector3 PIDTarget { + set { _PIDTarget = value; } + } + public override bool PIDActive { + set { _usePID = value; } + } + public override float PIDTau { + 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) { + if (force.IsFinite()) + { + _force.X += force.X; + _force.Y += force.Y; + _force.Z += force.Z; + // m_log.DebugFormat("{0}: AddForce. adding={1}, newForce={2}", LogHeader, force, _force); + PhysicsScene.TaintedObject("BSCharacter.AddForce", delegate() + { + DetailLog("{0},BSCharacter.setAddForce,taint,addedForce={1}", LocalID, _force); + if (PhysBody.HasPhysicalBody) + BulletSimAPI.SetObjectForce2(PhysBody.ptr, _force); + }); + } + else + { + m_log.ErrorFormat("{0}: Got a NaN force applied to a Character", LogHeader); + } + //m_lastUpdateSent = false; + } + + public override void AddAngularForce(OMV.Vector3 force, bool pushforce) { + } + public override void SetMomentum(OMV.Vector3 momentum) { + } + + private void ComputeAvatarScale(OMV.Vector3 size) + { + OMV.Vector3 newScale = size; + // newScale.X = PhysicsScene.Params.avatarCapsuleWidth; + // newScale.Y = PhysicsScene.Params.avatarCapsuleDepth; + + // From the total height, remove the capsule half spheres that are at each end + // The 1.15f came from ODE. Not sure what this factors in. + // newScale.Z = (size.Z * 1.15f) - (newScale.X + newScale.Y); + + // The total scale height is the central cylindar plus the caps on the two ends. + newScale.Z = size.Z + (Math.Min(size.X, size.Y) * 2f); + + // Convert diameters to radii and height to half height -- the way Bullet expects it. + Scale = newScale / 2f; + } + + // set _avatarVolume and _mass based on capsule size, _density and Scale + private void ComputeAvatarVolumeAndMass() + { + _avatarVolume = (float)( + Math.PI + * Scale.X + * Scale.Y // the area of capsule cylinder + * Scale.Z // times height of capsule cylinder + + 1.33333333f + * Math.PI + * Scale.X + * Math.Min(Scale.X, Scale.Y) + * Scale.Y // plus the volume of the capsule end caps + ); + _mass = _avatarDensity * _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; + _orientation = entprop.Rotation; + _velocity = 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 (_velocityMotor.Enabled) + { + // TODO: Decide if the step parameters should be changed depending on the avatar's + // state (flying, colliding, ...). + + OMV.Vector3 stepVelocity = _velocityMotor.Step(PhysicsScene.LastTimeStep); + + // If falling, we keep the world's downward vector no matter what the other axis specify. + if (!Flying && !IsColliding) + { + stepVelocity.Z = entprop.Velocity.Z; + DetailLog("{0},BSCharacter.UpdateProperties,taint,overrideStepZWithWorldZ,stepVel={1}", LocalID, stepVelocity); + } + + // If the user has said stop and we've stopped applying velocity correction, + // the motor can be turned off. Set the velocity to zero so the zero motion is sent to the viewer. + if (_velocityMotor.TargetValue.ApproxEquals(OMV.Vector3.Zero, 0.01f) && _velocityMotor.ErrorIsZero) + { + stepVelocity = OMV.Vector3.Zero; + _velocityMotor.Enabled = false; + DetailLog("{0},BSCharacter.UpdateProperties,taint,disableVelocityMotor,m={1}", LocalID, _velocityMotor); + } + + _velocity = stepVelocity; + entprop.Velocity = _velocity; + BulletSimAPI.SetLinearVelocity2(PhysBody.ptr, _velocity); + } + + // remember the current and last set values + LastEntityProperties = CurrentEntityProperties; + CurrentEntityProperties = entprop; + + // Tell the linkset about value changes + Linkset.UpdateProperties(this, true); + + // 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); + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSConstraint.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSConstraint.cs new file mode 100644 index 0000000000..426bdc2ea7 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSConstraint.cs @@ -0,0 +1,135 @@ +/* + * 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; +using OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + +public abstract class BSConstraint : IDisposable +{ + private static string LogHeader = "[BULLETSIM CONSTRAINT]"; + + protected BulletSim m_world; + protected BulletBody m_body1; + protected BulletBody m_body2; + protected BulletConstraint m_constraint; + protected bool m_enabled = false; + + public BulletBody Body1 { get { return m_body1; } } + public BulletBody Body2 { get { return m_body2; } } + public BulletConstraint Constraint { get { return m_constraint; } } + public abstract ConstraintType Type { get; } + public bool IsEnabled { get { return m_enabled; } } + + public BSConstraint() + { + } + + public virtual void Dispose() + { + if (m_enabled) + { + m_enabled = false; + if (m_constraint.HasPhysicalConstraint) + { + bool success = BulletSimAPI.DestroyConstraint2(m_world.ptr, m_constraint.ptr); + m_world.physicsScene.DetailLog("{0},BSConstraint.Dispose,taint,id1={1},body1={2},id2={3},body2={4},success={5}", + BSScene.DetailLogZero, + m_body1.ID, m_body1.ptr.ToString(), + m_body2.ID, m_body2.ptr.ToString(), + success); + m_constraint.Clear(); + } + } + } + + public virtual bool SetLinearLimits(Vector3 low, Vector3 high) + { + bool ret = false; + if (m_enabled) + ret = BulletSimAPI.SetLinearLimits2(m_constraint.ptr, low, high); + return ret; + } + + public virtual bool SetAngularLimits(Vector3 low, Vector3 high) + { + bool ret = false; + if (m_enabled) + ret = BulletSimAPI.SetAngularLimits2(m_constraint.ptr, low, high); + return ret; + } + + public virtual bool SetSolverIterations(float cnt) + { + bool ret = false; + if (m_enabled) + { + BulletSimAPI.SetConstraintNumSolverIterations2(m_constraint.ptr, cnt); + ret = true; + } + return ret; + } + + public virtual bool CalculateTransforms() + { + bool ret = false; + if (m_enabled) + { + // Recompute the internal transforms + BulletSimAPI.CalculateTransforms2(m_constraint.ptr); + ret = true; + } + return ret; + } + + // Reset this constraint making sure it has all its internal structures + // recomputed and is enabled and ready to go. + public virtual bool RecomputeConstraintVariables(float mass) + { + bool ret = false; + if (m_enabled) + { + ret = CalculateTransforms(); + if (ret) + { + // Setting an object's mass to zero (making it static like when it's selected) + // automatically disables the constraints. + // If the link is enabled, be sure to set the constraint itself to enabled. + BulletSimAPI.SetConstraintEnable2(m_constraint.ptr, BSParam.NumericBool(true)); + } + else + { + m_world.physicsScene.Logger.ErrorFormat("{0} CalculateTransforms failed. A={1}, B={2}", LogHeader, Body1.ID, Body2.ID); + } + } + return ret; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSConstraint6Dof.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSConstraint6Dof.cs new file mode 100644 index 0000000000..0181d9dffb --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSConstraint6Dof.cs @@ -0,0 +1,153 @@ +/* + * 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; +using OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + +public sealed class BSConstraint6Dof : BSConstraint +{ + private static string LogHeader = "[BULLETSIM 6DOF CONSTRAINT]"; + + public override ConstraintType Type { get { return ConstraintType.D6_CONSTRAINT_TYPE; } } + + // Create a btGeneric6DofConstraint + public BSConstraint6Dof(BulletSim world, BulletBody obj1, BulletBody obj2, + Vector3 frame1, Quaternion frame1rot, + Vector3 frame2, Quaternion frame2rot, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies) + { + m_world = world; + m_body1 = obj1; + m_body2 = obj2; + m_constraint = new BulletConstraint( + BulletSimAPI.Create6DofConstraint2(m_world.ptr, m_body1.ptr, m_body2.ptr, + frame1, frame1rot, + frame2, frame2rot, + useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); + m_enabled = true; + world.physicsScene.DetailLog("{0},BS6DofConstraint,createFrame,wID={1}, rID={2}, rBody={3}, cID={4}, cBody={5}", + BSScene.DetailLogZero, world.worldID, + obj1.ID, obj1.ptr.ToString(), obj2.ID, obj2.ptr.ToString()); + } + + public BSConstraint6Dof(BulletSim world, BulletBody obj1, BulletBody obj2, + Vector3 joinPoint, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies) + { + m_world = world; + m_body1 = obj1; + m_body2 = obj2; + if (!obj1.HasPhysicalBody || !obj2.HasPhysicalBody) + { + world.physicsScene.DetailLog("{0},BS6DOFConstraint,badBodyPtr,wID={1}, rID={2}, rBody={3}, cID={4}, cBody={5}", + BSScene.DetailLogZero, world.worldID, + obj1.ID, obj1.ptr.ToString(), obj2.ID, obj2.ptr.ToString()); + world.physicsScene.Logger.ErrorFormat("{0} Attempt to build 6DOF constraint with missing bodies: wID={1}, rID={2}, rBody={3}, cID={4}, cBody={5}", + LogHeader, world.worldID, obj1.ID, obj1.ptr.ToString(), obj2.ID, obj2.ptr.ToString()); + m_enabled = false; + } + else + { + m_constraint = new BulletConstraint( + BulletSimAPI.Create6DofConstraintToPoint2(m_world.ptr, m_body1.ptr, m_body2.ptr, + joinPoint, + useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); + world.physicsScene.DetailLog("{0},BS6DofConstraint,createMidPoint,wID={1}, csrt={2}, rID={3}, rBody={4}, cID={5}, cBody={6}", + BSScene.DetailLogZero, world.worldID, m_constraint.ptr.ToString(), + obj1.ID, obj1.ptr.ToString(), obj2.ID, obj2.ptr.ToString()); + if (!m_constraint.HasPhysicalConstraint) + { + world.physicsScene.Logger.ErrorFormat("{0} Failed creation of 6Dof constraint. rootID={1}, childID={2}", + LogHeader, obj1.ID, obj2.ID); + m_enabled = false; + } + else + { + m_enabled = true; + } + } + } + + public bool SetFrames(Vector3 frameA, Quaternion frameArot, Vector3 frameB, Quaternion frameBrot) + { + bool ret = false; + if (m_enabled) + { + BulletSimAPI.SetFrames2(m_constraint.ptr, frameA, frameArot, frameB, frameBrot); + ret = true; + } + return ret; + } + + public bool SetCFMAndERP(float cfm, float erp) + { + bool ret = false; + if (m_enabled) + { + BulletSimAPI.SetConstraintParam2(m_constraint.ptr, ConstraintParams.BT_CONSTRAINT_STOP_CFM, cfm, ConstraintParamAxis.AXIS_ALL); + BulletSimAPI.SetConstraintParam2(m_constraint.ptr, ConstraintParams.BT_CONSTRAINT_STOP_ERP, erp, ConstraintParamAxis.AXIS_ALL); + BulletSimAPI.SetConstraintParam2(m_constraint.ptr, ConstraintParams.BT_CONSTRAINT_CFM, cfm, ConstraintParamAxis.AXIS_ALL); + ret = true; + } + return ret; + } + + public bool UseFrameOffset(bool useOffset) + { + bool ret = false; + float onOff = useOffset ? ConfigurationParameters.numericTrue : ConfigurationParameters.numericFalse; + if (m_enabled) + ret = BulletSimAPI.UseFrameOffset2(m_constraint.ptr, onOff); + return ret; + } + + public bool TranslationalLimitMotor(bool enable, float targetVelocity, float maxMotorForce) + { + bool ret = false; + float onOff = enable ? ConfigurationParameters.numericTrue : ConfigurationParameters.numericFalse; + if (m_enabled) + { + ret = BulletSimAPI.TranslationalLimitMotor2(m_constraint.ptr, onOff, targetVelocity, maxMotorForce); + m_world.physicsScene.DetailLog("{0},BS6DOFConstraint,TransLimitMotor,enable={1},vel={2},maxForce={3}", + BSScene.DetailLogZero, enable, targetVelocity, maxMotorForce); + } + return ret; + } + + public bool SetBreakingImpulseThreshold(float threshold) + { + bool ret = false; + if (m_enabled) + ret = BulletSimAPI.SetBreakingImpulseThreshold2(m_constraint.ptr, threshold); + return ret; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSConstraintCollection.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSConstraintCollection.cs new file mode 100644 index 0000000000..5c00b1a3ce --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSConstraintCollection.cs @@ -0,0 +1,180 @@ +/* + * 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; +using log4net; +using OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + +public sealed class BSConstraintCollection : IDisposable +{ + // private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + // private static readonly string LogHeader = "[CONSTRAINT COLLECTION]"; + + delegate bool ConstraintAction(BSConstraint constrain); + + private List m_constraints; + private BulletSim m_world; + + public BSConstraintCollection(BulletSim world) + { + m_world = world; + m_constraints = new List(); + } + + public void Dispose() + { + this.Clear(); + } + + public void Clear() + { + lock (m_constraints) + { + foreach (BSConstraint cons in m_constraints) + { + cons.Dispose(); + } + m_constraints.Clear(); + } + } + + public bool AddConstraint(BSConstraint cons) + { + lock (m_constraints) + { + // There is only one constraint between any bodies. Remove any old just to make sure. + RemoveAndDestroyConstraint(cons.Body1, cons.Body2); + + m_constraints.Add(cons); + } + + return true; + } + + // Get the constraint between two bodies. There can be only one. + // Return 'true' if a constraint was found. + public bool TryGetConstraint(BulletBody body1, BulletBody body2, out BSConstraint returnConstraint) + { + bool found = false; + BSConstraint foundConstraint = null; + + uint lookingID1 = body1.ID; + uint lookingID2 = body2.ID; + lock (m_constraints) + { + foreach (BSConstraint constrain in m_constraints) + { + if ((constrain.Body1.ID == lookingID1 && constrain.Body2.ID == lookingID2) + || (constrain.Body1.ID == lookingID2 && constrain.Body2.ID == lookingID1)) + { + foundConstraint = constrain; + found = true; + break; + } + } + } + returnConstraint = foundConstraint; + return found; + } + + // Remove any constraint between the passed bodies. + // Presumed there is only one such constraint possible. + // Return 'true' if a constraint was found and destroyed. + public bool RemoveAndDestroyConstraint(BulletBody body1, BulletBody body2) + { + bool ret = false; + lock (m_constraints) + { + BSConstraint constrain; + if (this.TryGetConstraint(body1, body2, out constrain)) + { + // remove the constraint from our collection + RemoveAndDestroyConstraint(constrain); + ret = true; + } + } + + return ret; + } + + // The constraint MUST exist in the collection + public bool RemoveAndDestroyConstraint(BSConstraint constrain) + { + lock (m_constraints) + { + // remove the constraint from our collection + m_constraints.Remove(constrain); + } + // tell the engine that all its structures need to be freed + constrain.Dispose(); + // we destroyed something + return true; + } + + // Remove all constraints that reference the passed body. + // Return 'true' if any constraints were destroyed. + public bool RemoveAndDestroyConstraint(BulletBody body1) + { + List toRemove = new List(); + uint lookingID = body1.ID; + lock (m_constraints) + { + foreach (BSConstraint constrain in m_constraints) + { + if (constrain.Body1.ID == lookingID || constrain.Body2.ID == lookingID) + { + toRemove.Add(constrain); + } + } + foreach (BSConstraint constrain in toRemove) + { + m_constraints.Remove(constrain); + constrain.Dispose(); + } + } + return (toRemove.Count > 0); + } + + public bool RecalculateAllConstraints() + { + bool ret = false; + lock (m_constraints) + { + foreach (BSConstraint constrain in m_constraints) + { + constrain.CalculateTransforms(); + ret = true; + } + } + return ret; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSConstraintHinge.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSConstraintHinge.cs new file mode 100644 index 0000000000..7951f06b18 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSConstraintHinge.cs @@ -0,0 +1,57 @@ +/* + * 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; +using OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + +public sealed class BSConstraintHinge : BSConstraint +{ + public override ConstraintType Type { get { return ConstraintType.HINGE_CONSTRAINT_TYPE; } } + + public BSConstraintHinge(BulletSim world, BulletBody obj1, BulletBody obj2, + Vector3 pivotInA, Vector3 pivotInB, + Vector3 axisInA, Vector3 axisInB, + bool useLinearReferenceFrameA, bool disableCollisionsBetweenLinkedBodies) + { + m_world = world; + m_body1 = obj1; + m_body2 = obj2; + m_constraint = new BulletConstraint( + BulletSimAPI.CreateHingeConstraint2(m_world.ptr, m_body1.ptr, m_body2.ptr, + pivotInA, pivotInB, + axisInA, axisInB, + useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); + m_enabled = true; + } + +} + +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSDynamics.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSDynamics.cs new file mode 100644 index 0000000000..72afacc8f1 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSDynamics.cs @@ -0,0 +1,1374 @@ +/* + * 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.Region.Physics.Manager; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + public sealed class BSDynamics + { + private static string LogHeader = "[BULLETSIM VEHICLE]"; + + private BSScene PhysicsScene { get; set; } + // the prim this dynamic controller belongs to + private BSPrim Prim { get; set; } + + // mass of the vehicle fetched each time we're calles + private float m_vehicleMass; + + // Vehicle properties + public Vehicle Type { get; set; } + + // private Quaternion m_referenceFrame = Quaternion.Identity; // Axis modifier + private VehicleFlag m_flags = (VehicleFlag) 0; // Boolean settings: + // HOVER_TERRAIN_ONLY + // HOVER_GLOBAL_HEIGHT + // NO_DEFLECTION_UP + // HOVER_WATER_ONLY + // HOVER_UP_ONLY + // LIMIT_MOTOR_UP + // LIMIT_ROLL_ONLY + private Vector3 m_BlockingEndPoint = Vector3.Zero; + private Quaternion m_RollreferenceFrame = Quaternion.Identity; + private Quaternion m_referenceFrame = Quaternion.Identity; + + // Linear properties + private BSVMotor m_linearMotor = new BSVMotor("LinearMotor"); + private Vector3 m_linearMotorDirection = Vector3.Zero; // velocity requested by LSL, decayed by time + private Vector3 m_linearMotorOffset = Vector3.Zero; // the point of force can be offset from the center + private Vector3 m_linearMotorDirectionLASTSET = Vector3.Zero; // velocity requested by LSL + private Vector3 m_linearFrictionTimescale = Vector3.Zero; + private float m_linearMotorDecayTimescale = 0; + private float m_linearMotorTimescale = 0; + private Vector3 m_lastLinearVelocityVector = Vector3.Zero; + private Vector3 m_lastPositionVector = Vector3.Zero; + // private bool m_LinearMotorSetLastFrame = false; + // private Vector3 m_linearMotorOffset = Vector3.Zero; + + //Angular properties + private BSVMotor m_angularMotor = new BSVMotor("AngularMotor"); + private Vector3 m_angularMotorDirection = Vector3.Zero; // angular velocity requested by LSL motor + // private int m_angularMotorApply = 0; // application frame counter + private Vector3 m_angularMotorVelocity = Vector3.Zero; // current angular motor velocity + private float m_angularMotorTimescale = 0; // motor angular velocity ramp up rate + private float m_angularMotorDecayTimescale = 0; // motor angular velocity decay rate + private Vector3 m_angularFrictionTimescale = Vector3.Zero; // body angular velocity decay rate + private Vector3 m_lastAngularVelocity = Vector3.Zero; + private Vector3 m_lastVertAttractor = Vector3.Zero; // what VA was last applied to body + + //Deflection properties + private BSVMotor m_angularDeflectionMotor = new BSVMotor("AngularDeflection"); + private float m_angularDeflectionEfficiency = 0; + private float m_angularDeflectionTimescale = 0; + private float m_linearDeflectionEfficiency = 0; + private float m_linearDeflectionTimescale = 0; + + //Banking properties + private float m_bankingEfficiency = 0; + private float m_bankingMix = 0; + private float m_bankingTimescale = 0; + + //Hover and Buoyancy properties + private BSVMotor m_hoverMotor = new BSVMotor("Hover"); + private float m_VhoverHeight = 0f; + 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. + + //Attractor properties + private BSVMotor m_verticalAttractionMotor = new BSVMotor("VerticalAttraction"); + private float m_verticalAttractionEfficiency = 1.0f; // damped + private float m_verticalAttractionCutoff = 500f; // per the documentation + // Timescale > cutoff means no vert attractor. + private float m_verticalAttractionTimescale = 510f; + + // Just some recomputed constants: + static readonly float PIOverFour = ((float)Math.PI) / 4f; + static readonly float PIOverTwo = ((float)Math.PI) / 2f; + + public BSDynamics(BSScene myScene, BSPrim myPrim) + { + PhysicsScene = myScene; + Prim = myPrim; + Type = Vehicle.TYPE_NONE; + } + + // Return 'true' if this vehicle is doing vehicle things + public bool IsActive + { + get { return Type != Vehicle.TYPE_NONE && Prim.IsPhysical; } + } + + internal void ProcessFloatVehicleParam(Vehicle pParam, float pValue) + { + VDetailLog("{0},ProcessFloatVehicleParam,param={1},val={2}", Prim.LocalID, pParam, pValue); + switch (pParam) + { + case Vehicle.ANGULAR_DEFLECTION_EFFICIENCY: + m_angularDeflectionEfficiency = Math.Max(pValue, 0.01f); + break; + case Vehicle.ANGULAR_DEFLECTION_TIMESCALE: + m_angularDeflectionTimescale = Math.Max(pValue, 0.01f); + break; + case Vehicle.ANGULAR_MOTOR_DECAY_TIMESCALE: + m_angularMotorDecayTimescale = ClampInRange(0.01f, pValue, 120); + m_angularMotor.TargetValueDecayTimeScale = m_angularMotorDecayTimescale; + break; + case Vehicle.ANGULAR_MOTOR_TIMESCALE: + m_angularMotorTimescale = Math.Max(pValue, 0.01f); + m_angularMotor.TimeScale = m_angularMotorTimescale; + break; + case Vehicle.BANKING_EFFICIENCY: + m_bankingEfficiency = ClampInRange(-1f, pValue, 1f); + break; + case Vehicle.BANKING_MIX: + m_bankingMix = Math.Max(pValue, 0.01f); + break; + case Vehicle.BANKING_TIMESCALE: + m_bankingTimescale = Math.Max(pValue, 0.01f); + break; + case Vehicle.BUOYANCY: + m_VehicleBuoyancy = ClampInRange(-1f, pValue, 1f); + break; + case Vehicle.HOVER_EFFICIENCY: + m_VhoverEfficiency = ClampInRange(0f, pValue, 1f); + break; + case Vehicle.HOVER_HEIGHT: + m_VhoverHeight = pValue; + break; + case Vehicle.HOVER_TIMESCALE: + m_VhoverTimescale = Math.Max(pValue, 0.01f); + break; + case Vehicle.LINEAR_DEFLECTION_EFFICIENCY: + m_linearDeflectionEfficiency = Math.Max(pValue, 0.01f); + break; + case Vehicle.LINEAR_DEFLECTION_TIMESCALE: + m_linearDeflectionTimescale = Math.Max(pValue, 0.01f); + break; + case Vehicle.LINEAR_MOTOR_DECAY_TIMESCALE: + m_linearMotorDecayTimescale = ClampInRange(0.01f, pValue, 120); + m_linearMotor.TargetValueDecayTimeScale = m_linearMotorDecayTimescale; + break; + case Vehicle.LINEAR_MOTOR_TIMESCALE: + m_linearMotorTimescale = Math.Max(pValue, 0.01f); + m_linearMotor.TimeScale = m_linearMotorTimescale; + break; + case Vehicle.VERTICAL_ATTRACTION_EFFICIENCY: + m_verticalAttractionEfficiency = ClampInRange(0.1f, pValue, 1f); + m_verticalAttractionMotor.Efficiency = m_verticalAttractionEfficiency; + break; + case Vehicle.VERTICAL_ATTRACTION_TIMESCALE: + m_verticalAttractionTimescale = Math.Max(pValue, 0.01f); + m_verticalAttractionMotor.TimeScale = m_verticalAttractionTimescale; + break; + + // These are vector properties but the engine lets you use a single float value to + // 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.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); + m_linearMotorDirectionLASTSET = new Vector3(pValue, pValue, pValue); + m_linearMotor.SetTarget(m_linearMotorDirection); + break; + case Vehicle.LINEAR_MOTOR_OFFSET: + m_linearMotorOffset = new Vector3(pValue, pValue, pValue); + break; + + } + }//end ProcessFloatVehicleParam + + internal void ProcessVectorVehicleParam(Vehicle pParam, Vector3 pValue) + { + VDetailLog("{0},ProcessVectorVehicleParam,param={1},val={2}", Prim.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 + pValue.X = ClampInRange(-12.56f, pValue.X, 12.56f); + 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.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); + m_linearMotorDirectionLASTSET = new Vector3(pValue.X, pValue.Y, pValue.Z); + m_linearMotor.SetTarget(m_linearMotorDirection); + break; + case Vehicle.LINEAR_MOTOR_OFFSET: + m_linearMotorOffset = new Vector3(pValue.X, pValue.Y, pValue.Z); + break; + case Vehicle.BLOCK_EXIT: + m_BlockingEndPoint = new Vector3(pValue.X, pValue.Y, pValue.Z); + break; + } + }//end ProcessVectorVehicleParam + + internal void ProcessRotationVehicleParam(Vehicle pParam, Quaternion pValue) + { + VDetailLog("{0},ProcessRotationalVehicleParam,param={1},val={2}", Prim.LocalID, pParam, pValue); + switch (pParam) + { + case Vehicle.REFERENCE_FRAME: + m_referenceFrame = pValue; + break; + case Vehicle.ROLL_FRAME: + m_RollreferenceFrame = pValue; + break; + } + }//end ProcessRotationVehicleParam + + internal void ProcessVehicleFlags(int pParam, bool remove) + { + VDetailLog("{0},ProcessVehicleFlags,param={1},remove={2}", Prim.LocalID, pParam, remove); + VehicleFlag parm = (VehicleFlag)pParam; + if (pParam == -1) + m_flags = (VehicleFlag)0; + else + { + if (remove) + m_flags &= ~parm; + else + m_flags |= parm; + } + } + + internal void ProcessTypeChange(Vehicle pType) + { + VDetailLog("{0},ProcessTypeChange,type={1}", Prim.LocalID, pType); + // Set Defaults For Type + Type = pType; + switch (pType) + { + case Vehicle.TYPE_NONE: + m_linearMotorDirection = Vector3.Zero; + m_linearMotorTimescale = 0; + m_linearMotorDecayTimescale = 0; + m_linearFrictionTimescale = new Vector3(0, 0, 0); + + m_angularMotorDirection = Vector3.Zero; + m_angularMotorDecayTimescale = 0; + m_angularMotorTimescale = 0; + m_angularFrictionTimescale = new Vector3(0, 0, 0); + + m_VhoverHeight = 0; + m_VhoverEfficiency = 0; + m_VhoverTimescale = 0; + m_VehicleBuoyancy = 0; + + m_linearDeflectionEfficiency = 1; + m_linearDeflectionTimescale = 1; + + m_angularDeflectionEfficiency = 0; + m_angularDeflectionTimescale = 1000; + + m_verticalAttractionEfficiency = 0; + m_verticalAttractionTimescale = 0; + + m_bankingEfficiency = 0; + m_bankingTimescale = 1000; + m_bankingMix = 1; + + m_referenceFrame = Quaternion.Identity; + m_flags = (VehicleFlag)0; + + break; + + case Vehicle.TYPE_SLED: + m_linearMotorDirection = Vector3.Zero; + m_linearMotorTimescale = 1000; + m_linearMotorDecayTimescale = 120; + m_linearFrictionTimescale = new Vector3(30, 1, 1000); + + m_angularMotorDirection = Vector3.Zero; + m_angularMotorTimescale = 1000; + m_angularMotorDecayTimescale = 120; + m_angularFrictionTimescale = new Vector3(1000, 1000, 1000); + + m_VhoverHeight = 0; + m_VhoverEfficiency = 10; // TODO: this looks wrong!! + m_VhoverTimescale = 10; + m_VehicleBuoyancy = 0; + + m_linearDeflectionEfficiency = 1; + m_linearDeflectionTimescale = 1; + + m_angularDeflectionEfficiency = 1; + m_angularDeflectionTimescale = 1000; + + m_verticalAttractionEfficiency = 0; + m_verticalAttractionTimescale = 0; + + m_bankingEfficiency = 0; + m_bankingTimescale = 10; + m_bankingMix = 1; + + m_referenceFrame = Quaternion.Identity; + m_flags &= ~(VehicleFlag.HOVER_WATER_ONLY + | VehicleFlag.HOVER_TERRAIN_ONLY + | VehicleFlag.HOVER_GLOBAL_HEIGHT + | VehicleFlag.HOVER_UP_ONLY); + m_flags |= (VehicleFlag.NO_DEFLECTION_UP + | VehicleFlag.LIMIT_ROLL_ONLY + | VehicleFlag.LIMIT_MOTOR_UP); + + break; + case Vehicle.TYPE_CAR: + m_linearMotorDirection = Vector3.Zero; + m_linearMotorTimescale = 1; + m_linearMotorDecayTimescale = 60; + m_linearFrictionTimescale = new Vector3(100, 2, 1000); + + m_angularMotorDirection = Vector3.Zero; + m_angularMotorTimescale = 1; + m_angularMotorDecayTimescale = 0.8f; + m_angularFrictionTimescale = new Vector3(1000, 1000, 1000); + + m_VhoverHeight = 0; + m_VhoverEfficiency = 0; + m_VhoverTimescale = 1000; + m_VehicleBuoyancy = 0; + + m_linearDeflectionEfficiency = 1; + m_linearDeflectionTimescale = 2; + + m_angularDeflectionEfficiency = 0; + m_angularDeflectionTimescale = 10; + + m_verticalAttractionEfficiency = 1f; + m_verticalAttractionTimescale = 10f; + + m_bankingEfficiency = -0.2f; + m_bankingMix = 1; + m_bankingTimescale = 1; + + m_referenceFrame = Quaternion.Identity; + m_flags &= ~(VehicleFlag.HOVER_WATER_ONLY + | VehicleFlag.HOVER_TERRAIN_ONLY + | VehicleFlag.HOVER_GLOBAL_HEIGHT); + m_flags |= (VehicleFlag.NO_DEFLECTION_UP + | VehicleFlag.LIMIT_ROLL_ONLY + | VehicleFlag.LIMIT_MOTOR_UP + | VehicleFlag.HOVER_UP_ONLY); + break; + case Vehicle.TYPE_BOAT: + m_linearMotorDirection = Vector3.Zero; + m_linearMotorTimescale = 5; + m_linearMotorDecayTimescale = 60; + m_linearFrictionTimescale = new Vector3(10, 3, 2); + + m_angularMotorDirection = Vector3.Zero; + m_angularMotorTimescale = 4; + m_angularMotorDecayTimescale = 4; + m_angularFrictionTimescale = new Vector3(10,10,10); + + m_VhoverHeight = 0; + m_VhoverEfficiency = 0.5f; + m_VhoverTimescale = 2; + m_VehicleBuoyancy = 1; + + m_linearDeflectionEfficiency = 0.5f; + m_linearDeflectionTimescale = 3; + + m_angularDeflectionEfficiency = 0.5f; + m_angularDeflectionTimescale = 5; + + m_verticalAttractionEfficiency = 0.5f; + m_verticalAttractionTimescale = 5f; + + m_bankingEfficiency = -0.3f; + m_bankingMix = 0.8f; + m_bankingTimescale = 1; + + m_referenceFrame = Quaternion.Identity; + m_flags &= ~(VehicleFlag.HOVER_TERRAIN_ONLY + | VehicleFlag.HOVER_GLOBAL_HEIGHT + | VehicleFlag.LIMIT_ROLL_ONLY + | VehicleFlag.HOVER_UP_ONLY); + m_flags |= (VehicleFlag.NO_DEFLECTION_UP + | VehicleFlag.LIMIT_MOTOR_UP + | VehicleFlag.HOVER_WATER_ONLY); + break; + case Vehicle.TYPE_AIRPLANE: + m_linearMotorDirection = Vector3.Zero; + m_linearMotorTimescale = 2; + m_linearMotorDecayTimescale = 60; + m_linearFrictionTimescale = new Vector3(200, 10, 5); + + m_angularMotorDirection = Vector3.Zero; + m_angularMotorTimescale = 4; + m_angularMotorDecayTimescale = 4; + m_angularFrictionTimescale = new Vector3(20, 20, 20); + + m_VhoverHeight = 0; + m_VhoverEfficiency = 0.5f; + m_VhoverTimescale = 1000; + m_VehicleBuoyancy = 0; + + m_linearDeflectionEfficiency = 0.5f; + m_linearDeflectionTimescale = 3; + + m_angularDeflectionEfficiency = 1; + m_angularDeflectionTimescale = 2; + + m_verticalAttractionEfficiency = 0.9f; + m_verticalAttractionTimescale = 2f; + + m_bankingEfficiency = 1; + m_bankingMix = 0.7f; + m_bankingTimescale = 2; + + m_referenceFrame = Quaternion.Identity; + m_flags &= ~(VehicleFlag.HOVER_WATER_ONLY + | VehicleFlag.HOVER_TERRAIN_ONLY + | VehicleFlag.HOVER_GLOBAL_HEIGHT + | VehicleFlag.HOVER_UP_ONLY + | VehicleFlag.NO_DEFLECTION_UP + | VehicleFlag.LIMIT_MOTOR_UP); + m_flags |= (VehicleFlag.LIMIT_ROLL_ONLY); + break; + case Vehicle.TYPE_BALLOON: + m_linearMotorDirection = Vector3.Zero; + m_linearMotorTimescale = 5; + m_linearFrictionTimescale = new Vector3(5, 5, 5); + m_linearMotorDecayTimescale = 60; + + m_angularMotorDirection = Vector3.Zero; + m_angularMotorTimescale = 6; + m_angularFrictionTimescale = new Vector3(10, 10, 10); + m_angularMotorDecayTimescale = 10; + + m_VhoverHeight = 5; + m_VhoverEfficiency = 0.8f; + m_VhoverTimescale = 10; + m_VehicleBuoyancy = 1; + + m_linearDeflectionEfficiency = 0; + m_linearDeflectionTimescale = 5; + + m_angularDeflectionEfficiency = 0; + m_angularDeflectionTimescale = 5; + + m_verticalAttractionEfficiency = 1f; + m_verticalAttractionTimescale = 100f; + + m_bankingEfficiency = 0; + m_bankingMix = 0.7f; + m_bankingTimescale = 5; + + m_referenceFrame = Quaternion.Identity; + + m_referenceFrame = Quaternion.Identity; + m_flags &= ~(VehicleFlag.HOVER_WATER_ONLY + | VehicleFlag.HOVER_TERRAIN_ONLY + | VehicleFlag.HOVER_UP_ONLY + | VehicleFlag.NO_DEFLECTION_UP + | VehicleFlag.LIMIT_MOTOR_UP); + m_flags |= (VehicleFlag.LIMIT_ROLL_ONLY + | VehicleFlag.HOVER_GLOBAL_HEIGHT); + break; + } + + // Update any physical parameters based on this type. + Refresh(); + + 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_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) + } + + // Some of the properties of this prim may have changed. + // Do any updating needed for a vehicle + public void Refresh() + { + if (IsActive) + { + // Remember the mass so we don't have to fetch it every step + m_vehicleMass = Prim.Linkset.LinksetMass; + + // Friction affects are handled by this vehicle code + float friction = 0f; + BulletSimAPI.SetFriction2(Prim.PhysBody.ptr, friction); + + // 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; + BulletSimAPI.SetAngularDamping2(Prim.PhysBody.ptr, angularDamping); + + // Vehicles report collision events so we know when it's on the ground + BulletSimAPI.AddToCollisionFlags2(Prim.PhysBody.ptr, CollisionFlags.BS_VEHICLE_COLLISIONS); + + Vector3 localInertia = BulletSimAPI.CalculateLocalInertia2(Prim.PhysShape.ptr, m_vehicleMass); + BulletSimAPI.SetMassProps2(Prim.PhysBody.ptr, m_vehicleMass, localInertia); + BulletSimAPI.UpdateInertiaTensor2(Prim.PhysBody.ptr); + + VDetailLog("{0},BSDynamics.Refresh,mass={1},frict={2},inert={3},aDamp={4}", + Prim.LocalID, m_vehicleMass, friction, localInertia, angularDamping); + } + else + { + BulletSimAPI.RemoveFromCollisionFlags2(Prim.PhysBody.ptr, CollisionFlags.BS_VEHICLE_COLLISIONS); + } + } + + public bool RemoveBodyDependencies(BSPhysObject prim) + { + // 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(); + } + + #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. + // Changing is remembered and the parameter is stored back into the physics engine only if updated. + // This does two things: 1) saves continuious calls into unmanaged code, and + // 2) signals when a physics property update must happen back to the simulator + // to update values modified for the vehicle. + private int m_knownChanged; + private int m_knownHas; + private float m_knownTerrainHeight; + private float m_knownWaterLevel; + private Vector3 m_knownPosition; + private Vector3 m_knownVelocity; + private Vector3 m_knownForce; + private Quaternion m_knownOrientation; + private Vector3 m_knownRotationalVelocity; + private Vector3 m_knownRotationalForce; + 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 void ForgetKnownVehicleProperties() + { + m_knownHas = 0; + m_knownChanged = 0; + } + // Push all the changed values back into the physics engine + private void PushKnownChanged() + { + if (m_knownChanged != 0) + { + if ((m_knownChanged & m_knownChangedPosition) != 0) + Prim.ForcePosition = m_knownPosition; + + if ((m_knownChanged & m_knownChangedOrientation) != 0) + Prim.ForceOrientation = m_knownOrientation; + + if ((m_knownChanged & m_knownChangedVelocity) != 0) + { + Prim.ForceVelocity = m_knownVelocity; + BulletSimAPI.SetInterpolationLinearVelocity2(Prim.PhysBody.ptr, VehicleVelocity); + } + + if ((m_knownChanged & m_knownChangedForce) != 0) + Prim.AddForce((Vector3)m_knownForce, false, true); + + 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. + BulletSimAPI.SetInterpolationAngularVelocity2(Prim.PhysBody.ptr, m_knownRotationalVelocity); + } + + if ((m_knownChanged & m_knownChangedRotationalForce) != 0) + Prim.AddAngularForce((Vector3)m_knownRotationalForce, false, true); + + // 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. + BulletSimAPI.PushUpdate2(Prim.PhysBody.ptr); + } + 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. + private float GetTerrainHeight(Vector3 pos) + { + if ((m_knownHas & m_knownChangedTerrainHeight) == 0) + { + m_knownTerrainHeight = Prim.PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(pos); + m_knownHas |= m_knownChangedTerrainHeight; + } + return m_knownTerrainHeight; + } + + // Since the computation of water level can be a little involved, this routine + // is used ot fetch the level only once for each vehicle simulation step. + private float GetWaterLevel(Vector3 pos) + { + if ((m_knownHas & m_knownChangedWaterLevel) == 0) + { + m_knownWaterLevel = Prim.PhysicsScene.TerrainManager.GetWaterLevelAtXYZ(pos); + m_knownHas |= m_knownChangedWaterLevel; + } + return (float)m_knownWaterLevel; + } + + private Vector3 VehiclePosition + { + get + { + if ((m_knownHas & m_knownChangedPosition) == 0) + { + m_knownPosition = Prim.ForcePosition; + m_knownHas |= m_knownChangedPosition; + } + return m_knownPosition; + } + set + { + m_knownPosition = value; + m_knownChanged |= m_knownChangedPosition; + m_knownHas |= m_knownChangedPosition; + } + } + + private Quaternion VehicleOrientation + { + get + { + if ((m_knownHas & m_knownChangedOrientation) == 0) + { + m_knownOrientation = Prim.ForceOrientation; + m_knownHas |= m_knownChangedOrientation; + } + return m_knownOrientation; + } + set + { + m_knownOrientation = value; + m_knownChanged |= m_knownChangedOrientation; + m_knownHas |= m_knownChangedOrientation; + } + } + + private Vector3 VehicleVelocity + { + get + { + if ((m_knownHas & m_knownChangedVelocity) == 0) + { + m_knownVelocity = Prim.ForceVelocity; + m_knownHas |= m_knownChangedVelocity; + } + return (Vector3)m_knownVelocity; + } + set + { + m_knownVelocity = value; + m_knownChanged |= m_knownChangedVelocity; + m_knownHas |= m_knownChangedVelocity; + } + } + + private void VehicleAddForce(Vector3 aForce) + { + if ((m_knownHas & m_knownChangedForce) == 0) + { + m_knownForce = Vector3.Zero; + } + m_knownForce += aForce; + m_knownChanged |= m_knownChangedForce; + m_knownHas |= m_knownChangedForce; + } + + private Vector3 VehicleRotationalVelocity + { + get + { + if ((m_knownHas & m_knownChangedRotationalVelocity) == 0) + { + m_knownRotationalVelocity = Prim.ForceRotationalVelocity; + m_knownHas |= m_knownChangedRotationalVelocity; + } + return (Vector3)m_knownRotationalVelocity; + } + set + { + m_knownRotationalVelocity = value; + m_knownChanged |= m_knownChangedRotationalVelocity; + m_knownHas |= m_knownChangedRotationalVelocity; + } + } + private void VehicleAddAngularForce(Vector3 aForce) + { + if ((m_knownHas & m_knownChangedRotationalForce) == 0) + { + m_knownRotationalForce = Vector3.Zero; + } + m_knownRotationalForce += aForce; + m_knownChanged |= m_knownChangedRotationalForce; + m_knownHas |= m_knownChangedRotationalForce; + } + // Vehicle relative forward velocity + private Vector3 VehicleForwardVelocity + { + get + { + if ((m_knownHas & m_knownChangedForwardVelocity) == 0) + { + m_knownForwardVelocity = VehicleVelocity * Quaternion.Inverse(Quaternion.Normalize(VehicleOrientation)); + m_knownHas |= m_knownChangedForwardVelocity; + } + return m_knownForwardVelocity; + } + } + private float VehicleForwardSpeed + { + get + { + return VehicleForwardVelocity.X; + } + } + + #endregion // Known vehicle value functions + + // One step of the vehicle properties for the next 'pTimestep' seconds. + internal void Step(float pTimestep) + { + if (!IsActive) return; + + ForgetKnownVehicleProperties(); + + MoveLinear(pTimestep); + MoveAngular(pTimestep); + + LimitRotation(pTimestep); + + // remember the position so next step we can limit absolute movement effects + m_lastPositionVector = VehiclePosition; + + // If we forced the changing of some vehicle parameters, update the values and + // for the physics engine to note the changes so an UpdateProperties event will happen. + PushKnownChanged(); + + VDetailLog("{0},BSDynamics.Step,done,pos={1},force={2},velocity={3},angvel={4}", + Prim.LocalID, VehiclePosition, Prim.Force, VehicleVelocity, VehicleRotationalVelocity); + } + + // 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); + + // The movement computed in the linear motor is relative to the vehicle + // coordinates. Rotate the movement to world coordinates. + linearMotorContribution *= VehicleOrientation; + + // ================================================================== + // 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); + + ComputeLinearBlockingEndPoint(pTimestep); + + Vector3 limitMotorUpContribution = ComputeLinearMotorUp(pTimestep); + + // ================================================================== + Vector3 newVelocity = linearMotorContribution + + terrainHeightContribution + + hoverContribution + + limitMotorUpContribution; + + Vector3 newForce = buoyancyContribution; + + // 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; + + // ================================================================== + // Clamp high or low velocities + float newVelocityLengthSq = newVelocity.LengthSquared(); + if (newVelocityLengthSq > 1000f) + { + newVelocity /= newVelocity.Length(); + newVelocity *= 1000f; + } + else if (newVelocityLengthSq < 0.001f) + newVelocity = 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 + ); + + } // end MoveLinear() + + public Vector3 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. + Vector3 newPosition = VehiclePosition; + newPosition.Z = GetTerrainHeight(VehiclePosition) + 1f; + VehiclePosition = newPosition; + VDetailLog("{0}, MoveLinear,terrainHeight,terrainHeight={1},pos={2}", + Prim.LocalID, GetTerrainHeight(VehiclePosition), VehiclePosition); + } + return ret; + } + + public Vector3 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) + { + // We should hover, get the target height + if ((m_flags & VehicleFlag.HOVER_WATER_ONLY) != 0) + { + m_VhoverTargetHeight = GetWaterLevel(VehiclePosition) + m_VhoverHeight; + } + if ((m_flags & VehicleFlag.HOVER_TERRAIN_ONLY) != 0) + { + m_VhoverTargetHeight = GetTerrainHeight(VehiclePosition) + m_VhoverHeight; + } + if ((m_flags & VehicleFlag.HOVER_GLOBAL_HEIGHT) != 0) + { + m_VhoverTargetHeight = m_VhoverHeight; + } + + if ((m_flags & VehicleFlag.HOVER_UP_ONLY) != 0) + { + // If body is already heigher, use its height as target height + 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) + { + Vector3 pos = VehiclePosition; + pos.Z = m_VhoverTargetHeight; + VehiclePosition = pos; + } + } + else + { + // Error is positive if below the target and negative if above. + float verticalError = m_VhoverTargetHeight - VehiclePosition.Z; + float verticalCorrectionVelocity = verticalError / m_VhoverTimescale; + + // TODO: implement m_VhoverEfficiency correctly + if (Math.Abs(verticalError) > m_VhoverEfficiency) + { + ret = new Vector3(0f, 0f, verticalCorrectionVelocity); + } + } + + 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) + { + bool changed = false; + + Vector3 pos = VehiclePosition; + Vector3 posChange = pos - m_lastPositionVector; + if (m_BlockingEndPoint != Vector3.Zero) + { + if (pos.X >= (m_BlockingEndPoint.X - (float)1)) + { + pos.X -= posChange.X + 1; + changed = true; + } + if (pos.Y >= (m_BlockingEndPoint.Y - (float)1)) + { + pos.Y -= posChange.Y + 1; + changed = true; + } + if (pos.Z >= (m_BlockingEndPoint.Z - (float)1)) + { + pos.Z -= posChange.Z + 1; + changed = true; + } + if (pos.X <= 0) + { + pos.X += posChange.X + 1; + changed = true; + } + if (pos.Y <= 0) + { + pos.Y += posChange.Y + 1; + changed = true; + } + if (changed) + { + VehiclePosition = pos; + VDetailLog("{0}, MoveLinear,blockingEndPoint,block={1},origPos={2},pos={3}", + Prim.LocalID, m_BlockingEndPoint, posChange, pos); + } + } + return changed; + } + + // From http://wiki.secondlife.com/wiki/LlSetVehicleFlags : + // Prevent ground vehicles from motoring into the sky. This flag has a subtle effect when + // 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. + // 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) + { + Vector3 ret = Vector3.Zero; + float distanceAboveGround = 0f; + + if ((m_flags & (VehicleFlag.LIMIT_MOTOR_UP)) != 0) + { + 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); + } + // 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}", + Prim.LocalID, distanceAboveGround, Prim.IsColliding, ret); + return ret; + } + + // ======================================================================= + // ======================================================================= + // Apply the effect of the angular motor. + // The 'contribution' is how much angular correction velocity each function wants. + // All the contributions are added together and the resulting velocity is + // 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); + + // ================================================================== + // 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 + { + // The vehicle is not adding anything angular wise. + VehicleRotationalVelocity = Vector3.Zero; + VDetailLog("{0}, MoveAngular,done,zero", Prim.LocalID); + } + + // ================================================================== + //Offset section + if (m_linearMotorOffset != Vector3.Zero) + { + //Offset of linear velocity doesn't change the linear velocity, + // but causes a torque to be applied, for example... + // + // IIIII >>> IIIII + // IIIII >>> IIIII + // IIIII >>> IIIII + // ^ + // | Applying a force at the arrow will cause the object to move forward, but also rotate + // + // + // The torque created is the linear velocity crossed with the offset + + // TODO: this computation should be in the linear section + // because that is where we know the impulse being applied. + Vector3 torqueFromOffset = Vector3.Zero; + // torqueFromOffset = Vector3.Cross(m_linearMotorOffset, appliedImpulse); + if (float.IsNaN(torqueFromOffset.X)) + torqueFromOffset.X = 0; + if (float.IsNaN(torqueFromOffset.Y)) + torqueFromOffset.Y = 0; + if (float.IsNaN(torqueFromOffset.Z)) + torqueFromOffset.Z = 0; + + VehicleAddAngularForce(torqueFromOffset * m_vehicleMass); + VDetailLog("{0}, BSDynamic.MoveAngular,motorOffset,applyTorqueImpulse={1}", Prim.LocalID, torqueFromOffset); + } + + } + // 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 + // the world z-axis (a.k.a. "up"). To take advantage of this feature you would set the + // VEHICLE_VERTICAL_ATTRACTION_TIMESCALE to control the period of the spring frequency, + // 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() + { + Vector3 ret = Vector3.Zero; + + // If vertical attaction timescale is reasonable + if (m_verticalAttractionTimescale < m_verticalAttractionCutoff) + { + // Take a vector pointing up and convert it from world to vehicle relative coords. + Vector3 verticalError = Vector3.UnitZ * VehicleOrientation; + + // If vertical attraction correction is needed, the vector that was pointing up (UnitZ) + // is now: + // leaning to one side: rotated around the X axis with the Y value going + // from zero (nearly straight up) to one (completely to the side)) or + // leaning front-to-back: rotated around the Y axis with the value of X being between + // zero and one. + // The value of Z is how far the rotation is off with 1 meaning none and 0 being 90 degrees. + + // 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); + // (Tilt forward (positive X) needs to tilt back (rotate negative) around Y axis.) + ret.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; + } + + // 'ret' is now the necessary velocity to correct tilt in one second. + // Correction happens over a number of seconds. + Vector3 unscaledContrib = ret; + ret /= m_verticalAttractionTimescale; + + VDetailLog("{0}, MoveAngular,verticalAttraction,,verticalError={1},unscaled={2},eff={3},ts={4},vertAttr={5}", + Prim.LocalID, verticalError, unscaledContrib, m_verticalAttractionEfficiency, m_verticalAttractionTimescale, ret); + } + return ret; + } + + // Return the 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() + { + 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) + { + // The direction the vehicle is moving + Vector3 movingDirection = VehicleVelocity; + movingDirection.Normalize(); + + // The direction the vehicle is pointing + Vector3 pointingDirection = Vector3.UnitX * VehicleOrientation; + pointingDirection.Normalize(); + + // The difference between what is and what should be. + Vector3 deflectionError = movingDirection - pointingDirection; + + // Don't try to correct very large errors (not our job) + 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; + + // ret = m_angularDeflectionCorrectionMotor(1f, deflectionError); + + // Scale the correction by recovery timescale and efficiency + ret = (-deflectionError) * m_angularDeflectionEfficiency; + ret /= m_angularDeflectionTimescale; + + VDetailLog("{0}, MoveAngular,Deflection,movingDir={1},pointingDir={2},deflectError={3},ret={4}", + Prim.LocalID, movingDirection, pointingDirection, deflectionError, ret); + VDetailLog("{0}, MoveAngular,Deflection,fwdSpd={1},defEff={2},defTS={3}", + Prim.LocalID, VehicleForwardSpeed, m_angularDeflectionEfficiency, m_angularDeflectionTimescale); + } + return ret; + } + + // Return an 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 + // function. The way banking works is this: a rotation around the vehicle's roll-axis will + // 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. + // 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?). + // 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 + // it to turn while banking, however video-game motorcycles are often configured + // to turn in place when at a dead stop--because they are often easier to control + // that way using the limited interface of the keyboard or game controller. The + // VEHICLE_BANKING_MIX enables combinations of both realistic and non-realistic + // banking by functioning as a slider between a banking that is correspondingly + // 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. + // 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() + { + Vector3 ret = Vector3.Zero; + + if (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 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; + + // actual error = static turn error + dynamic turn error + float mixedBankingError = bankingTurnForce * (1f - m_bankingMix) + bankingTurnForce * m_bankingMix * VehicleForwardSpeed; + // TODO: the banking effect should not go to infinity but what to limit it to? + mixedBankingError = ClampInRange(-20f, mixedBankingError, 20f); + + // Build the force vector to change rotation from what it is to what it should be + ret.Z = -mixedBankingError; + + // Don't do it all at once. + ret /= m_bankingTimescale; + + VDetailLog("{0}, MoveAngular,Banking,rollComp={1},speed={2},turnComp={3},bankErr={4},mixedBankErr={5},ret={6}", + Prim.LocalID, rollComponents, VehicleForwardSpeed, turnComponent, bankingTurnForce, mixedBankingError, ret); + } + return ret; + } + + // This is from previous instantiations of XXXDynamics.cs. + // Applies roll reference frame. + // TODO: is this the right way to separate the code to do this operation? + // Should this be in MoveAngular()? + internal void LimitRotation(float timestep) + { + Quaternion rotq = VehicleOrientation; + Quaternion m_rot = rotq; + if (m_RollreferenceFrame != Quaternion.Identity) + { + if (rotq.X >= m_RollreferenceFrame.X) + { + m_rot.X = rotq.X - (m_RollreferenceFrame.X / 2); + } + if (rotq.Y >= m_RollreferenceFrame.Y) + { + m_rot.Y = rotq.Y - (m_RollreferenceFrame.Y / 2); + } + if (rotq.X <= -m_RollreferenceFrame.X) + { + m_rot.X = rotq.X + (m_RollreferenceFrame.X / 2); + } + if (rotq.Y <= -m_RollreferenceFrame.Y) + { + m_rot.Y = rotq.Y + (m_RollreferenceFrame.Y / 2); + } + } + if ((m_flags & VehicleFlag.LOCK_ROTATION) != 0) + { + m_rot.X = 0; + m_rot.Y = 0; + } + if (rotq != m_rot) + { + VehicleOrientation = m_rot; + VDetailLog("{0}, LimitRotation,done,orig={1},new={2}", Prim.LocalID, rotq, m_rot); + } + + } + + private float ClampInRange(float low, float val, float high) + { + return Math.Max(low, Math.Min(val, high)); + // return Utils.Clamp(val, low, high); + } + + // 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); + } + } +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSLinkset.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSLinkset.cs new file mode 100644 index 0000000000..253128b4ba --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSLinkset.cs @@ -0,0 +1,333 @@ +/* + * 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; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + +// A BSPrim can get individual information about its linkedness attached +// to it through an instance of a subclass of LinksetInfo. +// Each type of linkset will define the information needed for its type. +public abstract class BSLinksetInfo +{ + public virtual void Clear() { } +} + +public abstract class BSLinkset +{ + // private static string LogHeader = "[BULLETSIM LINKSET]"; + + public enum LinksetImplementation + { + Constraint = 0, // linkset tied together with constraints + Compound = 1, // linkset tied together as a compound object + 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) + { + BSLinkset ret = null; + + switch ((int)BSParam.LinksetImplementation) + { + case (int)LinksetImplementation.Constraint: + ret = new BSLinksetConstraints(physScene, parent); + break; + case (int)LinksetImplementation.Compound: + ret = new BSLinksetCompound(physScene, parent); + break; + case (int)LinksetImplementation.Manual: + // ret = new BSLinksetManual(physScene, parent); + break; + default: + ret = new BSLinksetCompound(physScene, parent); + break; + } + return ret; + } + + public BSPhysObject LinksetRoot { get; protected set; } + + public BSScene 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; + + // 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 + protected float m_mass; + public float LinksetMass + { + get + { + return m_mass; + } + } + + public virtual bool LinksetIsColliding { get { return false; } } + + public OMV.Vector3 CenterOfMass + { + get { return ComputeLinksetCenterOfMass(); } + } + + public OMV.Vector3 GeometricCenter + { + get { return ComputeLinksetGeometricCenter(); } + } + + protected BSLinkset(BSScene scene, BSPhysObject 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; + LinksetRoot = parent; + m_children = new HashSet(); + m_mass = parent.RawMass; + Rebuilding = false; + } + + // 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) + { + lock (m_linksetActivityLock) + { + // Don't add the root to its own linkset + if (!IsRoot(child)) + AddChildToLinkset(child); + m_mass = ComputeLinksetMass(); + } + return this; + } + + // Remove a child from a linkset. + // 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) + { + lock (m_linksetActivityLock) + { + if (IsRoot(child)) + { + // Cannot remove the root from a linkset. + return this; + } + RemoveChildFromLinkset(child); + m_mass = ComputeLinksetMass(); + } + + // The child is down to a linkset of just itself + return BSLinkset.Factory(PhysicsScene, child); + } + + // Return 'true' if the passed object is the root object of this linkset + public bool IsRoot(BSPhysObject requestor) + { + return (requestor.LocalID == LinksetRoot.LocalID); + } + + public int NumberOfChildren { get { return m_children.Count; } } + + // Return 'true' if this linkset has any children (more than the root member) + public bool HasAnyChildren { get { return (m_children.Count > 0); } } + + // Return 'true' if this child is in this linkset + public bool HasChild(BSPhysObject 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) + { + if (child.LocalID == bp.LocalID) + { + ret = true; + break; + } + } + */ + } + return ret; + } + + // 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 virtual bool ForEachMember(ForEachMemberAction action) + { + bool ret = false; + lock (m_linksetActivityLock) + { + action(LinksetRoot); + foreach (BSPhysObject po in m_children) + { + if (action(po)) + break; + } + } + return ret; + } + + // 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); + + // 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); + + // When physical properties are changed the linkset needs to recalculate + // its internal properties. + // May be called at runtime or taint-time. + public abstract void Refresh(BSPhysObject requestor); + + // Flag denoting the linkset is in the process of being rebuilt. + // Used to know not the schedule a rebuild in the middle of a rebuild. + protected bool Rebuilding { get; set; } + + // The object is going dynamic (physical). Do any setup necessary + // for a dynamic linkset. + // Only the state of the passed object can be modified. The rest of the linkset + // 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); + + // 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); + + // 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); + + // 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); + + // ================================================================ + protected virtual float ComputeLinksetMass() + { + float mass = LinksetRoot.RawMass; + if (HasAnyChildren) + { + lock (m_linksetActivityLock) + { + foreach (BSPhysObject bp in m_children) + { + mass += bp.RawMass; + } + } + } + return mass; + } + + protected virtual OMV.Vector3 ComputeLinksetCenterOfMass() + { + OMV.Vector3 com; + lock (m_linksetActivityLock) + { + com = LinksetRoot.Position * LinksetRoot.RawMass; + float totalMass = LinksetRoot.RawMass; + + foreach (BSPhysObject bp in m_children) + { + com += bp.Position * bp.RawMass; + totalMass += bp.RawMass; + } + if (totalMass != 0f) + com /= totalMass; + } + + return com; + } + + protected virtual OMV.Vector3 ComputeLinksetGeometricCenter() + { + OMV.Vector3 com; + lock (m_linksetActivityLock) + { + com = LinksetRoot.Position; + + foreach (BSPhysObject bp in m_children) + { + com += bp.Position * bp.RawMass; + } + com /= (m_children.Count + 1); + } + + return com; + } + + // 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); + } + +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSLinksetCompound.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSLinksetCompound.cs new file mode 100644 index 0000000000..23a0b8bbf6 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSLinksetCompound.cs @@ -0,0 +1,396 @@ +/* + * 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; + +using OpenSim.Framework; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + +// 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 OMV.Quaternion OffsetRot; + public BSLinksetCompoundInfo(OMV.Vector3 p, OMV.Quaternion r) + { + OffsetPos = p; + OffsetRot = r; + } + public override void Clear() + { + OffsetPos = 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) + { + } + + // 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) + { + // Something changed so do the rebuilding thing + // ScheduleRebuild(); + } + + // Schedule a refresh to happen after all the other taint processing. + private void ScheduleRebuild(BSPhysObject requestor) + { + DetailLog("{0},BSLinksetCompound.Refresh,schedulingRefresh,rebuilding={1}", + requestor.LocalID, Rebuilding); + // When rebuilding, it is possible to set properties that would normally require a rebuild. + // If already rebuilding, don't request another rebuild. + if (!Rebuilding) + { + PhysicsScene.PostTaintObject("BSLinksetCompound.Refresh", LinksetRoot.LocalID, delegate() + { + if (HasAnyChildren) + RecomputeLinksetCompound(); + }); + } + } + + // The object is going dynamic (physical). Do any setup necessary + // for a dynamic linkset. + // Only the state of the passed object can be modified. The rest of the linkset + // 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) + { + bool ret = false; + DetailLog("{0},BSLinksetCompound.MakeDynamic,call,IsRoot={1}", child.LocalID, IsRoot(child)); + if (IsRoot(child)) + { + // The root is going dynamic. Make sure mass is properly set. + m_mass = ComputeLinksetMass(); + ScheduleRebuild(LinksetRoot); + } + else + { + // The origional prims are removed from the world as the shape of the root compound + // shape takes over. + BulletSimAPI.AddToCollisionFlags2(child.PhysBody.ptr, CollisionFlags.CF_NO_CONTACT_RESPONSE); + BulletSimAPI.ForceActivationState2(child.PhysBody.ptr, ActivationState.DISABLE_SIMULATION); + // We don't want collisions from the old linkset children. + BulletSimAPI.RemoveFromCollisionFlags2(child.PhysBody.ptr, 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. + // 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) + { + bool ret = false; + DetailLog("{0},BSLinksetCompound.MakeStatic,call,IsRoot={1}", child.LocalID, IsRoot(child)); + if (IsRoot(child)) + { + ScheduleRebuild(LinksetRoot); + } + else + { + // The non-physical children can come back to life. + BulletSimAPI.RemoveFromCollisionFlags2(child.PhysBody.ptr, CollisionFlags.CF_NO_CONTACT_RESPONSE); + + child.PhysBody.collisionType = CollisionType.LinksetChild; + + // Don't force activation so setting of DISABLE_SIMULATION can stay if used. + BulletSimAPI.Activate2(child.PhysBody.ptr, false); + ret = true; + } + return ret; + } + + public override void UpdateProperties(BSPhysObject updated, bool physicalUpdate) + { + // 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 + // 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)) + { + updated.LinksetInfo = null; + 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. + // Returns 'true' of something was actually removed and would need restoring + // Called at taint-time!! + public override bool RemoveBodyDependencies(BSPrim child) + { + bool ret = false; + + DetailLog("{0},BSLinksetCompound.RemoveBodyDependencies,refreshIfChild,rID={1},rBody={2},isRoot={3}", + child.LocalID, LinksetRoot.LocalID, LinksetRoot.PhysBody.ptr.ToString(), 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); + + 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) + { + if (!HasChild(child)) + { + m_children.Add(child); + + DetailLog("{0},BSLinksetCompound.AddChildToLinkset,call,child={1}", LinksetRoot.LocalID, child.LocalID); + + // Rebuild the compound shape with the new child shape included + ScheduleRebuild(child); + } + return; + } + + // 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) + { + if (m_children.Remove(child)) + { + DetailLog("{0},BSLinksetCompound.RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}", + child.LocalID, + LinksetRoot.LocalID, LinksetRoot.PhysBody.ptr.ToString(), + child.LocalID, child.PhysBody.ptr.ToString()); + + // Cause the child's body to be rebuilt and thus restored to normal operation + RecomputeChildWorldPosition(child, false); + child.ForceBodyShapeRebuild(false); + + if (!HasAnyChildren) + { + // The linkset is now empty. The root needs rebuilding. + LinksetRoot.ForceBodyShapeRebuild(false); + } + else + { + // Rebuild the compound shape with the child removed + ScheduleRebuild(child); + } + } + return; + } + + // Called before the simulation step to make sure the compound based linkset + // is all initialized. + // 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 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 + 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) + { + 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; + BulletSimAPI.AddChildShapeToCompoundShape2(LinksetRoot.PhysShape.ptr, newShape.ptr, 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); + } + BulletSimAPI.AddChildShapeToCompoundShape2(LinksetRoot.PhysShape.ptr, cPrim.PhysShape.ptr, lci.OffsetPos, lci.OffsetRot); + } + } + return false; // 'false' says to move onto the next child in the list + }); + + // With all of the linkset packed into the root prim, it has the mass of everyone. + float linksetMass = LinksetMass; + LinksetRoot.UpdatePhysicalMassProperties(linksetMass); + } + finally + { + Rebuilding = false; + } + + BulletSimAPI.RecalculateCompoundShapeLocalAabb2(LinksetRoot.PhysShape.ptr); + + // DEBUG: see of inter-linkset collisions are causing problems for constraint linksets. + // BulletSimAPI.SetCollisionFilterMask2(LinksetRoot.BSBody.ptr, + // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); + + } +} +} \ No newline at end of file diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSLinksetConstraints.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSLinksetConstraints.cs new file mode 100644 index 0000000000..5757e64e78 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSLinksetConstraints.cs @@ -0,0 +1,314 @@ +/* + * 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; + +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public sealed class BSLinksetConstraints : BSLinkset +{ + // private static string LogHeader = "[BULLETSIM LINKSET CONSTRAINTS]"; + + public BSLinksetConstraints(BSScene scene, BSPhysObject parent) : base(scene, parent) + { + } + + // When physical properties are changed the linkset needs to recalculate + // 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) + { + // Queue to happen after all the other taint processing + PhysicsScene.PostTaintObject("BSLinksetContraints.Refresh", requestor.LocalID, delegate() + { + if (HasAnyChildren && IsRoot(requestor)) + RecomputeLinksetConstraints(); + }); + } + + // The object is going dynamic (physical). Do any setup necessary + // for a dynamic linkset. + // Only the state of the passed object can be modified. The rest of the linkset + // 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) + { + // What is done for each object in BSPrim is what we want. + return false; + } + + // 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. + // 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) + { + // 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) + { + // Nothing to do for constraints on property updates + } + + // Routine called when rebuilding the body of some member of the linkset. + // Destroy all the constraints have have been made to root and set + // 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) + { + bool ret = false; + + DetailLog("{0},BSLinksetConstraint.RemoveBodyDependencies,removeChildrenForRoot,rID={1},rBody={2}", + child.LocalID, LinksetRoot.LocalID, LinksetRoot.PhysBody.ptr.ToString()); + + lock (m_linksetActivityLock) + { + // Just undo all the constraints for this linkset. Rebuild at the end of the step. + ret = PhysicallyUnlinkAllChildrenFromRoot(LinksetRoot); + // Cause the constraints, et al to be rebuilt before the next simulation step. + Refresh(LinksetRoot); + } + 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) + { + if (!HasChild(child)) + { + m_children.Add(child); + + DetailLog("{0},BSLinksetConstraints.AddChildToLinkset,call,child={1}", LinksetRoot.LocalID, child.LocalID); + + // Cause constraints and assorted properties to be recomputed before the next simulation step. + Refresh(LinksetRoot); + } + return; + } + + // 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) + { + if (m_children.Remove(child)) + { + BSPhysObject rootx = LinksetRoot; // capture the root and body as of now + BSPhysObject childx = child; + + DetailLog("{0},BSLinksetConstraints.RemoveChildFromLinkset,call,rID={1},rBody={2},cID={3},cBody={4}", + childx.LocalID, + rootx.LocalID, rootx.PhysBody.ptr.ToString(), + childx.LocalID, childx.PhysBody.ptr.ToString()); + + PhysicsScene.TaintedObject("BSLinksetConstraints.RemoveChildFromLinkset", delegate() + { + PhysicallyUnlinkAChildFromRoot(rootx, childx); + }); + // See that the linkset parameters are recomputed at the end of the taint time. + Refresh(LinksetRoot); + } + else + { + // Non-fatal occurance. + // PhysicsScene.Logger.ErrorFormat("{0}: Asked to remove child from linkset that was not in linkset", LogHeader); + } + return; + } + + // 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) + { + // 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) + { + // Zero motion for children so they don't interpolate + childPrim.ZeroMotion(true); + + // Relative position normalized to the root prim + // Essentually a vector pointing from center of rootPrim to center of childPrim + OMV.Vector3 childRelativePosition = childPrim.Position - rootPrim.Position; + + // real world coordinate of midpoint between the two objects + OMV.Vector3 midPoint = rootPrim.Position + (childRelativePosition / 2); + + DetailLog("{0},BSLinksetConstraint.BuildConstraint,taint,root={1},rBody={2},child={3},cBody={4},rLoc={5},cLoc={6},midLoc={7}", + rootPrim.LocalID, + rootPrim.LocalID, rootPrim.PhysBody.ptr.ToString(), + childPrim.LocalID, childPrim.PhysBody.ptr.ToString(), + rootPrim.Position, childPrim.Position, midPoint); + + // create a constraint that allows no freedom of movement between the two objects + // http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=4818 + + BSConstraint6Dof constrain = new BSConstraint6Dof( + 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. + * Using the midpoint is easier since it lets the Bullet code manipulate the transforms + * of the objects. + * Code left for future programmers. + // ================================================================================== + // relative position normalized to the root prim + OMV.Quaternion invThisOrientation = OMV.Quaternion.Inverse(rootPrim.Orientation); + OMV.Vector3 childRelativePosition = (childPrim.Position - rootPrim.Position) * invThisOrientation; + + // relative rotation of the child to the parent + OMV.Quaternion childRelativeRotation = invThisOrientation * childPrim.Orientation; + OMV.Quaternion inverseChildRelativeRotation = OMV.Quaternion.Inverse(childRelativeRotation); + + DetailLog("{0},BSLinksetConstraint.PhysicallyLinkAChildToRoot,taint,root={1},child={2}", rootPrim.LocalID, rootPrim.LocalID, childPrim.LocalID); + BS6DofConstraint constrain = new BS6DofConstraint( + PhysicsScene.World, rootPrim.Body, childPrim.Body, + OMV.Vector3.Zero, + OMV.Quaternion.Inverse(rootPrim.Orientation), + OMV.Vector3.Zero, + OMV.Quaternion.Inverse(childPrim.Orientation), + true, + true + ); + // ================================================================================== + */ + + 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), + BSParam.LinkConstraintTransMotorMaxVel, + BSParam.LinkConstraintTransMotorMaxForce); + constrain.SetCFMAndERP(BSParam.LinkConstraintCFM, BSParam.LinkConstraintERP); + if (BSParam.LinkConstraintSolverIterations != 0f) + { + constrain.SetSolverIterations(BSParam.LinkConstraintSolverIterations); + } + return constrain; + } + + // Remove linkage between the linkset root and a particular child + // 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) + { + bool ret = false; + DetailLog("{0},BSLinksetConstraint.PhysicallyUnlinkAChildFromRoot,taint,root={1},rBody={2},child={3},cBody={4}", + rootPrim.LocalID, + rootPrim.LocalID, rootPrim.PhysBody.ptr.ToString(), + childPrim.LocalID, childPrim.PhysBody.ptr.ToString()); + + // 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)) + { + // Make the child refresh its location + BulletSimAPI.PushUpdate2(childPrim.PhysBody.ptr); + ret = true; + } + + return ret; + } + + // 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) + { + DetailLog("{0},BSLinksetConstraint.PhysicallyUnlinkAllChildren,taint", rootPrim.LocalID); + + return PhysicsScene.Constraints.RemoveAndDestroyConstraint(rootPrim.PhysBody); + } + + // Call each of the constraints that make up this linkset and recompute the + // various transforms and variables. Create constraints of not created yet. + // Called before the simulation step to make sure the constraint based linkset + // is all initialized. + // Called at taint time!! + private void RecomputeLinksetConstraints() + { + float linksetMass = LinksetMass; + LinksetRoot.UpdatePhysicalMassProperties(linksetMass); + + // DEBUG: see of inter-linkset collisions are causing problems + // BulletSimAPI.SetCollisionFilterMask2(LinksetRoot.BSBody.ptr, + // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); + DetailLog("{0},BSLinksetConstraint.RecomputeLinksetConstraints,set,rBody={1},linksetMass={2}", + LinksetRoot.LocalID, LinksetRoot.PhysBody.ptr.ToString(), linksetMass); + + foreach (BSPhysObject 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. + // (Also do the mass stuff before recomputing the constraint so mass is not zero.) + child.UpdatePhysicalMassProperties(linksetMass); + + BSConstraint constrain; + if (!PhysicsScene.Constraints.TryGetConstraint(LinksetRoot.PhysBody, child.PhysBody, out constrain)) + { + // If constraint doesn't exist yet, create it. + constrain = BuildConstraint(LinksetRoot, child); + } + constrain.RecomputeConstraintVariables(linksetMass); + + // DEBUG: see of inter-linkset collisions are causing problems + // BulletSimAPI.SetCollisionFilterMask2(child.BSBody.ptr, + // (uint)CollisionFilterGroups.LinksetFilter, (uint)CollisionFilterGroups.LinksetMask); + + // BulletSimAPI.DumpConstraint2(PhysicsScene.World.ptr, constrain.Constraint.ptr); // DEBUG DEBUG + } + + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSMaterials.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSMaterials.cs new file mode 100644 index 0000000000..732191f9b6 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSMaterials.cs @@ -0,0 +1,200 @@ +/* + * 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; +using System.Reflection; +using Nini.Config; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + +public struct MaterialAttributes +{ + // Material type values that correspond with definitions for LSL + public enum Material : int + { + Stone = 0, + Metal, + Glass, + Wood, + Flesh, + Plastic, + Rubber, + Light, + // Hereafter are BulletSim additions + Avatar, + NumberOfTypes // the count of types in the enum. + } + + // Names must be in the order of the above enum. + // These names must coorespond to the lower case field names in the MaterialAttributes + // structure as reflection is used to select the field to put the value in. + public static readonly string[] MaterialAttribs = { "Density", "Friction", "Restitution"}; + + public MaterialAttributes(string t, float d, float f, float r) + { + type = t; + density = d; + friction = f; + restitution = r; + } + public string type; + public float density; + public float friction; + public float restitution; +} + +public static class BSMaterials +{ + // Attributes for each material type + private static readonly MaterialAttributes[] Attributes; + + // Map of material name to material type code + public static readonly Dictionary MaterialMap; + + static BSMaterials() + { + // Attribute sets for both the non-physical and physical instances of materials. + Attributes = new MaterialAttributes[(int)MaterialAttributes.Material.NumberOfTypes * 2]; + + // Map of name to type code. + MaterialMap = new Dictionary(); + MaterialMap.Add("Stone", MaterialAttributes.Material.Stone); + MaterialMap.Add("Metal", MaterialAttributes.Material.Metal); + MaterialMap.Add("Glass", MaterialAttributes.Material.Glass); + MaterialMap.Add("Wood", MaterialAttributes.Material.Wood); + MaterialMap.Add("Flesh", MaterialAttributes.Material.Flesh); + MaterialMap.Add("Plastic", MaterialAttributes.Material.Plastic); + MaterialMap.Add("Rubber", MaterialAttributes.Material.Rubber); + MaterialMap.Add("Light", MaterialAttributes.Material.Light); + MaterialMap.Add("Avatar", MaterialAttributes.Material.Avatar); + } + + // This is where all the default material attributes are defined. + public static void InitializeFromDefaults(ConfigurationParameters parms) + { + // Values from http://wiki.secondlife.com/wiki/PRIM_MATERIAL + float dDensity = parms.defaultDensity; + float dFriction = parms.defaultFriction; + float dRestitution = parms.defaultRestitution; + Attributes[(int)MaterialAttributes.Material.Stone] = + new MaterialAttributes("stone",dDensity, 0.8f, 0.4f); + Attributes[(int)MaterialAttributes.Material.Metal] = + new MaterialAttributes("metal",dDensity, 0.3f, 0.4f); + Attributes[(int)MaterialAttributes.Material.Glass] = + new MaterialAttributes("glass",dDensity, 0.2f, 0.7f); + Attributes[(int)MaterialAttributes.Material.Wood] = + new MaterialAttributes("wood",dDensity, 0.6f, 0.5f); + Attributes[(int)MaterialAttributes.Material.Flesh] = + new MaterialAttributes("flesh",dDensity, 0.9f, 0.3f); + Attributes[(int)MaterialAttributes.Material.Plastic] = + new MaterialAttributes("plastic",dDensity, 0.4f, 0.7f); + Attributes[(int)MaterialAttributes.Material.Rubber] = + new MaterialAttributes("rubber",dDensity, 0.9f, 0.9f); + Attributes[(int)MaterialAttributes.Material.Light] = + new MaterialAttributes("light",dDensity, dFriction, dRestitution); + Attributes[(int)MaterialAttributes.Material.Avatar] = + new MaterialAttributes("avatar",60f, 0.2f, 0f); + + Attributes[(int)MaterialAttributes.Material.Stone + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("stonePhysical",dDensity, 0.8f, 0.4f); + Attributes[(int)MaterialAttributes.Material.Metal + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("metalPhysical",dDensity, 0.8f, 0.4f); + Attributes[(int)MaterialAttributes.Material.Glass + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("glassPhysical",dDensity, 0.8f, 0.7f); + Attributes[(int)MaterialAttributes.Material.Wood + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("woodPhysical",dDensity, 0.8f, 0.5f); + Attributes[(int)MaterialAttributes.Material.Flesh + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("fleshPhysical",dDensity, 0.8f, 0.3f); + Attributes[(int)MaterialAttributes.Material.Plastic + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("plasticPhysical",dDensity, 0.8f, 0.7f); + Attributes[(int)MaterialAttributes.Material.Rubber + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("rubberPhysical",dDensity, 0.8f, 0.9f); + Attributes[(int)MaterialAttributes.Material.Light + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("lightPhysical",dDensity, dFriction, dRestitution); + Attributes[(int)MaterialAttributes.Material.Avatar + (int)MaterialAttributes.Material.NumberOfTypes] = + new MaterialAttributes("avatarPhysical",60f, 0.2f, 0f); + } + + // Under the [BulletSim] section, one can change the individual material + // attribute values. The format of the configuration parameter is: + // ["Physical"] = floatValue + // For instance: + // [BulletSim] + // StoneFriction = 0.2 + // FleshRestitutionPhysical = 0.8 + // Materials can have different parameters for their static and + // physical instantiations. When setting the non-physical value, + // both values are changed. Setting the physical value only changes + // the physical value. + public static void InitializefromParameters(IConfig pConfig) + { + foreach (KeyValuePair kvp in MaterialMap) + { + string matName = kvp.Key; + foreach (string attribName in MaterialAttributes.MaterialAttribs) + { + string paramName = matName + attribName; + if (pConfig.Contains(paramName)) + { + float paramValue = pConfig.GetFloat(paramName); + SetAttributeValue((int)kvp.Value, attribName, paramValue); + // set the physical value also + SetAttributeValue((int)kvp.Value + (int)MaterialAttributes.Material.NumberOfTypes, attribName, paramValue); + } + paramName += "Physical"; + if (pConfig.Contains(paramName)) + { + float paramValue = pConfig.GetFloat(paramName); + SetAttributeValue((int)kvp.Value + (int)MaterialAttributes.Material.NumberOfTypes, attribName, paramValue); + } + } + } + } + + // Use reflection to set the value in the attribute structure. + private static void SetAttributeValue(int matType, string attribName, float val) + { + MaterialAttributes thisAttrib = Attributes[matType]; + FieldInfo fieldInfo = thisAttrib.GetType().GetField(attribName.ToLower()); + if (fieldInfo != null) + { + fieldInfo.SetValue(thisAttrib, val); + Attributes[matType] = thisAttrib; + } + } + + // Given a material type, return a structure of attributes. + public static MaterialAttributes GetAttributes(MaterialAttributes.Material type, bool isPhysical) + { + int ind = (int)type; + if (isPhysical) ind += (int)MaterialAttributes.Material.NumberOfTypes; + return Attributes[ind]; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSMotors.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSMotors.cs new file mode 100644 index 0000000000..7abc9b2b78 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSMotors.cs @@ -0,0 +1,347 @@ +/* + * 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.Collections.Generic; +using System.Text; +using OpenMetaverse; +using OpenSim.Framework; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public abstract class BSMotor +{ + // Timescales and other things can be turned off by setting them to 'infinite'. + public const float Infinite = 12345.6f; + public readonly static Vector3 InfiniteVector = new Vector3(BSMotor.Infinite, BSMotor.Infinite, BSMotor.Infinite); + + public BSMotor(string useName) + { + UseName = useName; + PhysicsScene = null; + Enabled = true; + } + public virtual bool Enabled { get; set; } + public virtual void Reset() { } + public virtual void Zero() { } + public virtual void GenerateTestOutput(float timeStep) { } + + // A name passed at motor creation for easily identifyable debugging messages. + public string UseName { get; private set; } + + // Used only for outputting debug information. Might not be set so check for null. + public BSScene PhysicsScene { get; set; } + protected void MDetailLog(string msg, params Object[] parms) + { + if (PhysicsScene != null) + { + if (PhysicsScene.VehicleLoggingEnabled) + { + 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. +// 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. +// 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, +// CurrentValue is X and TargetValue is Y. As the motor is stepped, new +// values of CurrentValue are returned that approach the TargetValue. +// The feature of decaying TargetValue is so vehicles will eventually +// come to a stop rather than run forever. This can be disabled by +// setting TargetValueDecayTimescale to 'infinite'. +// The change from CurrentValue to TargetValue is linear over TimeScale seconds. +public class BSVMotor : BSMotor +{ + // public Vector3 FrameOfReference { get; set; } + // public Vector3 Offset { get; set; } + + 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; } + + public virtual Vector3 TargetValue { get; protected set; } + 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 BSVMotor(string useName) + : base(useName) + { + 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) + : this(useName) + { + TimeScale = timeScale; + TargetValueDecayTimeScale = decayTimeScale; + FrictionTimescale = frictionTimeScale; + Efficiency = efficiency; + CurrentValue = TargetValue = Vector3.Zero; + } + public void SetCurrent(Vector3 current) + { + CurrentValue = current; + } + public void SetTarget(Vector3 target) + { + TargetValue = target; + } + public override void Zero() + { + base.Zero(); + CurrentValue = TargetValue = Vector3.Zero; + } + + // Compute the next step and return the new current value + public virtual Vector3 Step(float timeStep) + { + if (!Enabled) return TargetValue; + + Vector3 origTarget = TargetValue; // DEBUG + Vector3 origCurrVal = CurrentValue; // DEBUG + + Vector3 correction = Vector3.Zero; + Vector3 error = TargetValue - CurrentValue; + if (!error.ApproxEquals(Vector3.Zero, ErrorZeroThreshold)) + { + correction = Step(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); + } + + // 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); + } + else + { + // Difference between what we have and target is small. Motor is done. + CurrentValue = TargetValue; + MDetailLog("{0}, BSVMotor.Step,zero,{1},origTgt={2},origCurr={3},ret={4}", + BSScene.DetailLogZero, UseName, origCurrVal, origTarget, CurrentValue); + } + + return CurrentValue; + } + public virtual Vector3 Step(float timeStep, Vector3 error) + { + if (!Enabled) return Vector3.Zero; + + LastError = error; + Vector3 returnCorrection = Vector3.Zero; + if (!error.ApproxEquals(Vector3.Zero, ErrorZeroThreshold)) + { + // correction = error / secondsItShouldTakeToCorrect + Vector3 correctionAmount; + if (TimeScale == 0f || TimeScale == BSMotor.Infinite) + correctionAmount = error * timeStep; + else + correctionAmount = error / TimeScale * timeStep; + + returnCorrection = correctionAmount; + MDetailLog("{0}, BSVMotor.Step,nonZero,{1},timeStep={2},timeScale={3},err={4},corr={5}", + BSScene.DetailLogZero, UseName, timeStep, TimeScale, error, correctionAmount); + } + return returnCorrection; + } + + // The user sets all the parameters and calls this which outputs values until error is zero. + public override void GenerateTestOutput(float timeStep) + { + // 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}", + BSScene.DetailLogZero, UseName, + TimeScale, TargetValueDecayTimeScale, FrictionTimescale, Efficiency, + CurrentValue, TargetValue); + + LastError = BSMotor.InfiniteVector; + while (maxOutput-- > 0 && !LastError.ApproxEquals(Vector3.Zero, ErrorZeroThreshold)) + { + Vector3 lastStep = Step(timeStep); + MDetailLog("{0},BSVMotor.Test,{1},cur={2},tgt={3},lastError={4},lastStep={5}", + 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},decayTS={3},frictTS={4}>", + UseName, CurrentValue, TargetValue, TargetValueDecayTimeScale, FrictionTimescale); + } +} + +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 float Target { get; private set; } + public float CurrentValue { get; private set; } + + public BSFMotor(string useName, float timeScale, float decayTimescale, float friction, float efficiency) + : base(useName) + { + } + public void SetCurrent(float target) + { + } + public void SetTarget(float target) + { + } + public virtual float Step(float timeStep) + { + return 0f; + } +} + +// 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 +{ + // Larger makes more overshoot, smaller means converge quicker. Range of 0.1 to 10. + public Vector3 proportionFactor { get; set; } + public Vector3 integralFactor { get; set; } + public Vector3 derivFactor { get; set; } + + // Arbritrary factor range. + // EfficiencyHigh means move quickly to the correct number. EfficiencyLow means might over correct. + public float EfficiencyHigh = 0.4f; + public float EfficiencyLow = 4.0f; + + // Running integration of the error + Vector3 RunningIntegration { get; set; } + + public BSPIDVMotor(string useName) + : base(useName) + { + 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); + RunningIntegration = Vector3.Zero; + LastError = Vector3.Zero; + } + + public override void Zero() + { + base.Zero(); + } + + public override float Efficiency + { + get { return base.Efficiency; } + 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); + } + } + + // Ignore Current and Target Values and just advance the PID computation on this error. + public override Vector3 Step(float timeStep, Vector3 error) + { + if (!Enabled) return Vector3.Zero; + + // Add up the error so we can integrate over the accumulated errors + RunningIntegration += error * timeStep; + + // A simple derivitive is the rate of change from the last error. + Vector3 derivFactor = (error - LastError) * timeStep; + LastError = error; + + // Correction = -(proportionOfPresentError + accumulationOfPastError + rateOfChangeOfError) + Vector3 ret = -( + error * proportionFactor + + RunningIntegration * integralFactor + + derivFactor * derivFactor + ); + + return ret; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSParam.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSParam.cs new file mode 100644 index 0000000000..0bb167467f --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSParam.cs @@ -0,0 +1,559 @@ +/* + * 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; + +using OpenSim.Region.Physics.Manager; + +using OpenMetaverse; +using Nini.Config; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public static class BSParam +{ + // Level of Detail values kept as float because that's what the Meshmerizer wants + public static float MeshLOD { 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 float MinimumObjectMass { get; private set; } + public static float MaximumObjectMass { get; private set; } + + public static float LinearDamping { get; private set; } + public static float AngularDamping { get; private set; } + public static float DeactivationTime { get; private set; } + public static float LinearSleepingThreshold { get; private set; } + public static float AngularSleepingThreshold { get; private set; } + public static float CcdMotionThreshold { get; private set; } + public static float CcdSweptSphereRadius { get; private set; } + public static float ContactProcessingThreshold { get; private set; } + + 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 float TerrainImplementation { 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 TerrainCollisionMargin { get; private set; } + + // Avatar parameters + public static float AvatarFriction { get; private set; } + public static float AvatarStandingFriction { get; private set; } + public static float AvatarDensity { get; private set; } + public static float AvatarRestitution { get; private set; } + public static float AvatarCapsuleWidth { get; private set; } + public static float AvatarCapsuleDepth { get; private set; } + public static float AvatarCapsuleHeight { get; private set; } + public static float AvatarContactProcessingThreshold { get; private set; } + + public static float VehicleAngularDamping { get; private set; } + + public static float LinksetImplementation { get; private set; } + public static float LinkConstraintUseFrameOffset { get; private set; } + public static float 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; } + public static float LinkConstraintCFM { get; private set; } + public static float LinkConstraintSolverIterations { get; private set; } + + public static float PID_D { get; private set; } // derivative + public static float PID_P { get; private set; } // proportional + + 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 + { + 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) + { + name = n; + desc = d; + defaultValue = v; + userParam = u; + getter = g; + setter = s; + onObject = null; + } + public ParameterDefn(string n, string d, float v, ParamUser u, ParamGet g, ParamSet s, SetOnObject o) + { + name = n; + desc = d; + defaultValue = v; + userParam = u; + getter = g; + setter = s; + onObject = o; + } + } + + // List of all of the externally visible parameters. + // For each parameter, this table maps a text name to getter and setters. + // To add a new externally referencable/settable parameter, add the paramter storage + // 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: + // -- 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 + // -- 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 = + { + 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("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)", + 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", + 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)", + 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("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)", + 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)", + 10000.01f, + (s,cf,p,v) => { MaximumObjectMass = cf.GetFloat(p, v); }, + (s) => { return (float)MaximumObjectMass; }, + (s,p,l,v) => { MaximumObjectMass = v; } ), + + 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("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" , + 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" , + 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!)", + 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)", + -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) => { BulletSimAPI.SetGravity2(s.World.ptr, new Vector3(0f,0f,v)); } ), + + + 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) => { BulletSimAPI.SetDamping2(o.PhysBody.ptr, v, 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) => { BulletSimAPI.SetDamping2(o.PhysBody.ptr, LinearDamping, v); } ), + 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) => { BulletSimAPI.SetDeactivationTime2(o.PhysBody.ptr, v); } ), + 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) => { BulletSimAPI.SetSleepingThresholds2(o.PhysBody.ptr, v, v); } ), + 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) => { BulletSimAPI.SetSleepingThresholds2(o.PhysBody.ptr, 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) => { return CcdMotionThreshold; }, + (s,p,l,v) => { s.UpdateParameterObject((x)=>{CcdMotionThreshold=x;}, p, l, v); }, + (s,o,v) => { BulletSimAPI.SetCcdMotionThreshold2(o.PhysBody.ptr, 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) => { BulletSimAPI.SetCcdSweptSphereRadius2(o.PhysBody.ptr, 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) => { BulletSimAPI.SetContactProcessingThreshold2(o.PhysBody.ptr, 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.", + 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("AvatarDensity", "Density of an avatar. Changed on avatar recreation.", + 60f, + (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); } ), + + 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("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)", + 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)", + 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("LogPhysicsStatisticsFrames", "Frames between outputting detailed phys stats. (0 is off)", + 0f, + (s,cf,p,v) => { s.UnmanagedParams[0].physicsLoggingFrames = cf.GetInt(p, (int)v); }, + (s) => { return (float)s.UnmanagedParams[0].physicsLoggingFrames; }, + (s,p,l,v) => { s.UnmanagedParams[0].physicsLoggingFrames = (int)v; } ), + }; + + // Convert a boolean to our numeric true and false values + public static float NumericBool(bool b) + { + return (b ? ConfigurationParameters.numericTrue : ConfigurationParameters.numericFalse); + } + + // Convert numeric true and false values to a boolean + public static bool BoolNumeric(float b) + { + return (b == ConfigurationParameters.numericTrue ? true : false); + } + + // Search through the parameter definitions and return the matching + // 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) + { + bool ret = false; + ParameterDefn foundDefn = new ParameterDefn(); + string pName = paramName.ToLower(); + + foreach (ParameterDefn parm in ParameterDefinitions) + { + if (pName == parm.name.ToLower()) + { + foundDefn = parm; + ret = true; + break; + } + } + defn = foundDefn; + return ret; + } + + // Pass through the settable parameters and set the default values + internal static void SetParameterDefaultValues(BSScene physicsScene) + { + foreach (ParameterDefn parm in ParameterDefinitions) + { + parm.setter(physicsScene, parm.name, PhysParameterEntry.APPLY_TO_NONE, parm.defaultValue); + } + } + + // Get user set values out of the ini file. + internal static void SetParameterConfigurationValues(BSScene physicsScene, IConfig cfg) + { + foreach (ParameterDefn parm in ParameterDefinitions) + { + parm.userParam(physicsScene, cfg, parm.name, parm.defaultValue); + } + } + + internal static PhysParameterEntry[] SettableParameters = new PhysParameterEntry[1]; + + // This creates an array in the correct format for returning the list of + // parameters. This is used by the 'list' option of the 'physics' command. + internal static void BuildParameterTable() + { + if (SettableParameters.Length < ParameterDefinitions.Length) + { + List entries = new List(); + for (int ii = 0; ii < ParameterDefinitions.Length; ii++) + { + ParameterDefn 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); + }); + + SettableParameters = entries.ToArray(); + } + } + + +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSPhysObject.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSPhysObject.cs new file mode 100644 index 0000000000..4096ef8583 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSPhysObject.cs @@ -0,0 +1,345 @@ +/* + * 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; + +using OMV = OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Physics.Manager; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +/* + * 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. + */ + +/* + * As of 20121221, the following are the call sequences (going down) for different script physical functions: + * llApplyImpulse llApplyRotImpulse llSetTorque llSetForce + * 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 + */ + +public abstract class BSPhysObject : PhysicsActor +{ + protected BSPhysObject() + { + } + protected BSPhysObject(BSScene parentScene, uint localID, string name, string typeName) + { + PhysicsScene = parentScene; + LocalID = localID; + PhysObjectName = name; + TypeName = typeName; + + Linkset = BSLinkset.Factory(PhysicsScene, this); + LastAssetBuildFailed = false; + + // Default material type + Material = MaterialAttributes.Material.Wood; + + CollisionCollection = new CollisionEventUpdate(); + SubscribedEventsMs = 0; + CollidingStep = 0; + CollidingGroundStep = 0; + } + + // Tell the object to clean up. + public virtual void Destroy() + { + UnRegisterAllPreStepActions(); + } + + public BSScene PhysicsScene { 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; } + // Set the raw mass but also update physical mass properties (inertia, ...) + public abstract void UpdatePhysicalMassProperties(float mass); + + // 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; + + // '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 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 + // between the current update and the previous values. + public EntityProperties CurrentEntityProperties { get; set; } + public EntityProperties LastEntityProperties { get; set; } + + public virtual OMV.Vector3 Scale { get; set; } + public abstract bool IsSolid { get; } + public abstract bool IsStatic { get; } + + // Materialness + public MaterialAttributes.Material Material { get; private set; } + public override void SetMaterial(int material) + { + Material = (MaterialAttributes.Material)material; + } + + // 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); + + public abstract OMV.Vector3 RawPosition { get; set; } + public abstract OMV.Vector3 ForcePosition { get; set; } + + 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 abstract OMV.Vector3 ForceVelocity { get; set; } + + public abstract OMV.Vector3 ForceRotationalVelocity { get; set; } + + public abstract float ForceBuoyancy { get; set; } + + public virtual bool ForceBodyShapeRebuild(bool inTaintTime) { return false; } + + #region Collisions + + // Requested number of milliseconds between collision events. Zero means disabled. + protected int SubscribedEventsMs { get; set; } + // Given subscription, the time that a collision may be passed up + protected int NextCollisionOkTime { get; set; } + // The simulation step that last had a collision + protected long CollidingStep { get; set; } + // The simulation step that last had a collision with the ground + protected long CollidingGroundStep { get; set; } + // The collision flags we think are set in Bullet + protected CollisionFlags CurrentCollisionFlags { get; set; } + + // The collisions that have been collected this tick + protected CollisionEventUpdate CollisionCollection; + + // The simulation step is telling this object about a collision. + // Return 'true' if a collision was processed and should be sent up. + // 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) + { + bool ret = false; + + // The following lines make IsColliding() and IsCollidingGround() work + CollidingStep = PhysicsScene.SimulationStep; + if (collidingWith <= PhysicsScene.TerrainManager.HighestTerrainID) + { + CollidingGroundStep = PhysicsScene.SimulationStep; + } + + // prims in the same linkset cannot collide with each other + if (collidee != null && (this.Linkset.LinksetID == collidee.Linkset.LinksetID)) + { + return ret; + } + + // if someone has subscribed for collision events.... + 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); + + ret = true; + } + return ret; + } + + // Send the collected collisions into the simulator. + // Called at taint time from within the Step() function thus no locking problems + // with CollisionCollection and ObjectsWithNoMoreCollisions. + // Return 'true' if there were some actual collisions passed up + 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); + + // throttle the collisions to the number of milliseconds specified in the subscription + if (force || (PhysicsScene.SimulationNowTime >= NextCollisionOkTime)) + { + NextCollisionOkTime = PhysicsScene.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. + if (CollisionCollection.Count == 0) + { + // If I have no collisions this time, remove me from the list of objects with collisions. + ret = false; + } + + // DetailLog("{0},{1}.SendCollisionUpdate,call,numCollisions={2}", LocalID, TypeName, CollisionCollection.Count); + base.SendCollisionUpdate(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, + // a race condition is created for the other users of this instance. + CollisionCollection = new CollisionEventUpdate(); + } + return ret; + } + + // Subscribe for collision events. + // Parameter is the millisecond rate the caller wishes collision events to occur. + public override void SubscribeEvents(int ms) { + // DetailLog("{0},{1}.SubscribeEvents,subscribing,ms={2}", LocalID, TypeName, ms); + SubscribedEventsMs = ms; + if (ms > 0) + { + // make sure first collision happens + NextCollisionOkTime = Util.EnvironmentTickCountSubtract(SubscribedEventsMs); + + PhysicsScene.TaintedObject(TypeName+".SubscribeEvents", delegate() + { + if (PhysBody.HasPhysicalBody) + CurrentCollisionFlags = BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + }); + } + else + { + // Subscribing for zero or less is the same as unsubscribing + UnSubscribeEvents(); + } + } + public override void UnSubscribeEvents() { + // DetailLog("{0},{1}.UnSubscribeEvents,unsubscribing", LocalID, TypeName); + SubscribedEventsMs = 0; + PhysicsScene.TaintedObject(TypeName+".UnSubscribeEvents", delegate() + { + // Make sure there is a body there because sometimes destruction happens in an un-ideal order. + if (PhysBody.HasPhysicalBody) + CurrentCollisionFlags = BulletSimAPI.RemoveFromCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + }); + } + // Return 'true' if the simulator wants collision events + public override bool SubscribedEvents() { + return (SubscribedEventsMs > 0); + } + + #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) + { + string identifier = op + "-" + id.ToString(); + RegisteredActions[identifier] = actn; + PhysicsScene.BeforeStep += actn; + DetailLog("{0},BSPhysObject.RegisterPreStepAction,id={1}", LocalID, identifier); + } + + // 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); + } + +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSPlugin.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSPlugin.cs new file mode 100644 index 0000000000..75963ee5e5 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSPlugin.cs @@ -0,0 +1,81 @@ +/* + * 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 OpenSim.Framework; +using OpenSim.Region.Physics.Manager; +using OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + /// + /// Entry for a port of Bullet (http://bulletphysics.org/) to OpenSim. + /// This module interfaces to an unmanaged C++ library which makes the + /// actual calls into the Bullet physics engine. + /// The unmanaged library is found in opensim-libs::trunk/unmanaged/BulletSim/. + /// The unmanaged library is compiled and linked statically with Bullet + /// to create BulletSim.dll and libBulletSim.so (for both 32 and 64 bit). + /// +public class BSPlugin : IPhysicsPlugin +{ + //private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + private BSScene _mScene; + + public BSPlugin() + { + } + + public bool Init() + { + return true; + } + + public PhysicsScene GetScene(String sceneIdentifier) + { + if (_mScene == null) + { + + // If not Windows, loading is performed by the + // Mono loader as specified in + // "bin/Physics/OpenSim.Region.Physics.BulletSNPlugin.dll.config". + + _mScene = new BSScene(sceneIdentifier); + } + return (_mScene); + } + + public string GetName() + { + return ("BulletSimN"); + } + + public void Dispose() + { + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSPrim.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSPrim.cs new file mode 100644 index 0000000000..a889c2415f --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSPrim.cs @@ -0,0 +1,1467 @@ +/* + * 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.Reflection; +using System.Collections.Generic; +using System.Xml; +using log4net; +using OMV = OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Physics.Manager; +using OpenSim.Region.Physics.ConvexDecompositionDotNet; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + + [Serializable] +public sealed class BSPrim : BSPhysObject +{ + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[BULLETS PRIM]"; + + // _size is what the user passed. Scale is what we pass to the physics engine with the mesh. + private OMV.Vector3 _size; // the multiplier for each mesh dimension as passed by the user + + private bool _grabbed; + private bool _isSelected; + private bool _isVolumeDetect; + 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 OMV.Vector3 _PIDTarget; + private bool _usePID; + private float _PIDTau; + private bool _useHoverPID; + private float _PIDHoverHeight; + private PIDHoverType _PIDHoverType; + private float _PIDHoverTao; + + public BSPrim(uint localID, String primName, BSScene parent_scene, OMV.Vector3 pos, OMV.Vector3 size, + OMV.Quaternion rotation, PrimitiveBaseShape pbs, bool pisPhysical) + : base(parent_scene, localID, primName, "BSPrim") + { + // m_log.DebugFormat("{0}: BSPrim creation of {1}, id={2}", LogHeader, primName, localID); + _physicsActorType = (int)ActorTypes.Prim; + _position = pos; + _size = size; + Scale = size; // prims are the size the user wants them to be (different for BSCharactes). + _orientation = rotation; + _buoyancy = 1f; + _velocity = 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 + + _mass = CalculateMass(); + + // No body or shape yet + PhysBody = new BulletBody(LocalID); + PhysShape = new BulletShape(); + + DetailLog("{0},BSPrim.constructor,call", LocalID); + // do the actual object creation at taint time + PhysicsScene.TaintedObject("BSPrim.create", delegate() + { + CreateGeomAndObject(true); + + CurrentCollisionFlags = BulletSimAPI.GetCollisionFlags2(PhysBody.ptr); + }); + } + + // called when this prim is being destroyed and we should free all the resources + public override void Destroy() + { + // 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() + { + DetailLog("{0},BSPrim.Destroy,taint,", LocalID); + // If there are physical body and shape, release my use of same. + PhysicsScene.Shapes.DereferenceBody(PhysBody, true, null); + PhysBody.Clear(); + PhysicsScene.Shapes.DereferenceShape(PhysShape, true, null); + PhysShape.Clear(); + }); + } + + // No one uses this property. + public override bool Stopped { + get { return false; } + } + public override OMV.Vector3 Size { + get { return _size; } + set { + // We presume the scale and size are the same. If scale must be changed for + // the physical shape, that is done when the geometry is built. + _size = value; + Scale = _size; + ForceBodyShapeRebuild(false); + } + } + + public override PrimitiveBaseShape Shape { + set { + BaseShape = value; + 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() + { + _mass = CalculateMass(); // changing the shape changes the mass + CreateGeomAndObject(true); + }); + return true; + } + public override bool Grabbed { + set { _grabbed = value; + } + } + public override bool Selected { + set + { + if (value != _isSelected) + { + _isSelected = value; + PhysicsScene.TaintedObject("BSPrim.setSelected", delegate() + { + DetailLog("{0},BSPrim.selected,taint,selected={1}", LocalID, _isSelected); + SetObjectDynamic(false); + }); + } + } + } + public override void CrossingFailure() { return; } + + // link me to the specified parent + public override void link(PhysicsActor obj) { + BSPrim parent = obj as BSPrim; + if (parent != null) + { + 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); + } + return; + } + + // 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. + // Do it to the properties so the values get set in the physics engine. + // Push the setting of the values to the viewer. + // Called at taint time! + public override void ZeroMotion(bool inTaintTime) + { + _velocity = 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() + { + if (PhysBody.HasPhysicalBody) + BulletSimAPI.ClearAllForces2(PhysBody.ptr); + }); + } + public override void ZeroAngularMotion(bool inTaintTime) + { + _rotationalVelocity = OMV.Vector3.Zero; + // Zero some other properties in the physics engine + PhysicsScene.TaintedObject(inTaintTime, "BSPrim.ZeroMotion", delegate() + { + // DetailLog("{0},BSPrim.ZeroAngularMotion,call,rotVel={1}", LocalID, _rotationalVelocity); + if (PhysBody.HasPhysicalBody) + { + BulletSimAPI.SetInterpolationAngularVelocity2(PhysBody.ptr, _rotationalVelocity); + BulletSimAPI.SetAngularVelocity2(PhysBody.ptr, _rotationalVelocity); + } + }); + } + + public override void LockAngularMotion(OMV.Vector3 axis) + { + DetailLog("{0},BSPrim.LockAngularMotion,call,axis={1}", LocalID, axis); + return; + } + + public override OMV.Vector3 RawPosition + { + get { return _position; } + set { _position = value; } + } + 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 = BulletSimAPI.GetObjectPosition2(PhysicsScene.World.ptr, BSBody.ptr); + return _position; + } + set { + // If the position must be forced into the physics engine, use ForcePosition. + // All positions are given in world positions. + if (_position == value) + { + DetailLog("{0},BSPrim.setPosition,taint,positionNotChanging,pos={1},orient={2}", LocalID, _position, _orientation); + return; + } + _position = value; + PositionSanityCheck(false); + + // A linkset might need to know if a component information changed. + Linkset.UpdateProperties(this, false); + + PhysicsScene.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 = BulletSimAPI.GetPosition2(PhysBody.ptr); + return _position; + } + set { + _position = value; + if (PhysBody.HasPhysicalBody) + { + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + ActivateIfPhysical(false); + } + } + } + + // Check that the current position is sane and, if not, modify the position to make it so. + // Check for being below terrain and being out of bounds. + // Returns 'true' of the position was made sane by some action. + private bool PositionSanityCheck(bool inTaintTime) + { + bool ret = false; + + if (!PhysicsScene.TerrainManager.IsWithinKnownTerrain(_position)) + { + // The physical object 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; + } + + float terrainHeight = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(_position); + OMV.Vector3 upForce = OMV.Vector3.Zero; + if (RawPosition.Z < terrainHeight) + { + DetailLog("{0},BSPrim.PositionAdjustUnderGround,call,pos={1},terrain={2}", LocalID, _position, 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; + ret = true; + } + + if ((CurrentCollisionFlags & CollisionFlags.BS_FLOATS_ON_WATER) != 0) + { + float waterHeight = PhysicsScene.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; + 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) + { + // 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); + } + return ret; + } + + // Return the effective mass of the object. + // If there are multiple items in the linkset, add them together for the root + public override float Mass + { + get + { + return Linkset.LinksetMass; + // return _mass; + } + } + + // used when we only want this prim's mass and not the linkset thing + 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) + { + if (IsStatic) + { + Inertia = OMV.Vector3.Zero; + BulletSimAPI.SetMassProps2(PhysBody.ptr, 0f, Inertia); + BulletSimAPI.UpdateInertiaTensor2(PhysBody.ptr); + } + else + { + Inertia = BulletSimAPI.CalculateLocalInertia2(PhysShape.ptr, physMass); + BulletSimAPI.SetMassProps2(PhysBody.ptr, physMass, Inertia); + BulletSimAPI.UpdateInertiaTensor2(PhysBody.ptr); + // center of mass is at the zero of the object + // DEBUG DEBUG BulletSimAPI.SetCenterOfMassByPosRot2(PhysBody.ptr, ForcePosition, ForceOrientation); + DetailLog("{0},BSPrim.UpdateMassProperties,mass={1},localInertia={2}", LocalID, physMass, Inertia); + } + } + + // Is this used? + public override OMV.Vector3 CenterOfMass + { + get { return Linkset.CenterOfMass; } + } + + // Is this used? + public override OMV.Vector3 GeometricCenter + { + get { return Linkset.GeometricCenter; } + } + + public override OMV.Vector3 Force { + get { return _force; } + set { + _force = value; + if (_force != OMV.Vector3.Zero) + { + // 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) + { + BulletSimAPI.ApplyCentralForce2(PhysBody.ptr, _force); + ActivateIfPhysical(false); + } + } + ); + } + else + { + UnRegisterPreStepAction("BSPrim.setForce", LocalID); + } + } + } + + public override int VehicleType { + get { + return (int)_vehicle.Type; // if we are a vehicle, return that type + } + set { + Vehicle type = (Vehicle)value; + + PhysicsScene.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); + 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() + { + _vehicle.ProcessFloatVehicleParam((Vehicle)param, value); + ActivateIfPhysical(false); + }); + } + public override void VehicleVectorParam(int param, OMV.Vector3 value) + { + PhysicsScene.TaintedObject("BSPrim.VehicleVectorParam", delegate() + { + _vehicle.ProcessVectorVehicleParam((Vehicle)param, value); + ActivateIfPhysical(false); + }); + } + public override void VehicleRotationParam(int param, OMV.Quaternion rotation) + { + PhysicsScene.TaintedObject("BSPrim.VehicleRotationParam", delegate() + { + _vehicle.ProcessRotationVehicleParam((Vehicle)param, rotation); + ActivateIfPhysical(false); + }); + } + public override void VehicleFlags(int param, bool remove) + { + PhysicsScene.TaintedObject("BSPrim.VehicleFlags", delegate() + { + _vehicle.ProcessVehicleFlags(param, remove); + }); + } + + // Allows the detection of collisions with inherently non-physical prims. see llVolumeDetect for more + public override void SetVolumeDetect(int param) { + bool newValue = (param != 0); + if (_isVolumeDetect != newValue) + { + _isVolumeDetect = newValue; + PhysicsScene.TaintedObject("BSPrim.SetVolumeDetect", delegate() + { + // DetailLog("{0},setVolumeDetect,taint,volDetect={1}", LocalID, _isVolumeDetect); + SetObjectDynamic(true); + }); + } + return; + } + public override OMV.Vector3 Velocity { + get { return _velocity; } + set { + _velocity = value; + PhysicsScene.TaintedObject("BSPrim.setVelocity", delegate() + { + // DetailLog("{0},BSPrim.SetVelocity,taint,vel={1}", LocalID, _velocity); + ForceVelocity = _velocity; + }); + } + } + public override OMV.Vector3 ForceVelocity { + get { return _velocity; } + set { + PhysicsScene.AssertInTaintTime("BSPrim.ForceVelocity"); + + _velocity = value; + if (PhysBody.HasPhysicalBody) + { + BulletSimAPI.SetLinearVelocity2(PhysBody.ptr, _velocity); + ActivateIfPhysical(false); + } + } + } + public override OMV.Vector3 Torque { + get { return _torque; } + set { + _torque = value; + if (_torque != OMV.Vector3.Zero) + { + // 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; + } + } + public override OMV.Vector3 Acceleration { + get { return _acceleration; } + set { _acceleration = value; } + } + public override OMV.Quaternion RawOrientation + { + get { return _orientation; } + set { _orientation = value; } + } + 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 { + if (_orientation == value) + return; + _orientation = value; + + // A linkset might need to know if a component information changed. + Linkset.UpdateProperties(this, false); + + PhysicsScene.TaintedObject("BSPrim.setOrientation", delegate() + { + if (PhysBody.HasPhysicalBody) + { + // _position = BulletSimAPI.GetObjectPosition2(PhysicsScene.World.ptr, BSBody.ptr); + // DetailLog("{0},BSPrim.setOrientation,taint,pos={1},orient={2}", LocalID, _position, _orientation); + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + } + }); + } + } + // Go directly to Bullet to get/set the value. + public override OMV.Quaternion ForceOrientation + { + get + { + _orientation = BulletSimAPI.GetOrientation2(PhysBody.ptr); + return _orientation; + } + set + { + _orientation = value; + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + } + } + public override int PhysicsActorType { + get { return _physicsActorType; } + set { _physicsActorType = value; } + } + public override bool IsPhysical { + get { return _isPhysical; } + set { + if (_isPhysical != value) + { + _isPhysical = value; + PhysicsScene.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); + }); + } + } + } + + // An object is static (does not move) if selected or not physical + public override bool IsStatic + { + get { return _isSelected || !IsPhysical; } + } + + // An object is solid if it's not phantom and if it's not doing VolumeDetect + public override bool IsSolid + { + get { return !IsPhantom && !_isVolumeDetect; } + } + + // Make gravity work if the object is physical and not selected + // Called at taint-time!! + private void SetObjectDynamic(bool forceRebuild) + { + // Recreate the physical object if necessary + CreateGeomAndObject(forceRebuild); + } + + // Convert the simulator's physical properties into settings on BulletSim objects. + // There are four flags we're interested in: + // IsStatic: Object does not move, otherwise the object has mass and moves + // 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() + { + // DetailLog("{0},BSPrim.UpdatePhysicalParameters,entry,body={1},shape={2}", LocalID, BSBody, BSShape); + + // 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). + BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, PhysBody.ptr); + + // 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(); + + // Arrange for collision events if the simulator wants them + EnableCollisions(SubscribedEvents()); + + // Make solid or not (do things bounce off or pass through this object). + MakeSolid(IsSolid); + + BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, PhysBody.ptr, _position, _orientation); + + // Rebuild its shape + BulletSimAPI.UpdateSingleAabb2(PhysicsScene.World.ptr, PhysBody.ptr); + + // Collision filter can be set only when the object is in the world + PhysBody.ApplyCollisionMask(); + + // 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); + + DetailLog("{0},BSPrim.UpdatePhysicalParameters,taintExit,static={1},solid={2},mass={3},collide={4},cf={5:X},body={6},shape={7}", + LocalID, IsStatic, IsSolid, _mass, SubscribedEvents(), CurrentCollisionFlags, PhysBody, PhysShape); + } + + // "Making dynamic" means changing to and from static. + // When static, gravity does not effect the object and it is fixed in space. + // 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) + { + if (makeStatic) + { + // Become a Bullet 'static' object type + CurrentCollisionFlags = BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, CollisionFlags.CF_STATIC_OBJECT); + // Stop all movement + ZeroMotion(true); + + // Set various physical properties so other object interact properly + MaterialAttributes matAttrib = BSMaterials.GetAttributes(Material, false); + BulletSimAPI.SetFriction2(PhysBody.ptr, matAttrib.friction); + BulletSimAPI.SetRestitution2(PhysBody.ptr, matAttrib.restitution); + + // Mass is zero which disables a bunch of physics stuff in Bullet + UpdatePhysicalMassProperties(0f); + // Set collision detection parameters + if (BSParam.CcdMotionThreshold > 0f) + { + BulletSimAPI.SetCcdMotionThreshold2(PhysBody.ptr, BSParam.CcdMotionThreshold); + BulletSimAPI.SetCcdSweptSphereRadius2(PhysBody.ptr, BSParam.CcdSweptSphereRadius); + } + + // The activation state is 'disabled' so Bullet will not try to act on it. + // BulletSimAPI.ForceActivationState2(PhysBody.ptr, ActivationState.DISABLE_SIMULATION); + // Start it out sleeping and physical actions could wake it up. + BulletSimAPI.ForceActivationState2(PhysBody.ptr, 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 = BulletSimAPI.RemoveFromCollisionFlags2(PhysBody.ptr, CollisionFlags.CF_STATIC_OBJECT); + + // Set various physical properties so other object interact properly + MaterialAttributes matAttrib = BSMaterials.GetAttributes(Material, true); + BulletSimAPI.SetFriction2(PhysBody.ptr, matAttrib.friction); + BulletSimAPI.SetRestitution2(PhysBody.ptr, matAttrib.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 + // BulletSimAPI.ClearAllForces2(BSBody.ptr); + + // For good measure, make sure the transform is set through to the motion state + BulletSimAPI.SetTranslation2(PhysBody.ptr, _position, _orientation); + + // Center of mass is at the center of the object + // DEBUG DEBUG BulletSimAPI.SetCenterOfMassByPosRot2(Linkset.LinksetRoot.PhysBody.ptr, _position, _orientation); + + // A dynamic object has mass + UpdatePhysicalMassProperties(RawMass); + + // Set collision detection parameters + if (BSParam.CcdMotionThreshold > 0f) + { + BulletSimAPI.SetCcdMotionThreshold2(PhysBody.ptr, BSParam.CcdMotionThreshold); + BulletSimAPI.SetCcdSweptSphereRadius2(PhysBody.ptr, BSParam.CcdSweptSphereRadius); + } + + // Various values for simulation limits + BulletSimAPI.SetDamping2(PhysBody.ptr, BSParam.LinearDamping, BSParam.AngularDamping); + BulletSimAPI.SetDeactivationTime2(PhysBody.ptr, BSParam.DeactivationTime); + BulletSimAPI.SetSleepingThresholds2(PhysBody.ptr, BSParam.LinearSleepingThreshold, BSParam.AngularSleepingThreshold); + BulletSimAPI.SetContactProcessingThreshold2(PhysBody.ptr, 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. + BulletSimAPI.ForceActivationState2(PhysBody.ptr, ActivationState.ACTIVE_TAG); + + // There might be special things needed for implementing linksets. + Linkset.MakeDynamic(this); + } + } + + // "Making solid" means that other object will not pass through this object. + // To make transparent, we create a Bullet ghost object. + // Note: This expects to be called from the UpdatePhysicalParameters() routine as + // the functions after this one set up the state of a possibly newly created collision body. + private void MakeSolid(bool makeSolid) + { + CollisionObjectTypes bodyType = (CollisionObjectTypes)BulletSimAPI.GetBodyType2(PhysBody.ptr); + if (makeSolid) + { + // Verify the previous code created the correct shape for this type of thing. + if ((bodyType & CollisionObjectTypes.CO_RIGID_BODY) == 0) + { + m_log.ErrorFormat("{0} MakeSolid: physical body of wrong type for solidity. id={1}, type={2}", LogHeader, LocalID, bodyType); + } + CurrentCollisionFlags = BulletSimAPI.RemoveFromCollisionFlags2(PhysBody.ptr, CollisionFlags.CF_NO_CONTACT_RESPONSE); + } + else + { + if ((bodyType & CollisionObjectTypes.CO_GHOST_OBJECT) == 0) + { + m_log.ErrorFormat("{0} MakeSolid: physical body of wrong type for non-solidness. id={1}, type={2}", LogHeader, LocalID, bodyType); + } + CurrentCollisionFlags = BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, 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) + BulletSimAPI.Activate2(PhysBody.ptr, forceIt); + } + + // Turn on or off the flag controlling whether collision events are returned to the simulator. + private void EnableCollisions(bool wantsCollisionEvents) + { + if (wantsCollisionEvents) + { + CurrentCollisionFlags = BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + } + else + { + CurrentCollisionFlags = BulletSimAPI.RemoveFromCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_SUBSCRIBE_COLLISION_EVENTS); + } + } + + // prims don't fly + public override bool Flying { + get { return _flying; } + set { + _flying = value; + } + } + public override bool SetAlwaysRun { + get { return _setAlwaysRun; } + set { _setAlwaysRun = value; } + } + public override bool ThrottleUpdates { + get { return _throttleUpdates; } + set { _throttleUpdates = value; } + } + public override bool IsColliding { + get { return (CollidingStep == PhysicsScene.SimulationStep); } + set { _isColliding = value; } + } + public override bool CollidingGround { + get { return (CollidingGroundStep == PhysicsScene.SimulationStep); } + set { _collidingGround = value; } + } + public override bool CollidingObj { + get { return _collidingObj; } + set { _collidingObj = value; } + } + public bool IsPhantom { + get { + // SceneObjectPart removes phantom objects from the physics scene + // so, although we could implement touching and such, we never + // are invoked as a phantom object + return false; + } + } + public override bool FloatOnWater { + set { + _floatOnWater = value; + PhysicsScene.TaintedObject("BSPrim.setFloatOnWater", delegate() + { + if (_floatOnWater) + CurrentCollisionFlags = BulletSimAPI.AddToCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_FLOATS_ON_WATER); + else + CurrentCollisionFlags = BulletSimAPI.RemoveFromCollisionFlags2(PhysBody.ptr, CollisionFlags.BS_FLOATS_ON_WATER); + }); + } + } + public override OMV.Vector3 RotationalVelocity { + get { + return _rotationalVelocity; + } + set { + _rotationalVelocity = value; + // m_log.DebugFormat("{0}: RotationalVelocity={1}", LogHeader, _rotationalVelocity); + PhysicsScene.TaintedObject("BSPrim.setRotationalVelocity", delegate() + { + DetailLog("{0},BSPrim.SetRotationalVel,taint,rotvel={1}", LocalID, _rotationalVelocity); + ForceRotationalVelocity = _rotationalVelocity; + }); + } + } + public override OMV.Vector3 ForceRotationalVelocity { + get { + return _rotationalVelocity; + } + set { + _rotationalVelocity = value; + if (PhysBody.HasPhysicalBody) + { + BulletSimAPI.SetAngularVelocity2(PhysBody.ptr, _rotationalVelocity); + ActivateIfPhysical(false); + } + } + } + public override bool Kinematic { + get { return _kinematic; } + set { _kinematic = value; + // m_log.DebugFormat("{0}: Kinematic={1}", LogHeader, _kinematic); + } + } + public override float Buoyancy { + get { return _buoyancy; } + set { + _buoyancy = value; + PhysicsScene.TaintedObject("BSPrim.setBuoyancy", delegate() + { + ForceBuoyancy = _buoyancy; + }); + } + } + public override float ForceBuoyancy { + get { return _buoyancy; } + set { + _buoyancy = value; + // DetailLog("{0},BSPrim.setForceBuoyancy,taint,buoy={1}", LocalID, _buoyancy); + // Buoyancy is faked by changing the gravity applied to the object + if (PhysBody.HasPhysicalBody) + { + float grav = PhysicsScene.Params.gravity * (1f - _buoyancy); + BulletSimAPI.SetGravity2(PhysBody.ptr, new OMV.Vector3(0f, 0f, grav)); + ActivateIfPhysical(false); + } + } + } + + // Used for MoveTo + public override OMV.Vector3 PIDTarget { + set { _PIDTarget = value; } + } + public override bool PIDActive { + set { _usePID = value; } + } + public override float PIDTau { + 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) { + AddForce(force, pushforce, false); + } + // 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()) + { + 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) + { + BulletSimAPI.ApplyCentralForce2(PhysBody.ptr, addForce); + ActivateIfPhysical(false); + } + }); + } + else + { + m_log.WarnFormat("{0}: 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 AddAngularForce(OMV.Vector3 force, bool pushforce, bool inTaintTime) + { + if (force.IsFinite()) + { + OMV.Vector3 angForce = force; + PhysicsScene.TaintedObject(inTaintTime, "BSPrim.AddAngularForce", delegate() + { + if (PhysBody.HasPhysicalBody) + { + BulletSimAPI.ApplyTorque2(PhysBody.ptr, angForce); + ActivateIfPhysical(false); + } + }); + } + else + { + m_log.WarnFormat("{0}: Got a NaN force applied to a prim. LocalID={1}", LogHeader, LocalID); + return; + } + } + + // A torque impulse. + // ApplyTorqueImpulse adds torque directly to the angularVelocity. + // AddAngularForce accumulates the force and applied it to the angular velocity all at once. + // Computed as: angularVelocity += impulse * inertia; + public void ApplyTorqueImpulse(OMV.Vector3 impulse, bool inTaintTime) + { + OMV.Vector3 applyImpulse = impulse; + PhysicsScene.TaintedObject(inTaintTime, "BSPrim.ApplyTorqueImpulse", delegate() + { + if (PhysBody.HasPhysicalBody) + { + BulletSimAPI.ApplyTorqueImpulse2(PhysBody.ptr, applyImpulse); + ActivateIfPhysical(false); + } + }); + } + + public override void SetMomentum(OMV.Vector3 momentum) { + // DetailLog("{0},BSPrim.SetMomentum,call,mom={1}", LocalID, momentum); + } + #region Mass Calculation + + private float CalculateMass() + { + float volume = _size.X * _size.Y * _size.Z; // default + float tmp; + + float returnMass = 0; + float hollowAmount = (float)BaseShape.ProfileHollow * 2.0e-5f; + float hollowVolume = hollowAmount * hollowAmount; + + switch (BaseShape.ProfileShape) + { + case ProfileShape.Square: + // default box + + if (BaseShape.PathCurve == (byte)Extrusion.Straight) + { + if (hollowAmount > 0.0) + { + switch (BaseShape.HollowShape) + { + case HollowShape.Square: + case HollowShape.Same: + break; + + case HollowShape.Circle: + + hollowVolume *= 0.78539816339f; + break; + + case HollowShape.Triangle: + + hollowVolume *= (0.5f * .5f); + break; + + default: + hollowVolume = 0; + break; + } + volume *= (1.0f - hollowVolume); + } + } + + else if (BaseShape.PathCurve == (byte)Extrusion.Curve1) + { + //a tube + + volume *= 0.78539816339e-2f * (float)(200 - BaseShape.PathScaleX); + tmp= 1.0f -2.0e-2f * (float)(200 - BaseShape.PathScaleY); + volume -= volume*tmp*tmp; + + if (hollowAmount > 0.0) + { + hollowVolume *= hollowAmount; + + switch (BaseShape.HollowShape) + { + case HollowShape.Square: + case HollowShape.Same: + break; + + case HollowShape.Circle: + hollowVolume *= 0.78539816339f;; + break; + + case HollowShape.Triangle: + hollowVolume *= 0.5f * 0.5f; + break; + default: + hollowVolume = 0; + break; + } + volume *= (1.0f - hollowVolume); + } + } + + break; + + case ProfileShape.Circle: + + if (BaseShape.PathCurve == (byte)Extrusion.Straight) + { + volume *= 0.78539816339f; // elipse base + + if (hollowAmount > 0.0) + { + switch (BaseShape.HollowShape) + { + case HollowShape.Same: + case HollowShape.Circle: + break; + + case HollowShape.Square: + hollowVolume *= 0.5f * 2.5984480504799f; + break; + + case HollowShape.Triangle: + hollowVolume *= .5f * 1.27323954473516f; + break; + + default: + hollowVolume = 0; + break; + } + volume *= (1.0f - hollowVolume); + } + } + + else if (BaseShape.PathCurve == (byte)Extrusion.Curve1) + { + volume *= 0.61685027506808491367715568749226e-2f * (float)(200 - BaseShape.PathScaleX); + tmp = 1.0f - .02f * (float)(200 - BaseShape.PathScaleY); + volume *= (1.0f - tmp * tmp); + + if (hollowAmount > 0.0) + { + + // calculate the hollow volume by it's shape compared to the prim shape + hollowVolume *= hollowAmount; + + switch (BaseShape.HollowShape) + { + case HollowShape.Same: + case HollowShape.Circle: + break; + + case HollowShape.Square: + hollowVolume *= 0.5f * 2.5984480504799f; + break; + + case HollowShape.Triangle: + hollowVolume *= .5f * 1.27323954473516f; + break; + + default: + hollowVolume = 0; + break; + } + volume *= (1.0f - hollowVolume); + } + } + break; + + case ProfileShape.HalfCircle: + if (BaseShape.PathCurve == (byte)Extrusion.Curve1) + { + volume *= 0.52359877559829887307710723054658f; + } + break; + + case ProfileShape.EquilateralTriangle: + + if (BaseShape.PathCurve == (byte)Extrusion.Straight) + { + volume *= 0.32475953f; + + if (hollowAmount > 0.0) + { + + // calculate the hollow volume by it's shape compared to the prim shape + switch (BaseShape.HollowShape) + { + case HollowShape.Same: + case HollowShape.Triangle: + hollowVolume *= .25f; + break; + + case HollowShape.Square: + hollowVolume *= 0.499849f * 3.07920140172638f; + break; + + case HollowShape.Circle: + // Hollow shape is a perfect cyllinder in respect to the cube's scale + // Cyllinder hollow volume calculation + + hollowVolume *= 0.1963495f * 3.07920140172638f; + break; + + default: + hollowVolume = 0; + break; + } + volume *= (1.0f - hollowVolume); + } + } + else if (BaseShape.PathCurve == (byte)Extrusion.Curve1) + { + volume *= 0.32475953f; + volume *= 0.01f * (float)(200 - BaseShape.PathScaleX); + tmp = 1.0f - .02f * (float)(200 - BaseShape.PathScaleY); + volume *= (1.0f - tmp * tmp); + + if (hollowAmount > 0.0) + { + + hollowVolume *= hollowAmount; + + switch (BaseShape.HollowShape) + { + case HollowShape.Same: + case HollowShape.Triangle: + hollowVolume *= .25f; + break; + + case HollowShape.Square: + hollowVolume *= 0.499849f * 3.07920140172638f; + break; + + case HollowShape.Circle: + + hollowVolume *= 0.1963495f * 3.07920140172638f; + break; + + default: + hollowVolume = 0; + break; + } + volume *= (1.0f - hollowVolume); + } + } + break; + + default: + break; + } + + + + float taperX1; + float taperY1; + float taperX; + float taperY; + float pathBegin; + float pathEnd; + float profileBegin; + float profileEnd; + + if (BaseShape.PathCurve == (byte)Extrusion.Straight || BaseShape.PathCurve == (byte)Extrusion.Flexible) + { + taperX1 = BaseShape.PathScaleX * 0.01f; + if (taperX1 > 1.0f) + taperX1 = 2.0f - taperX1; + taperX = 1.0f - taperX1; + + taperY1 = BaseShape.PathScaleY * 0.01f; + if (taperY1 > 1.0f) + taperY1 = 2.0f - taperY1; + taperY = 1.0f - taperY1; + } + else + { + taperX = BaseShape.PathTaperX * 0.01f; + if (taperX < 0.0f) + taperX = -taperX; + taperX1 = 1.0f - taperX; + + taperY = BaseShape.PathTaperY * 0.01f; + if (taperY < 0.0f) + taperY = -taperY; + taperY1 = 1.0f - taperY; + + } + + + volume *= (taperX1 * taperY1 + 0.5f * (taperX1 * taperY + taperX * taperY1) + 0.3333333333f * taperX * taperY); + + pathBegin = (float)BaseShape.PathBegin * 2.0e-5f; + pathEnd = 1.0f - (float)BaseShape.PathEnd * 2.0e-5f; + volume *= (pathEnd - pathBegin); + + // this is crude aproximation + profileBegin = (float)BaseShape.ProfileBegin * 2.0e-5f; + 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 = Util.Clamp(returnMass, BSParam.MinimumObjectMass, BSParam.MaximumObjectMass); + + return returnMass; + }// end CalculateMass + #endregion Mass Calculation + + // Rebuild the geometry and object. + // This is called when the shape changes so we need to recreate the mesh/hull. + // 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) + { + // 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); + }); + + 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; + } + + // 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)) + { + // 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(); + } + /* + 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); + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSScene.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSScene.cs new file mode 100644 index 0000000000..6bcea3f0fc --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSScene.cs @@ -0,0 +1,954 @@ +/* + * 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.Runtime.InteropServices; +using System.Text; +using System.Threading; +using OpenSim.Framework; +using OpenSim.Region.Framework; +using OpenSim.Region.CoreModules; +using Logging = OpenSim.Region.CoreModules.Framework.Statistics.Logging; +using OpenSim.Region.Physics.Manager; +using Nini.Config; +using log4net; +using OpenMetaverse; + +// TODOs for BulletSim (for BSScene, BSPrim, BSCharacter and BulletSim) +// Based on material, set density and friction +// More efficient memory usage when passing hull information from BSPrim to BulletSim +// Do attachments need to be handled separately? Need collision events. Do not collide with VolumeDetect +// Implement LockAngularMotion +// Add PID movement operations. What does ScenePresence.MoveToTarget do? +// Check terrain size. 128 or 127? +// Raycast +// +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public sealed class BSScene : PhysicsScene, IPhysicsParameters +{ + private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + private static readonly string LogHeader = "[BULLETS SCENE]"; + + // The name of the region we're working for. + public string RegionName { get; private set; } + + public string BulletSimVersion = "?"; + + public Dictionary PhysObjects; + public BSShapeCollection Shapes; + + // Keeping track of the objects with collisions so we can report begin and end of a collision + public HashSet ObjectsWithCollisions = new HashSet(); + public HashSet ObjectsWithNoMoreCollisions = new HashSet(); + // Keep track of all the avatars so we can send them a collision event + // every tick so OpenSim will update its animation. + private HashSet m_avatars = new HashSet(); + + // let my minuions use my logger + public ILog Logger { get { return m_log; } } + + public IMesher mesher; + public uint WorldID { get; private set; } + public BulletSim World { get; private set; } + + // All the constraints that have been allocated in this instance. + public BSConstraintCollection Constraints { get; private set; } + + // Simulation parameters + internal int m_maxSubSteps; + internal float m_fixedTimeStep; + internal long m_simulationStep = 0; + 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; + + // 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 + public int SimulationNowTime { get; private set; } + + // True if initialized and ready to do simulation steps + private bool m_initialized = false; + + // Flag which is true when processing taints. + // Not guaranteed to be correct all the time (don't depend on this) but good for debugging. + public bool InTaintTime { get; private set; } + + // Pinned memory used to pass step information between managed and unmanaged + internal int m_maxCollisionsPerFrame; + private List m_collisionArray; + //private GCHandle m_collisionArrayPinnedHandle; + + internal int m_maxUpdatesPerFrame; + private List m_updateArray; + //private GCHandle m_updateArrayPinnedHandle; + + + public const uint TERRAIN_ID = 0; // OpenSim senses terrain with a localID of zero + public const uint GROUNDPLANE_ID = 1; + public const uint CHILDTERRAIN_ID = 2; // Terrain allocated based on our mega-prim childre start here + + public float SimpleWaterLevel { get; set; } + public BSTerrainManager TerrainManager { get; private set; } + + public ConfigurationParameters Params + { + get { return UnmanagedParams[0]; } + } + public Vector3 DefaultGravity + { + get { return new Vector3(0f, 0f, Params.gravity); } + } + // Just the Z value of the gravity + public float DefaultGravityZ + { + get { return Params.gravity; } + } + + // When functions in the unmanaged code must be called, it is only + // done at a known time just before the simulation step. The taint + // system saves all these function calls and executes them in + // order before the simulation. + public delegate void TaintCallback(); + private struct TaintCallbackEntry + { + public String ident; + public TaintCallback callback; + public TaintCallbackEntry(string i, TaintCallback c) + { + ident = i; + callback = c; + } + } + private Object _taintLock = new Object(); // lock for using the next object + private List _taintOperations; + private Dictionary _postTaintOperations; + private List _postStepOperations; + + // A pointer to an instance if this structure is passed to the C++ code + // Used to pass basic configuration values to the unmanaged code. + internal ConfigurationParameters[] UnmanagedParams; + //GCHandle m_paramsHandle; + + // Handle to the callback used by the unmanaged code to call into the managed code. + // Used for debug logging. + // Need to store the handle in a persistant variable so it won't be freed. + private BulletSimAPI.DebugLogCallback m_DebugLogCallbackHandle; + + // Sometimes you just have to log everything. + public Logging.LogWriter PhysicsLogging; + private bool m_physicsLoggingEnabled; + private string m_physicsLoggingDir; + private string m_physicsLoggingPrefix; + private int m_physicsLoggingFileMinutes; + private bool m_physicsLoggingDoFlush; + // '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) + { + m_initialized = false; + // we are passed the name of the region we're working for. + RegionName = identifier; + } + + public override void Initialise(IMesher meshmerizer, IConfigSource config) + { + mesher = meshmerizer; + _taintOperations = new List(); + _postTaintOperations = new Dictionary(); + _postStepOperations = new List(); + PhysObjects = new Dictionary(); + Shapes = new BSShapeCollection(this); + + // Allocate pinned memory to pass parameters. + UnmanagedParams = new ConfigurationParameters[1]; + //m_paramsHandle = GCHandle.Alloc(UnmanagedParams, GCHandleType.Pinned); + + // Set default values for physics parameters plus any overrides from the ini file + GetInitialParameterValues(config); + + // allocate more pinned memory close to the above in an attempt to get the memory all together + m_collisionArray = new List(); + //m_collisionArrayPinnedHandle = GCHandle.Alloc(m_collisionArray, GCHandleType.Pinned); + m_updateArray = new List(); + //m_updateArrayPinnedHandle = GCHandle.Alloc(m_updateArray, GCHandleType.Pinned); + + // Enable very detailed logging. + // By creating an empty logger when not logging, the log message invocation code + // can be left in and every call doesn't have to check for null. + if (m_physicsLoggingEnabled) + { + PhysicsLogging = new Logging.LogWriter(m_physicsLoggingDir, m_physicsLoggingPrefix, m_physicsLoggingFileMinutes); + PhysicsLogging.ErrorLogger = m_log; // for DEBUG. Let's the logger output error messages. + } + else + { + PhysicsLogging = new Logging.LogWriter(); + } + + // If Debug logging level, enable logging from the unmanaged code + m_DebugLogCallbackHandle = null; + if (m_log.IsDebugEnabled || PhysicsLogging.Enabled) + { + m_log.DebugFormat("{0}: Initialize: Setting debug callback for unmanaged code", LogHeader); + if (PhysicsLogging.Enabled) + // The handle is saved in a variable to make sure it doesn't get freed after this call + m_DebugLogCallbackHandle = new BulletSimAPI.DebugLogCallback(BulletLoggerPhysLog); + else + m_DebugLogCallbackHandle = new BulletSimAPI.DebugLogCallback(BulletLogger); + } + + // Get the version of the DLL + // TODO: this doesn't work yet. Something wrong with marshaling the returned string. + // BulletSimVersion = BulletSimAPI.GetVersion(); + // m_log.WarnFormat("{0}: BulletSim.dll version='{1}'", LogHeader, BulletSimVersion); + + // The bounding box for the simulated world. The origin is 0,0,0 unless we're + // a child in a mega-region. + // Bullet actually doesn't care about the extents of the simulated + // area. It tracks active objects no matter where they are. + Vector3 worldExtent = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight); + + // m_log.DebugFormat("{0}: Initialize: Calling BulletSimAPI.Initialize.", LogHeader); + + World = new BulletSim(0, this, BulletSimAPI.Initialize2(worldExtent, UnmanagedParams, + m_maxCollisionsPerFrame, ref m_collisionArray, + m_maxUpdatesPerFrame,ref m_updateArray, + m_DebugLogCallbackHandle)); + + Constraints = new BSConstraintCollection(World); + + TerrainManager = new BSTerrainManager(this); + TerrainManager.CreateInitialGroundPlaneAndTerrain(); + + m_log.WarnFormat("{0} Linksets implemented with {1}", LogHeader, (BSLinkset.LinksetImplementation)BSParam.LinksetImplementation); + + InTaintTime = false; + m_initialized = true; + } + + // All default parameter values are set here. There should be no values set in the + // variable definitions. + private void GetInitialParameterValues(IConfigSource config) + { + ConfigurationParameters parms = new ConfigurationParameters(); + UnmanagedParams[0] = parms; + + BSParam.SetParameterDefaultValues(this); + + if (config != null) + { + // If there are specifications in the ini file, use those values + IConfig pConfig = config.Configs["BulletSim"]; + if (pConfig != null) + { + BSParam.SetParameterConfigurationValues(this, pConfig); + + // Very detailed logging for physics debugging + m_physicsLoggingEnabled = pConfig.GetBoolean("PhysicsLoggingEnabled", false); + m_physicsLoggingDir = pConfig.GetString("PhysicsLoggingDir", "."); + m_physicsLoggingPrefix = pConfig.GetString("PhysicsLoggingPrefix", "physics-%REGIONNAME%-"); + m_physicsLoggingFileMinutes = pConfig.GetInt("PhysicsLoggingFileMinutes", 5); + m_physicsLoggingDoFlush = pConfig.GetBoolean("PhysicsLoggingDoFlush", false); + // Very detailed logging for vehicle debugging + VehicleLoggingEnabled = pConfig.GetBoolean("VehicleLoggingEnabled", false); + VehiclePhysicalLoggingEnabled = pConfig.GetBoolean("VehiclePhysicalLoggingEnabled", false); + + // Do any replacements in the parameters + m_physicsLoggingPrefix = m_physicsLoggingPrefix.Replace("%REGIONNAME%", RegionName); + } + + // The material characteristics. + BSMaterials.InitializeFromDefaults(Params); + if (pConfig != null) + { + // Let the user add new and interesting material property values. + BSMaterials.InitializefromParameters(pConfig); + } + } + } + + // A helper function that handles a true/false parameter and returns the proper float number encoding + float ParamBoolean(IConfig config, string parmName, float deflt) + { + float ret = deflt; + if (config.Contains(parmName)) + { + ret = ConfigurationParameters.numericFalse; + if (config.GetBoolean(parmName, false)) + { + ret = ConfigurationParameters.numericTrue; + } + } + return ret; + } + + // Called directly from unmanaged code so don't do much + private void BulletLogger(string msg) + { + m_log.Debug("[BULLETS UNMANAGED]:" + msg); + } + + // Called directly from unmanaged code so don't do much + private void BulletLoggerPhysLog(string msg) + { + DetailLog("[BULLETS UNMANAGED]:" + msg); + } + + public override void Dispose() + { + // m_log.DebugFormat("{0}: Dispose()", LogHeader); + + // make sure no stepping happens while we're deleting stuff + m_initialized = false; + + foreach (KeyValuePair kvp in PhysObjects) + { + kvp.Value.Destroy(); + } + PhysObjects.Clear(); + + // Now that the prims are all cleaned up, there should be no constraints left + if (Constraints != null) + { + Constraints.Dispose(); + Constraints = null; + } + + if (Shapes != null) + { + Shapes.Dispose(); + Shapes = null; + } + + if (TerrainManager != null) + { + TerrainManager.ReleaseGroundPlaneAndTerrain(); + TerrainManager.Dispose(); + TerrainManager = null; + } + + // Anything left in the unmanaged code should be cleaned out + BulletSimAPI.Shutdown2(World.ptr); + + // Not logging any more + PhysicsLogging.Close(); + } + #endregion // Construction and Initialization + + #region Prim and Avatar addition and removal + + public override PhysicsActor AddAvatar(string avName, Vector3 position, Vector3 size, bool isFlying) + { + m_log.ErrorFormat("{0}: CALL TO AddAvatar in BSScene. NOT IMPLEMENTED", LogHeader); + return null; + } + + public override PhysicsActor AddAvatar(uint localID, string avName, Vector3 position, Vector3 size, bool isFlying) + { + // m_log.DebugFormat("{0}: AddAvatar: {1}", LogHeader, avName); + + if (!m_initialized) return null; + + BSCharacter actor = new BSCharacter(localID, avName, this, position, size, isFlying); + 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); + + return actor; + } + + public override void RemoveAvatar(PhysicsActor actor) + { + // m_log.DebugFormat("{0}: RemoveAvatar", LogHeader); + + if (!m_initialized) return; + + BSCharacter bsactor = actor as BSCharacter; + if (bsactor != null) + { + try + { + lock (PhysObjects) PhysObjects.Remove(actor.LocalID); + // Remove kludge someday + lock (m_avatars) m_avatars.Remove(bsactor); + } + catch (Exception e) + { + m_log.WarnFormat("{0}: Attempt to remove avatar that is not in physics scene: {1}", LogHeader, e); + } + bsactor.Destroy(); + // bsactor.dispose(); + } + } + + public override void RemovePrim(PhysicsActor prim) + { + if (!m_initialized) return; + + BSPrim bsprim = prim as BSPrim; + if (bsprim != null) + { + DetailLog("{0},RemovePrim,call", bsprim.LocalID); + // m_log.DebugFormat("{0}: RemovePrim. id={1}/{2}", LogHeader, bsprim.Name, bsprim.LocalID); + try + { + lock (PhysObjects) PhysObjects.Remove(bsprim.LocalID); + } + catch (Exception e) + { + m_log.ErrorFormat("{0}: Attempt to remove prim that is not in physics scene: {1}", LogHeader, e); + } + bsprim.Destroy(); + // bsprim.dispose(); + } + else + { + m_log.ErrorFormat("{0}: Attempt to remove prim that is not a BSPrim type.", LogHeader); + } + } + + public override PhysicsActor AddPrimShape(string primName, PrimitiveBaseShape pbs, Vector3 position, + Vector3 size, Quaternion rotation, bool isPhysical, uint localID) + { + // m_log.DebugFormat("{0}: AddPrimShape2: {1}", LogHeader, primName); + + if (!m_initialized) return null; + + DetailLog("{0},AddPrimShape,call", localID); + + BSPrim prim = new BSPrim(localID, primName, this, position, size, rotation, pbs, isPhysical); + lock (PhysObjects) PhysObjects.Add(localID, prim); + return prim; + } + + // This is a call from the simulator saying that some physical property has been updated. + // The BulletSim driver senses the changing of relevant properties so this taint + // information call is not needed. + public override void AddPhysicsActorTaint(PhysicsActor prim) { } + + #endregion // Prim and Avatar addition and removal + + #region Simulation + // Simulate one timestep + public override float Simulate(float timeStep) + { + // prevent simulation until we've been initialized + if (!m_initialized) return 5.0f; + + LastTimeStep = timeStep; + + int updatedEntityCount = 0; + //Object updatedEntitiesPtr; + int collidersCount = 0; + //Object collidersPtr; + + int beforeTime = 0; + int simTime = 0; + + // update the prim states while we know the physics engine is not busy + int numTaints = _taintOperations.Count; + + InTaintTime = true; // Only used for debugging so locking is not necessary. + + ProcessTaints(); + + // Some of the physical objects requre individual, pre-step calls + TriggerPreStepEvent(timeStep); + + // the prestep actions might have added taints + ProcessTaints(); + + InTaintTime = false; // Only used for debugging so locking is not necessary. + + // step the physical world one interval + m_simulationStep++; + int numSubSteps = 0; + + try + { + //if (VehicleLoggingEnabled) DumpVehicles(); // DEBUG + if (PhysicsLogging.Enabled) beforeTime = Util.EnvironmentTickCount(); + + numSubSteps = BulletSimAPI.PhysicsStep2(World.ptr, timeStep, m_maxSubSteps, m_fixedTimeStep, + out updatedEntityCount, out m_updateArray, out collidersCount, out m_collisionArray); + + if (PhysicsLogging.Enabled) simTime = Util.EnvironmentTickCountSubtract(beforeTime); + DetailLog("{0},Simulate,call, frame={1}, nTaints={2}, simTime={3}, substeps={4}, updates={5}, colliders={6}, objWColl={7}", + DetailLogZero, m_simulationStep, numTaints, simTime, numSubSteps, + updatedEntityCount, collidersCount, ObjectsWithCollisions.Count); + } + catch (Exception e) + { + m_log.WarnFormat("{0},PhysicsStep Exception: nTaints={1}, substeps={2}, updates={3}, colliders={4}, e={5}", + LogHeader, numTaints, numSubSteps, updatedEntityCount, collidersCount, e); + DetailLog("{0},PhysicsStepException,call, nTaints={1}, substeps={2}, updates={3}, colliders={4}", + DetailLogZero, numTaints, numSubSteps, updatedEntityCount, collidersCount); + updatedEntityCount = 0; + collidersCount = 0; + } + + // Don't have to use the pointers passed back since we know it is the same pinned memory we passed in. + + // Get a value for 'now' so all the collision and update routines don't have to get their own. + SimulationNowTime = Util.EnvironmentTickCount(); + + // If there were collisions, process them by sending the event to the prim. + // Collisions must be processed before updates. + if (collidersCount > 0) + { + for (int ii = 0; ii < collidersCount; ii++) + { + uint cA = m_collisionArray[ii].aID; + uint cB = m_collisionArray[ii].bID; + Vector3 point = new Vector3(m_collisionArray[ii].point.X, m_collisionArray[ii].point.Y, + m_collisionArray[ii].point.Z); + Vector3 normal = new Vector3(m_collisionArray[ii].normal.X, m_collisionArray[ii].normal.Y, + m_collisionArray[ii].normal.Z); + SendCollision(cA, cB, point, normal, 0.01f); + SendCollision(cB, cA, point, -normal, 0.01f); + } + } + + // The above SendCollision's batch up the collisions on the objects. + // Now push the collisions into the simulator. + if (ObjectsWithCollisions.Count > 0) + { + foreach (BSPhysObject bsp in ObjectsWithCollisions) + if (!bsp.SendCollisions()) + { + // If the object is done colliding, see that it's removed from the colliding list + ObjectsWithNoMoreCollisions.Add(bsp); + } + } + + // This is a kludge to get avatar movement updates. + // The simulator expects collisions for avatars even if there are have been no collisions. + // The event updates avatar animations and stuff. + // If you fix avatar animation updates, remove this overhead and let normal collision processing happen. + foreach (BSPhysObject bsp in m_avatars) + if (!ObjectsWithCollisions.Contains(bsp)) // don't call avatars twice + bsp.SendCollisions(); + + // Objects that are done colliding are removed from the ObjectsWithCollisions list. + // Not done above because it is inside an iteration of ObjectWithCollisions. + // This complex collision processing is required to create an empty collision + // event call after all collisions have happened on an object. This enables + // the simulator to generate the 'collision end' event. + if (ObjectsWithNoMoreCollisions.Count > 0) + { + foreach (BSPhysObject po in ObjectsWithNoMoreCollisions) + ObjectsWithCollisions.Remove(po); + ObjectsWithNoMoreCollisions.Clear(); + } + // Done with collisions. + + // If any of the objects had updated properties, tell the object it has been changed by the physics engine + if (updatedEntityCount > 0) + { + for (int ii = 0; ii < updatedEntityCount; ii++) + { + + BulletXNA.EntityProperties entprop = m_updateArray[ii]; + BSPhysObject pobj; + if (PhysObjects.TryGetValue(entprop.ID, out pobj)) + { + EntityProperties prop = new EntityProperties() + { + Acceleration = new Vector3(entprop.Acceleration.X, entprop.Acceleration.Y, entprop.Acceleration.Z), + ID = entprop.ID, + Position = new Vector3(entprop.Position.X,entprop.Position.Y,entprop.Position.Z), + Rotation = new Quaternion(entprop.Rotation.X,entprop.Rotation.Y,entprop.Rotation.Z,entprop.Rotation.W), + RotationalVelocity = new Vector3(entprop.AngularVelocity.X,entprop.AngularVelocity.Y,entprop.AngularVelocity.Z), + Velocity = new Vector3(entprop.Velocity.X,entprop.Velocity.Y,entprop.Velocity.Z) + }; + //m_log.Debug(pobj.Name + ":" + prop.ToString() + "\n"); + pobj.UpdateProperties(prop); + } + } + } + + TriggerPostStepEvent(timeStep); + + // The following causes the unmanaged code to output ALL the values found in ALL the objects in the world. + // Only enable this in a limited test world with few objects. + // BulletSimAPI.DumpAllInfo2(World.ptr); // DEBUG DEBUG DEBUG + + // The physics engine returns the number of milliseconds it simulated this call. + // These are summed and normalized to one second and divided by 1000 to give the reported physics FPS. + // Multiply by 55 to give a nominal frame rate of 55. + return (float)numSubSteps * m_fixedTimeStep * 1000f * 55f; + } + + // Something has collided + private void SendCollision(uint localID, uint collidingWith, Vector3 collidePoint, Vector3 collideNormal, float penetration) + { + if (localID <= TerrainManager.HighestTerrainID) + { + return; // don't send collisions to the terrain + } + + BSPhysObject collider; + if (!PhysObjects.TryGetValue(localID, out collider)) + { + // If the object that is colliding cannot be found, just ignore the collision. + DetailLog("{0},BSScene.SendCollision,colliderNotInObjectList,id={1},with={2}", DetailLogZero, localID, collidingWith); + return; + } + + // The terrain is not in the physical object list so 'collidee' can be null when Collide() is called. + BSPhysObject collidee = null; + PhysObjects.TryGetValue(collidingWith, out collidee); + + // DetailLog("{0},BSScene.SendCollision,collide,id={1},with={2}", DetailLogZero, localID, collidingWith); + + if (collider.Collide(collidingWith, collidee, collidePoint, collideNormal, penetration)) + { + // If a collision was posted, remember to send it to the simulator + ObjectsWithCollisions.Add(collider); + } + + return; + } + + #endregion // Simulation + + public override void GetResults() { } + + #region Terrain + + public override void SetTerrain(float[] heightMap) { + TerrainManager.SetTerrain(heightMap); + } + + public override void SetWaterLevel(float baseheight) + { + SimpleWaterLevel = baseheight; + } + + public override void DeleteTerrain() + { + // m_log.DebugFormat("{0}: DeleteTerrain()", LogHeader); + } + + // Although no one seems to check this, I do support combining. + public override bool SupportsCombining() + { + return TerrainManager.SupportsCombining(); + } + // This call says I am a child to region zero in a mega-region. 'pScene' is that + // of region zero, 'offset' is my offset from regions zero's origin, and + // 'extents' is the largest XY that is handled in my region. + public override void Combine(PhysicsScene pScene, Vector3 offset, Vector3 extents) + { + TerrainManager.Combine(pScene, offset, extents); + } + + // Unhook all the combining that I know about. + public override void UnCombine(PhysicsScene pScene) + { + TerrainManager.UnCombine(pScene); + } + + #endregion // Terrain + + public override Dictionary GetTopColliders() + { + return new Dictionary(); + } + + public override bool IsThreaded { get { return false; } } + + #region Taints + // The simulation execution order is: + // Simulate() + // DoOneTimeTaints + // TriggerPreStepEvent + // DoOneTimeTaints + // Step() + // ProcessAndForwardCollisions + // ProcessAndForwardPropertyUpdates + // TriggerPostStepEvent + + // Calls to the PhysicsActors can't directly call into the physics engine + // because it might be busy. We delay changes to a known time. + // We rely on C#'s closure to save and restore the context for the delegate. + public void TaintedObject(String ident, TaintCallback callback) + { + if (!m_initialized) return; + + lock (_taintLock) + { + _taintOperations.Add(new TaintCallbackEntry(ident, callback)); + } + + return; + } + + // Sometimes a potentially tainted operation can be used in and out of taint time. + // This routine executes the command immediately if in taint-time otherwise it is queued. + public void TaintedObject(bool inTaintTime, string ident, TaintCallback callback) + { + if (inTaintTime) + callback(); + else + TaintedObject(ident, callback); + } + + private void TriggerPreStepEvent(float timeStep) + { + PreStepAction actions = BeforeStep; + if (actions != null) + actions(timeStep); + + } + + private void TriggerPostStepEvent(float timeStep) + { + PreStepAction actions = AfterStep; + if (actions != null) + actions(timeStep); + + } + + // When someone tries to change a property on a BSPrim or BSCharacter, the object queues + // a callback into itself to do the actual property change. That callback is called + // here just before the physics engine is called to step the simulation. + public void ProcessTaints() + { + ProcessRegularTaints(); + ProcessPostTaintTaints(); + } + + private void ProcessRegularTaints() + { + if (_taintOperations.Count > 0) // save allocating new list if there is nothing to process + { + // swizzle a new list into the list location so we can process what's there + List oldList; + lock (_taintLock) + { + oldList = _taintOperations; + _taintOperations = new List(); + } + + foreach (TaintCallbackEntry tcbe in oldList) + { + try + { + DetailLog("{0},BSScene.ProcessTaints,doTaint,id={1}", DetailLogZero, tcbe.ident); // DEBUG DEBUG DEBUG + tcbe.callback(); + } + catch (Exception e) + { + m_log.ErrorFormat("{0}: ProcessTaints: {1}: Exception: {2}", LogHeader, tcbe.ident, e); + } + } + oldList.Clear(); + } + } + + // Schedule an update to happen after all the regular taints are processed. + // Note that new requests for the same operation ("ident") for the same object ("ID") + // will replace any previous operation by the same object. + public void PostTaintObject(String ident, uint ID, TaintCallback callback) + { + string uniqueIdent = ident + "-" + ID.ToString(); + lock (_taintLock) + { + _postTaintOperations[uniqueIdent] = new TaintCallbackEntry(uniqueIdent, callback); + } + + return; + } + + // Taints that happen after the normal taint processing but before the simulation step. + private void ProcessPostTaintTaints() + { + if (_postTaintOperations.Count > 0) + { + Dictionary oldList; + lock (_taintLock) + { + oldList = _postTaintOperations; + _postTaintOperations = new Dictionary(); + } + + foreach (KeyValuePair kvp in oldList) + { + try + { + DetailLog("{0},BSScene.ProcessPostTaintTaints,doTaint,id={1}", DetailLogZero, kvp.Key); // DEBUG DEBUG DEBUG + kvp.Value.callback(); + } + catch (Exception e) + { + m_log.ErrorFormat("{0}: ProcessPostTaintTaints: {1}: Exception: {2}", LogHeader, kvp.Key, e); + } + } + oldList.Clear(); + } + } + + // Only used for debugging. Does not change state of anything so locking is not necessary. + public bool AssertInTaintTime(string whereFrom) + { + if (!InTaintTime) + { + 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(); // Prints the stack into the DEBUG log file. + } + 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() + { + BSParam.BuildParameterTable(); + return BSParam.SettableParameters; + } + + // Set parameter on a specific or all instances. + // Return 'false' if not able to set the parameter. + // Setting the value in the m_params block will change the value the physics engine + // 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) + { + bool ret = false; + BSParam.ParameterDefn theParam; + if (BSParam.TryGetParameter(parm, out theParam)) + { + theParam.setter(this, parm, localID, val); + 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) + { + float xval = val; + List xlIDs = lIDs; + string xparm = parm; + TaintedObject("BSScene.UpdateParameterSet", delegate() { + BSParam.ParameterDefn thisParam; + if (BSParam.TryGetParameter(xparm, out thisParam)) + { + if (thisParam.onObject != null) + { + foreach (uint lID in xlIDs) + { + BSPhysObject theObject = null; + PhysObjects.TryGetValue(lID, out theObject); + thisParam.onObject(this, theObject, xval); + } + } + } + }); + } + + // Get parameter. + // Return 'false' if not able to get the parameter. + public bool GetPhysicsParameter(string parm, out float value) + { + float val = 0f; + bool ret = false; + BSParam.ParameterDefn theParam; + if (BSParam.TryGetParameter(parm, out theParam)) + { + val = theParam.getter(this); + ret = true; + } + value = val; + return ret; + } + + #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) + { + PhysicsLogging.Write(msg, args); + // Add the Flush() if debugging crashes. Gets all the messages written out. + if (m_physicsLoggingDoFlush) PhysicsLogging.Flush(); + } + // Used to fill in the LocalID when there isn't one. It's the correct number of characters. + public const string DetailLogZero = "0000000000"; + +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSShapeCollection.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSShapeCollection.cs new file mode 100644 index 0000000000..398ece095c --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSShapeCollection.cs @@ -0,0 +1,1015 @@ +/* + * 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; +using OMV = OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Physics.Manager; +using OpenSim.Region.Physics.ConvexDecompositionDotNet; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public sealed class BSShapeCollection : IDisposable +{ + private static string LogHeader = "[BULLETSIM SHAPE COLLECTION]"; + + private BSScene PhysicsScene { get; set; } + + private Object m_collectionActivityLock = new Object(); + + // Description of a Mesh + private struct MeshDesc + { + public Object ptr; + 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 Object ptr; + 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; + // 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 + // statements can be commented/removed. + DDetail = true; + } + + public void Dispose() + { + // TODO!!!!!!!!! + } + + // Callbacks called just before either the body or shape is destroyed. + // 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); + + // 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. + // 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, BulletSim sim, BSPhysObject prim, + ShapeDestructionCallback shapeCallback, BodyDestructionCallback bodyCallback) + { + PhysicsScene.AssertInTaintTime("BSShapeCollection.GetBodyAndShape"); + + bool ret = false; + + // This lock could probably be pushed down lower but building shouldn't take long + lock (m_collectionActivityLock) + { + // 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); + // 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); + ret = newGeom || newBody; + } + DetailLog("{0},BSShapeCollection.GetBodyAndShape,taintExit,force={1},ret={2},body={3},shape={4}", + prim.LocalID, forceRebuild, ret, prim.PhysBody, prim.PhysShape); + + return ret; + } + + public bool GetBodyAndShape(bool forceRebuild, BulletSim sim, BSPhysObject prim) + { + return GetBodyAndShape(forceRebuild, sim, prim, null, 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) + { + lock (m_collectionActivityLock) + { + if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,newBody,body={1}", body.ID, body); + PhysicsScene.TaintedObject(inTaintTime, "BSShapeCollection.ReferenceBody", delegate() + { + if (!BulletSimAPI.IsInWorld2(PhysicsScene.World.ptr, body.ptr)) + { + BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, body.ptr); + 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 (BulletSimAPI.IsInWorld2(PhysicsScene.World.ptr, body.ptr)) + { + BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, body.ptr); + 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. + BulletSimAPI.SetCollisionShape2(PhysicsScene.World.ptr, body.ptr, null); + BulletSimAPI.DestroyObject2(PhysicsScene.World.ptr, body.ptr); + }); + } + } + + // 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.ptr = shape.ptr; + 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.ptr = shape.ptr; + 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.ptr.ToString(), inTaintTime); + if (shapeCallback != null) shapeCallback(shape); + BulletSimAPI.DeleteCollisionShape2(PhysicsScene.World.ptr, shape.ptr); + } + 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 (!BulletSimAPI.IsCompound2(shape.ptr)) + { + // 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.ptr.ToString()); + if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceCompound,notACompoundShape,type={1},ptr={2}", + BSScene.DetailLogZero, shape.type, shape.ptr.ToString()); + return; + } + + int numChildren = BulletSimAPI.GetNumberOfCompoundChildren2(shape.ptr); + if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceCompound,shape={1},children={2}", BSScene.DetailLogZero, shape, numChildren); + + for (int ii = numChildren - 1; ii >= 0; ii--) + { + Object childShape = BulletSimAPI.RemoveChildShapeFromCompoundShapeIndex2(shape.ptr, ii); + DereferenceAnonCollisionShape(childShape); + } + BulletSimAPI.DeleteCollisionShape2(PhysicsScene.World.ptr, shape.ptr); + } + + // 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(Object cShape) + { + MeshDesc meshDesc; + HullDesc hullDesc; + + BulletShape shapeInfo = new BulletShape(cShape); + if (TryGetMeshByPtr(cShape, out meshDesc)) + { + shapeInfo.type = BSPhysicsShapeType.SHAPE_MESH; + shapeInfo.shapeKey = meshDesc.shapeKey; + } + else + { + if (TryGetHullByPtr(cShape, out hullDesc)) + { + shapeInfo.type = BSPhysicsShapeType.SHAPE_HULL; + shapeInfo.shapeKey = hullDesc.shapeKey; + } + else + { + if (BulletSimAPI.IsCompound2(cShape)) + { + shapeInfo.type = BSPhysicsShapeType.SHAPE_COMPOUND; + } + else + { + if (BulletSimAPI.IsNativeShape2(cShape)) + { + 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, cShape.ToString()); + } + } + + // Create the geometry information in Bullet for later use. + // The objects needs a hull if it's physical otherwise a mesh is enough. + // if 'forceRebuild' is true, the geometry is unconditionally rebuilt. For meshes and hulls, + // shared geometries will be used. If the parameters of the existing shape are the same + // as this request, the shape is not rebuilt. + // 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) + { + bool ret = false; + bool haveShape = false; + bool nativeShapePossible = true; + PrimitiveBaseShape pbs = prim.BaseShape; + + // If the prim attributes are simple, this could be a simple Bullet native shape + 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) ) ) + { + // 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 = BulletSimAPI.GetLocalScaling2(prim.PhysShape.ptr); + + 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); + + // It doesn't look like Bullet scales 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 + ) + { + 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); + } + } + if (!haveShape && pbs.ProfileShape == ProfileShape.Square && pbs.PathCurve == (byte)Extrusion.Straight) + { + haveShape = true; + if (forceRebuild + || prim.Scale != scaleOfExistingShape + || prim.PhysShape.type != 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); + } + } + } + + // If a simple shape is not happening, create a mesh and possibly a hull. + if (!haveShape && pbs != null) + { + ret = CreateGeomMeshOrHull(prim, shapeCallback); + } + + return ret; + } + + public bool CreateGeomMeshOrHull(BSPhysObject prim, ShapeDestructionCallback shapeCallback) + { + + bool ret = false; + // Note that if it's a native shape, the check for physical/non-physical is not + // 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")); + } + 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")); + } + 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) + { + // 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) + { + // The proper scale has been calculated in the prim. + newShape = new BulletShape( + BulletSimAPI.BuildCapsuleShape2(PhysicsScene.World.ptr, 1f, 1f, prim.Scale) + , shapeType); + 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 = new BulletShape(BulletSimAPI.BuildNativeShape2(PhysicsScene.World.ptr, nativeShapeData), shapeType); + } + 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) + { + IMesh meshData = null; + Object meshPtr = null; + MeshDesc meshDesc; + if (Meshes.TryGetValue(newMeshKey, out meshDesc)) + { + // If the mesh has already been built just use it. + meshPtr = meshDesc.ptr; + } + else + { + meshData = PhysicsScene.mesher.CreateMesh(objName, pbs, size, lod, true, false); + + if (meshData != null) + { + 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); + + meshPtr = BulletSimAPI.CreateMeshShape2(PhysicsScene.World.ptr, + indices.GetLength(0), indices, vertices.Count, verticesAsFloats); + } + } + BulletShape newShape = new BulletShape(meshPtr, BSPhysicsShapeType.SHAPE_MESH); + 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) + { + BulletShape newShape; + + float lod; + System.UInt64 newHullKey = ComputeShapeKey(prim.Size, prim.BaseShape, out lod); + + // 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) + { + + Object hullPtr = null; + HullDesc hullDesc; + if (Hulls.TryGetValue(newHullKey, out hullDesc)) + { + // If the hull shape already is created, just use it. + hullPtr = hullDesc.ptr; + } + 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 + hullPtr = BulletSimAPI.CreateHullShape2(PhysicsScene.World.ptr, hullCount, convHulls); + } + } + + BulletShape newShape = new BulletShape(hullPtr, BSPhysicsShapeType.SHAPE_HULL); + 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 = new BulletShape( + BulletSimAPI.CreateCompoundShape2(PhysicsScene.World.ptr, false), BSPhysicsShapeType.SHAPE_COMPOUND); + + // Create the shape for the root prim and add it to the compound shape. Cannot be a native shape. + CreateGeomMeshOrHull(prim, shapeCallback); + BulletSimAPI.AddChildShapeToCompoundShape2(cShape.ptr, prim.PhysShape.ptr, 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, BulletSim sim, BulletShape shape, + BodyDestructionCallback bodyCallback) + { + bool ret = false; + + // the mesh, hull or native shape must have already been created in Bullet + bool mustRebuild = !prim.PhysBody.HasPhysicalBody; + + // If there is an existing body, verify it's of an acceptable type. + // If not a solid object, body is a GhostObject. Otherwise a RigidBody. + if (!mustRebuild) + { + CollisionObjectTypes bodyType = (CollisionObjectTypes)BulletSimAPI.GetBodyType2(prim.PhysBody.ptr); + 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 (mustRebuild || forceRebuild) + { + // Free any old body + DereferenceBody(prim.PhysBody, true, bodyCallback); + + BulletBody aBody; + Object bodyPtr = null; + if (prim.IsSolid) + { + bodyPtr = BulletSimAPI.CreateBodyFromShape2(sim.ptr, shape.ptr, + prim.LocalID, prim.RawPosition, prim.RawOrientation); + if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,mesh,ptr={1}", prim.LocalID, bodyPtr.ToString()); + } + else + { + bodyPtr = BulletSimAPI.CreateGhostFromShape2(sim.ptr, shape.ptr, + prim.LocalID, prim.RawPosition, prim.RawOrientation); + if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,ghost,ptr={1}", prim.LocalID, bodyPtr.ToString()); + } + aBody = new BulletBody(prim.LocalID, bodyPtr); + + ReferenceBody(aBody, true); + + prim.PhysBody = aBody; + + ret = true; + } + + return ret; + } + + private bool TryGetMeshByPtr(Object addr, out MeshDesc outDesc) + { + bool ret = false; + MeshDesc foundDesc = new MeshDesc(); + foreach (MeshDesc md in Meshes.Values) + { + if (md.ptr == addr) + { + foundDesc = md; + ret = true; + break; + } + + } + outDesc = foundDesc; + return ret; + } + + private bool TryGetHullByPtr(Object addr, out HullDesc outDesc) + { + bool ret = false; + HullDesc foundDesc = new HullDesc(); + foreach (HullDesc hd in Hulls.Values) + { + if (hd.ptr == addr) + { + 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); + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSShapes.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSShapes.cs new file mode 100644 index 0000000000..8ff027535c --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSShapes.cs @@ -0,0 +1,208 @@ +/* + * 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; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public abstract class BSShape +{ + public Object ptr { get; set; } + public BSPhysicsShapeType type { get; set; } + public System.UInt64 key { get; set; } + public int referenceCount { get; set; } + public DateTime lastReferenced { get; set; } + + public BSShape() + { + ptr = null; + type = BSPhysicsShapeType.SHAPE_UNKNOWN; + key = 0; + referenceCount = 0; + 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) + { + 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; + } + + // 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(); + + public override string ToString() + { + StringBuilder buff = new StringBuilder(); + buff.Append(""); + return buff.ToString(); + } +} + +public class BSShapeNull : BSShape +{ + public BSShapeNull() : base() + { + } + public static BSShape GetReference() { 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 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); + } + + private BSShapeNative(BSScene physicsScene, BSPhysObject prim, + BSPhysicsShapeType shapeType, FixedShapeKey shapeKey) + { + ShapeData nativeShapeData = new ShapeData(); + nativeShapeData.Type = shapeType; + nativeShapeData.ID = prim.LocalID; + nativeShapeData.Scale = prim.Scale; + nativeShapeData.Size = prim.Scale; + nativeShapeData.MeshKey = (ulong)shapeKey; + nativeShapeData.HullKey = (ulong)shapeKey; + + + if (shapeType == BSPhysicsShapeType.SHAPE_CAPSULE) + { + ptr = BulletSimAPI.BuildCapsuleShape2(physicsScene.World.ptr, 1f, 1f, prim.Scale); + physicsScene.DetailLog("{0},BSShapeCollection.BuiletPhysicalNativeShape,capsule,scale={1}", prim.LocalID, prim.Scale); + } + else + { + ptr = BulletSimAPI.BuildNativeShape2(physicsScene.World.ptr, nativeShapeData); + } + if (ptr == null) + { + 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); + BulletSimAPI.DeleteCollisionShape2(physicsScene.World.ptr, ptr); + ptr = null; + // Garbage collection will free up this instance. + } +} + +public class BSShapeMesh : BSShape +{ + private static string LogHeader = "[BULLETSIM SHAPE MESH]"; + private static Dictionary Meshes = new Dictionary(); + + public BSShapeMesh() : base() + { + } + public static BSShape GetReference() { return new BSShapeNull(); } + public override void Dereference(BSScene physicsScene) { } +} + +public class BSShapeHull : BSShape +{ + private static string LogHeader = "[BULLETSIM SHAPE HULL]"; + private static Dictionary Hulls = new Dictionary(); + + public BSShapeHull() : base() + { + } + public static BSShape GetReference() { return new BSShapeNull(); } + public override void Dereference(BSScene physicsScene) { } +} + +public class BSShapeCompound : BSShape +{ + private static string LogHeader = "[BULLETSIM SHAPE COMPOUND]"; + public BSShapeCompound() : base() + { + } + public static BSShape GetReference(BSPhysObject prim) + { + return new BSShapeNull(); + } + public override void Dereference(BSScene physicsScene) { } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainHeightmap.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainHeightmap.cs new file mode 100644 index 0000000000..252953ba58 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainHeightmap.cs @@ -0,0 +1,175 @@ +/* + * 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; + +using OpenSim.Framework; +using OpenSim.Region.Framework; +using OpenSim.Region.CoreModules; +using OpenSim.Region.Physics.Manager; + +using Nini.Config; +using log4net; + +using OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public sealed class BSTerrainHeightmap : BSTerrainPhys +{ + static string LogHeader = "[BULLETSIM TERRAIN HEIGHTMAP]"; + + BulletHeightMapInfo m_mapInfo = null; + + // Constructor to build a default, flat heightmap terrain. + public BSTerrainHeightmap(BSScene physicsScene, Vector3 regionBase, uint id, Vector3 regionSize) + : base(physicsScene, regionBase, id) + { + Vector3 minTerrainCoords = new Vector3(0f, 0f, BSTerrainManager.HEIGHT_INITIALIZATION - BSTerrainManager.HEIGHT_EQUAL_FUDGE); + Vector3 maxTerrainCoords = new Vector3(regionSize.X, regionSize.Y, BSTerrainManager.HEIGHT_INITIALIZATION); + int totalHeights = (int)maxTerrainCoords.X * (int)maxTerrainCoords.Y; + float[] initialMap = new float[totalHeights]; + for (int ii = 0; ii < totalHeights; ii++) + { + initialMap[ii] = BSTerrainManager.HEIGHT_INITIALIZATION; + } + m_mapInfo = new BulletHeightMapInfo(id, initialMap, null); + m_mapInfo.minCoords = minTerrainCoords; + m_mapInfo.maxCoords = maxTerrainCoords; + m_mapInfo.terrainRegionBase = TerrainBase; + // Don't have to free any previous since we just got here. + BuildHeightmapTerrain(); + } + + // 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, + Vector3 minCoords, Vector3 maxCoords) + : base(physicsScene, regionBase, id) + { + m_mapInfo = new BulletHeightMapInfo(id, initialMap, null); + m_mapInfo.minCoords = minCoords; + m_mapInfo.maxCoords = maxCoords; + m_mapInfo.minZ = minCoords.Z; + m_mapInfo.maxZ = maxCoords.Z; + m_mapInfo.terrainRegionBase = TerrainBase; + + // Don't have to free any previous since we just got here. + BuildHeightmapTerrain(); + } + + public override void Dispose() + { + ReleaseHeightMapTerrain(); + } + + // Using the information in m_mapInfo, create the physical representation of the heightmap. + private void BuildHeightmapTerrain() + { + m_mapInfo.Ptr = BulletSimAPI.CreateHeightMapInfo2(PhysicsScene.World.ptr, m_mapInfo.ID, + m_mapInfo.minCoords, m_mapInfo.maxCoords, + m_mapInfo.heightMap, BSParam.TerrainCollisionMargin); + + // Create the terrain shape from the mapInfo + m_mapInfo.terrainShape = new BulletShape(BulletSimAPI.CreateTerrainShape2(m_mapInfo.Ptr), + BSPhysicsShapeType.SHAPE_TERRAIN); + + // The terrain object initial position is at the center of the object + Vector3 centerPos; + centerPos.X = m_mapInfo.minCoords.X + (m_mapInfo.sizeX / 2f); + centerPos.Y = m_mapInfo.minCoords.Y + (m_mapInfo.sizeY / 2f); + centerPos.Z = m_mapInfo.minZ + ((m_mapInfo.maxZ - m_mapInfo.minZ) / 2f + 0.5f); + + m_mapInfo.terrainBody = new BulletBody(m_mapInfo.ID, + BulletSimAPI.CreateBodyWithDefaultMotionState2(m_mapInfo.terrainShape.ptr, + m_mapInfo.ID, centerPos, Quaternion.Identity)); + + // Set current terrain attributes + BulletSimAPI.SetFriction2(m_mapInfo.terrainBody.ptr, BSParam.TerrainFriction); + BulletSimAPI.SetHitFraction2(m_mapInfo.terrainBody.ptr, BSParam.TerrainHitFraction); + BulletSimAPI.SetRestitution2(m_mapInfo.terrainBody.ptr, BSParam.TerrainRestitution); + BulletSimAPI.SetCollisionFlags2(m_mapInfo.terrainBody.ptr, CollisionFlags.CF_STATIC_OBJECT); + + // Return the new terrain to the world of physical objects + BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, m_mapInfo.terrainBody.ptr, centerPos, Quaternion.Identity); + + // redo its bounding box now that it is in the world + BulletSimAPI.UpdateSingleAabb2(PhysicsScene.World.ptr, m_mapInfo.terrainBody.ptr); + + m_mapInfo.terrainBody.collisionType = CollisionType.Terrain; + m_mapInfo.terrainBody.ApplyCollisionMask(); + + // Make it so the terrain will not move or be considered for movement. + BulletSimAPI.ForceActivationState2(m_mapInfo.terrainBody.ptr, ActivationState.DISABLE_SIMULATION); + + return; + } + + // If there is information in m_mapInfo pointing to physical structures, release same. + private void ReleaseHeightMapTerrain() + { + if (m_mapInfo != null) + { + if (m_mapInfo.terrainBody.HasPhysicalBody) + { + BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, m_mapInfo.terrainBody.ptr); + // Frees both the body and the shape. + BulletSimAPI.DestroyObject2(PhysicsScene.World.ptr, m_mapInfo.terrainBody.ptr); + BulletSimAPI.ReleaseHeightMapInfo2(m_mapInfo.Ptr); + } + } + m_mapInfo = null; + } + + // The passed position is relative to the base of the region. + public override float GetTerrainHeightAtXYZ(Vector3 pos) + { + float ret = BSTerrainManager.HEIGHT_GETHEIGHT_RET; + + int mapIndex = (int)pos.Y * (int)m_mapInfo.sizeY + (int)pos.X; + try + { + ret = m_mapInfo.heightMap[mapIndex]; + } + 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}", + LogHeader, m_mapInfo.terrainRegionBase, pos); + ret = BSTerrainManager.HEIGHT_GETHEIGHT_RET; + } + return ret; + } + + // The passed position is relative to the base of the region. + public override float GetWaterLevelAtXYZ(Vector3 pos) + { + return PhysicsScene.SimpleWaterLevel; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs new file mode 100644 index 0000000000..dfad70ed2b --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainManager.cs @@ -0,0 +1,460 @@ +/* + * 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; + +using OpenSim.Framework; +using OpenSim.Region.Framework; +using OpenSim.Region.CoreModules; +using OpenSim.Region.Physics.Manager; + +using Nini.Config; +using log4net; + +using OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ + +// The physical implementation of the terrain is wrapped in this class. +public abstract class BSTerrainPhys : IDisposable +{ + public enum TerrainImplementation + { + Heightmap = 0, + Mesh = 1 + } + + public BSScene 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; + TerrainBase = regionBase; + ID = id; + } + public abstract void Dispose(); + public abstract float GetTerrainHeightAtXYZ(Vector3 pos); + public abstract float GetWaterLevelAtXYZ(Vector3 pos); +} + +// ========================================================================================== +public sealed class BSTerrainManager : IDisposable +{ + static string LogHeader = "[BULLETSIM TERRAIN MANAGER]"; + + // These height values are fractional so the odd values will be + // noticable when debugging. + public const float HEIGHT_INITIALIZATION = 24.987f; + public const float HEIGHT_INITIAL_LASTHEIGHT = 24.876f; + public const float HEIGHT_GETHEIGHT_RET = 24.765f; + public const float WATER_HEIGHT_GETHEIGHT_RET = 19.998f; + + // If the min and max height are equal, we reduce the min by this + // amount to make sure that a bounding box is built for the terrain. + public const float HEIGHT_EQUAL_FUDGE = 0.2f; + + // Until the whole simulator is changed to pass us the region size, we rely on constants. + public Vector3 DefaultRegionSize = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight); + + // The scene that I am part of + private BSScene PhysicsScene { get; set; } + + // The ground plane created to keep thing from falling to infinity. + private BulletBody m_groundPlane; + + // If doing mega-regions, if we're region zero we will be managing multiple + // region terrains since region zero does the physics for the whole mega-region. + private Dictionary m_terrains; + + // Flags used to know when to recalculate the height. + private bool m_terrainModified = false; + + // If we are doing mega-regions, terrains are added from TERRAIN_ID to m_terrainCount. + // This is incremented before assigning to new region so it is the last ID allocated. + private uint m_terrainCount = BSScene.CHILDTERRAIN_ID - 1; + public uint HighestTerrainID { get {return m_terrainCount; } } + + // If doing mega-regions, this holds our offset from region zero of + // the mega-regions. "parentScene" points to the PhysicsScene of region zero. + private Vector3 m_worldOffset; + // If the parent region (region 0), this is the extent of the combined regions + // relative to the origin of region zero + private Vector3 m_worldMax; + private PhysicsScene MegaRegionParentPhysicsScene { get; set; } + + public BSTerrainManager(BSScene physicsScene) + { + PhysicsScene = physicsScene; + m_terrains = new Dictionary(); + + // Assume one region of default size + m_worldOffset = Vector3.Zero; + m_worldMax = new Vector3(DefaultRegionSize); + MegaRegionParentPhysicsScene = null; + } + + public void Dispose() + { + ReleaseGroundPlaneAndTerrain(); + } + + // Create the initial instance of terrain and the underlying ground plane. + // This is called from the initialization routine so we presume it is + // safe to call Bullet in real time. We hope no one is moving prims around yet. + public void CreateInitialGroundPlaneAndTerrain() + { + // The ground plane is here to catch things that are trying to drop to negative infinity + BulletShape groundPlaneShape = new BulletShape( + BulletSimAPI.CreateGroundPlaneShape2(BSScene.GROUNDPLANE_ID, 1f, + BSParam.TerrainCollisionMargin), + BSPhysicsShapeType.SHAPE_GROUNDPLANE); + m_groundPlane = new BulletBody(BSScene.GROUNDPLANE_ID, + BulletSimAPI.CreateBodyWithDefaultMotionState2(groundPlaneShape.ptr, BSScene.GROUNDPLANE_ID, + Vector3.Zero, Quaternion.Identity)); + BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, m_groundPlane.ptr); + BulletSimAPI.UpdateSingleAabb2(PhysicsScene.World.ptr, m_groundPlane.ptr); + // Ground plane does not move + BulletSimAPI.ForceActivationState2(m_groundPlane.ptr, ActivationState.DISABLE_SIMULATION); + // Everything collides with the ground plane. + m_groundPlane.collisionType = CollisionType.Groundplane; + m_groundPlane.ApplyCollisionMask(); + + // 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); + } + + // Release all the terrain structures we might have allocated + public void ReleaseGroundPlaneAndTerrain() + { + if (m_groundPlane.HasPhysicalBody) + { + if (BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, m_groundPlane.ptr)) + { + BulletSimAPI.DestroyObject2(PhysicsScene.World.ptr, m_groundPlane.ptr); + } + m_groundPlane.Clear(); + } + + ReleaseTerrain(); + } + + // Release all the terrain we have allocated + public void ReleaseTerrain() + { + lock (m_terrains) + { + foreach (KeyValuePair kvp in m_terrains) + { + kvp.Value.Dispose(); + } + m_terrains.Clear(); + } + } + + // The simulator wants to set a new heightmap for the terrain. + public void SetTerrain(float[] heightMap) { + 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() + { + if (m_worldOffset != Vector3.Zero && MegaRegionParentPhysicsScene != null) + { + // If a child of a mega-region, we shouldn't have any terrain allocated for us + ReleaseGroundPlaneAndTerrain(); + // If doing the mega-prim stuff and we are the child of the zero region, + // 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); + } + } + else + { + // 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); + } + }); + } + + // If called with no mapInfo for the terrain, this will create a new mapInfo and terrain + // 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 + // 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) + { + DetailLog("{0},BSTerrainManager.UpdateTerrain,call,minC={1},maxC={2},inTaintTime={3}", + BSScene.DetailLogZero, minCoords, maxCoords, inTaintTime); + + // Find high and low points of passed heightmap. + // The min and max passed in is usually the area objects can be in (maximum + // object height, for instance). The terrain wants the bounding box for the + // terrain so replace passed min and max Z with the actual terrain min/max Z. + float minZ = float.MaxValue; + float maxZ = float.MinValue; + foreach (float height in heightMap) + { + if (height < minZ) minZ = height; + if (height > maxZ) maxZ = height; + } + if (minZ == maxZ) + { + // If min and max are the same, reduce min a little bit so a good bounding box is created. + minZ -= BSTerrainManager.HEIGHT_EQUAL_FUDGE; + } + minCoords.Z = minZ; + maxCoords.Z = maxZ; + + Vector3 terrainRegionBase = new Vector3(minCoords.X, minCoords.Y, 0f); + + lock (m_terrains) + { + BSTerrainPhys terrainPhys; + 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}", + BSScene.DetailLogZero, id, terrainRegionBase, minCoords, minCoords); + + // Remove old terrain from the collection + m_terrains.Remove(terrainRegionBase); + // Release any physical memory it may be using. + terrainPhys.Dispose(); + + if (MegaRegionParentPhysicsScene == null) + { + BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords); + m_terrains.Add(terrainRegionBase, newTerrainPhys); + + m_terrainModified = true; + } + else + { + // It's possible that Combine() was called after this code was queued. + // If we are a child of combined regions, we don't create any terrain for us. + DetailLog("{0},BSTerrainManager.UpdateTerrain:AmACombineChild,taint", BSScene.DetailLogZero); + + // Get rid of any terrain that may have been allocated for us. + ReleaseGroundPlaneAndTerrain(); + + // I hate doing this, but just bail + return; + } + } + else + { + // We don't know about this terrain so either we are creating a new terrain or + // our mega-prim child is giving us a new terrain to add to the phys world + + // if this is a child terrain, calculate a unique terrain id + uint newTerrainID = id; + if (newTerrainID >= BSScene.CHILDTERRAIN_ID) + newTerrainID = ++m_terrainCount; + + DetailLog("{0},UpdateTerrain:NewTerrain,taint,newID={1},minCoord={2},maxCoord={3}", + BSScene.DetailLogZero, newTerrainID, minCoords, minCoords); + BSTerrainPhys newTerrainPhys = BuildPhysicalTerrain(terrainRegionBase, id, heightMap, minCoords, maxCoords); + m_terrains.Add(terrainRegionBase, newTerrainPhys); + + m_terrainModified = true; + } + } + } + + // 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, + (BSTerrainPhys.TerrainImplementation)BSParam.TerrainImplementation); + BSTerrainPhys newTerrainPhys = null; + switch ((int)BSParam.TerrainImplementation) + { + case (int)BSTerrainPhys.TerrainImplementation.Heightmap: + newTerrainPhys = new BSTerrainHeightmap(PhysicsScene, terrainRegionBase, id, + heightMap, minCoords, maxCoords); + break; + case (int)BSTerrainPhys.TerrainImplementation.Mesh: + newTerrainPhys = new BSTerrainMesh(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, + BSParam.TerrainImplementation, + PhysicsScene.RegionName, terrainRegionBase); + break; + } + return newTerrainPhys; + } + + // Return 'true' of this position is somewhere in known physical terrain space + public bool IsWithinKnownTerrain(Vector3 pos) + { + Vector3 terrainBaseXYZ; + BSTerrainPhys physTerrain; + return GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ); + } + + // 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 + // the same size and that is the default. + // Once the heightMapInfo is found, we have all the information to + // compute the offset into the array. + private float lastHeightTX = 999999f; + private float lastHeightTY = 999999f; + private float lastHeight = HEIGHT_INITIAL_LASTHEIGHT; + public float GetTerrainHeightAtXYZ(Vector3 pos) + { + float tX = pos.X; + float tY = pos.Y; + // You'd be surprized at the number of times this routine is called + // with the same parameters as last time. + if (!m_terrainModified && (lastHeightTX == tX) && (lastHeightTY == tY)) + return lastHeight; + m_terrainModified = false; + + lastHeightTX = tX; + lastHeightTY = tY; + float ret = HEIGHT_GETHEIGHT_RET; + + Vector3 terrainBaseXYZ; + BSTerrainPhys physTerrain; + if (GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ)) + { + ret = physTerrain.GetTerrainHeightAtXYZ(pos - terrainBaseXYZ); + } + else + { + PhysicsScene.Logger.ErrorFormat("{0} GetTerrainHeightAtXY: terrain not found: region={1}, x={2}, y={3}", + LogHeader, PhysicsScene.RegionName, tX, tY); + DetailLog("{0},BSTerrainManager.GetTerrainHeightAtXYZ,terrainNotFound,pos={1},base={2}", + BSScene.DetailLogZero, pos, terrainBaseXYZ); + } + lastHeight = ret; + return ret; + } + + public float GetWaterLevelAtXYZ(Vector3 pos) + { + float ret = WATER_HEIGHT_GETHEIGHT_RET; + + Vector3 terrainBaseXYZ; + BSTerrainPhys physTerrain; + if (GetTerrainPhysicalAtXYZ(pos, out physTerrain, out terrainBaseXYZ)) + { + ret = physTerrain.GetWaterLevelAtXYZ(pos); + } + else + { + PhysicsScene.Logger.ErrorFormat("{0} GetWaterHeightAtXY: terrain not found: pos={1}, terrainBase={2}, height={3}", + LogHeader, PhysicsScene.RegionName, pos, terrainBaseXYZ, ret); + } + return ret; + } + + // Given an address, return 'true' of there is a description of that terrain and output + // 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); + + BSTerrainPhys physTerrain = null; + lock (m_terrains) + { + m_terrains.TryGetValue(terrainBaseXYZ, out physTerrain); + } + outTerrainBase = terrainBaseXYZ; + outPhysTerrain = physTerrain; + return (physTerrain != null); + } + + // Although no one seems to check this, I do support combining. + public bool SupportsCombining() + { + return true; + } + + // This routine is called two ways: + // One with 'offset' and 'pScene' zero and null but 'extents' giving the maximum + // extent of the combined regions. This is to inform the parent of the size + // of the combined regions. + // and one with 'offset' as the offset of the child region to the base region, + // 'pScene' pointing to the parent and 'extents' of zero. This informs the + // child of its relative base and new parent. + public void Combine(PhysicsScene pScene, Vector3 offset, Vector3 extents) + { + m_worldOffset = offset; + m_worldMax = extents; + MegaRegionParentPhysicsScene = pScene; + if (pScene != null) + { + // We are a child. + // We want m_worldMax to be the highest coordinate of our piece of terrain. + m_worldMax = offset + DefaultRegionSize; + } + DetailLog("{0},BSTerrainManager.Combine,offset={1},extents={2},wOffset={3},wMax={4}", + BSScene.DetailLogZero, offset, extents, m_worldOffset, m_worldMax); + } + + // Unhook all the combining that I know about. + public void UnCombine(PhysicsScene pScene) + { + // Just like ODE, we don't do anything yet. + DetailLog("{0},BSTerrainManager.UnCombine", BSScene.DetailLogZero); + } + + + private void DetailLog(string msg, params Object[] args) + { + PhysicsScene.PhysicsLogging.Write(msg, args); + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainMesh.cs b/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainMesh.cs new file mode 100644 index 0000000000..6083dd451a --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BSTerrainMesh.cs @@ -0,0 +1,267 @@ +/* + * 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; + +using OpenSim.Framework; +using OpenSim.Region.Framework; +using OpenSim.Region.CoreModules; +using OpenSim.Region.Physics.Manager; + +using Nini.Config; +using log4net; + +using OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +public sealed class BSTerrainMesh : BSTerrainPhys +{ + static string LogHeader = "[BULLETSIM TERRAIN MESH]"; + + private float[] m_savedHeightMap; + int m_sizeX; + int m_sizeY; + + BulletShape m_terrainShape; + BulletBody m_terrainBody; + + public BSTerrainMesh(BSScene physicsScene, Vector3 regionBase, uint id, Vector3 regionSize) + : base(physicsScene, regionBase, id) + { + } + + public BSTerrainMesh(BSScene physicsScene, Vector3 regionBase, uint id /* parameters for making mesh */) + : base(physicsScene, regionBase, id) + { + } + + // Create terrain mesh from a heightmap. + public BSTerrainMesh(BSScene physicsScene, Vector3 regionBase, uint id, float[] initialMap, + Vector3 minCoords, Vector3 maxCoords) + : base(physicsScene, regionBase, id) + { + int indicesCount; + int[] indices; + int verticesCount; + float[] vertices; + + m_savedHeightMap = initialMap; + + 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)) + { + // DISASTER!! + PhysicsScene.DetailLog("{0},BSTerrainMesh.create,failedConversionOfHeightmap", ID); + 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 = new BulletShape(BulletSimAPI.CreateMeshShape2(PhysicsScene.World.ptr, + indicesCount, indices, verticesCount, vertices), + BSPhysicsShapeType.SHAPE_MESH); + 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); + // Something is very messed up and a crash is in our future. + return; + } + + Vector3 pos = regionBase; + Quaternion rot = Quaternion.Identity; + + m_terrainBody = new BulletBody(id, BulletSimAPI.CreateBodyWithDefaultMotionState2( m_terrainShape.ptr, ID, pos, rot)); + if (!m_terrainBody.HasPhysicalBody) + { + // DISASTER!! + 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; + } + + // Set current terrain attributes + BulletSimAPI.SetFriction2(m_terrainBody.ptr, BSParam.TerrainFriction); + BulletSimAPI.SetHitFraction2(m_terrainBody.ptr, BSParam.TerrainHitFraction); + BulletSimAPI.SetRestitution2(m_terrainBody.ptr, BSParam.TerrainRestitution); + BulletSimAPI.SetCollisionFlags2(m_terrainBody.ptr, CollisionFlags.CF_STATIC_OBJECT); + + // Static objects are not very massive. + BulletSimAPI.SetMassProps2(m_terrainBody.ptr, 0f, Vector3.Zero); + + // Put the new terrain to the world of physical objects + BulletSimAPI.AddObjectToWorld2(PhysicsScene.World.ptr, m_terrainBody.ptr, pos, rot); + + // Redo its bounding box now that it is in the world + BulletSimAPI.UpdateSingleAabb2(PhysicsScene.World.ptr, m_terrainBody.ptr); + + m_terrainBody.collisionType = CollisionType.Terrain; + m_terrainBody.ApplyCollisionMask(); + + // Make it so the terrain will not move or be considered for movement. + BulletSimAPI.ForceActivationState2(m_terrainBody.ptr, ActivationState.DISABLE_SIMULATION); + } + + public override void Dispose() + { + if (m_terrainBody.HasPhysicalBody) + { + BulletSimAPI.RemoveObjectFromWorld2(PhysicsScene.World.ptr, m_terrainBody.ptr); + // Frees both the body and the shape. + BulletSimAPI.DestroyObject2(PhysicsScene.World.ptr, m_terrainBody.ptr); + } + } + + public override float GetTerrainHeightAtXYZ(Vector3 pos) + { + // For the moment use the saved heightmap to get the terrain height. + // TODO: raycast downward to find the true terrain below the position. + float ret = BSTerrainManager.HEIGHT_GETHEIGHT_RET; + + int mapIndex = (int)pos.Y * m_sizeY + (int)pos.X; + try + { + ret = m_savedHeightMap[mapIndex]; + } + 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}", + LogHeader, TerrainBase, pos); + ret = BSTerrainManager.HEIGHT_GETHEIGHT_RET; + } + return ret; + } + + // The passed position is relative to the base of the region. + public override float GetWaterLevelAtXYZ(Vector3 pos) + { + return PhysicsScene.SimpleWaterLevel; + } + + // Convert the passed heightmap to mesh information suitable for CreateMeshShape2(). + // Return 'true' if successfully created. + 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) + { + bool ret = false; + + int indicesCount = 0; + int verticesCount = 0; + int[] indices = new int[0]; + float[] vertices = new float[0]; + + // Simple mesh creation which assumes magnification == 1. + // TODO: do a more general solution that scales, adds new vertices and smoothes the result. + + // Create an array of vertices that is sizeX+1 by sizeY+1 (note the loop + // from zero to <= sizeX). The triangle indices are then generated as two triangles + // per heightmap point. There are sizeX by sizeY 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 + { + // One vertice per heightmap value plus the vertices off the top 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); + 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++) + { + for (int xx = 0; xx <= sizeX; xx++) // Hint: the "<=" means we go around sizeX + 1 times + { + int offset = yy * sizeX + xx; + // Extend the height with the height from the last row or column + if (yy == sizeY) offset -= sizeX; + 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 + 2] = height + extentBase.Z; + verticesCount += 3; + } + } + verticesCount = verticesCount / 3; + + for (int yy = 0; yy < sizeY; yy++) + { + for (int xx = 0; xx < sizeX; xx++) + { + int offset = yy * (sizeX + 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 + sizeX + 1; // accounting for the extra column + indices[indicesCount + 3] = offset + 1; + indices[indicesCount + 4] = offset + sizeX + 2; + indices[indicesCount + 5] = offset + sizeX + 1; + indicesCount += 6; + } + } + + ret = true; + } + catch (Exception e) + { + 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; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BulletSimAPI.cs b/OpenSim/Region/Physics/BulletSNPlugin/BulletSimAPI.cs new file mode 100644 index 0000000000..6af59d69b7 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BulletSimAPI.cs @@ -0,0 +1,1604 @@ +/* + * 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.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using BulletXNA; +using OpenMetaverse; +using BulletXNA.LinearMath; +using BulletXNA.BulletCollision; +using BulletXNA.BulletDynamics; +using BulletXNA.BulletCollision.CollisionDispatch; +using OpenSim.Framework; + +namespace OpenSim.Region.Physics.BulletSNPlugin { + +// Classes to allow some type checking for the API +// These hold pointers to allocated objects in the unmanaged space. + + + + // Constraint type values as defined by Bullet +public enum ConstraintType : int +{ + POINT2POINT_CONSTRAINT_TYPE = 3, + HINGE_CONSTRAINT_TYPE, + CONETWIST_CONSTRAINT_TYPE, + D6_CONSTRAINT_TYPE, + SLIDER_CONSTRAINT_TYPE, + CONTACT_CONSTRAINT_TYPE, + D6_SPRING_CONSTRAINT_TYPE, + MAX_CONSTRAINT_TYPE +} + + +// =============================================================================== +[StructLayout(LayoutKind.Sequential)] +public struct ConvexHull +{ + Vector3 Offset; + int VertexCount; + Vector3[] Vertices; +} +public enum BSPhysicsShapeType +{ + SHAPE_UNKNOWN = 0, + SHAPE_CAPSULE = 1, + SHAPE_BOX = 2, + SHAPE_CONE = 3, + SHAPE_CYLINDER = 4, + SHAPE_SPHERE = 5, + SHAPE_MESH = 6, + SHAPE_HULL = 7, + // following defined by BulletSim + SHAPE_GROUNDPLANE = 20, + SHAPE_TERRAIN = 21, + SHAPE_COMPOUND = 22, + SHAPE_HEIGHTMAP = 23, +}; + +// The native shapes have predefined shape hash keys +public enum FixedShapeKey : ulong +{ + KEY_NONE = 0, + KEY_BOX = 1, + KEY_SPHERE = 2, + KEY_CONE = 3, + KEY_CYLINDER = 4, + KEY_CAPSULE = 5, +} + +[StructLayout(LayoutKind.Sequential)] +public struct ShapeData +{ + public uint ID; + public BSPhysicsShapeType Type; + public Vector3 Position; + public Quaternion Rotation; + public Vector3 Velocity; + public Vector3 Scale; + public float Mass; + public float Buoyancy; + public System.UInt64 HullKey; + public System.UInt64 MeshKey; + public float Friction; + public float Restitution; + public float Collidable; // true of things bump into this + public float Static; // true if a static object. Otherwise gravity, etc. + public float Solid; // true if object cannot be passed through + public Vector3 Size; + + // note that bools are passed as floats since bool size changes by language and architecture + public const float numericTrue = 1f; + public const float numericFalse = 0f; +} +[StructLayout(LayoutKind.Sequential)] +public struct SweepHit +{ + public uint ID; + public float Fraction; + public Vector3 Normal; + public Vector3 Point; +} +[StructLayout(LayoutKind.Sequential)] +public struct RaycastHit +{ + public uint ID; + public float Fraction; + public Vector3 Normal; +} +[StructLayout(LayoutKind.Sequential)] +public struct CollisionDesc +{ + public uint aID; + public uint bID; + public Vector3 point; + public Vector3 normal; +} +[StructLayout(LayoutKind.Sequential)] +public struct EntityProperties +{ + public uint ID; + public Vector3 Position; + public Quaternion Rotation; + public Vector3 Velocity; + public Vector3 Acceleration; + public Vector3 RotationalVelocity; + public override string ToString() + { + return string.Format("ID:{0}, Pos:<{1:F},{2:F},{3:F}>, Rot:<{4:F},{5:F},{6:F},{7:F}>, LVel:<{8:F},{9:F},{10:F}>, AVel:<{11:F},{12:F},{13:F}>", + ID.ToString(), + Position.X,Position.Y,Position.Z, + Rotation.X,Rotation.Y,Rotation.Z,Rotation.W, + Velocity.X,Velocity.Y,Velocity.Z, + RotationalVelocity.X,RotationalVelocity.Y,RotationalVelocity.Z + ); + } +} + +// Format of this structure must match the definition in the C++ code +// NOTE: adding the X causes compile breaks if used. These are unused symbols +// that can be removed from both here and the unmanaged definition of this structure. +[StructLayout(LayoutKind.Sequential)] +public struct ConfigurationParameters +{ + public float defaultFriction; + public float defaultDensity; + public float defaultRestitution; + 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; + public float shouldForceUpdateAllAabbs; + public float shouldRandomizeSolverOrder; + public float shouldSplitSimulationIslands; + public float shouldEnableFrictionCaching; + public float numberOfSolverIterations; + + 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 physicsLoggingFrames; + + public const float numericTrue = 1f; + public const float numericFalse = 0f; +} + + +// The states a bullet collision object can have + +public enum ActivationState : uint +{ + UNDEFINED = 0, + ACTIVE_TAG = 1, + ISLAND_SLEEPING = 2, + WANTS_DEACTIVATION = 3, + DISABLE_DEACTIVATION = 4, + DISABLE_SIMULATION = 5, +} + +public enum CollisionObjectTypes : int +{ + CO_COLLISION_OBJECT = 1 << 0, + CO_RIGID_BODY = 1 << 1, + CO_GHOST_OBJECT = 1 << 2, + CO_SOFT_BODY = 1 << 3, + CO_HF_FLUID = 1 << 4, + CO_USER_TYPE = 1 << 5, +} + +// Values used by Bullet and BulletSim to control object properties. +// Bullet's "CollisionFlags" has more to do with operations on the +// object (if collisions happen, if gravity effects it, ...). + [Flags] +public enum CollisionFlags : uint +{ + CF_STATIC_OBJECT = 1 << 0, + CF_KINEMATIC_OBJECT = 1 << 1, + CF_NO_CONTACT_RESPONSE = 1 << 2, + CF_CUSTOM_MATERIAL_CALLBACK = 1 << 3, + CF_CHARACTER_OBJECT = 1 << 4, + 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_NONE = 0, + BS_ALL = 0xFFFFFFFF, + + // These are the collision flags switched depending on physical state. + // The other flags are used for other things and should not be fooled with. + BS_ACTIVE = CF_STATIC_OBJECT + | CF_KINEMATIC_OBJECT + | CF_NO_CONTACT_RESPONSE +}; + +// Values for collisions groups and masks +public enum CollisionFilterGroups : uint +{ + // Don't use the bit definitions!! Define the use in a + // filter/mask definition below. This way collision interactions + // are more easily debugged. + BNoneGroup = 0, + BDefaultGroup = 1 << 0, + BStaticGroup = 1 << 1, + BKinematicGroup = 1 << 2, + BDebrisGroup = 1 << 3, + BSensorTrigger = 1 << 4, + BCharacterGroup = 1 << 5, + BAllGroup = 0xFFFFFFFF, + // Filter groups defined by BulletSim + BGroundPlaneGroup = 1 << 10, + BTerrainGroup = 1 << 11, + BRaycastGroup = 1 << 12, + BSolidGroup = 1 << 13, + // BLinksetGroup = xx // a linkset proper is either static or dynamic + BLinksetChildGroup = 1 << 14, + // The collsion filters and masked are defined in one place -- don't want them scattered + AvatarGroup = BCharacterGroup, + AvatarMask = BAllGroup, + ObjectGroup = BSolidGroup, + ObjectMask = BAllGroup, + StaticObjectGroup = BStaticGroup, + StaticObjectMask = AvatarGroup | ObjectGroup, // static things don't interact with much + LinksetGroup = BLinksetChildGroup, + LinksetMask = BAllGroup & ~BLinksetChildGroup, // linkset objects don't collide with each other + VolumeDetectGroup = BSensorTrigger, + VolumeDetectMask = ~BSensorTrigger, + TerrainGroup = BTerrainGroup, + TerrainMask = BAllGroup & ~BStaticGroup, // static objects on the ground don't collide + GroundPlaneGroup = BGroundPlaneGroup, + GroundPlaneMask = BAllGroup + +}; + +// CFM controls the 'hardness' of the constraint. 0=fixed, 0..1=violatable. Default=0 +// ERP controls amount of correction per tick. Usable range=0.1..0.8. Default=0.2. +public enum ConstraintParams : int +{ + BT_CONSTRAINT_ERP = 1, // this one is not used in Bullet as of 20120730 + BT_CONSTRAINT_STOP_ERP, + BT_CONSTRAINT_CFM, + BT_CONSTRAINT_STOP_CFM, +}; +public enum ConstraintParamAxis : int +{ + AXIS_LINEAR_X = 0, + AXIS_LINEAR_Y, + AXIS_LINEAR_Z, + AXIS_ANGULAR_X, + AXIS_ANGULAR_Y, + AXIS_ANGULAR_Z, + AXIS_LINEAR_ALL = 20, // these last three added by BulletSim so we don't have to do zillions of calls + AXIS_ANGULAR_ALL, + AXIS_ALL +}; + +// =============================================================================== +static class BulletSimAPI { + private static int m_collisionsThisFrame; + public delegate void DebugLogCallback(string msg); + /// + /// + /// + /// + /// + internal static bool RemoveObjectFromWorld2(object pWorld, object pBody) + { + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + RigidBody body = pBody as RigidBody; + world.RemoveRigidBody(body); + return true; + } + + internal static void SetRestitution2(object pBody, float pRestitution) + { + RigidBody body = pBody as RigidBody; + body.SetRestitution(pRestitution); + } + + internal static void SetMargin2(object pShape, float pMargin) + { + CollisionShape shape = pShape as CollisionShape; + shape.SetMargin(pMargin); + } + + internal static void SetLocalScaling2(object pShape, Vector3 pScale) + { + CollisionShape shape = pShape as CollisionShape; + IndexedVector3 vec = new IndexedVector3(pScale.X, pScale.Y, pScale.Z); + shape.SetLocalScaling(ref vec); + + } + + internal static void SetContactProcessingThreshold2(object pBody, float contactprocessingthreshold) + { + RigidBody body = pBody as RigidBody; + body.SetContactProcessingThreshold(contactprocessingthreshold); + } + + internal static void SetCcdMotionThreshold2(object pBody, float pccdMotionThreashold) + { + RigidBody body = pBody as RigidBody; + body.SetCcdMotionThreshold(pccdMotionThreashold); + } + + internal static void SetCcdSweptSphereRadius2(object pBody, float pCcdSweptSphereRadius) + { + RigidBody body = pBody as RigidBody; + body.SetCcdSweptSphereRadius(pCcdSweptSphereRadius); + } + + internal static void SetAngularFactorV2(object pBody, Vector3 pAngularFactor) + { + RigidBody body = pBody as RigidBody; + body.SetAngularFactor(new IndexedVector3(pAngularFactor.X, pAngularFactor.Y, pAngularFactor.Z)); + } + + internal static CollisionFlags AddToCollisionFlags2(object pBody, CollisionFlags pcollisionFlags) + { + CollisionObject body = pBody as CollisionObject; + CollisionFlags existingcollisionFlags = (CollisionFlags)(uint)body.GetCollisionFlags(); + existingcollisionFlags |= pcollisionFlags; + body.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags)(uint)existingcollisionFlags); + return (CollisionFlags) (uint) existingcollisionFlags; + } + + internal static void AddObjectToWorld2(object pWorld, object pBody) + { + RigidBody body = pBody as RigidBody; + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + //if (!(body.GetCollisionShape().GetShapeType() == BroadphaseNativeTypes.STATIC_PLANE_PROXYTYPE && body.GetCollisionShape().GetShapeType() == BroadphaseNativeTypes.TERRAIN_SHAPE_PROXYTYPE)) + + world.AddRigidBody(body); + + //if (body.GetBroadphaseHandle() != null) + // world.UpdateSingleAabb(body); + } + + internal static void AddObjectToWorld2(object pWorld, object pBody, Vector3 _position, Quaternion _orientation) + { + RigidBody body = pBody as RigidBody; + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + //if (!(body.GetCollisionShape().GetShapeType() == BroadphaseNativeTypes.STATIC_PLANE_PROXYTYPE && body.GetCollisionShape().GetShapeType() == BroadphaseNativeTypes.TERRAIN_SHAPE_PROXYTYPE)) + + world.AddRigidBody(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); + //if (body.GetBroadphaseHandle() != null) + // world.UpdateSingleAabb(body); + } + + internal static void ForceActivationState2(object pBody, ActivationState pActivationState) + { + CollisionObject body = pBody as CollisionObject; + body.ForceActivationState((BulletXNA.BulletCollision.ActivationState)(uint)pActivationState); + } + + internal static void UpdateSingleAabb2(object pWorld, object pBody) + { + CollisionObject body = pBody as CollisionObject; + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + world.UpdateSingleAabb(body); + } + + internal static bool SetCollisionGroupMask2(object pBody, uint pGroup, uint pMask) + { + RigidBody body = pBody as RigidBody; + body.GetBroadphaseHandle().m_collisionFilterGroup = (BulletXNA.BulletCollision.CollisionFilterGroups) pGroup; + body.GetBroadphaseHandle().m_collisionFilterGroup = (BulletXNA.BulletCollision.CollisionFilterGroups) pGroup; + if ((uint) body.GetBroadphaseHandle().m_collisionFilterGroup == 0) + return false; + return true; + } + + internal static void ClearAllForces2(object pBody) + { + CollisionObject body = pBody as CollisionObject; + IndexedVector3 zeroVector = new IndexedVector3(0, 0, 0); + body.SetInterpolationLinearVelocity(ref zeroVector); + body.SetInterpolationAngularVelocity(ref zeroVector); + IndexedMatrix bodytransform = body.GetWorldTransform(); + + body.SetInterpolationWorldTransform(ref bodytransform); + + if (body is RigidBody) + { + RigidBody rigidbody = body as RigidBody; + rigidbody.SetLinearVelocity(zeroVector); + rigidbody.SetAngularVelocity(zeroVector); + rigidbody.ClearForces(); + } + } + + internal static void SetInterpolationAngularVelocity2(object pBody, Vector3 pVector3) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 vec = new IndexedVector3(pVector3.X, pVector3.Y, pVector3.Z); + body.SetInterpolationAngularVelocity(ref vec); + } + + internal static void SetAngularVelocity2(object pBody, Vector3 pVector3) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 vec = new IndexedVector3(pVector3.X, pVector3.Y, pVector3.Z); + body.SetAngularVelocity(ref vec); + } + + internal static void ClearForces2(object pBody) + { + RigidBody body = pBody as RigidBody; + body.ClearForces(); + } + + internal static void SetTranslation2(object pBody, Vector3 _position, Quaternion _orientation) + { + RigidBody body = pBody as RigidBody; + 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); + + } + + internal static Vector3 GetPosition2(object pBody) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 pos = body.GetInterpolationWorldTransform()._origin; + return new Vector3(pos.X, pos.Y, pos.Z); + } + + internal static Vector3 CalculateLocalInertia2(object pShape, float pphysMass) + { + CollisionShape shape = pShape as CollisionShape; + IndexedVector3 inertia = IndexedVector3.Zero; + shape.CalculateLocalInertia(pphysMass, out inertia); + return new Vector3(inertia.X, inertia.Y, inertia.Z); + } + + internal static void SetMassProps2(object pBody, float pphysMass, Vector3 plocalInertia) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 inertia = new IndexedVector3(plocalInertia.X, plocalInertia.Y, plocalInertia.Z); + body.SetMassProps(pphysMass, inertia); + } + + + internal static void SetObjectForce2(object pBody, Vector3 _force) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 force = new IndexedVector3(_force.X, _force.Y, _force.Z); + body.SetTotalForce(ref force); + } + + internal static void SetFriction2(object pBody, float _currentFriction) + { + RigidBody body = pBody as RigidBody; + body.SetFriction(_currentFriction); + } + + internal static void SetLinearVelocity2(object pBody, Vector3 _velocity) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 velocity = new IndexedVector3(_velocity.X, _velocity.Y, _velocity.Z); + body.SetLinearVelocity(velocity); + } + + internal static void Activate2(object pBody, bool pforceactivation) + { + RigidBody body = pBody as RigidBody; + body.Activate(pforceactivation); + + } + + internal static Quaternion GetOrientation2(object pBody) + { + RigidBody body = pBody as RigidBody; + IndexedQuaternion mat = body.GetInterpolationWorldTransform().GetRotation(); + return new Quaternion(mat.X, mat.Y, mat.Z, mat.W); + } + + internal static CollisionFlags RemoveFromCollisionFlags2(object pBody, CollisionFlags pcollisionFlags) + { + RigidBody body = pBody as RigidBody; + CollisionFlags existingcollisionFlags = (CollisionFlags)(uint)body.GetCollisionFlags(); + existingcollisionFlags &= ~pcollisionFlags; + body.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags)(uint)existingcollisionFlags); + return (CollisionFlags)(uint)existingcollisionFlags; + } + + internal static void SetGravity2(object pBody, Vector3 pGravity) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 gravity = new IndexedVector3(pGravity.X, pGravity.Y, pGravity.Z); + body.SetGravity(gravity); + } + + internal static bool DestroyConstraint2(object pBody, object pConstraint) + { + RigidBody body = pBody as RigidBody; + TypedConstraint constraint = pConstraint as TypedConstraint; + body.RemoveConstraintRef(constraint); + return true; + } + + internal static bool SetLinearLimits2(object pConstraint, Vector3 low, Vector3 high) + { + Generic6DofConstraint constraint = pConstraint 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); + constraint.SetLinearUpperLimit(highlimit); + return true; + } + + internal static bool SetAngularLimits2(object pConstraint, Vector3 low, Vector3 high) + { + Generic6DofConstraint constraint = pConstraint 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); + constraint.SetAngularUpperLimit(highlimit); + return true; + } + + internal static void SetConstraintNumSolverIterations2(object pConstraint, float cnt) + { + Generic6DofConstraint constraint = pConstraint as Generic6DofConstraint; + constraint.SetOverrideNumSolverIterations((int)cnt); + } + + internal static void CalculateTransforms2(object pConstraint) + { + Generic6DofConstraint constraint = pConstraint as Generic6DofConstraint; + constraint.CalculateTransforms(); + } + + internal static void SetConstraintEnable2(object pConstraint, float p_2) + { + Generic6DofConstraint constraint = pConstraint as Generic6DofConstraint; + constraint.SetEnabled((p_2 == 0) ? false : true); + } + + + //BulletSimAPI.Create6DofConstraint2(m_world.ptr, m_body1.ptr, m_body2.ptr,frame1, frame1rot,frame2, frame2rot,useLinearReferenceFrameA, disableCollisionsBetweenLinkedBodies)); + internal static object Create6DofConstraint2(object pWorld, object pBody1, object pBody2, Vector3 pframe1, Quaternion pframe1rot, Vector3 pframe2, Quaternion pframe2rot, bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) + + { + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + RigidBody body1 = pBody1 as RigidBody; + RigidBody body2 = pBody2 as 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; + + 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; + + Generic6DofConstraint consttr = new Generic6DofConstraint(body1, body2, ref frame1, ref frame2, + puseLinearReferenceFrameA); + consttr.CalculateTransforms(); + world.AddConstraint(consttr,pdisableCollisionsBetweenLinkedBodies); + + return consttr; + } + + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal static object Create6DofConstraintToPoint2(object pWorld, object pBody1, object pBody2, Vector3 pjoinPoint, bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) + { + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + RigidBody body1 = pBody1 as RigidBody; + RigidBody body2 = pBody2 as RigidBody; + IndexedMatrix frame1 = new IndexedMatrix(IndexedBasisMatrix.Identity, new IndexedVector3(0, 0, 0)); + IndexedMatrix frame2 = new IndexedMatrix(IndexedBasisMatrix.Identity, new IndexedVector3(0, 0, 0)); + + IndexedVector3 joinPoint = new IndexedVector3(pjoinPoint.X, pjoinPoint.Y, pjoinPoint.Z); + IndexedMatrix mat = IndexedMatrix.Identity; + mat._origin = new IndexedVector3(pjoinPoint.X, pjoinPoint.Y, pjoinPoint.Z); + frame1._origin = body1.GetWorldTransform().Inverse()*joinPoint; + frame2._origin = body2.GetWorldTransform().Inverse()*joinPoint; + + Generic6DofConstraint consttr = new Generic6DofConstraint(body1, body2, ref frame1, ref frame2, puseLinearReferenceFrameA); + consttr.CalculateTransforms(); + world.AddConstraint(consttr, pdisableCollisionsBetweenLinkedBodies); + + return consttr; + } + //SetFrames2(m_constraint.ptr, frameA, frameArot, frameB, frameBrot); + internal static void SetFrames2(object pConstraint, Vector3 pframe1, Quaternion pframe1rot, Vector3 pframe2, Quaternion pframe2rot) + { + Generic6DofConstraint constraint = pConstraint 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); + 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; + constraint.SetFrames(ref frame1, ref frame2); + } + + + + + internal static bool IsInWorld2(object pWorld, object pShapeObj) + { + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + CollisionObject shape = pShapeObj as CollisionObject; + return world.IsInWorld(shape); + } + + internal static void SetInterpolationLinearVelocity2(object pBody, Vector3 VehicleVelocity) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 velocity = new IndexedVector3(VehicleVelocity.X, VehicleVelocity.Y, VehicleVelocity.Z); + body.SetInterpolationLinearVelocity(ref velocity); + } + + internal static bool UseFrameOffset2(object pConstraint, float onOff) + { + Generic6DofConstraint constraint = pConstraint as Generic6DofConstraint; + constraint.SetUseFrameOffset((onOff == 0) ? false : true); + return true; + } + //SetBreakingImpulseThreshold2(m_constraint.ptr, threshold); + internal static bool SetBreakingImpulseThreshold2(object pConstraint, float threshold) + { + Generic6DofConstraint constraint = pConstraint as Generic6DofConstraint; + constraint.SetBreakingImpulseThreshold(threshold); + return true; + } + //BulletSimAPI.SetAngularDamping2(Prim.PhysBody.ptr, angularDamping); + internal static void SetAngularDamping2(object pBody, float angularDamping) + { + RigidBody body = pBody as RigidBody; + float lineardamping = body.GetLinearDamping(); + body.SetDamping(lineardamping, angularDamping); + + } + + internal static void UpdateInertiaTensor2(object pBody) + { + RigidBody body = pBody as RigidBody; + body.UpdateInertiaTensor(); + } + + internal static void RecalculateCompoundShapeLocalAabb2( object pCompoundShape) + { + + CompoundShape shape = pCompoundShape as CompoundShape; + shape.RecalculateLocalAabb(); + } + + //BulletSimAPI.GetCollisionFlags2(PhysBody.ptr) + internal static CollisionFlags GetCollisionFlags2(object pBody) + { + RigidBody body = pBody as RigidBody; + uint flags = (uint)body.GetCollisionFlags(); + return (CollisionFlags) flags; + } + + internal static void SetDamping2(object pBody, float pLinear, float pAngular) + { + RigidBody body = pBody as RigidBody; + body.SetDamping(pLinear, pAngular); + } + //PhysBody.ptr, PhysicsScene.Params.deactivationTime); + internal static void SetDeactivationTime2(object pBody, float pDeactivationTime) + { + RigidBody body = pBody as RigidBody; + body.SetDeactivationTime(pDeactivationTime); + } + //SetSleepingThresholds2(PhysBody.ptr, PhysicsScene.Params.linearSleepingThreshold, PhysicsScene.Params.angularSleepingThreshold); + internal static void SetSleepingThresholds2(object pBody, float plinearSleepingThreshold, float pangularSleepingThreshold) + { + RigidBody body = pBody as RigidBody; + body.SetSleepingThresholds(plinearSleepingThreshold, pangularSleepingThreshold); + } + + internal static CollisionObjectTypes GetBodyType2(object pBody) + { + RigidBody body = pBody as RigidBody; + return (CollisionObjectTypes)(int) body.GetInternalType(); + } + + //BulletSimAPI.ApplyCentralForce2(PhysBody.ptr, fSum); + internal static void ApplyCentralForce2(object pBody, Vector3 pfSum) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 fSum = new IndexedVector3(pfSum.X, pfSum.Y, pfSum.Z); + body.ApplyCentralForce(ref fSum); + } + internal static void ApplyCentralImpulse2(object pBody, Vector3 pfSum) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 fSum = new IndexedVector3(pfSum.X, pfSum.Y, pfSum.Z); + body.ApplyCentralImpulse(ref fSum); + } + internal static void ApplyTorque2(object pBody, Vector3 pfSum) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 fSum = new IndexedVector3(pfSum.X, pfSum.Y, pfSum.Z); + body.ApplyTorque(ref fSum); + } + internal static void ApplyTorqueImpulse2(object pBody, Vector3 pfSum) + { + RigidBody body = pBody as RigidBody; + IndexedVector3 fSum = new IndexedVector3(pfSum.X, pfSum.Y, pfSum.Z); + body.ApplyTorqueImpulse(ref fSum); + } + + internal static void DumpRigidBody2(object p, object p_2) + { + //TODO: + } + + internal static void DumpCollisionShape2(object p, object p_2) + { + //TODO: + } + + internal static void DestroyObject2(object p, object p_2) + { + //TODO: + } + + internal static void Shutdown2(object pWorld) + { + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + world.Cleanup(); + } + + internal static void DeleteCollisionShape2(object p, object p_2) + { + //TODO: + } + //(sim.ptr, shape.ptr, prim.LocalID, prim.RawPosition, prim.RawOrientation); + + internal static object CreateBodyFromShape2(object pWorld, object pShape, uint pLocalID, Vector3 pRawPosition, Quaternion pRawOrientation) + { + CollisionWorld world = pWorld as CollisionWorld; + 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 = pShape as CollisionShape; + //UpdateSingleAabb2(world, shape); + // TODO: Feed Update array into null + RigidBody body = new RigidBody(0,new SimMotionState(world,pLocalID,mat,null),shape,IndexedVector3.Zero); + + body.SetUserPointer(pLocalID); + return body; + } + + + internal static object CreateBodyWithDefaultMotionState2( object pShape, uint pLocalID, Vector3 pRawPosition, Quaternion pRawOrientation) + { + + 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 = pShape as CollisionShape; + + // TODO: Feed Update array into null + RigidBody body = new RigidBody(0, new DefaultMotionState( mat, IndexedMatrix.Identity), shape, IndexedVector3.Zero); + body.SetWorldTransform(mat); + body.SetUserPointer(pLocalID); + return body; + } + //(m_mapInfo.terrainBody.ptr, CollisionFlags.CF_STATIC_OBJECT); + internal static void SetCollisionFlags2(object pBody, CollisionFlags collisionFlags) + { + RigidBody body = pBody as RigidBody; + body.SetCollisionFlags((BulletXNA.BulletCollision.CollisionFlags) (uint) collisionFlags); + } + //(m_mapInfo.terrainBody.ptr, PhysicsScene.Params.terrainHitFraction); + internal static void SetHitFraction2(object pBody, float pHitFraction) + { + RigidBody body = pBody as RigidBody; + body.SetHitFraction(pHitFraction); + } + //BuildCapsuleShape2(physicsScene.World.ptr, 1f, 1f, prim.Scale); + internal static object BuildCapsuleShape2(object pWorld, float pRadius, float pHeight, Vector3 pScale) + { + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + IndexedVector3 scale = new IndexedVector3(pScale.X, pScale.Y, pScale.Z); + CapsuleShapeZ capsuleShapeZ = new CapsuleShapeZ(pRadius, pHeight); + capsuleShapeZ.SetMargin(world.WorldSettings.Params.collisionMargin); + capsuleShapeZ.SetLocalScaling(ref scale); + + return capsuleShapeZ; + } + + public static object Initialize2(Vector3 worldExtent, ConfigurationParameters[] o, int mMaxCollisionsPerFrame, ref List collisionArray, int mMaxUpdatesPerFrame, ref List updateArray, object mDebugLogCallbackHandle) + { + CollisionWorld.WorldData.ParamData p = new CollisionWorld.WorldData.ParamData(); + + p.angularDamping = o[0].XangularDamping; + p.defaultFriction = o[0].defaultFriction; + p.defaultFriction = o[0].defaultFriction; + p.defaultDensity = o[0].defaultDensity; + p.defaultRestitution = o[0].defaultRestitution; + 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.terrainImplementation = o[0].XterrainImplementation; + p.terrainFriction = o[0].XterrainFriction; + + p.terrainHitFraction = o[0].XterrainHitFraction; + p.terrainRestitution = o[0].XterrainRestitution; + p.terrainCollisionMargin = o[0].XterrainCollisionMargin; + + 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; + p.shouldForceUpdateAllAabbs = o[0].shouldForceUpdateAllAabbs; + p.shouldRandomizeSolverOrder = o[0].shouldRandomizeSolverOrder; + p.shouldSplitSimulationIslands = o[0].shouldSplitSimulationIslands; + 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].physicsLoggingFrames; + DefaultCollisionConstructionInfo ccci = new DefaultCollisionConstructionInfo(); + + DefaultCollisionConfiguration cci = new DefaultCollisionConfiguration(); + CollisionDispatcher m_dispatcher = new CollisionDispatcher(cci); + + + if (p.maxPersistantManifoldPoolSize > 0) + cci.m_persistentManifoldPoolSize = (int)p.maxPersistantManifoldPoolSize; + if (p.shouldDisableContactPoolDynamicAllocation !=0) + m_dispatcher.SetDispatcherFlags(DispatcherFlags.CD_DISABLE_CONTACTPOOL_DYNAMIC_ALLOCATION); + //if (p.maxCollisionAlgorithmPoolSize >0 ) + + DbvtBroadphase m_broadphase = new DbvtBroadphase(); + //IndexedVector3 aabbMin = new IndexedVector3(0, 0, 0); + //IndexedVector3 aabbMax = new IndexedVector3(256, 256, 256); + + //AxisSweep3Internal m_broadphase2 = new AxisSweep3Internal(ref aabbMin, ref aabbMax, Convert.ToInt32(0xfffe), 0xffff, ushort.MaxValue/2, null, true); + m_broadphase.GetOverlappingPairCache().SetInternalGhostPairCallback(new GhostPairCallback()); + + SequentialImpulseConstraintSolver m_solver = new SequentialImpulseConstraintSolver(); + + DiscreteDynamicsWorld world = new DiscreteDynamicsWorld(m_dispatcher, m_broadphase, m_solver, cci); + world.UpdatedObjects = updateArray; + world.UpdatedCollisions = collisionArray; + world.WorldSettings.Params = p; + world.SetForceUpdateAllAabbs(p.shouldForceUpdateAllAabbs != 0); + world.GetSolverInfo().m_solverMode = SolverMode.SOLVER_USE_WARMSTARTING | SolverMode.SOLVER_SIMD; + if (p.shouldRandomizeSolverOrder != 0) + world.GetSolverInfo().m_solverMode |= SolverMode.SOLVER_RANDMIZE_ORDER; + + world.GetSimulationIslandManager().SetSplitIslands(p.shouldSplitSimulationIslands != 0); + //world.GetDispatchInfo().m_enableSatConvex Not implemented in C# port + + if (p.shouldEnableFrictionCaching != 0) + world.GetSolverInfo().m_solverMode |= SolverMode.SOLVER_ENABLE_FRICTION_DIRECTION_CACHING; + + if (p.numberOfSolverIterations > 0) + world.GetSolverInfo().m_numIterations = (int) p.numberOfSolverIterations; + + + world.GetSolverInfo().m_damping = world.WorldSettings.Params.linearDamping; + world.GetSolverInfo().m_restitution = world.WorldSettings.Params.defaultRestitution; + world.GetSolverInfo().m_globalCfm = 0.0f; + world.GetSolverInfo().m_tau = 0.6f; + world.GetSolverInfo().m_friction = 0.3f; + world.GetSolverInfo().m_maxErrorReduction = 20f; + world.GetSolverInfo().m_numIterations = 10; + world.GetSolverInfo().m_erp = 0.2f; + world.GetSolverInfo().m_erp2 = 0.1f; + world.GetSolverInfo().m_sor = 1.0f; + world.GetSolverInfo().m_splitImpulse = false; + world.GetSolverInfo().m_splitImpulsePenetrationThreshold = -0.02f; + world.GetSolverInfo().m_linearSlop = 0.0f; + world.GetSolverInfo().m_warmstartingFactor = 0.85f; + world.GetSolverInfo().m_restingContactRestitutionThreshold = 2; + world.SetForceUpdateAllAabbs(true); + + + world.SetGravity(new IndexedVector3(0,0,p.gravity)); + + return world; + } + //m_constraint.ptr, ConstraintParams.BT_CONSTRAINT_STOP_CFM, cfm, ConstraintParamAxis.AXIS_ALL + internal static bool SetConstraintParam2(object pConstraint, ConstraintParams paramIndex, float paramvalue, ConstraintParamAxis axis) + { + Generic6DofConstraint constrain = pConstraint as Generic6DofConstraint; + if (axis == ConstraintParamAxis.AXIS_LINEAR_ALL || axis == ConstraintParamAxis.AXIS_ALL) + { + constrain.SetParam((BulletXNA.BulletDynamics.ConstraintParams) (int) paramIndex, paramvalue, 0); + constrain.SetParam((BulletXNA.BulletDynamics.ConstraintParams) (int) paramIndex, paramvalue, 1); + constrain.SetParam((BulletXNA.BulletDynamics.ConstraintParams) (int) paramIndex, paramvalue, 2); + } + if (axis == ConstraintParamAxis.AXIS_ANGULAR_ALL || axis == ConstraintParamAxis.AXIS_ALL) + { + constrain.SetParam((BulletXNA.BulletDynamics.ConstraintParams)(int)paramIndex, paramvalue, 3); + constrain.SetParam((BulletXNA.BulletDynamics.ConstraintParams)(int)paramIndex, paramvalue, 4); + constrain.SetParam((BulletXNA.BulletDynamics.ConstraintParams)(int)paramIndex, paramvalue, 5); + } + if (axis == ConstraintParamAxis.AXIS_LINEAR_ALL) + { + constrain.SetParam((BulletXNA.BulletDynamics.ConstraintParams)(int)paramIndex, paramvalue, (int)axis); + } + return true; + } + + internal static bool PushUpdate2(object pCollisionObject) + { + bool ret = false; + RigidBody rb = pCollisionObject as RigidBody; + if (rb != null) + { + SimMotionState sms = rb.GetMotionState() as SimMotionState; + if (sms != null) + { + IndexedMatrix wt = IndexedMatrix.Identity; + sms.GetWorldTransform(out wt); + sms.SetWorldTransform(ref wt, true); + ret = true; + } + } + return ret; + + } + + internal static bool IsCompound2(object pShape) + { + CollisionShape shape = pShape as CollisionShape; + return shape.IsCompound(); + } + internal static bool IsPloyhedral2(object pShape) + { + CollisionShape shape = pShape as CollisionShape; + return shape.IsPolyhedral(); + } + internal static bool IsConvex2d2(object pShape) + { + CollisionShape shape = pShape as CollisionShape; + return shape.IsConvex2d(); + } + internal static bool IsConvex2(object pShape) + { + CollisionShape shape = pShape as CollisionShape; + return shape.IsConvex(); + } + internal static bool IsNonMoving2(object pShape) + { + CollisionShape shape = pShape as CollisionShape; + return shape.IsNonMoving(); + } + internal static bool IsConcave2(object pShape) + { + CollisionShape shape = pShape as CollisionShape; + return shape.IsConcave(); + } + internal static bool IsInfinite2(object pShape) + { + CollisionShape shape = pShape as CollisionShape; + return shape.IsInfinite(); + } + internal static bool IsNativeShape2(object pShape) + { + CollisionShape shape = pShape as CollisionShape; + bool ret; + switch (shape.GetShapeType()) + { + case BroadphaseNativeTypes.BOX_SHAPE_PROXYTYPE: + case BroadphaseNativeTypes.CONE_SHAPE_PROXYTYPE: + case BroadphaseNativeTypes.SPHERE_SHAPE_PROXYTYPE: + case BroadphaseNativeTypes.CYLINDER_SHAPE_PROXYTYPE: + ret = true; + break; + default: + ret = false; + break; + } + return ret; + } + //sim.ptr, shape.ptr,prim.LocalID, prim.RawPosition, prim.RawOrientation + internal static object CreateGhostFromShape2(object pWorld, object pShape, uint pLocalID, Vector3 pRawPosition, Quaternion pRawOrientation) + { + 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 = pShape as CollisionShape; + gObj.SetCollisionShape(shape); + gObj.SetUserPointer(pLocalID); + // TODO: Add to Special CollisionObjects! + return gObj; + } + + public static void SetCollisionShape2(object pWorld, object pObj, object pShape) + { + var world = pWorld as DiscreteDynamicsWorld; + var obj = pObj as CollisionObject; + var shape = pShape as CollisionShape; + obj.SetCollisionShape(shape); + + } + //(PhysicsScene.World.ptr, nativeShapeData) + internal static object BuildNativeShape2(object pWorld, ShapeData pShapeData) + { + var world = pWorld as DiscreteDynamicsWorld; + CollisionShape shape = null; + switch (pShapeData.Type) + { + case BSPhysicsShapeType.SHAPE_BOX: + shape = new BoxShape(new IndexedVector3(0.5f,0.5f,0.5f)); + break; + case BSPhysicsShapeType.SHAPE_CONE: + shape = new ConeShapeZ(0.5f, 1.0f); + break; + case BSPhysicsShapeType.SHAPE_CYLINDER: + shape = new CylinderShapeZ(new IndexedVector3(0.5f, 0.5f, 0.5f)); + break; + case BSPhysicsShapeType.SHAPE_SPHERE: + shape = new SphereShape(0.5f); + break; + + } + if (shape != null) + { + IndexedVector3 scaling = new IndexedVector3(pShapeData.Scale.X, pShapeData.Scale.Y, pShapeData.Scale.Z); + shape.SetMargin(world.WorldSettings.Params.collisionMargin); + shape.SetLocalScaling(ref scaling); + + } + return shape; + } + //PhysicsScene.World.ptr, false + internal static object CreateCompoundShape2(object pWorld, bool enableDynamicAabbTree) + { + return new CompoundShape(enableDynamicAabbTree); + } + + internal static int GetNumberOfCompoundChildren2(object pCompoundShape) + { + var compoundshape = pCompoundShape as CompoundShape; + return compoundshape.GetNumChildShapes(); + } + //LinksetRoot.PhysShape.ptr, newShape.ptr, displacementPos, displacementRot + internal static void AddChildShapeToCompoundShape2(object pCShape, object paddShape, Vector3 displacementPos, Quaternion displacementRot) + { + IndexedMatrix relativeTransform = new IndexedMatrix(); + var compoundshape = pCShape as CompoundShape; + var addshape = paddShape as CollisionShape; + + relativeTransform._origin = new IndexedVector3(displacementPos.X, displacementPos.Y, displacementPos.Z); + relativeTransform.SetRotation(new IndexedQuaternion(displacementRot.X,displacementRot.Y,displacementRot.Z,displacementRot.W)); + compoundshape.AddChildShape(ref relativeTransform, addshape); + + } + + internal static object RemoveChildShapeFromCompoundShapeIndex2(object pCShape, int pii) + { + var compoundshape = pCShape as CompoundShape; + CollisionShape ret = null; + ret = compoundshape.GetChildShape(pii); + compoundshape.RemoveChildShapeByIndex(pii); + return ret; + } + + internal static object CreateGroundPlaneShape2(uint pLocalId, float pheight, float pcollisionMargin) + { + StaticPlaneShape m_planeshape = new StaticPlaneShape(new IndexedVector3(0,0,1),(int)pheight ); + m_planeshape.SetMargin(pcollisionMargin); + m_planeshape.SetUserPointer(pLocalId); + return m_planeshape; + } + + internal static object CreateHingeConstraint2(object pWorld, object pBody1, object ppBody2, Vector3 ppivotInA, Vector3 ppivotInB, Vector3 paxisInA, Vector3 paxisInB, bool puseLinearReferenceFrameA, bool pdisableCollisionsBetweenLinkedBodies) + { + HingeConstraint constrain = null; + var rb1 = pBody1 as RigidBody; + var rb2 = ppBody2 as 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); + var world = pWorld as DiscreteDynamicsWorld; + world.AddConstraint(constrain, pdisableCollisionsBetweenLinkedBodies); + } + return constrain; + } + + internal static bool ReleaseHeightMapInfo2(object pMapInfo) + { + if (pMapInfo != null) + { + BulletHeightMapInfo mapinfo = pMapInfo as BulletHeightMapInfo; + if (mapinfo.heightMap != null) + mapinfo.heightMap = null; + + + } + return true; + } + + internal static object CreateHullShape2(object pWorld, int pHullCount, float[] pConvHulls) + { + CompoundShape compoundshape = new CompoundShape(false); + var world = pWorld as DiscreteDynamicsWorld; + + + compoundshape.SetMargin(world.WorldSettings.Params.collisionMargin); + int ii = 1; + + for (int i = 0; i < pHullCount; i++) + { + int vertexCount = (int) pConvHulls[ii]; + + IndexedVector3 centroid = new IndexedVector3(pConvHulls[ii + 1], pConvHulls[ii + 2], pConvHulls[ii + 3]); + IndexedMatrix childTrans = IndexedMatrix.Identity; + childTrans._origin = centroid; + + List virts = new List(); + 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); + convexShape.SetMargin(world.WorldSettings.Params.collisionMargin); + compoundshape.AddChildShape(ref childTrans, convexShape); + ii += (vertexCount*3 + 4); + } + + + return compoundshape; + } + + internal static object CreateMeshShape2(object 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); + var world = pWorld as DiscreteDynamicsWorld; + IndexedMesh mesh = new IndexedMesh(); + mesh.m_indexType = PHY_ScalarType.PHY_INTEGER; + mesh.m_numTriangles = pIndicesCount/3; + mesh.m_numVertices = pVerticesCount; + mesh.m_triangleIndexBase = indicesarr; + mesh.m_vertexBase = vertices; + 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); + meshShape.SetMargin(world.WorldSettings.Params.collisionMargin); + // world.UpdateSingleAabb(meshShape); + return meshShape; + + } + 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); + IndexedMesh mesh = new IndexedMesh(); + + mesh.m_indexType = PHY_ScalarType.PHY_INTEGER; + mesh.m_numTriangles = pIndicesCount / 3; + mesh.m_numVertices = pVerticesCount; + mesh.m_triangleIndexBase = indices; + mesh.m_vertexBase = vertices; + 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); + + + + for (int i = 0; i < pVerticesCount; i++) + { + + 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"); + } + + sw.Close(); + } + public static void DumpRaw(int[] indices, float[] vertices, int pIndicesCount, int pVerticesCount) + { + + String fileName = "objTest6.raw"; + String completePath = System.IO.Path.Combine(Util.configDir(), fileName); + StreamWriter sw = new StreamWriter(completePath); + IndexedMesh mesh = new IndexedMesh(); + + mesh.m_indexType = PHY_ScalarType.PHY_INTEGER; + mesh.m_numTriangles = pIndicesCount / 3; + mesh.m_numVertices = pVerticesCount; + mesh.m_triangleIndexBase = indices; + mesh.m_vertexBase = vertices; + 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); + + + sw.WriteLine("Indices"); + sw.WriteLine(string.Format("int[] indices = new int[{0}];",pIndicesCount)); + for (int iter = 0; iter < indices.Length; iter++) + { + sw.WriteLine(string.Format("indices[{0}]={1};",iter,indices[iter])); + } + sw.WriteLine("VerticesFloats"); + sw.WriteLine(string.Format("float[] vertices = new float[{0}];", pVerticesCount)); + for (int iter = 0; iter < vertices.Length; iter++) + { + sw.WriteLine(string.Format("Vertices[{0}]={1};", iter, vertices[iter].ToString("0.0000"))); + } + + // for (int i = 0; i < pVerticesCount; i++) + // { + // + // 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"); + //} + + sw.Close(); + } + //PhysicsScene.World.ptr, m_mapInfo.ID, m_mapInfo.minCoords, m_mapInfo.maxCoords, m_mapInfo.heightMap, PhysicsScene.Params.terrainCollisionMargin + internal static object CreateHeightMapInfo2(object pWorld, uint pId, Vector3 pminCoords, Vector3 pmaxCoords, float[] pheightMap, float pCollisionMargin) + { + BulletHeightMapInfo mapInfo = new BulletHeightMapInfo(pId, pheightMap, null); + mapInfo.heightMap = null; + mapInfo.minCoords = pminCoords; + mapInfo.maxCoords = pmaxCoords; + mapInfo.sizeX = (int) (pmaxCoords.X - pminCoords.X); + mapInfo.sizeY = (int) (pmaxCoords.Y - pminCoords.Y); + mapInfo.ID = pId; + mapInfo.minZ = pminCoords.Z; + mapInfo.maxZ = pmaxCoords.Z; + mapInfo.collisionMargin = pCollisionMargin; + if (mapInfo.minZ == mapInfo.maxZ) + mapInfo.minZ -= 0.2f; + mapInfo.heightMap = pheightMap; + + return mapInfo; + + } + + internal static object CreateTerrainShape2(object pMapInfo) + { + BulletHeightMapInfo mapinfo = pMapInfo as BulletHeightMapInfo; + const int upAxis = 2; + const float scaleFactor = 1.0f; + HeightfieldTerrainShape terrainShape = new HeightfieldTerrainShape((int)mapinfo.sizeX, (int)mapinfo.sizeY, + mapinfo.heightMap, scaleFactor, + mapinfo.minZ, mapinfo.maxZ, upAxis, + false); + terrainShape.SetMargin(mapinfo.collisionMargin + 0.5f); + terrainShape.SetUseDiamondSubdivision(true); + terrainShape.SetUserPointer(mapinfo.ID); + return terrainShape; + } + + internal static bool TranslationalLimitMotor2(object pConstraint, float ponOff, float targetVelocity, float maxMotorForce) + { + TypedConstraint tconstrain = pConstraint as TypedConstraint; + bool onOff = ponOff != 0; + bool ret = false; + + switch (tconstrain.GetConstraintType()) + { + case TypedConstraintType.D6_CONSTRAINT_TYPE: + Generic6DofConstraint constrain = pConstraint as Generic6DofConstraint; + constrain.GetTranslationalLimitMotor().m_enableMotor[0] = onOff; + constrain.GetTranslationalLimitMotor().m_targetVelocity[0] = targetVelocity; + constrain.GetTranslationalLimitMotor().m_maxMotorForce[0] = maxMotorForce; + ret = true; + break; + } + + + return ret; + + } + + internal static int PhysicsStep2(object pWorld, float timeStep, int m_maxSubSteps, float m_fixedTimeStep, out int updatedEntityCount, out List updatedEntities, out int collidersCount, out Listcolliders) + { + int epic = PhysicsStepint2(pWorld, timeStep, m_maxSubSteps, m_fixedTimeStep, out updatedEntityCount, out updatedEntities, + out collidersCount, out colliders); + return epic; + } + + private static int PhysicsStepint2(object pWorld,float timeStep, int m_maxSubSteps, float m_fixedTimeStep, out int updatedEntityCount, out List updatedEntities, out int collidersCount, out List colliders) + { + int numSimSteps = 0; + + + //if (updatedEntities is null) + // updatedEntities = new List(); + + //if (colliders is null) + // colliders = new List(); + + + if (pWorld is DiscreteDynamicsWorld) + { + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + + 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(); + + + 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); + int numContacts = contactManifold.GetNumContacts(); + if (numContacts == 0) + continue; + + CollisionObject objA = contactManifold.GetBody0() as CollisionObject; + CollisionObject objB = contactManifold.GetBody1() as CollisionObject; + + ManifoldPoint manifoldPoint = contactManifold.GetContactPoint(0); + IndexedVector3 contactPoint = manifoldPoint.GetPositionWorldOnB(); + IndexedVector3 contactNormal = -manifoldPoint.m_normalWorldOnB; // make relative to A + + RecordCollision(world, objA, objB, contactPoint, contactNormal); + m_collisionsThisFrame ++; + if (m_collisionsThisFrame >= 9999999) + break; + + + } + + + } + else + { + //if (updatedEntities is null) + updatedEntities = new List(); + updatedEntityCount = 0; + //if (colliders is null) + colliders = new List(); + collidersCount = 0; + } + return numSimSteps; + } + + private static void RecordCollision(CollisionWorld world,CollisionObject objA, CollisionObject objB, IndexedVector3 contact, IndexedVector3 norm) + { + + IndexedVector3 contactNormal = norm; + if ((objA.GetCollisionFlags() & BulletXNA.BulletCollision.CollisionFlags.BS_WANTS_COLLISIONS) == 0 && + (objB.GetCollisionFlags() & BulletXNA.BulletCollision.CollisionFlags.BS_WANTS_COLLISIONS) == 0) + { + return; + } + uint idA = (uint)objA.GetUserPointer(); + uint idB = (uint)objB.GetUserPointer(); + if (idA > idB) + { + uint temp = idA; + idA = idB; + idB = temp; + contactNormal = -contactNormal; + } + + ulong collisionID = ((ulong) idA << 32) | idB; + + BulletXNA.CollisionDesc cDesc = new BulletXNA.CollisionDesc() + { + aID = idA, + bID = idB, + point = contact, + normal = contactNormal + }; + world.UpdatedCollisions.Add(cDesc); + m_collisionsThisFrame++; + + + } + private static EntityProperties GetDebugProperties(object pWorld, object pBody) + { + EntityProperties ent = new EntityProperties(); + DiscreteDynamicsWorld world = pWorld as DiscreteDynamicsWorld; + RigidBody body = pBody as RigidBody; + IndexedMatrix transform = body.GetWorldTransform(); + IndexedVector3 LinearVelocity = body.GetInterpolationLinearVelocity(); + IndexedVector3 AngularVelocity = body.GetInterpolationAngularVelocity(); + IndexedQuaternion rotation = transform.GetRotation(); + ent.Acceleration = Vector3.Zero; + ent.ID = (uint)body.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); + ent.RotationalVelocity = new Vector3(AngularVelocity.X, AngularVelocity.Y, AngularVelocity.Z); + return ent; + + + } + + + internal static Vector3 GetLocalScaling2(object pBody) + { + CollisionShape shape = pBody as CollisionShape; + IndexedVector3 scale = shape.GetLocalScaling(); + return new Vector3(scale.X,scale.Y,scale.Z); + } + + internal static bool RayCastGround(object pWorld, Vector3 _RayOrigin, float pRayHeight, object NotMe) + { + DynamicsWorld world = pWorld as DynamicsWorld; + if (world != null) + { + if (NotMe is CollisionObject || NotMe is RigidBody) + { + CollisionObject AvoidBody = NotMe as CollisionObject; + + IndexedVector3 rOrigin = new IndexedVector3(_RayOrigin.X, _RayOrigin.Y, _RayOrigin.Z); + IndexedVector3 rEnd = new IndexedVector3(_RayOrigin.X, _RayOrigin.Y, _RayOrigin.Z - pRayHeight); + using ( + ClosestNotMeRayResultCallback rayCallback = new ClosestNotMeRayResultCallback(rOrigin, + rEnd, AvoidBody) + ) + { + world.RayTest(ref rOrigin, ref rEnd, rayCallback); + if (rayCallback.HasHit()) + { + IndexedVector3 hitLocation = rayCallback.m_hitPointWorld; + + } + return rayCallback.HasHit(); + } + } + } + return false; + } +} +} diff --git a/OpenSim/Region/Physics/BulletSNPlugin/BulletSimData.cs b/OpenSim/Region/Physics/BulletSNPlugin/BulletSimData.cs new file mode 100644 index 0000000000..a1ed8d86c3 --- /dev/null +++ b/OpenSim/Region/Physics/BulletSNPlugin/BulletSimData.cs @@ -0,0 +1,280 @@ +/* + * 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; +using OMV = OpenMetaverse; + +namespace OpenSim.Region.Physics.BulletSNPlugin +{ +// Classes to allow some type checking for the API +// These hold pointers to allocated objects in the unmanaged space. + +// The physics engine controller class created at initialization +public struct BulletSim +{ + public BulletSim(uint worldId, BSScene bss, object xx) + { + ptr = xx; + worldID = worldId; + physicsScene = bss; + } + public object ptr; + public uint worldID; + // The scene is only in here so very low level routines have a handle to print debug/error messages + public BSScene physicsScene; +} + +// An allocated Bullet btRigidBody +public struct BulletBody +{ + public BulletBody(uint id) : this(id, null) + { + } + public BulletBody(uint id, object xx) + { + ID = id; + ptr = xx; + collisionType = CollisionType.Static; + } + public object ptr; + public uint ID; + public CollisionType collisionType; + + public void Clear() + { + ptr = null; + } + public bool HasPhysicalBody { get { return ptr != null; } } + + // Apply the specificed collision mask into the physical world + public void ApplyCollisionMask() + { + // Should assert the body has been added to the physical world. + // (The collision masks are stored in the collision proxy cache which only exists for + // a collision body that is in the world.) + BulletSimAPI.SetCollisionGroupMask2(ptr, + BulletSimData.CollisionTypeMasks[collisionType].group, + BulletSimData.CollisionTypeMasks[collisionType].mask); + } + + public override string ToString() + { + StringBuilder buff = new StringBuilder(); + buff.Append(""); + return buff.ToString(); + } +} + +public struct BulletShape +{ + public BulletShape(object xx) : this(xx, BSPhysicsShapeType.SHAPE_UNKNOWN) + { + } + public BulletShape(object xx, BSPhysicsShapeType typ) + { + ptr = xx; + type = typ; + shapeKey = (System.UInt64)FixedShapeKey.KEY_NONE; + isNativeShape = false; + } + public object ptr; + public BSPhysicsShapeType type; + public System.UInt64 shapeKey; + public bool isNativeShape; + + public void Clear() + { + ptr = null; + } + public bool HasPhysicalShape { get { return ptr != null; } } + + public override string ToString() + { + StringBuilder buff = new StringBuilder(); + buff.Append(""); + return buff.ToString(); + } +} + +// An allocated Bullet btConstraint +public struct BulletConstraint +{ + public BulletConstraint(object xx) + { + ptr = xx; + } + public object ptr; + + public void Clear() + { + ptr = null; + } + public bool HasPhysicalConstraint { get { return ptr != null; } } +} + +// An allocated HeightMapThing which holds various heightmap info. +// Made a class rather than a struct so there would be only one +// instance of this and C# will pass around pointers rather +// than making copies. +public class BulletHeightMapInfo +{ + public BulletHeightMapInfo(uint id, float[] hm, object xx) { + ID = id; + Ptr = xx; + heightMap = hm; + terrainRegionBase = OMV.Vector3.Zero; + minCoords = new OMV.Vector3(100f, 100f, 25f); + maxCoords = new OMV.Vector3(101f, 101f, 26f); + minZ = maxZ = 0f; + sizeX = sizeY = 256f; + } + public uint ID; + public object Ptr; + public float[] heightMap; + public OMV.Vector3 terrainRegionBase; + public OMV.Vector3 minCoords; + public OMV.Vector3 maxCoords; + public float sizeX, sizeY; + public float minZ, maxZ; + public BulletShape terrainShape; + public BulletBody terrainBody; + + public float collisionMargin { get; set; } +} + +// The general class of collsion object. +public enum CollisionType +{ + Avatar, + Groundplane, + Terrain, + Static, + Dynamic, + VolumeDetect, + // Linkset, // A linkset should be either Static or Dynamic + LinksetChild, + Unknown +}; + +// Hold specification of group and mask collision flags for a CollisionType +public struct CollisionTypeFilterGroup +{ + public CollisionTypeFilterGroup(CollisionType t, uint g, uint m) + { + type = t; + group = g; + mask = m; + } + public CollisionType type; + public uint group; + public uint mask; +}; + + /* NOTE: old definitions kept for reference. Delete when things are working. + // The collsion filters and masked are defined in one place -- don't want them scattered + AvatarGroup = BCharacterGroup, + AvatarMask = BAllGroup, + ObjectGroup = BSolidGroup, + ObjectMask = BAllGroup, + StaticObjectGroup = BStaticGroup, + StaticObjectMask = AvatarGroup | ObjectGroup, // static things don't interact with much + LinksetGroup = BLinksetGroup, + LinksetMask = BAllGroup, + LinksetChildGroup = BLinksetChildGroup, + LinksetChildMask = BNoneGroup, // Linkset children disappear from the world + VolumeDetectGroup = BSensorTrigger, + VolumeDetectMask = ~BSensorTrigger, + TerrainGroup = BTerrainGroup, + TerrainMask = BAllGroup & ~BStaticGroup, // static objects on the ground don't collide + GroundPlaneGroup = BGroundPlaneGroup, + GroundPlaneMask = BAllGroup + */ + +public static class BulletSimData +{ + +// Map of collisionTypes to flags for collision groups and masks. +// As mentioned above, don't use the CollisionFilterGroups definitions directly in the code +// but, instead, use references to this dictionary. Finding and debugging +// collision flag problems will be made easier. +public static Dictionary CollisionTypeMasks + = new Dictionary() +{ + { CollisionType.Avatar, + new CollisionTypeFilterGroup(CollisionType.Avatar, + (uint)CollisionFilterGroups.BCharacterGroup, + (uint)CollisionFilterGroups.BAllGroup) + }, + { CollisionType.Groundplane, + new CollisionTypeFilterGroup(CollisionType.Groundplane, + (uint)CollisionFilterGroups.BGroundPlaneGroup, + (uint)CollisionFilterGroups.BAllGroup) + }, + { CollisionType.Terrain, + new CollisionTypeFilterGroup(CollisionType.Terrain, + (uint)CollisionFilterGroups.BTerrainGroup, + (uint)(CollisionFilterGroups.BAllGroup & ~CollisionFilterGroups.BStaticGroup)) + }, + { CollisionType.Static, + new CollisionTypeFilterGroup(CollisionType.Static, + (uint)CollisionFilterGroups.BStaticGroup, + (uint)(CollisionFilterGroups.BCharacterGroup | CollisionFilterGroups.BSolidGroup)) + }, + { CollisionType.Dynamic, + new CollisionTypeFilterGroup(CollisionType.Dynamic, + (uint)CollisionFilterGroups.BSolidGroup, + (uint)(CollisionFilterGroups.BAllGroup)) + }, + { CollisionType.VolumeDetect, + new CollisionTypeFilterGroup(CollisionType.VolumeDetect, + (uint)CollisionFilterGroups.BSensorTrigger, + (uint)(~CollisionFilterGroups.BSensorTrigger)) + }, + { CollisionType.LinksetChild, + new CollisionTypeFilterGroup(CollisionType.LinksetChild, + (uint)CollisionFilterGroups.BTerrainGroup, + (uint)(CollisionFilterGroups.BNoneGroup)) + }, +}; + +} +} diff --git a/bin/BulletXNA.dll b/bin/BulletXNA.dll new file mode 100644 index 0000000000..1e3f04253f Binary files /dev/null and b/bin/BulletXNA.dll differ diff --git a/bin/BulletXNA.pdb b/bin/BulletXNA.pdb new file mode 100644 index 0000000000..4b83b83f28 Binary files /dev/null and b/bin/BulletXNA.pdb differ diff --git a/prebuild.xml b/prebuild.xml index 14ffc997cd..741b660cbc 100644 --- a/prebuild.xml +++ b/prebuild.xml @@ -1756,6 +1756,40 @@ + + + + ../../../../bin/Physics/ + + + + + ../../../../bin/Physics/ + + + + ../../../../bin/ + + + + + + + + + + + + + + + + + + + + +