OpenSimMirror/addon-modules/BulletSPlugin/BSScene.cs

482 lines
19 KiB
C#

/*
* Copyright (c) 2011 Intel Corporation
*
* 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 Intel Corporation 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 Nini.Config;
using log4net;
using OpenSim.Framework;
using OpenSim.Region.Physics.Manager;
using OpenMetaverse;
using OpenSim.Region.Framework;
// TODOs for BulletSim (both BSScene and BSPrim)
// Does NeedsMeshing() really need to exclude all the different shapes?
// Based on material, set density and friction
// More efficient memory usage in passing hull information from BSPrim to BulletSim
// Four states of prim: Physical, regular, phantom and selected. Are we modeling these correctly?
// In SL one can set both physical and phantom (gravity, does not effect others, makes collisions with ground)
// LinkSets
// Freeing of memory of linksets in BulletSim::DestroyObject
// Set child prims phantom since the physicality is handled by the parent prim
// Linked children need rotation relative to parent (passed as world rotation)
// Should prim.link() and prim.delink() membership checking happen at taint time?
// Pass collision enable flags to BulletSim code so collisions are not reported up unless they are really needed
// Set bouyancy(). Maybe generalize SetFlying() to SetBouyancy() and use the factor to change the gravity effect
// Test sculpties
// Mesh sharing. Use meshHash to tell if we already have a hull of that shape and only create once
// Do attachments need to be handled separately? Need collision events. Do not collide with VolumeDetect
// Parameterize BulletSim. Pass a structure of parameters to the C++ code. Capsule size, friction, ...
//
namespace OpenSim.Region.Physics.BulletSPlugin
{
public class BSScene : PhysicsScene
{
private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string LogHeader = "[BULLETS SCENE]";
private Dictionary<uint, BSCharacter> m_avatars = new Dictionary<uint, BSCharacter>();
private Dictionary<uint, BSPrim> m_prims = new Dictionary<uint, BSPrim>();
private float[] m_heightMap;
private uint m_worldID;
public uint WorldID { get { return m_worldID; } }
public IMesher mesher;
public int meshLOD = 32;
private int m_maxSubSteps = 10;
private float m_fixedTimeStep = 1f / 60f;
private long m_simulationStep = 0;
public long SimulationStep { get { return m_simulationStep; } }
private bool _meshSculptedPrim = true; // cause scuplted prims to get meshed
private bool _forceSimplePrimMeshing = false; // if a cube or sphere, let Bullet do internal shapes
public float maximumMassObject = 10000.01f;
public const uint TERRAIN_ID = 0;
public const uint GROUNDPLANE_ID = 1;
public float DefaultFriction = 0.70f;
public float DefaultDensity = 10.000006836f; // Aluminum g/cm3; TODO: compute based on object material
public delegate void TaintCallback();
private List<TaintCallback> _taintedObjects;
private Object _taintLock = new Object();
private BulletSimAPI.DebugLogCallback debugLogCallbackHandle;
public BSScene(string identifier)
{
}
public override void Initialise(IMesher meshmerizer, IConfigSource config)
{
if (config != null)
{
IConfig pConfig = config.Configs["BulletSim"];
if (pConfig != null)
{
DefaultFriction = pConfig.GetFloat("Friction", DefaultFriction);
DefaultDensity = pConfig.GetFloat("Density", DefaultDensity);
}
}
// if Debug, enable logging from the unmanaged code
if (m_log.IsDebugEnabled)
{
m_log.DebugFormat("{0}: Initialize: Setting debug callback for unmanaged code", LogHeader);
debugLogCallbackHandle = new BulletSimAPI.DebugLogCallback(BulletLogger);
BulletSimAPI.SetDebugLogCallback(debugLogCallbackHandle);
}
_meshSculptedPrim = true; // mesh sculpted prims
_forceSimplePrimMeshing = false; // use complex meshing if called for
_taintedObjects = new List<TaintCallback>();
mesher = meshmerizer;
// m_log.DebugFormat("{0}: Initialize: Calling BulletSimAPI.Initialize.", LogHeader);
m_worldID = BulletSimAPI.Initialize(new Vector3(Constants.RegionSize, Constants.RegionSize, 1000f));
}
private void BulletLogger(string msg)
{
m_log.Debug("[BULLETS UNMANAGED]:" + msg);
}
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);
BSCharacter actor = new BSCharacter(localID, avName, this, position, size, isFlying);
lock (m_avatars) m_avatars.Add(localID, actor);
return actor;
}
public override void RemoveAvatar(PhysicsActor actor)
{
// m_log.DebugFormat("{0}: RemoveAvatar", LogHeader);
if (actor is BSCharacter)
{
((BSCharacter)actor).Destroy();
}
try
{
lock (m_avatars) m_avatars.Remove(actor.LocalID);
}
catch (Exception e)
{
m_log.WarnFormat("{0}: Attempt to remove avatar that is not in physics scene: {1}", LogHeader, e);
}
}
public override void RemovePrim(PhysicsActor prim)
{
// m_log.DebugFormat("{0}: RemovePrim", LogHeader);
if (prim is BSPrim)
{
((BSPrim)prim).Destroy();
}
try
{
lock (m_prims) m_prims.Remove(prim.LocalID);
}
catch (Exception e)
{
m_log.WarnFormat("{0}: Attempt to remove prim that is not in physics scene: {1}", LogHeader, e);
}
}
public override PhysicsActor AddPrimShape(string primName, PrimitiveBaseShape pbs, Vector3 position,
Vector3 size, Quaternion rotation) // deprecated
{
return null;
}
public override PhysicsActor AddPrimShape(string primName, PrimitiveBaseShape pbs, Vector3 position,
Vector3 size, Quaternion rotation, bool isPhysical)
{
m_log.ErrorFormat("{0}: CALL TO AddPrimShape in BSScene. NOT IMPLEMENTED", LogHeader);
return null;
}
public override PhysicsActor AddPrimShape(uint localID, string primName, PrimitiveBaseShape pbs, Vector3 position,
Vector3 size, Quaternion rotation, bool isPhysical)
{
// m_log.DebugFormat("{0}: AddPrimShape2: {1}", LogHeader, primName);
IMesh mesh = null;
if (NeedsMeshing(pbs))
{
// if the prim is complex, create the mesh for it.
// If simple (box or sphere) leave 'mesh' null and physics will do a native shape.
// m_log.DebugFormat("{0}: AddPrimShape2: creating mesh", LogHeader);
mesh = mesher.CreateMesh(primName, pbs, size, this.meshLOD, isPhysical);
}
BSPrim prim = new BSPrim(localID, primName, this, position, size, rotation, mesh, pbs, isPhysical);
lock (m_prims) m_prims.Add(localID, prim);
return prim;
}
public override void AddPhysicsActorTaint(PhysicsActor prim) { }
public override float Simulate(float timeStep)
{
int updatedEntityCount;
IntPtr updatedEntitiesPtr;
IntPtr[] updatedEntities;
int collidersCount;
IntPtr collidersPtr;
int[] colliders; // should be uint but Marshal.Copy does not have that overload
// update the prim states while we know the physics engine is not busy
ProcessTaints();
// step the physical world one interval
m_simulationStep++;
int numSubSteps = BulletSimAPI.PhysicsStep(m_worldID, timeStep, m_maxSubSteps, m_fixedTimeStep,
out updatedEntityCount, out updatedEntitiesPtr, out collidersCount, out collidersPtr);
// if there were collisions, they show up here
if (collidersCount > 0)
{
colliders = new int[collidersCount];
Marshal.Copy(collidersPtr, colliders, 0, collidersCount);
for (int ii = 0; ii < collidersCount; ii+=2)
{
uint cA = (uint)colliders[ii];
uint cB = (uint)colliders[ii+1];
SendCollision(cA, cB);
SendCollision(cB, cA);
}
}
// if any of the objects had updated properties, they are returned in the updatedEntity structure
if (updatedEntityCount > 0)
{
updatedEntities = new IntPtr[updatedEntityCount];
Marshal.Copy(updatedEntitiesPtr, updatedEntities, 0, updatedEntityCount);
for (int ii = 0; ii < updatedEntityCount; ii++)
{
IntPtr updatePointer = updatedEntities[ii];
EntityProperties entprop = (EntityProperties)Marshal.PtrToStructure(updatePointer, typeof(EntityProperties));
// m_log.DebugFormat("{0}: entprop: id={1}, pos={2}", LogHeader, entprop.ID, entprop.Position);
BSCharacter actor;
if (m_avatars.TryGetValue(entprop.ID, out actor))
{
actor.UpdateProperties(entprop);
continue;
}
BSPrim prim;
if (m_prims.TryGetValue(entprop.ID, out prim))
{
prim.UpdateProperties(entprop);
}
}
}
// if (collidersCount > 0 || updatedEntityCount > 0) m_log.WarnFormat("{0}: collisions={1}, updates={2}", LogHeader, collidersCount/2, updatedEntityCount);
return 11f; // returns frames per second
}
// Something has collided
private void SendCollision(uint localID, uint collidingWith)
{
if (localID == TERRAIN_ID || localID == GROUNDPLANE_ID)
{
// we never send collisions to the terrain
return;
}
ActorTypes type = ActorTypes.Prim;
if (collidingWith == TERRAIN_ID || collidingWith == GROUNDPLANE_ID)
type = ActorTypes.Ground;
else if (m_avatars.ContainsKey(collidingWith))
type = ActorTypes.Agent;
BSPrim prim;
if (m_prims.TryGetValue(localID, out prim)) {
prim.Collide(collidingWith, type, Vector3.Zero, Vector3.UnitZ, 0.01f);
return;
}
BSCharacter actor;
if (m_avatars.TryGetValue(localID, out actor)) {
actor.Collide(collidingWith, type, Vector3.Zero, Vector3.UnitZ, 0.01f);
return;
}
return;
}
public override void GetResults() { }
public override void SetTerrain(float[] heightMap) {
m_log.DebugFormat("{0}: SetTerrain", LogHeader);
m_heightMap = heightMap;
this.TaintedObject(delegate()
{
BulletSimAPI.SetHeightmap(m_worldID, m_heightMap);
});
}
public override void SetWaterLevel(float baseheight) { }
public override void DeleteTerrain()
{
m_log.DebugFormat("{0}: DeleteTerrain()", LogHeader);
}
public override void Dispose()
{
m_log.DebugFormat("{0}: Dispose()", LogHeader);
}
public override Dictionary<uint, float> GetTopColliders()
{
return new Dictionary<uint, float>();
}
public override bool IsThreaded { get { return false; } }
/// <summary>
/// Routine to figure out if we need to mesh this prim with our mesher
/// </summary>
/// <param name="pbs"></param>
/// <returns>true if the prim needs meshing</returns>
public bool NeedsMeshing(PrimitiveBaseShape pbs)
{
// most of this is redundant now as the mesher will return null if it cant mesh a prim
// but we still need to check for sculptie meshing being enabled so this is the most
// convenient place to do it for now...
// int iPropertiesNotSupportedDefault = 0;
if (pbs.SculptEntry && !_meshSculptedPrim)
{
// m_log.DebugFormat("{0}: NeedsMeshing: scultpy mesh", LogHeader);
return false;
}
// if it's a standard box or sphere with no cuts, hollows, twist or top shear, return false since Bullet
// can use an internal representation for the prim
if (!_forceSimplePrimMeshing)
{
// m_log.DebugFormat("{0}: NeedsMeshing: simple mesh: profshape={1}, curve={2}", LogHeader, pbs.ProfileShape, pbs.PathCurve);
if ((pbs.ProfileShape == ProfileShape.Square && pbs.PathCurve == (byte)Extrusion.Straight)
|| (pbs.ProfileShape == ProfileShape.HalfCircle && pbs.PathCurve == (byte)Extrusion.Curve1
&& pbs.Scale.X == pbs.Scale.Y && pbs.Scale.Y == pbs.Scale.Z))
{
if (pbs.ProfileBegin == 0 && pbs.ProfileEnd == 0
&& pbs.ProfileHollow == 0
&& pbs.PathTwist == 0 && pbs.PathTwistBegin == 0
&& pbs.PathBegin == 0 && pbs.PathEnd == 0
&& pbs.PathTaperX == 0 && pbs.PathTaperY == 0
&& pbs.PathScaleX == 100 && pbs.PathScaleY == 100
&& pbs.PathShearX == 0 && pbs.PathShearY == 0)
{
return false;
}
}
}
/* TODO: verify that the mesher will now do all these shapes
if (pbs.ProfileHollow != 0)
iPropertiesNotSupportedDefault++;
if ((pbs.PathBegin != 0) || pbs.PathEnd != 0)
iPropertiesNotSupportedDefault++;
if ((pbs.PathTwistBegin != 0) || (pbs.PathTwist != 0))
iPropertiesNotSupportedDefault++;
if ((pbs.ProfileBegin != 0) || pbs.ProfileEnd != 0)
iPropertiesNotSupportedDefault++;
if ((pbs.PathScaleX != 100) || (pbs.PathScaleY != 100))
iPropertiesNotSupportedDefault++;
if ((pbs.PathShearX != 0) || (pbs.PathShearY != 0))
iPropertiesNotSupportedDefault++;
if (pbs.ProfileShape == ProfileShape.Circle && pbs.PathCurve == (byte)Extrusion.Straight)
iPropertiesNotSupportedDefault++;
if (pbs.ProfileShape == ProfileShape.HalfCircle && pbs.PathCurve == (byte)Extrusion.Curve1 && (pbs.Scale.X != pbs.Scale.Y || pbs.Scale.Y != pbs.Scale.Z || pbs.Scale.Z != pbs.Scale.X))
iPropertiesNotSupportedDefault++;
if (pbs.ProfileShape == ProfileShape.HalfCircle && pbs.PathCurve == (byte) Extrusion.Curve1)
iPropertiesNotSupportedDefault++;
// test for torus
if ((pbs.ProfileCurve & 0x07) == (byte)ProfileShape.Square)
{
if (pbs.PathCurve == (byte)Extrusion.Curve1)
{
iPropertiesNotSupportedDefault++;
}
}
else if ((pbs.ProfileCurve & 0x07) == (byte)ProfileShape.Circle)
{
if (pbs.PathCurve == (byte)Extrusion.Straight)
{
iPropertiesNotSupportedDefault++;
}
// ProfileCurve seems to combine hole shape and profile curve so we need to only compare against the lower 3 bits
else if (pbs.PathCurve == (byte)Extrusion.Curve1)
{
iPropertiesNotSupportedDefault++;
}
}
else if ((pbs.ProfileCurve & 0x07) == (byte)ProfileShape.HalfCircle)
{
if (pbs.PathCurve == (byte)Extrusion.Curve1 || pbs.PathCurve == (byte)Extrusion.Curve2)
{
iPropertiesNotSupportedDefault++;
}
}
else if ((pbs.ProfileCurve & 0x07) == (byte)ProfileShape.EquilateralTriangle)
{
if (pbs.PathCurve == (byte)Extrusion.Straight)
{
iPropertiesNotSupportedDefault++;
}
else if (pbs.PathCurve == (byte)Extrusion.Curve1)
{
iPropertiesNotSupportedDefault++;
}
}
if (iPropertiesNotSupportedDefault == 0)
{
return false;
}
*/
return true;
}
// The calls to the PhysicsActors can't directly call into the physics engine
// because it might be busy. We 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(TaintCallback callback)
{
lock (_taintLock)
_taintedObjects.Add(callback);
return;
}
// 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()
{
// swizzle a new list into the list location so we can process what's there
List<TaintCallback> oldList;
lock (_taintLock)
{
oldList = _taintedObjects;
_taintedObjects = new List<TaintCallback>();
}
foreach (TaintCallback callback in oldList)
{
try
{
callback();
}
catch (Exception e)
{
m_log.ErrorFormat("{0}: ProcessTaints: Exception: {1}", LogHeader, e);
}
}
oldList.Clear();
}
}
}