/* * AJLDuarte 2012 */ using System; using System.Threading; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using OpenSim.Framework; using OpenSim.Region.Physics.Manager; using OdeAPI; using log4net; using Nini.Config; using OpenMetaverse; namespace OpenSim.Region.Physics.OdePlugin { public enum MeshState : byte { noNeed = 0, loadingAsset = 1, AssetOK = 0x0f, // 00001111 NeedMask = 0x30, // 00110000 needMesh = 0x10, // 00010000 needAsset = 0x20, // 00100000 FailMask = 0xC0, // 11000000 AssetFailed = 0x40, // 01000000 MeshFailed = 0x80 // 10000000 } public enum meshWorkerCmnds : byte { nop = 0, addnew, changefull, changesize, changeshapetype, getmesh, } public class ODEPhysRepData { public PhysicsActor actor; public PrimitiveBaseShape pbs; public IMesh mesh; public Vector3 size; public Vector3 OBB; public Vector3 OBBOffset; public float volume; public byte shapetype; public bool hasOBB; public bool hasMeshVolume; public MeshState meshState; public UUID? assetID; public meshWorkerCmnds comand; } public class ODEMeshWorker { private ILog m_log; private OdeScene m_scene; private IMesher m_mesher; public bool meshSculptedPrim = true; public bool forceSimplePrimMeshing = false; public float meshSculptLOD = 32; public float MeshSculptphysicalLOD = 32; private OpenSim.Framework.BlockingQueue createqueue = new OpenSim.Framework.BlockingQueue(); private bool m_running; private Thread m_thread; public ODEMeshWorker(OdeScene pScene, ILog pLog, IMesher pMesher, IConfig pConfig) { m_scene = pScene; m_log = pLog; m_mesher = pMesher; if (pConfig != null) { forceSimplePrimMeshing = pConfig.GetBoolean("force_simple_prim_meshing", forceSimplePrimMeshing); meshSculptedPrim = pConfig.GetBoolean("mesh_sculpted_prim", meshSculptedPrim); meshSculptLOD = pConfig.GetFloat("mesh_lod", meshSculptLOD); MeshSculptphysicalLOD = pConfig.GetFloat("mesh_physical_lod", MeshSculptphysicalLOD); } m_running = true; m_thread = new Thread(DoWork); m_thread.Start(); } private void DoWork() { while(m_running) { ODEPhysRepData nextRep = createqueue.Dequeue(); if(!m_running) return; if (nextRep == null) continue; if (m_scene.haveActor(nextRep.actor)) { switch (nextRep.comand) { case meshWorkerCmnds.changefull: case meshWorkerCmnds.changeshapetype: case meshWorkerCmnds.changesize: if (CreateActorPhysRep(nextRep) && m_scene.haveActor(nextRep.actor)) m_scene.AddChange(nextRep.actor, changes.PhysRepData, nextRep); break; case meshWorkerCmnds.addnew: if (CreateActorPhysRep(nextRep)) m_scene.AddChange(nextRep.actor, changes.AddPhysRep, nextRep); break; case meshWorkerCmnds.getmesh: DoRepDataGetMesh(nextRep); break; } } } } public void Stop() { try { m_thread.Abort(); createqueue.Clear(); } catch { } } public void ChangeActorPhysRep(PhysicsActor actor, PrimitiveBaseShape pbs, Vector3 size, byte shapetype) { ODEPhysRepData repData = new ODEPhysRepData(); repData.actor = actor; repData.pbs = pbs; repData.size = size; repData.shapetype = shapetype; CheckMeshDone(repData); CalcVolumeData(repData); m_scene.AddChange(actor, changes.PhysRepData, repData); return; } public void NewActorPhysRep(PhysicsActor actor, PrimitiveBaseShape pbs, Vector3 size, byte shapetype) { ODEPhysRepData repData = new ODEPhysRepData(); repData.actor = actor; repData.pbs = pbs; repData.size = size; repData.shapetype = shapetype; CheckMeshDone(repData); CalcVolumeData(repData); m_scene.AddChange(actor, changes.AddPhysRep, repData); } public void RequestMesh(ODEPhysRepData repData) { repData.mesh = null; if (repData.meshState == MeshState.needMesh) { repData.comand = meshWorkerCmnds.changefull; createqueue.Enqueue(repData); } else if (repData.meshState == MeshState.needAsset) { PrimitiveBaseShape pbs = repData.pbs; // check if we got outdated if (!pbs.SculptEntry || pbs.SculptTexture == UUID.Zero) { repData.meshState = MeshState.noNeed; return; } if (pbs.SculptTexture != repData.assetID) return; repData.meshState = MeshState.loadingAsset; repData.comand = meshWorkerCmnds.getmesh; createqueue.Enqueue(repData); } } // creates and prepares a mesh to use and calls parameters estimation public bool CreateActorPhysRep(ODEPhysRepData repData) { getMesh(repData); IMesh mesh = repData.mesh; if (mesh != null) { IntPtr vertices, indices; int vertexCount, indexCount; int vertexStride, triStride; mesh.getVertexListAsPtrToFloatArray(out vertices, out vertexStride, out vertexCount); mesh.getIndexListAsPtrToIntArray(out indices, out triStride, out indexCount); if (vertexCount == 0 || indexCount == 0) { m_log.WarnFormat("[PHYSICS]: Invalid mesh data on prim {0} mesh UUID {1}", repData.actor.Name, repData.pbs.SculptTexture.ToString()); repData.meshState = MeshState.MeshFailed; repData.hasOBB = false; repData.mesh = null; m_scene.mesher.ReleaseMesh(mesh); } else { repData.OBBOffset = mesh.GetCentroid(); repData.OBB = mesh.GetOBB(); repData.hasOBB = true; mesh.releaseSourceMeshData(); } } CalcVolumeData(repData); return true; } public void AssetLoaded(ODEPhysRepData repData) { if (m_scene.haveActor(repData.actor)) { if (needsMeshing(repData.pbs)) // no need for pbs now? { repData.comand = meshWorkerCmnds.changefull; createqueue.Enqueue(repData); } } else repData.pbs.SculptData = Utils.EmptyBytes; } public void DoRepDataGetMesh(ODEPhysRepData repData) { if (!repData.pbs.SculptEntry) return; if (repData.meshState != MeshState.loadingAsset) return; if (repData.assetID == null || repData.assetID == UUID.Zero) return; if (repData.assetID != repData.pbs.SculptTexture) return; RequestAssetDelegate assetProvider = m_scene.RequestAssetMethod; if (assetProvider == null) return; ODEAssetRequest asr = new ODEAssetRequest(this, assetProvider, repData, m_log); } /// /// Routine to figure out if we need to mesh this prim with our mesher /// /// /// public bool needsMeshing(PrimitiveBaseShape pbs) { // check sculpts or meshs if (pbs.SculptEntry) { if (meshSculptedPrim) return true; if (pbs.SculptType == (byte)SculptType.Mesh) // always do meshs return true; return false; } if (forceSimplePrimMeshing) return true; // if it's a standard box or sphere with no cuts, hollows, twist or top shear, return false since ODE can use an internal representation for the prim 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; } } // following code doesn't give meshs to boxes and spheres ever // and it's odd.. so for now just return true if asked to force meshs // hopefully mesher will fail if doesn't suport so things still get basic boxes int iPropertiesNotSupportedDefault = 0; 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; } // see if we need a mesh and if so if we have a cached one // called with a new repData public bool CheckMeshDone(ODEPhysRepData repData) { PhysicsActor actor = repData.actor; PrimitiveBaseShape pbs = repData.pbs; if (!needsMeshing(pbs)) { repData.meshState = MeshState.noNeed; return true; } IMesh mesh = null; Vector3 size = repData.size; byte shapetype = repData.shapetype; bool convex; int clod = (int)LevelOfDetail.High; if (shapetype == 0) convex = false; else { convex = true; if (pbs.SculptType != (byte)SculptType.Mesh) clod = (int)LevelOfDetail.Low; } mesh = m_mesher.GetMesh(actor.Name, pbs, size, clod, true, convex); if (mesh == null) { if (pbs.SculptEntry) { if (pbs.SculptTexture != null && pbs.SculptTexture != UUID.Zero) { repData.assetID = pbs.SculptTexture; repData.meshState = MeshState.needAsset; } else repData.meshState = MeshState.MeshFailed; } else repData.meshState = MeshState.needMesh; return false; } repData.mesh = mesh; if (pbs.SculptEntry) { repData.meshState = MeshState.AssetOK; repData.assetID = pbs.SculptTexture; } pbs.SculptData = Utils.EmptyBytes; return true; } public bool getMesh(ODEPhysRepData repData) { PhysicsActor actor = repData.actor; PrimitiveBaseShape pbs = repData.pbs; repData.mesh = null; repData.hasOBB = false; if (!needsMeshing(pbs)) return false; if (repData.meshState == MeshState.MeshFailed) return false; if (pbs.SculptEntry) { if (repData.meshState == MeshState.AssetFailed) { if (pbs.SculptTexture == repData.assetID) return true; } } repData.meshState = MeshState.noNeed; IMesh mesh = null; Vector3 size = repData.size; byte shapetype = repData.shapetype; bool convex; int clod = (int)LevelOfDetail.High; if (shapetype == 0) convex = false; else { convex = true; if (pbs.SculptType != (byte)SculptType.Mesh) clod = (int)LevelOfDetail.Low; } // check cached mesh = m_mesher.GetMesh(actor.Name, pbs, size, clod, true, convex); if (mesh == null) { if (pbs.SculptEntry) { if (pbs.SculptTexture == UUID.Zero) return false; repData.assetID = pbs.SculptTexture; repData.meshState = MeshState.AssetOK; if (pbs.SculptData == null || pbs.SculptData.Length == 0) { repData.meshState = MeshState.needAsset; return false; } } mesh = m_mesher.CreateMesh(actor.Name, pbs, size, clod, true, convex); } repData.mesh = mesh; repData.pbs.SculptData = Utils.EmptyBytes; if (mesh == null) { if (pbs.SculptEntry) repData.meshState = MeshState.AssetFailed; else repData.meshState = MeshState.MeshFailed; return false; } repData.meshState = MeshState.AssetOK; return true; } private void CalculateBasicPrimVolume(ODEPhysRepData repData) { PrimitiveBaseShape _pbs = repData.pbs; Vector3 _size = repData.size; float volume = _size.X * _size.Y * _size.Z; // default float tmp; float hollowAmount = (float)_pbs.ProfileHollow * 2.0e-5f; float hollowVolume = hollowAmount * hollowAmount; switch (_pbs.ProfileShape) { case ProfileShape.Square: // default box if (_pbs.PathCurve == (byte)Extrusion.Straight) { if (hollowAmount > 0.0) { switch (_pbs.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 (_pbs.PathCurve == (byte)Extrusion.Curve1) { //a tube volume *= 0.78539816339e-2f * (float)(200 - _pbs.PathScaleX); tmp = 1.0f - 2.0e-2f * (float)(200 - _pbs.PathScaleY); volume -= volume * tmp * tmp; if (hollowAmount > 0.0) { hollowVolume *= hollowAmount; switch (_pbs.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 (_pbs.PathCurve == (byte)Extrusion.Straight) { volume *= 0.78539816339f; // elipse base if (hollowAmount > 0.0) { switch (_pbs.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 (_pbs.PathCurve == (byte)Extrusion.Curve1) { volume *= 0.61685027506808491367715568749226e-2f * (float)(200 - _pbs.PathScaleX); tmp = 1.0f - .02f * (float)(200 - _pbs.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 (_pbs.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 (_pbs.PathCurve == (byte)Extrusion.Curve1) { volume *= 0.5236f; if (hollowAmount > 0.0) { hollowVolume *= hollowAmount; switch (_pbs.HollowShape) { case HollowShape.Circle: case HollowShape.Triangle: // diference in sl is minor and odd case HollowShape.Same: break; case HollowShape.Square: hollowVolume *= 0.909f; break; // case HollowShape.Triangle: // hollowVolume *= .827f; // break; default: hollowVolume = 0; break; } volume *= (1.0f - hollowVolume); } } break; case ProfileShape.EquilateralTriangle: if (_pbs.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 (_pbs.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 (_pbs.PathCurve == (byte)Extrusion.Curve1) { volume *= 0.32475953f; volume *= 0.01f * (float)(200 - _pbs.PathScaleX); tmp = 1.0f - .02f * (float)(200 - _pbs.PathScaleY); volume *= (1.0f - tmp * tmp); if (hollowAmount > 0.0) { hollowVolume *= hollowAmount; switch (_pbs.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 (_pbs.PathCurve == (byte)Extrusion.Straight || _pbs.PathCurve == (byte)Extrusion.Flexible) { taperX1 = _pbs.PathScaleX * 0.01f; if (taperX1 > 1.0f) taperX1 = 2.0f - taperX1; taperX = 1.0f - taperX1; taperY1 = _pbs.PathScaleY * 0.01f; if (taperY1 > 1.0f) taperY1 = 2.0f - taperY1; taperY = 1.0f - taperY1; } else { taperX = _pbs.PathTaperX * 0.01f; if (taperX < 0.0f) taperX = -taperX; taperX1 = 1.0f - taperX; taperY = _pbs.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)_pbs.PathBegin * 2.0e-5f; pathEnd = 1.0f - (float)_pbs.PathEnd * 2.0e-5f; volume *= (pathEnd - pathBegin); // this is crude aproximation profileBegin = (float)_pbs.ProfileBegin * 2.0e-5f; profileEnd = 1.0f - (float)_pbs.ProfileEnd * 2.0e-5f; volume *= (profileEnd - profileBegin); repData.volume = volume; } private void CalcVolumeData(ODEPhysRepData repData) { if (repData.hasOBB) { Vector3 OBB = repData.OBB; } else { Vector3 OBB = repData.size; OBB.X *= 0.5f; OBB.Y *= 0.5f; OBB.Z *= 0.5f; repData.OBB = OBB; repData.OBBOffset = Vector3.Zero; } CalculateBasicPrimVolume(repData); } } public class ODEAssetRequest { ODEMeshWorker m_worker; private ILog m_log; ODEPhysRepData repData; public ODEAssetRequest(ODEMeshWorker pWorker, RequestAssetDelegate provider, ODEPhysRepData pRepData, ILog plog) { m_worker = pWorker; m_log = plog; repData = pRepData; repData.meshState = MeshState.AssetFailed; if (provider == null) return; if (repData.assetID == null) return; UUID assetID = (UUID) repData.assetID; if (assetID == UUID.Zero) return; repData.meshState = MeshState.loadingAsset; provider(assetID, ODEassetReceived); } void ODEassetReceived(AssetBase asset) { repData.meshState = MeshState.AssetFailed; if (asset != null) { if (asset.Data != null && asset.Data.Length > 0) { repData.meshState = MeshState.noNeed; if (!repData.pbs.SculptEntry) return; if (repData.pbs.SculptTexture != repData.assetID) return; repData.pbs.SculptData = new byte[asset.Data.Length]; asset.Data.CopyTo(repData.pbs.SculptData,0); repData.meshState = MeshState.AssetOK; m_worker.AssetLoaded(repData); } else m_log.WarnFormat("[PHYSICS]: asset provider returned invalid mesh data for prim {0} asset UUID {1}.", repData.actor.Name, asset.ID.ToString()); } else m_log.WarnFormat("[PHYSICS]: asset provider returned null asset fo mesh of prim {0}.", repData.actor.Name); } } }