347 lines
16 KiB
C#
Executable File
347 lines
16 KiB
C#
Executable File
/*
|
|
* 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.BulletSPlugin
|
|
{
|
|
public sealed class BSShapeCollection : IDisposable
|
|
{
|
|
private static string LogHeader = "[BULLETSIM SHAPE COLLECTION]";
|
|
|
|
private BSScene m_physicsScene { get; set; }
|
|
|
|
private Object m_collectionActivityLock = new Object();
|
|
|
|
private bool DDetail = false;
|
|
|
|
public BSShapeCollection(BSScene physScene)
|
|
{
|
|
m_physicsScene = physScene;
|
|
// Set the next to 'true' for very detailed shape update detailed logging (detailed details?)
|
|
// While detailed debugging is still active, this is better than commenting out all the
|
|
// DetailLog statements. When debugging slows down, this and the protected logging
|
|
// 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 PhysicalDestructionCallback(BulletBody pBody, BulletShape pShape);
|
|
|
|
// Called to update/change the body and shape for an object.
|
|
// The object has some shape and body on it. Here we decide if that is the correct shape
|
|
// for the current state of the object (static/dynamic/...).
|
|
// If bodyCallback is not null, it is called if either the body or the shape are changed
|
|
// so dependencies (like constraints) can be removed before the physical object is dereferenced.
|
|
// Return 'true' if either the body or the shape changed.
|
|
// Called at taint-time.
|
|
public bool GetBodyAndShape(bool forceRebuild, BulletWorld sim, BSPhysObject prim, PhysicalDestructionCallback bodyCallback)
|
|
{
|
|
m_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, bodyCallback);
|
|
// If we had to select a new shape geometry for the object,
|
|
// rebuild the body around it.
|
|
// Updates prim.BSBody with information/pointers to requested body
|
|
// Returns 'true' if BSBody was changed.
|
|
bool newBody = CreateBody((newGeom || forceRebuild), prim, m_physicsScene.World, 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, BulletWorld sim, BSPhysObject prim)
|
|
{
|
|
return GetBodyAndShape(forceRebuild, sim, prim, null);
|
|
}
|
|
|
|
// If the existing prim's shape is to be replaced, remove the tie to the existing shape
|
|
// before replacing it.
|
|
private void DereferenceExistingShape(BSPhysObject prim, PhysicalDestructionCallback shapeCallback)
|
|
{
|
|
if (prim.PhysShape.HasPhysicalShape)
|
|
{
|
|
if (shapeCallback != null)
|
|
shapeCallback(prim.PhysBody, prim.PhysShape.physShapeInfo);
|
|
prim.PhysShape.Dereference(m_physicsScene);
|
|
}
|
|
prim.PhysShape = new BSShapeNull();
|
|
}
|
|
|
|
// Create the geometry information in Bullet for later use.
|
|
// 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, PhysicalDestructionCallback shapeCallback)
|
|
{
|
|
bool ret = false;
|
|
bool haveShape = false;
|
|
bool nativeShapePossible = true;
|
|
PrimitiveBaseShape pbs = prim.BaseShape;
|
|
|
|
// Kludge to create the capsule for the avatar.
|
|
BSCharacter theChar = prim as BSCharacter;
|
|
if (theChar != null)
|
|
{
|
|
DereferenceExistingShape(prim, shapeCallback);
|
|
prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim,
|
|
BSPhysicsShapeType.SHAPE_CAPSULE, FixedShapeKey.KEY_CAPSULE);
|
|
ret = true;
|
|
haveShape = true;
|
|
}
|
|
|
|
// If the prim attributes are simple, this could be a simple Bullet native shape
|
|
// Native shapes work whether to object is static or physical.
|
|
if (!haveShape
|
|
&& nativeShapePossible
|
|
&& pbs != null
|
|
&& PrimHasNoCuts(pbs)
|
|
&& ( !pbs.SculptEntry || (pbs.SculptEntry && !BSParam.ShouldMeshSculptedPrim) )
|
|
)
|
|
{
|
|
// Get the scale of any existing shape so we can see if the new shape is same native type and same size.
|
|
OMV.Vector3 scaleOfExistingShape = OMV.Vector3.Zero;
|
|
if (prim.PhysShape.HasPhysicalShape)
|
|
scaleOfExistingShape = m_physicsScene.PE.GetLocalScaling(prim.PhysShape.physShapeInfo);
|
|
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,maybeNative,force={1},primScale={2},primSize={3},primShape={4}",
|
|
prim.LocalID, forceRebuild, prim.Scale, prim.Size, prim.PhysShape.physShapeInfo.shapeType);
|
|
|
|
// It doesn't look like Bullet scales native spheres so make sure the scales are all equal
|
|
if ((pbs.ProfileShape == ProfileShape.HalfCircle && pbs.PathCurve == (byte)Extrusion.Curve1)
|
|
&& pbs.Scale.X == pbs.Scale.Y && pbs.Scale.Y == pbs.Scale.Z)
|
|
{
|
|
haveShape = true;
|
|
if (forceRebuild
|
|
|| prim.PhysShape.ShapeType != BSPhysicsShapeType.SHAPE_SPHERE
|
|
)
|
|
{
|
|
DereferenceExistingShape(prim, shapeCallback);
|
|
prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim,
|
|
BSPhysicsShapeType.SHAPE_SPHERE, FixedShapeKey.KEY_SPHERE);
|
|
ret = true;
|
|
}
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,sphere,force={1},rebuilt={2},shape={3}",
|
|
prim.LocalID, forceRebuild, ret, prim.PhysShape);
|
|
}
|
|
// If we didn't make a sphere, maybe a box will work.
|
|
if (!haveShape && pbs.ProfileShape == ProfileShape.Square && pbs.PathCurve == (byte)Extrusion.Straight)
|
|
{
|
|
haveShape = true;
|
|
if (forceRebuild
|
|
|| prim.Scale != scaleOfExistingShape
|
|
|| prim.PhysShape.ShapeType != BSPhysicsShapeType.SHAPE_BOX
|
|
)
|
|
{
|
|
DereferenceExistingShape(prim, shapeCallback);
|
|
prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim,
|
|
BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX);
|
|
ret = true;
|
|
}
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,box,force={1},rebuilt={2},shape={3}",
|
|
prim.LocalID, forceRebuild, ret, prim.PhysShape);
|
|
}
|
|
}
|
|
|
|
// If a simple shape is not happening, create a mesh and possibly a hull.
|
|
if (!haveShape && pbs != null)
|
|
{
|
|
ret = CreateGeomMeshOrHull(prim, shapeCallback);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// return 'true' if this shape description does not include any cutting or twisting.
|
|
public static bool PrimHasNoCuts(PrimitiveBaseShape pbs)
|
|
{
|
|
return pbs.ProfileBegin == 0 && pbs.ProfileEnd == 0
|
|
&& pbs.ProfileHollow == 0
|
|
&& pbs.PathTwist == 0 && pbs.PathTwistBegin == 0
|
|
&& pbs.PathBegin == 0 && pbs.PathEnd == 0
|
|
&& pbs.PathTaperX == 0 && pbs.PathTaperY == 0
|
|
&& pbs.PathScaleX == 100 && pbs.PathScaleY == 100
|
|
&& pbs.PathShearX == 0 && pbs.PathShearY == 0;
|
|
}
|
|
|
|
// return 'true' if the prim's shape was changed.
|
|
private bool CreateGeomMeshOrHull(BSPhysObject prim, PhysicalDestructionCallback shapeCallback)
|
|
{
|
|
|
|
bool ret = false;
|
|
// 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.
|
|
DereferenceExistingShape(prim, shapeCallback);
|
|
prim.PhysShape = BSShapeMesh.GetReference(m_physicsScene, false /*forceRebuild*/, prim);
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,hull,shape={1},key={2}",
|
|
prim.LocalID, prim.PhysShape, prim.PhysShape.physShapeInfo.shapeKey.ToString("X"));
|
|
}
|
|
else
|
|
{
|
|
// Update prim.BSShape to reference a mesh of this shape.
|
|
DereferenceExistingShape(prim, shapeCallback);
|
|
prim.PhysShape = BSShapeHull.GetReference(m_physicsScene, false /*forceRebuild*/, prim);
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,mesh,shape={1},key={2}",
|
|
prim.LocalID, prim.PhysShape, prim.PhysShape.physShapeInfo.shapeKey.ToString("X"));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Track another user of a body.
|
|
// We presume the caller has allocated the body.
|
|
// Bodies only have one user so the body is just put into the world if not already there.
|
|
private void ReferenceBody(BulletBody body)
|
|
{
|
|
lock (m_collectionActivityLock)
|
|
{
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,newBody,body={1}", body.ID, body);
|
|
if (!m_physicsScene.PE.IsInWorld(m_physicsScene.World, body))
|
|
{
|
|
m_physicsScene.PE.AddObjectToWorld(m_physicsScene.World, body);
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,addedToWorld,ref={1}", body.ID, body);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release the usage of a body.
|
|
// Called when releasing use of a BSBody. BSShape is handled separately.
|
|
// Called in taint time.
|
|
public void DereferenceBody(BulletBody body, PhysicalDestructionCallback bodyCallback )
|
|
{
|
|
if (!body.HasPhysicalBody)
|
|
return;
|
|
|
|
m_physicsScene.AssertInTaintTime("BSShapeCollection.DereferenceBody");
|
|
|
|
lock (m_collectionActivityLock)
|
|
{
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceBody,DestroyingBody,body={1}", body.ID, body);
|
|
// If the caller needs to know the old body is going away, pass the event up.
|
|
if (bodyCallback != null)
|
|
bodyCallback(body, null);
|
|
|
|
// Removing an object not in the world is a NOOP
|
|
m_physicsScene.PE.RemoveObjectFromWorld(m_physicsScene.World, body);
|
|
|
|
// Zero any reference to the shape so it is not freed when the body is deleted.
|
|
m_physicsScene.PE.SetCollisionShape(m_physicsScene.World, body, null);
|
|
|
|
m_physicsScene.PE.DestroyObject(m_physicsScene.World, body);
|
|
}
|
|
}
|
|
|
|
// Create a body object in Bullet.
|
|
// Updates prim.BSBody with the information about the new body if one is created.
|
|
// Returns 'true' if an object was actually created.
|
|
// Called at taint-time.
|
|
private bool CreateBody(bool forceRebuild, BSPhysObject prim, BulletWorld sim, PhysicalDestructionCallback 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)m_physicsScene.PE.GetBodyType(prim.PhysBody);
|
|
if (prim.IsSolid && bodyType != CollisionObjectTypes.CO_RIGID_BODY
|
|
|| !prim.IsSolid && bodyType != CollisionObjectTypes.CO_GHOST_OBJECT)
|
|
{
|
|
// If the collisionObject is not the correct type for solidness, rebuild what's there
|
|
mustRebuild = true;
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,forceRebuildBecauseChangingBodyType,bodyType={1}", prim.LocalID, bodyType);
|
|
}
|
|
}
|
|
|
|
if (mustRebuild || forceRebuild)
|
|
{
|
|
// Free any old body
|
|
DereferenceBody(prim.PhysBody, bodyCallback);
|
|
|
|
BulletBody aBody;
|
|
if (prim.IsSolid)
|
|
{
|
|
aBody = m_physicsScene.PE.CreateBodyFromShape(sim, prim.PhysShape.physShapeInfo, prim.LocalID, prim.RawPosition, prim.RawOrientation);
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,mesh,body={1}", prim.LocalID, aBody);
|
|
}
|
|
else
|
|
{
|
|
aBody = m_physicsScene.PE.CreateGhostFromShape(sim, prim.PhysShape.physShapeInfo, prim.LocalID, prim.RawPosition, prim.RawOrientation);
|
|
if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,ghost,body={1}", prim.LocalID, aBody);
|
|
}
|
|
|
|
ReferenceBody(aBody);
|
|
|
|
prim.PhysBody = aBody;
|
|
|
|
ret = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
private void DetailLog(string msg, params Object[] args)
|
|
{
|
|
if (m_physicsScene.PhysicsLogging.Enabled)
|
|
m_physicsScene.DetailLog(msg, args);
|
|
}
|
|
}
|
|
}
|