/* * 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. */ //#define USE_DRAWSTUFF //#define SPAM using System; using System.Collections.Generic; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.IO; using System.Diagnostics; using log4net; using Nini.Config; using OdeAPI; #if USE_DRAWSTUFF using ODEDrawstuff; #endif using OpenSim.Framework; using OpenSim.Region.Physics.Manager; using OpenMetaverse; namespace OpenSim.Region.Physics.OdePlugin { public enum StatusIndicators : int { Generic = 0, Start = 1, End = 2 } public struct sCollisionData { public uint ColliderLocalId; public uint CollidedWithLocalId; public int NumberOfCollisions; public int CollisionType; public int StatusIndicator; public int lastframe; } [Flags] public enum CollisionCategories : int { Disabled = 0, Geom = 0x00000001, Body = 0x00000002, Space = 0x00000004, Character = 0x00000008, Land = 0x00000010, Water = 0x00000020, Wind = 0x00000040, Sensor = 0x00000080, Selected = 0x00000100 } /// /// Material type for a primitive /// public enum Material : int { /// Stone = 0, /// Metal = 1, /// Glass = 2, /// Wood = 3, /// Flesh = 4, /// Plastic = 5, /// Rubber = 6, light = 7 // compatibility with old viewers } public enum changes : int { Add = 0, // arg null. finishs the prim creation. should be used internally only ( to remove later ?) Remove, Link, // arg AuroraODEPrim new parent prim or null to delink. Makes the prim part of a object with prim parent as root // or removes from a object if arg is null DeLink, Position, // arg Vector3 new position in world coords. Changes prim position. Prim must know if it is root or child Orientation, // arg Quaternion new orientation in world coords. Changes prim position. Prim must know it it is root or child PosOffset, // not in use // arg Vector3 new position in local coords. Changes prim position in object OriOffset, // not in use // arg Vector3 new position in local coords. Changes prim position in object Velocity, AngVelocity, Acceleration, Force, Torque, AddForce, AddAngForce, AngLock, Size, Shape, CollidesWater, VolumeDtc, Physical, Selected, disabled, building, Null //keep this last used do dim the methods array. does nothing but pulsing the prim } public struct ODEchangeitem { public OdePrim prim; public OdeCharacter character; public changes what; public Object arg; } public class OdeScene : PhysicsScene { private readonly ILog m_log; // private Dictionary m_storedCollisions = new Dictionary(); private int threadid = 0; private Random fluidRandomizer = new Random(Environment.TickCount); const d.ContactFlags comumContactFlags = d.ContactFlags.SoftERP | d.ContactFlags.SoftCFM |d.ContactFlags.Approx1 | d.ContactFlags.Bounce; const float comumContactERP = 0.6f; const float comumSoftContactERP = 0.1f; const float comumContactCFM = 0.0001f; float frictionScale = 1.0f; float frictionMovementMult = 0.3f; float TerrainBounce = 0.3f; float TerrainFriction = 0.3f; public float AvatarBounce = 0.3f; public float AvatarFriction = 0;// 0.9f * 0.5f; private const uint m_regionWidth = Constants.RegionSize; private const uint m_regionHeight = Constants.RegionSize; public float ODE_STEPSIZE = 0.020f; private float metersInSpace = 25.6f; private float m_timeDilation = 1.0f; public float gravityx = 0f; public float gravityy = 0f; public float gravityz = -9.8f; private float waterlevel = 0f; private int framecount = 0; internal IntPtr WaterGeom; public float avPIDD = 3200f; // make it visible public float avPIDP = 1400f; // make it visible private float avCapRadius = 0.37f; private float avDensity = 3f; private float avMovementDivisorWalk = 1.3f; private float avMovementDivisorRun = 0.8f; private float minimumGroundFlightOffset = 3f; public float maximumMassObject = 10000.01f; public bool meshSculptedPrim = true; public bool forceSimplePrimMeshing = false; public float meshSculptLOD = 32; public float MeshSculptphysicalLOD = 16; public float geomDefaultDensity = 10.000006836f; public int geomContactPointsStartthrottle = 3; public int geomUpdatesPerThrottledUpdate = 15; public float bodyPIDD = 35f; public float bodyPIDG = 25; public int geomCrossingFailuresBeforeOutofbounds = 6; public int bodyFramesAutoDisable = 20; private float[] _watermap; private bool m_filterCollisions = true; private d.NearCallback nearCallback; private readonly HashSet _characters = new HashSet(); private readonly HashSet _prims = new HashSet(); private readonly HashSet _activeprims = new HashSet(); private readonly Object _taintedCharacterLock = new Object(); private readonly HashSet _taintedCharacterH = new HashSet(); // faster verification of repeated character taints private readonly Queue _taintedCharacterQ = new Queue(); // character taints public OpenSim.Framework.LocklessQueue ChangesQueue = new OpenSim.Framework.LocklessQueue(); /// /// A list of actors that should receive collision events. /// private readonly List _collisionEventPrim = new List(); private readonly HashSet _badCharacter = new HashSet(); public Dictionary geom_name_map = new Dictionary(); public Dictionary actor_name_map = new Dictionary(); private float contactsurfacelayer = 0.002f; private int contactsPerCollision = 80; internal IntPtr ContactgeomsArray = IntPtr.Zero; private IntPtr GlobalContactsArray = IntPtr.Zero; const int maxContactsbeforedeath = 4000; private volatile int m_global_contactcount = 0; private readonly IntPtr contactgroup; public ContactData[] m_materialContactsData = new ContactData[8]; private readonly DoubleDictionary RegionTerrain = new DoubleDictionary(); private readonly Dictionary TerrainHeightFieldHeights = new Dictionary(); private readonly Dictionary TerrainHeightFieldHeightsHandlers = new Dictionary(); private int m_physicsiterations = 10; private const float m_SkipFramesAtms = 0.40f; // Drop frames gracefully at a 400 ms lag private readonly PhysicsActor PANull = new NullPhysicsActor(); private float step_time = 0.0f; public IntPtr world; private uint obj2LocalID = 0; private OdeCharacter cc1; private OdePrim cp1; private OdeCharacter cc2; private OdePrim cp2; // split the spaces acording to contents type // ActiveSpace contains characters and active prims // StaticSpace contains land and other that is mostly static in enviroment // this can contain subspaces, like the grid in staticspace // as now space only contains this 2 top spaces public IntPtr TopSpace; // the global space public IntPtr ActiveSpace; // space for active prims public IntPtr StaticSpace; // space for the static things around // some speedup variables private int spaceGridMaxX; private int spaceGridMaxY; private float spacesPerMeter; // split static geometry collision into a grid as before private IntPtr[,] staticPrimspace; private Object OdeLock; private static Object SimulationLock; public IMesher mesher; private IConfigSource m_config; public bool physics_logging = false; public int physics_logging_interval = 0; public bool physics_logging_append_existing_logfile = false; private Vector3 m_worldOffset = Vector3.Zero; public Vector2 WorldExtents = new Vector2((int)Constants.RegionSize, (int)Constants.RegionSize); private PhysicsScene m_parentScene = null; private ODERayCastRequestManager m_rayCastManager; /* maybe needed if ode uses tls private void checkThread() { int th = Thread.CurrentThread.ManagedThreadId; if(th != threadid) { threadid = th; d.AllocateODEDataForThread(~0U); } } */ /// /// Initiailizes the scene /// Sets many properties that ODE requires to be stable /// These settings need to be tweaked 'exactly' right or weird stuff happens. /// public OdeScene(string sceneIdentifier) { m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType.ToString() + "." + sceneIdentifier); // checkThread(); Name = sceneIdentifier; OdeLock = new Object(); SimulationLock = new Object(); nearCallback = near; m_rayCastManager = new ODERayCastRequestManager(this); lock (OdeLock) { // Create the world and the first space try { world = d.WorldCreate(); TopSpace = d.HashSpaceCreate(IntPtr.Zero); // now the major subspaces ActiveSpace = d.HashSpaceCreate(TopSpace); StaticSpace = d.HashSpaceCreate(TopSpace); } catch { // i must RtC#FM } d.HashSpaceSetLevels(TopSpace, -2, 8); // cell sizes from .25 to 256 ?? need check what this really does d.HashSpaceSetLevels(ActiveSpace, -2, 8); d.HashSpaceSetLevels(StaticSpace, -2, 8); // demote to second level d.SpaceSetSublevel(ActiveSpace, 1); d.SpaceSetSublevel(StaticSpace, 1); contactgroup = d.JointGroupCreate(0); //contactgroup d.WorldSetAutoDisableFlag(world, false); #if USE_DRAWSTUFF Thread viewthread = new Thread(new ParameterizedThreadStart(startvisualization)); viewthread.Start(); #endif } _watermap = new float[258 * 258]; } #if USE_DRAWSTUFF public void startvisualization(object o) { ds.Functions fn; fn.version = ds.VERSION; fn.start = new ds.CallbackFunction(start); fn.step = new ds.CallbackFunction(step); fn.command = new ds.CallbackFunction(command); fn.stop = null; fn.path_to_textures = "./textures"; string[] args = new string[0]; ds.SimulationLoop(args.Length, args, 352, 288, ref fn); } #endif // Initialize the mesh plugin // public override void Initialise(IMesher meshmerizer, IConfigSource config, RegionInfo region ) public override void Initialise(IMesher meshmerizer, IConfigSource config) { // checkThread(); mesher = meshmerizer; m_config = config; // m_log.WarnFormat("ODE configuration: {0}", d.GetConfiguration("ODE")); /* if (region != null) { WorldExtents.X = region.RegionSizeX; WorldExtents.Y = region.RegionSizeY; } */ // Defaults avPIDD = 2200.0f; avPIDP = 900.0f; int contactsPerCollision = 80; if (m_config != null) { IConfig physicsconfig = m_config.Configs["ODEPhysicsSettings"]; if (physicsconfig != null) { gravityx = physicsconfig.GetFloat("world_gravityx", 0f); gravityy = physicsconfig.GetFloat("world_gravityy", 0f); gravityz = physicsconfig.GetFloat("world_gravityz", -9.8f); metersInSpace = physicsconfig.GetFloat("meters_in_small_space", 29.9f); contactsurfacelayer = physicsconfig.GetFloat("world_contact_surface_layer", contactsurfacelayer); ODE_STEPSIZE = physicsconfig.GetFloat("world_stepsize", 0.020f); m_physicsiterations = physicsconfig.GetInt("world_internal_steps_without_collisions", 10); avDensity = physicsconfig.GetFloat("av_density", avDensity); avMovementDivisorWalk = physicsconfig.GetFloat("av_movement_divisor_walk", 1.3f); avMovementDivisorRun = physicsconfig.GetFloat("av_movement_divisor_run", 0.8f); avCapRadius = physicsconfig.GetFloat("av_capsule_radius", 0.37f); contactsPerCollision = physicsconfig.GetInt("contacts_per_collision", 80); geomContactPointsStartthrottle = physicsconfig.GetInt("geom_contactpoints_start_throttling", 3); geomUpdatesPerThrottledUpdate = physicsconfig.GetInt("geom_updates_before_throttled_update", 15); geomCrossingFailuresBeforeOutofbounds = physicsconfig.GetInt("geom_crossing_failures_before_outofbounds", 5); geomDefaultDensity = physicsconfig.GetFloat("geometry_default_density", 10.000006836f); bodyFramesAutoDisable = physicsconfig.GetInt("body_frames_auto_disable", 20); bodyPIDD = physicsconfig.GetFloat("body_pid_derivative", 35f); bodyPIDG = physicsconfig.GetFloat("body_pid_gain", 25f); forceSimplePrimMeshing = physicsconfig.GetBoolean("force_simple_prim_meshing", forceSimplePrimMeshing); meshSculptedPrim = physicsconfig.GetBoolean("mesh_sculpted_prim", true); meshSculptLOD = physicsconfig.GetFloat("mesh_lod", 32f); MeshSculptphysicalLOD = physicsconfig.GetFloat("mesh_physical_lod", 16f); m_filterCollisions = physicsconfig.GetBoolean("filter_collisions", false); if (Environment.OSVersion.Platform == PlatformID.Unix) { avPIDD = physicsconfig.GetFloat("av_pid_derivative_linux", 2200.0f); avPIDP = physicsconfig.GetFloat("av_pid_proportional_linux", 900.0f); } else { avPIDD = physicsconfig.GetFloat("av_pid_derivative_win", 2200.0f); avPIDP = physicsconfig.GetFloat("av_pid_proportional_win", 900.0f); } physics_logging = physicsconfig.GetBoolean("physics_logging", false); physics_logging_interval = physicsconfig.GetInt("physics_logging_interval", 0); physics_logging_append_existing_logfile = physicsconfig.GetBoolean("physics_logging_append_existing_logfile", false); minimumGroundFlightOffset = physicsconfig.GetFloat("minimum_ground_flight_offset", 3f); maximumMassObject = physicsconfig.GetFloat("maximum_mass_object", 10000.01f); } } ContactgeomsArray = Marshal.AllocHGlobal(contactsPerCollision * d.ContactGeom.unmanagedSizeOf); GlobalContactsArray = GlobalContactsArray = Marshal.AllocHGlobal(maxContactsbeforedeath * d.Contact.unmanagedSizeOf); m_materialContactsData[(int)Material.Stone].mu = frictionScale * 0.8f; m_materialContactsData[(int)Material.Stone].bounce = 0.4f; m_materialContactsData[(int)Material.Metal].mu = frictionScale * 0.3f; m_materialContactsData[(int)Material.Metal].bounce = 0.4f; m_materialContactsData[(int)Material.Glass].mu = frictionScale * 0.2f; m_materialContactsData[(int)Material.Glass].bounce = 0.7f; m_materialContactsData[(int)Material.Wood].mu = frictionScale * 0.6f; m_materialContactsData[(int)Material.Wood].bounce = 0.5f; m_materialContactsData[(int)Material.Flesh].mu = frictionScale * 0.9f; m_materialContactsData[(int)Material.Flesh].bounce = 0.3f; m_materialContactsData[(int)Material.Plastic].mu = frictionScale * 0.4f; m_materialContactsData[(int)Material.Plastic].bounce = 0.7f; m_materialContactsData[(int)Material.Rubber].mu = frictionScale * 0.9f; m_materialContactsData[(int)Material.Rubber].bounce = 0.95f; m_materialContactsData[(int)Material.light].mu = 0.0f; m_materialContactsData[(int)Material.light].bounce = 0.0f; TerrainFriction *= frictionScale; // AvatarFriction *= frictionScale; // Set the gravity,, don't disable things automatically (we set it explicitly on some things) d.WorldSetGravity(world, gravityx, gravityy, gravityz); d.WorldSetContactSurfaceLayer(world, contactsurfacelayer); d.WorldSetLinearDamping(world, 0.001f); d.WorldSetAngularDamping(world, 0.001f); d.WorldSetAngularDampingThreshold(world, 0f); d.WorldSetLinearDampingThreshold(world, 0f); d.WorldSetMaxAngularSpeed(world, 256f); d.WorldSetCFM(world,1e-6f); // a bit harder than default //d.WorldSetCFM(world, 1e-4f); // a bit harder than default d.WorldSetERP(world, 0.6f); // higher than original // Set how many steps we go without running collision testing // This is in addition to the step size. // Essentially Steps * m_physicsiterations d.WorldSetQuickStepNumIterations(world, m_physicsiterations); d.WorldSetContactMaxCorrectingVel(world, 100.0f); spacesPerMeter = 1 / metersInSpace; spaceGridMaxX = (int)(WorldExtents.X * spacesPerMeter); spaceGridMaxY = (int)(WorldExtents.Y * spacesPerMeter); staticPrimspace = new IntPtr[spaceGridMaxX, spaceGridMaxY]; // create all spaces now int i, j; IntPtr newspace; for (i = 0; i < spaceGridMaxX; i++) for (j = 0; j < spaceGridMaxY; j++) { newspace = d.HashSpaceCreate(StaticSpace); d.GeomSetCategoryBits(newspace, (int)CollisionCategories.Space); waitForSpaceUnlock(newspace); d.SpaceSetSublevel(newspace, 2); d.HashSpaceSetLevels(newspace, -2, 8); staticPrimspace[i, j] = newspace; } // let this now be real maximum values spaceGridMaxX--; spaceGridMaxY--; } internal void waitForSpaceUnlock(IntPtr space) { //if (space != IntPtr.Zero) //while (d.SpaceLockQuery(space)) { } // Wait and do nothing } #region Collision Detection // sets a global contact for a joint for contactgeom , and base contact description) private IntPtr CreateContacJoint(ref d.ContactGeom contactGeom, float mu, float bounce, bool softerp) { if (GlobalContactsArray == IntPtr.Zero || m_global_contactcount >= maxContactsbeforedeath) return IntPtr.Zero; d.Contact newcontact = new d.Contact(); newcontact.geom.depth = contactGeom.depth; newcontact.geom.g1 = contactGeom.g1; newcontact.geom.g2 = contactGeom.g2; newcontact.geom.pos = contactGeom.pos; newcontact.geom.normal = contactGeom.normal; newcontact.geom.side1 = contactGeom.side1; newcontact.geom.side2 = contactGeom.side2; // this needs bounce also newcontact.surface.mode = comumContactFlags; newcontact.surface.mu = mu; newcontact.surface.bounce = bounce; newcontact.surface.soft_cfm = comumContactCFM; if (softerp) newcontact.surface.soft_erp = comumSoftContactERP; else newcontact.surface.soft_erp = comumContactERP; IntPtr contact = new IntPtr(GlobalContactsArray.ToInt64() + (Int64)(m_global_contactcount * d.Contact.unmanagedSizeOf)); Marshal.StructureToPtr(newcontact, contact, true); return d.JointCreateContactPtr(world, contactgroup, contact); } /// /// This is our near callback. A geometry is near a body /// /// The space that contains the geoms. Remember, spaces are also geoms /// a geometry or space /// another geometry or space /// private bool GetCurContactGeom(int index, ref d.ContactGeom newcontactgeom) { if (ContactgeomsArray == IntPtr.Zero || index >= contactsPerCollision) return false; IntPtr contactptr = new IntPtr(ContactgeomsArray.ToInt64() + (Int64)(index * d.ContactGeom.unmanagedSizeOf)); newcontactgeom = (d.ContactGeom)Marshal.PtrToStructure(contactptr, typeof(d.ContactGeom)); return true; } private void near(IntPtr space, IntPtr g1, IntPtr g2) { // no lock here! It's invoked from within Simulate(), which is thread-locked if (m_global_contactcount >= maxContactsbeforedeath) return; // Test if we're colliding a geom with a space. // If so we have to drill down into the space recursively if (g1 == IntPtr.Zero || g2 == IntPtr.Zero) return; if (d.GeomIsSpace(g1) || d.GeomIsSpace(g2)) { // We'll be calling near recursivly if one // of them is a space to find all of the // contact points in the space try { d.SpaceCollide2(g1, g2, IntPtr.Zero, nearCallback); } catch (AccessViolationException) { m_log.Warn("[PHYSICS]: Unable to collide test a space"); return; } //here one should check collisions of geoms inside a space // but on each space we only should have geoms that not colide amoung each other // so we don't dig inside spaces return; } // get geom bodies to check if we already a joint contact // guess this shouldn't happen now IntPtr b1 = d.GeomGetBody(g1); IntPtr b2 = d.GeomGetBody(g2); // d.GeomClassID id = d.GeomGetClass(g1); // Figure out how many contact points we have int count = 0; try { // Colliding Geom To Geom // This portion of the function 'was' blatantly ripped off from BoxStack.cs if (g1 == g2) return; // Can't collide with yourself if (b1 != IntPtr.Zero && b2 != IntPtr.Zero && d.AreConnectedExcluding(b1, b2, d.JointType.Contact)) return; count = d.CollidePtr(g1, g2, (contactsPerCollision & 0xffff), ContactgeomsArray, d.ContactGeom.unmanagedSizeOf); } catch (SEHException) { m_log.Error("[PHYSICS]: The Operating system shut down ODE because of corrupt memory. This could be a result of really irregular terrain. If this repeats continuously, restart using Basic Physics and terrain fill your terrain. Restarting the sim."); // ode.drelease(world); base.TriggerPhysicsBasedRestart(); } catch (Exception e) { m_log.WarnFormat("[PHYSICS]: Unable to collide test an object: {0}", e.Message); return; } // id contacts done if (count == 0) return; // try get physical actors PhysicsActor p1; PhysicsActor p2; if (!actor_name_map.TryGetValue(g1, out p1)) { p1 = PANull; } if (!actor_name_map.TryGetValue(g2, out p2)) { p2 = PANull; } // update actors collision score if (p1.CollisionScore >= float.MaxValue - count) p1.CollisionScore = 0; p1.CollisionScore += count; if (p2.CollisionScore >= float.MaxValue - count) p2.CollisionScore = 0; p2.CollisionScore += count; // get first contact d.ContactGeom curContact = new d.ContactGeom(); if (!GetCurContactGeom(0, ref curContact)) return; // for now it's the one with max depth ContactPoint maxDepthContact = new ContactPoint( new Vector3(curContact.pos.X, curContact.pos.Y, curContact.pos.Z), new Vector3(curContact.normal.X, curContact.normal.Y, curContact.normal.Z), curContact.depth ); // do volume detection case if ( (p1 is OdePrim) && (((OdePrim)p1).m_isVolumeDetect) || (p2 is OdePrim) && (((OdePrim)p2).m_isVolumeDetect)) { collision_accounting_events(p1, p2, maxDepthContact); return; } // big messy collision analises float mu = 0; float bounce = 0; ContactData contactdata1; ContactData contactdata2; bool erpSoft = false; String name = null; bool dop1foot = false; bool dop2foot = false; bool ignore = false; switch (p1.PhysicsActorType) { case (int)ActorTypes.Agent: switch (p2.PhysicsActorType) { case (int)ActorTypes.Agent: contactdata1 = p1.ContactData; contactdata2 = p2.ContactData; bounce = contactdata1.bounce * contactdata2.bounce; mu = (float)Math.Sqrt(contactdata1.mu * contactdata2.mu); if ((Math.Abs(p2.Velocity.X - p1.Velocity.X) > 0.1f || Math.Abs(p2.Velocity.Y - p1.Velocity.Y) > 0.1f)) mu *= frictionMovementMult; p1.CollidingObj = true; p2.CollidingObj = true; break; case (int)ActorTypes.Prim: contactdata1 = p1.ContactData; contactdata2 = p2.ContactData; bounce = contactdata1.bounce * contactdata2.bounce; mu = (float)Math.Sqrt(contactdata1.mu * contactdata2.mu); if ((Math.Abs(p2.Velocity.X - p1.Velocity.X) > 0.1f || Math.Abs(p2.Velocity.Y - p1.Velocity.Y) > 0.1f)) mu *= frictionMovementMult; if (p2.Velocity.LengthSquared() > 0.0f) p2.CollidingObj = true; dop1foot = true; break; default: ignore=true; // avatar to terrain and water ignored break; } break; case (int)ActorTypes.Prim: switch (p2.PhysicsActorType) { case (int)ActorTypes.Agent: contactdata1 = p1.ContactData; contactdata2 = p2.ContactData; bounce = contactdata1.bounce * contactdata2.bounce; mu = (float)Math.Sqrt(contactdata1.mu * contactdata2.mu); if ((Math.Abs(p2.Velocity.X - p1.Velocity.X) > 0.1f || Math.Abs(p2.Velocity.Y - p1.Velocity.Y) > 0.1f)) mu *= frictionMovementMult; dop2foot = true; if (p1.Velocity.LengthSquared() > 0.0f) p1.CollidingObj = true; break; case (int)ActorTypes.Prim: if ((p1.Velocity - p2.Velocity).LengthSquared() > 0.0f) { p1.CollidingObj = true; p2.CollidingObj = true; } contactdata1 = p1.ContactData; contactdata2 = p2.ContactData; bounce = contactdata1.bounce * contactdata2.bounce; erpSoft = true; mu = (float)Math.Sqrt(contactdata1.mu * contactdata2.mu); if ((Math.Abs(p2.Velocity.X - p1.Velocity.X) > 0.1f || Math.Abs(p2.Velocity.Y - p1.Velocity.Y) > 0.1f)) mu *= frictionMovementMult; break; default: if (geom_name_map.TryGetValue(g2, out name)) { if (name == "Terrain") { erpSoft = true; contactdata1 = p1.ContactData; bounce = contactdata1.bounce * TerrainBounce; mu = (float)Math.Sqrt(contactdata1.mu * TerrainFriction); if (Math.Abs(p1.Velocity.X) > 0.1f || Math.Abs(p1.Velocity.Y) > 0.1f) mu *= frictionMovementMult; p1.CollidingGround = true; } else if (name == "Water") { erpSoft = true; } } else ignore=true; break; } break; default: if (geom_name_map.TryGetValue(g1, out name)) { if (name == "Terrain") { if (p2.PhysicsActorType == (int)ActorTypes.Prim) { erpSoft = true; p2.CollidingGround = true; contactdata2 = p2.ContactData; bounce = contactdata2.bounce * TerrainBounce; mu = (float)Math.Sqrt(contactdata2.mu * TerrainFriction); if (Math.Abs(p2.Velocity.X) > 0.1f || Math.Abs(p2.Velocity.Y) > 0.1f) mu *= frictionMovementMult; } else ignore = true; } else if (name == "Water" && (p2.PhysicsActorType == (int)ActorTypes.Prim || p2.PhysicsActorType == (int)ActorTypes.Agent)) { erpSoft = true; } } else ignore = true; break; } if (ignore) return; IntPtr Joint; int i = 0; while(true) { if (dop1foot && (p1.Position.Z - curContact.pos.Z) > (p1.Size.Z - avCapRadius) * 0.5f) p1.IsColliding = true; if (dop2foot && (p2.Position.Z - curContact.pos.Z) > (p2.Size.Z - avCapRadius) * 0.5f) p2.IsColliding = true; Joint = CreateContacJoint(ref curContact, mu, bounce, erpSoft); d.JointAttach(Joint, b1, b2); if (++m_global_contactcount >= maxContactsbeforedeath) break; if(++i >= count) break; if (!GetCurContactGeom(i, ref curContact)) break; if (curContact.depth > maxDepthContact.PenetrationDepth) { maxDepthContact.Position.X = curContact.pos.X; maxDepthContact.Position.Y = curContact.pos.Y; maxDepthContact.Position.Z = curContact.pos.Z; maxDepthContact.SurfaceNormal.X = curContact.normal.X; maxDepthContact.SurfaceNormal.Y = curContact.normal.Y; maxDepthContact.SurfaceNormal.Z = curContact.normal.Z; maxDepthContact.PenetrationDepth = curContact.depth; } } collision_accounting_events(p1, p2, maxDepthContact); /* if (notskipedcount > geomContactPointsStartthrottle) { // If there are more then 3 contact points, it's likely // that we've got a pile of objects, so ... // We don't want to send out hundreds of terse updates over and over again // so lets throttle them and send them again after it's somewhat sorted out. this needs checking so out for now if (b1 != IntPtr.Zero) p1.ThrottleUpdates = true; if (b2 != IntPtr.Zero) p2.ThrottleUpdates = true; } */ } private void collision_accounting_events(PhysicsActor p1, PhysicsActor p2, ContactPoint contact) { // obj1LocalID = 0; //returncollisions = false; obj2LocalID = 0; //ctype = 0; //cStartStop = 0; if (!(p2.SubscribedEvents() || p1.SubscribedEvents())) return; switch ((ActorTypes)p1.PhysicsActorType) { case ActorTypes.Agent: cc1 = (OdeCharacter)p1; switch ((ActorTypes)p2.PhysicsActorType) { case ActorTypes.Agent: cc2 = (OdeCharacter)p2; obj2LocalID = cc2.m_localID; if (p2.SubscribedEvents()) cc2.AddCollisionEvent(cc1.m_localID, contact); break; case ActorTypes.Prim: if (p2 is OdePrim) { cp2 = (OdePrim)p2; obj2LocalID = cp2.m_localID; if (p2.SubscribedEvents()) cp2.AddCollisionEvent(cc1.m_localID, contact); } break; case ActorTypes.Ground: case ActorTypes.Unknown: default: obj2LocalID = 0; break; } if (p1.SubscribedEvents()) { contact.SurfaceNormal = -contact.SurfaceNormal; cc1.AddCollisionEvent(obj2LocalID, contact); } break; case ActorTypes.Prim: if (p1 is OdePrim) { cp1 = (OdePrim)p1; // obj1LocalID = cp2.m_localID; switch ((ActorTypes)p2.PhysicsActorType) { case ActorTypes.Agent: if (p2 is OdeCharacter) { cc2 = (OdeCharacter)p2; obj2LocalID = cc2.m_localID; if (p2.SubscribedEvents()) cc2.AddCollisionEvent(cp1.m_localID, contact); } break; case ActorTypes.Prim: if (p2 is OdePrim) { cp2 = (OdePrim)p2; obj2LocalID = cp2.m_localID; if (p2.SubscribedEvents()) cp2.AddCollisionEvent(cp1.m_localID, contact); } break; case ActorTypes.Ground: case ActorTypes.Unknown: default: obj2LocalID = 0; break; } if (p1.SubscribedEvents()) { contact.SurfaceNormal = -contact.SurfaceNormal; cp1.AddCollisionEvent(obj2LocalID, contact); } } break; } } /// /// This is our collision testing routine in ODE /// /// private void collision_optimized() { // _perloopContact.Clear(); // clear characts IsColliding until we do it some other way lock (_characters) { foreach (OdeCharacter chr in _characters) { // this are odd checks if they are needed something is wrong elsewhere // keep for now if (chr == null) continue; if (chr.Shell == IntPtr.Zero || chr.Body == IntPtr.Zero) continue; chr.IsColliding = false; // chr.CollidingGround = false; not done here chr.CollidingObj = false; } } // now let ode do its job // colide active things amoung them int st = Util.EnvironmentTickCount(); int ta; int ts; try { d.SpaceCollide(ActiveSpace, IntPtr.Zero, nearCallback); } catch (AccessViolationException) { m_log.Warn("[PHYSICS]: Unable to Active space collide"); } ta = Util.EnvironmentTickCountSubtract(st); // then active things with static enviroment try { d.SpaceCollide2(ActiveSpace,StaticSpace, IntPtr.Zero, nearCallback); } catch (AccessViolationException) { m_log.Warn("[PHYSICS]: Unable to Active to static space collide"); } ts = Util.EnvironmentTickCountSubtract(st); // _perloopContact.Clear(); } #endregion public float GetTerrainHeightAtXY(float x, float y) { // assumes 1m size grid and constante size square regions // region offset in mega position int offsetX = ((int)(x / (int)Constants.RegionSize)) * (int)Constants.RegionSize; int offsetY = ((int)(y / (int)Constants.RegionSize)) * (int)Constants.RegionSize; IntPtr heightFieldGeom = IntPtr.Zero; // get region map if (!RegionTerrain.TryGetValue(new Vector3(offsetX, offsetY, 0), out heightFieldGeom)) return 0f; if (heightFieldGeom == IntPtr.Zero) return 0f; if (!TerrainHeightFieldHeights.ContainsKey(heightFieldGeom)) return 0f; // TerrainHeightField for ODE as offset 1m x += 1f - offsetX; y += 1f - offsetY; // make position fit into array if (x < 0) x = 0; if (y < 0) y = 0; // integer indexs int ix; int iy; // interpolators offset float dx; float dy; int regsize = (int)Constants.RegionSize + 2; // map size see setterrain // we still have square fixed size regions // also flip x and y because of how map is done for ODE fliped axis // so ix,iy,dx and dy are inter exchanged if (x < regsize - 1) { iy = (int)x; dy = x - (float)iy; } else // out world use external height { iy = regsize - 1; dy = 0; } if (y < regsize - 1) { ix = (int)y; dx = y - (float)ix; } else { ix = regsize - 1; dx = 0; } float h0; float h1; float h2; iy *= regsize; iy += ix; // all indexes have iy + ix float[] heights = TerrainHeightFieldHeights[heightFieldGeom]; if ((dx + dy) <= 1.0f) { h0 = ((float)heights[iy]); // 0,0 vertice h1 = (((float)heights[iy + 1]) - h0) * dx; // 1,0 vertice minus 0,0 h2 = (((float)heights[iy + regsize]) - h0) * dy; // 0,1 vertice minus 0,0 } else { h0 = ((float)heights[iy + regsize + 1]); // 1,1 vertice h1 = (((float)heights[iy + 1]) - h0) * (1 - dy); // 1,1 vertice minus 1,0 h2 = (((float)heights[iy + regsize]) - h0) * (1 - dx); // 1,1 vertice minus 0,1 } return h0 + h1 + h2; } /// /// Add actor to the list that should receive collision events in the simulate loop. /// /// public void AddCollisionEventReporting(PhysicsActor obj) { lock (_collisionEventPrim) { if (!_collisionEventPrim.Contains(obj)) _collisionEventPrim.Add(obj); } } /// /// Remove actor from the list that should receive collision events in the simulate loop. /// /// public void RemoveCollisionEventReporting(PhysicsActor obj) { lock (_collisionEventPrim) { if (_collisionEventPrim.Contains(obj)) _collisionEventPrim.Remove(obj); } } #region Add/Remove Entities public override PhysicsActor AddAvatar(string avName, Vector3 position, Vector3 size, bool isFlying) { Vector3 pos; pos.X = position.X; pos.Y = position.Y; pos.Z = position.Z; OdeCharacter newAv = new OdeCharacter(avName, this, pos, size, avPIDD, avPIDP, avCapRadius, avDensity, avMovementDivisorWalk, avMovementDivisorRun); newAv.Flying = isFlying; newAv.MinimumGroundFlightOffset = minimumGroundFlightOffset; return newAv; } public void AddCharacter(OdeCharacter chr) { lock (_characters) { if (!_characters.Contains(chr)) { _characters.Add(chr); if (chr.bad) m_log.DebugFormat("[PHYSICS] Added BAD actor {0} to characters list", chr.m_uuid); } } } public void RemoveCharacter(OdeCharacter chr) { lock (_characters) { if (_characters.Contains(chr)) { _characters.Remove(chr); } } } public void BadCharacter(OdeCharacter chr) { lock (_badCharacter) { if (!_badCharacter.Contains(chr)) _badCharacter.Add(chr); } } public override void RemoveAvatar(PhysicsActor actor) { //m_log.Debug("[PHYSICS]:ODELOCK"); ((OdeCharacter) actor).Destroy(); } private PhysicsActor AddPrim(String name, Vector3 position, Vector3 size, Quaternion rotation, PrimitiveBaseShape pbs, bool isphysical, uint localID) { Vector3 pos = position; Vector3 siz = size; Quaternion rot = rotation; OdePrim newPrim; lock (OdeLock) { newPrim = new OdePrim(name, this, pos, siz, rot, pbs, isphysical); lock (_prims) _prims.Add(newPrim); } newPrim.LocalID = localID; return newPrim; } public void addActivePrim(OdePrim activatePrim) { // adds active prim.. (ones that should be iterated over in collisions_optimized lock (_activeprims) { if (!_activeprims.Contains(activatePrim)) _activeprims.Add(activatePrim); //else // m_log.Warn("[PHYSICS]: Double Entry in _activeprims detected, potential crash immenent"); } } public override PhysicsActor AddPrimShape(string primName, PrimitiveBaseShape pbs, Vector3 position, Vector3 size, Quaternion rotation, bool isPhysical, uint localid) { #if SPAM m_log.DebugFormat("[PHYSICS]: Adding physics actor to {0}", primName); #endif return AddPrim(primName, position, size, rotation, pbs, isPhysical, localid); } public override float TimeDilation { get { return m_timeDilation; } } public override bool SupportsNINJAJoints { get { return false; } } public void remActivePrim(OdePrim deactivatePrim) { lock (_activeprims) { _activeprims.Remove(deactivatePrim); } } public override void RemovePrim(PhysicsActor prim) { // As with all ODE physics operations, we don't remove the prim immediately but signal that it should be // removed in the next physics simulate pass. if (prim is OdePrim) { // lock (OdeLock) { OdePrim p = (OdePrim)prim; p.setPrimForRemoval(); } } } /// /// This is called from within simulate but outside the locked portion /// We need to do our own locking here /// (Note: As of 20110801 this no longer appears to be true - this is being called within lock (odeLock) in /// Simulate() -- justincc). /// /// Essentially, we need to remove the prim from our space segment, whatever segment it's in. /// /// If there are no more prim in the segment, we need to empty (spacedestroy)the segment and reclaim memory /// that the space was using. /// /// public void RemovePrimThreadLocked(OdePrim prim) { //Console.WriteLine("RemovePrimThreadLocked " + prim.m_primName); lock (prim) { RemoveCollisionEventReporting(prim); lock (_prims) _prims.Remove(prim); } } #endregion #region Space Separation Calculation /// /// Called when a static prim moves or becomes static /// Places the prim in a space one the static sub-spaces grid /// /// the pointer to the geom that moved /// the position that the geom moved to /// a pointer to the space it was in before it was moved. /// a pointer to the new space it's in public IntPtr MoveGeomToStaticSpace(IntPtr geom, Vector3 pos, IntPtr currentspace) { // moves a prim into another static sub-space or from another space into a static sub-space // Called ODEPrim so // it's already in locked space. if (geom == IntPtr.Zero) // shouldn't happen return IntPtr.Zero; // get the static sub-space for current position IntPtr newspace = calculateSpaceForGeom(pos); if (newspace == currentspace) // if we are there all done return newspace; // else remove it from its current space if (currentspace != IntPtr.Zero && d.SpaceQuery(currentspace, geom)) { if (d.GeomIsSpace(currentspace)) { waitForSpaceUnlock(currentspace); d.SpaceRemove(currentspace, geom); } else { m_log.Info("[Physics]: Invalid or empty Space passed to 'MoveGeomToStaticSpace':" + currentspace + " Geom:" + geom); } } else // odd currentspace is null or doesn't contain the geom? lets try the geom ideia of current space { currentspace = d.GeomGetSpace(geom); if (currentspace != IntPtr.Zero) { if (d.GeomIsSpace(currentspace)) { waitForSpaceUnlock(currentspace); d.SpaceRemove(currentspace, geom); } } } // put the geom in the newspace waitForSpaceUnlock(newspace); d.SpaceAdd(newspace, geom); // let caller know this newspace return newspace; } /// /// Calculates the space the prim should be in by its position /// /// /// a pointer to the space. This could be a new space or reused space. public IntPtr calculateSpaceForGeom(Vector3 pos) { int x, y; x = (int)(pos.X * spacesPerMeter); if (x < 0) x = 0; else if (x > spaceGridMaxX) x = spaceGridMaxX; y = (int)(pos.Y * spacesPerMeter); if (y < 0) y = 0; else if (y >spaceGridMaxY) y = spaceGridMaxY; IntPtr tmpSpace = staticPrimspace[x, y]; return tmpSpace; } #endregion /// /// Routine to figure out if we need to mesh this prim with our mesher /// /// /// 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... // //if (pbs.PathCurve == (byte)Primitive.PathCurve.Circle && pbs.ProfileCurve == (byte)Primitive.ProfileCurve.Circle && pbs.PathScaleY <= 0.75f) // //m_log.Debug("needsMeshing: " + " pathCurve: " + pbs.PathCurve.ToString() + " profileCurve: " + pbs.ProfileCurve.ToString() + " pathScaleY: " + Primitive.UnpackPathScale(pbs.PathScaleY).ToString()); int iPropertiesNotSupportedDefault = 0; if (pbs.SculptEntry) { if(!meshSculptedPrim) return false; } // 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 (!forceSimplePrimMeshing && !pbs.SculptEntry) { 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) { #if SPAM m_log.Warn("NonMesh"); #endif 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 if (forceSimplePrimMeshing) return true; 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 (pbs.SculptEntry && meshSculptedPrim) iPropertiesNotSupportedDefault++; if (iPropertiesNotSupportedDefault == 0) { #if SPAM m_log.Warn("NonMesh"); #endif return false; } #if SPAM m_log.Debug("Mesh"); #endif return true; } public void AddChange(OdePrim prim, changes what, Object arg) { ODEchangeitem item = new ODEchangeitem(); item.prim = prim; item.what = what; item.arg = arg; ChangesQueue.Enqueue(item); } /// /// Called to queue a change to a prim /// to use in place of old taint mechanism so changes do have a time sequence /// public void AddChange(OdeCharacter character, changes what, Object arg) { ODEchangeitem item = new ODEchangeitem(); item.character = character; item.what = what; item.arg = arg; ChangesQueue.Enqueue(item); } /// /// Called after our prim properties are set Scale, position etc. /// We use this event queue like method to keep changes to the physical scene occuring in the threadlocked mutex /// This assures us that we have no race conditions /// /// public override void AddPhysicsActorTaint(PhysicsActor prim) { if (prim is OdePrim) { /* OdePrim taintedprim = ((OdePrim) prim); lock (_taintedPrimLock) { if (!(_taintedPrimH.Contains(taintedprim))) { _taintedPrimH.Add(taintedprim); // HashSet for searching _taintedPrimQ.Enqueue(taintedprim); // List for ordered readout } } */ return; } else if (prim is OdeCharacter) { OdeCharacter taintedchar = ((OdeCharacter)prim); lock (_taintedCharacterLock) { if (!(_taintedCharacterH.Contains(taintedchar))) { _taintedCharacterH.Add(taintedchar); _taintedCharacterQ.Enqueue(taintedchar); if (taintedchar.bad) m_log.DebugFormat("[PHYSICS]: Added BAD actor {0} to tainted actors", taintedchar.m_uuid); } } } } /// /// This is our main simulate loop /// It's thread locked by a Mutex in the scene. /// It holds Collisions, it instructs ODE to step through the physical reactions /// It moves the objects around in memory /// It calls the methods that report back to the object owners.. (scenepresence, SceneObjectGroup) /// /// /// public override float Simulate(float timeStep) { int statstart; int statchanges = 0; int statchmove = 0; int statactmove = 0; int statray = 0; int statcol = 0; int statstep = 0; int statmovchar = 0; int statmovprim; int totjcontact = 0; // acumulate time so we can reduce error step_time += timeStep; if (step_time < ODE_STEPSIZE) return 0; if (framecount >= int.MaxValue) framecount = 0; framecount++; int curphysiteractions = m_physicsiterations; if (step_time >= m_SkipFramesAtms) { // if in trouble reduce step resolution curphysiteractions /= 2; } int nodeframes = 0; // checkThread(); lock (SimulationLock) { // adjust number of iterations per step try { d.WorldSetQuickStepNumIterations(world, curphysiteractions); } catch (StackOverflowException) { m_log.Error("[PHYSICS]: The operating system wasn't able to allocate enough memory for the simulation. Restarting the sim."); // ode.drelease(world); base.TriggerPhysicsBasedRestart(); } while (step_time >= ODE_STEPSIZE && nodeframes < 10) //limit number of steps so we don't say here for ever { try { statstart = Util.EnvironmentTickCount(); // clear pointer/counter to contacts to pass into joints m_global_contactcount = 0; // do characters requested changes OdeCharacter character; int numtaints; lock (_taintedCharacterLock) { numtaints = _taintedCharacterQ.Count; // if (numtaints > 50) // numtaints = 50; while (numtaints > 0) { character = _taintedCharacterQ.Dequeue(); character.ProcessTaints(ODE_STEPSIZE); _taintedCharacterH.Remove(character); numtaints--; } } // do other objects requested changes ODEchangeitem item; if(ChangesQueue.Count >0) { int ttmpstart = Util.EnvironmentTickCount(); int ttmp; int ttmp2; while(ChangesQueue.Dequeue(out item)) { if (item.prim != null) { try { if (item.prim.DoAChange(item.what, item.arg)) RemovePrimThreadLocked(item.prim); } catch { }; } ttmp = Util.EnvironmentTickCountSubtract(ttmpstart); if (ttmp > 20) break; } ttmp2 = Util.EnvironmentTickCountSubtract(ttmpstart); if (ttmp2 > 50) ttmp2 = 0; } statchanges += Util.EnvironmentTickCountSubtract(statstart); // Move characters lock (_characters) { List defects = new List(); foreach (OdeCharacter actor in _characters) { if (actor != null) actor.Move(ODE_STEPSIZE, defects); } if (defects.Count != 0) { foreach (OdeCharacter defect in defects) { RemoveCharacter(defect); } } } statchmove += Util.EnvironmentTickCountSubtract(statstart); // Move other active objects lock (_activeprims) { foreach (OdePrim aprim in _activeprims) { aprim.CollisionScore = 0; aprim.IsColliding = false; aprim.Move(); } } statactmove += Util.EnvironmentTickCountSubtract(statstart); //if ((framecount % m_randomizeWater) == 0) // randomizeWater(waterlevel); m_rayCastManager.ProcessQueuedRequests(); statray += Util.EnvironmentTickCountSubtract(statstart); collision_optimized(); statcol += Util.EnvironmentTickCountSubtract(statstart); lock (_collisionEventPrim) { foreach (PhysicsActor obj in _collisionEventPrim) { if (obj == null) continue; switch ((ActorTypes)obj.PhysicsActorType) { case ActorTypes.Agent: OdeCharacter cobj = (OdeCharacter)obj; cobj.AddCollisionFrameTime((int)(ODE_STEPSIZE*1000.0f)); cobj.SendCollisions(); break; case ActorTypes.Prim: OdePrim pobj = (OdePrim)obj; pobj.SendCollisions(); break; } } } d.WorldQuickStep(world, ODE_STEPSIZE); statstep += Util.EnvironmentTickCountSubtract(statstart); d.JointGroupEmpty(contactgroup); totjcontact += m_global_contactcount; //ode.dunlock(world); } catch (Exception e) { m_log.ErrorFormat("[PHYSICS]: {0}, {1}, {2}", e.Message, e.TargetSite, e); // ode.dunlock(world); } step_time -= ODE_STEPSIZE; nodeframes++; } statstart = Util.EnvironmentTickCount(); lock (_characters) { foreach (OdeCharacter actor in _characters) { if (actor != null) { if (actor.bad) m_log.WarnFormat("[PHYSICS]: BAD Actor {0} in _characters list was not removed?", actor.m_uuid); actor.UpdatePositionAndVelocity(); } } } lock (_badCharacter) { if (_badCharacter.Count > 0) { foreach (OdeCharacter chr in _badCharacter) { RemoveCharacter(chr); } _badCharacter.Clear(); } } statmovchar = Util.EnvironmentTickCountSubtract(statstart); lock (_activeprims) { { foreach (OdePrim actor in _activeprims) { if (actor.IsPhysical) { actor.UpdatePositionAndVelocity((float)nodeframes * ODE_STEPSIZE); } } } } statmovprim = Util.EnvironmentTickCountSubtract(statstart); int nactivegeoms = d.SpaceGetNumGeoms(ActiveSpace); int nstaticgeoms = d.SpaceGetNumGeoms(StaticSpace); int ntopgeoms = d.SpaceGetNumGeoms(TopSpace); int nbodies = d.NTotalBodies; int ngeoms = d.NTotalGeoms; // Finished with all sim stepping. If requested, dump world state to file for debugging. // TODO: This call to the export function is already inside lock (OdeLock) - but is an extra lock needed? // TODO: This overwrites all dump files in-place. Should this be a growing logfile, or separate snapshots? if (physics_logging && (physics_logging_interval > 0) && (framecount % physics_logging_interval == 0)) { string fname = "state-" + world.ToString() + ".DIF"; // give each physics world a separate filename string prefix = "world" + world.ToString(); // prefix for variable names in exported .DIF file if (physics_logging_append_existing_logfile) { string header = "-------------- START OF PHYSICS FRAME " + framecount.ToString() + " --------------"; TextWriter fwriter = File.AppendText(fname); fwriter.WriteLine(header); fwriter.Close(); } d.WorldExportDIF(world, fname, physics_logging_append_existing_logfile, prefix); } // think time dilation is not a physics issue alone.. but ok let's fake something if (step_time < ODE_STEPSIZE) // we did the required loops m_timeDilation = 1.0f; else { // we didn't forget the lost ones and let user know something m_timeDilation = 1 - step_time / timeStep; if (m_timeDilation < 0) m_timeDilation = 0; step_time = 0; } } // return nodeframes * ODE_STEPSIZE; // return real simulated time return 1000 * nodeframes; // return steps for now * 1000 to keep core happy } /// public override void GetResults() { } public override bool IsThreaded { // for now we won't be multithreaded get { return (false); } } #region ODE Specific Terrain Fixes public float[] ResizeTerrain512NearestNeighbour(float[] heightMap) { float[] returnarr = new float[262144]; float[,] resultarr = new float[(int)WorldExtents.X, (int)WorldExtents.Y]; // Filling out the array into its multi-dimensional components for (int y = 0; y < WorldExtents.Y; y++) { for (int x = 0; x < WorldExtents.X; x++) { resultarr[y, x] = heightMap[y * (int)WorldExtents.Y + x]; } } // Resize using Nearest Neighbour // This particular way is quick but it only works on a multiple of the original // The idea behind this method can be described with the following diagrams // second pass and third pass happen in the same loop really.. just separated // them to show what this does. // First Pass // ResultArr: // 1,1,1,1,1,1 // 1,1,1,1,1,1 // 1,1,1,1,1,1 // 1,1,1,1,1,1 // 1,1,1,1,1,1 // 1,1,1,1,1,1 // Second Pass // ResultArr2: // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // Third pass fills in the blanks // ResultArr2: // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // X,Y = . // X+1,y = ^ // X,Y+1 = * // X+1,Y+1 = # // Filling in like this; // .* // ^# // 1st . // 2nd * // 3rd ^ // 4th # // on single loop. float[,] resultarr2 = new float[512, 512]; for (int y = 0; y < WorldExtents.Y; y++) { for (int x = 0; x < WorldExtents.X; x++) { resultarr2[y * 2, x * 2] = resultarr[y, x]; if (y < WorldExtents.Y) { resultarr2[(y * 2) + 1, x * 2] = resultarr[y, x]; } if (x < WorldExtents.X) { resultarr2[y * 2, (x * 2) + 1] = resultarr[y, x]; } if (x < WorldExtents.X && y < WorldExtents.Y) { resultarr2[(y * 2) + 1, (x * 2) + 1] = resultarr[y, x]; } } } //Flatten out the array int i = 0; for (int y = 0; y < 512; y++) { for (int x = 0; x < 512; x++) { if (resultarr2[y, x] <= 0) returnarr[i] = 0.0000001f; else returnarr[i] = resultarr2[y, x]; i++; } } return returnarr; } public float[] ResizeTerrain512Interpolation(float[] heightMap) { float[] returnarr = new float[262144]; float[,] resultarr = new float[512,512]; // Filling out the array into its multi-dimensional components for (int y = 0; y < 256; y++) { for (int x = 0; x < 256; x++) { resultarr[y, x] = heightMap[y * 256 + x]; } } // Resize using interpolation // This particular way is quick but it only works on a multiple of the original // The idea behind this method can be described with the following diagrams // second pass and third pass happen in the same loop really.. just separated // them to show what this does. // First Pass // ResultArr: // 1,1,1,1,1,1 // 1,1,1,1,1,1 // 1,1,1,1,1,1 // 1,1,1,1,1,1 // 1,1,1,1,1,1 // 1,1,1,1,1,1 // Second Pass // ResultArr2: // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // ,,,,,,,,,, // 1,,1,,1,,1,,1,,1, // Third pass fills in the blanks // ResultArr2: // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // 1,1,1,1,1,1,1,1,1,1,1,1 // X,Y = . // X+1,y = ^ // X,Y+1 = * // X+1,Y+1 = # // Filling in like this; // .* // ^# // 1st . // 2nd * // 3rd ^ // 4th # // on single loop. float[,] resultarr2 = new float[512,512]; for (int y = 0; y < (int)Constants.RegionSize; y++) { for (int x = 0; x < (int)Constants.RegionSize; x++) { resultarr2[y*2, x*2] = resultarr[y, x]; if (y < (int)Constants.RegionSize) { if (y + 1 < (int)Constants.RegionSize) { if (x + 1 < (int)Constants.RegionSize) { resultarr2[(y*2) + 1, x*2] = ((resultarr[y, x] + resultarr[y + 1, x] + resultarr[y, x + 1] + resultarr[y + 1, x + 1])/4); } else { resultarr2[(y*2) + 1, x*2] = ((resultarr[y, x] + resultarr[y + 1, x])/2); } } else { resultarr2[(y*2) + 1, x*2] = resultarr[y, x]; } } if (x < (int)Constants.RegionSize) { if (x + 1 < (int)Constants.RegionSize) { if (y + 1 < (int)Constants.RegionSize) { resultarr2[y*2, (x*2) + 1] = ((resultarr[y, x] + resultarr[y + 1, x] + resultarr[y, x + 1] + resultarr[y + 1, x + 1])/4); } else { resultarr2[y*2, (x*2) + 1] = ((resultarr[y, x] + resultarr[y, x + 1])/2); } } else { resultarr2[y*2, (x*2) + 1] = resultarr[y, x]; } } if (x < (int)Constants.RegionSize && y < (int)Constants.RegionSize) { if ((x + 1 < (int)Constants.RegionSize) && (y + 1 < (int)Constants.RegionSize)) { resultarr2[(y*2) + 1, (x*2) + 1] = ((resultarr[y, x] + resultarr[y + 1, x] + resultarr[y, x + 1] + resultarr[y + 1, x + 1])/4); } else { resultarr2[(y*2) + 1, (x*2) + 1] = resultarr[y, x]; } } } } //Flatten out the array int i = 0; for (int y = 0; y < 512; y++) { for (int x = 0; x < 512; x++) { if (Single.IsNaN(resultarr2[y, x]) || Single.IsInfinity(resultarr2[y, x])) { m_log.Warn("[PHYSICS]: Non finite heightfield element detected. Setting it to 0"); resultarr2[y, x] = 0; } returnarr[i] = resultarr2[y, x]; i++; } } return returnarr; } #endregion public override void SetTerrain(float[] heightMap) { if (m_worldOffset != Vector3.Zero && m_parentScene != null) { if (m_parentScene is OdeScene) { ((OdeScene)m_parentScene).SetTerrain(heightMap, m_worldOffset); } } else { SetTerrain(heightMap, m_worldOffset); } } public override void CombineTerrain(float[] heightMap, Vector3 pOffset) { SetTerrain(heightMap, pOffset); } public void SetTerrain(float[] heightMap, Vector3 pOffset) { float[] _heightmap; _heightmap = new float[(((int)Constants.RegionSize + 2) * ((int)Constants.RegionSize + 2))]; uint heightmapWidth = Constants.RegionSize + 2; uint heightmapHeight = Constants.RegionSize + 2; uint heightmapWidthSamples; uint heightmapHeightSamples; heightmapWidthSamples = (uint)Constants.RegionSize + 2; heightmapHeightSamples = (uint)Constants.RegionSize + 2; const float scale = 1.0f; const float offset = 0.0f; const float thickness = 10f; const int wrap = 0; int regionsize = (int) Constants.RegionSize + 2; float hfmin = float.MaxValue; float hfmax = float.MinValue; float val; int xx; int yy; int maxXXYY = regionsize - 3; // flipping map adding one margin all around so things don't fall in edges int xt = 0; xx = 0; for (int x = 0; x < heightmapWidthSamples; x++) { if (x > 1 && xx < maxXXYY) xx++; yy = 0; for (int y = 0; y < heightmapHeightSamples; y++) { if (y > 1 && y < maxXXYY) yy += (int)Constants.RegionSize; val = heightMap[yy + xx]; _heightmap[xt + y] = val; if (hfmin > val) hfmin = val; if (hfmax < val) hfmax = val; } xt += regionsize; } lock (OdeLock) { IntPtr GroundGeom = IntPtr.Zero; if (RegionTerrain.TryGetValue(pOffset, out GroundGeom)) { RegionTerrain.Remove(pOffset); if (GroundGeom != IntPtr.Zero) { if (TerrainHeightFieldHeights.ContainsKey(GroundGeom)) { TerrainHeightFieldHeightsHandlers[GroundGeom].Free(); TerrainHeightFieldHeightsHandlers.Remove(GroundGeom); TerrainHeightFieldHeights.Remove(GroundGeom); } d.SpaceRemove(StaticSpace, GroundGeom); d.GeomDestroy(GroundGeom); } } IntPtr HeightmapData = d.GeomHeightfieldDataCreate(); GCHandle _heightmaphandler = GCHandle.Alloc(_heightmap, GCHandleType.Pinned); d.GeomHeightfieldDataBuildSingle(HeightmapData, _heightmaphandler.AddrOfPinnedObject(), 0, heightmapWidth , heightmapHeight, (int)heightmapWidthSamples, (int)heightmapHeightSamples, scale, offset, thickness, wrap); d.GeomHeightfieldDataSetBounds(HeightmapData, hfmin - 1, hfmax + 1); GroundGeom = d.CreateHeightfield(StaticSpace, HeightmapData, 1); if (GroundGeom != IntPtr.Zero) { d.GeomSetCategoryBits(GroundGeom, (int)(CollisionCategories.Land)); d.GeomSetCollideBits(GroundGeom, (int)(CollisionCategories.Space)); } geom_name_map[GroundGeom] = "Terrain"; d.Matrix3 R = new d.Matrix3(); Quaternion q1 = Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), 1.5707f); Quaternion q2 = Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), 1.5707f); q1 = q1 * q2; Vector3 v3; float angle; q1.GetAxisAngle(out v3, out angle); d.RFromAxisAndAngle(out R, v3.X, v3.Y, v3.Z, angle); d.GeomSetRotation(GroundGeom, ref R); d.GeomSetPosition(GroundGeom, pOffset.X + (float)Constants.RegionSize * 0.5f - 0.5f, pOffset.Y + (float)Constants.RegionSize * 0.5f - 0.5f, 0); IntPtr testGround = IntPtr.Zero; if (RegionTerrain.TryGetValue(pOffset, out testGround)) { RegionTerrain.Remove(pOffset); } RegionTerrain.Add(pOffset, GroundGeom, GroundGeom); // TerrainHeightFieldHeights.Add(GroundGeom, ODElandMap); TerrainHeightFieldHeights.Add(GroundGeom, _heightmap); TerrainHeightFieldHeightsHandlers.Add(GroundGeom, _heightmaphandler); } } public override void DeleteTerrain() { } public float GetWaterLevel() { return waterlevel; } public override bool SupportsCombining() { return true; } /* public override void UnCombine(PhysicsScene pScene) { IntPtr localGround = IntPtr.Zero; // float[] localHeightfield; bool proceed = false; List geomDestroyList = new List(); lock (OdeLock) { if (RegionTerrain.TryGetValue(Vector3.Zero, out localGround)) { foreach (IntPtr geom in TerrainHeightFieldHeights.Keys) { if (geom == localGround) { // localHeightfield = TerrainHeightFieldHeights[geom]; proceed = true; } else { geomDestroyList.Add(geom); } } if (proceed) { m_worldOffset = Vector3.Zero; WorldExtents = new Vector2((int)Constants.RegionSize, (int)Constants.RegionSize); m_parentScene = null; foreach (IntPtr g in geomDestroyList) { // removingHeightField needs to be done or the garbage collector will // collect the terrain data before we tell ODE to destroy it causing // memory corruption if (TerrainHeightFieldHeights.ContainsKey(g)) { // float[] removingHeightField = TerrainHeightFieldHeights[g]; TerrainHeightFieldHeights.Remove(g); if (RegionTerrain.ContainsKey(g)) { RegionTerrain.Remove(g); } d.GeomDestroy(g); //removingHeightField = new float[0]; } } } else { m_log.Warn("[PHYSICS]: Couldn't proceed with UnCombine. Region has inconsistant data."); } } } } */ public override void SetWaterLevel(float baseheight) { waterlevel = baseheight; randomizeWater(waterlevel); } public void randomizeWater(float baseheight) { const uint heightmapWidth = m_regionWidth + 2; const uint heightmapHeight = m_regionHeight + 2; const uint heightmapWidthSamples = m_regionWidth + 2; const uint heightmapHeightSamples = m_regionHeight + 2; const float scale = 1.0f; const float offset = 0.0f; const float thickness = 2.9f; const int wrap = 0; for (int i = 0; i < (258 * 258); i++) { _watermap[i] = (baseheight-0.1f) + ((float)fluidRandomizer.Next(1,9) / 10f); // m_log.Info((baseheight - 0.1f) + ((float)fluidRandomizer.Next(1, 9) / 10f)); } lock (OdeLock) { if (WaterGeom != IntPtr.Zero) { d.SpaceRemove(StaticSpace, WaterGeom); } IntPtr HeightmapData = d.GeomHeightfieldDataCreate(); d.GeomHeightfieldDataBuildSingle(HeightmapData, _watermap, 0, heightmapWidth, heightmapHeight, (int)heightmapWidthSamples, (int)heightmapHeightSamples, scale, offset, thickness, wrap); d.GeomHeightfieldDataSetBounds(HeightmapData, m_regionWidth, m_regionHeight); WaterGeom = d.CreateHeightfield(StaticSpace, HeightmapData, 1); if (WaterGeom != IntPtr.Zero) { d.GeomSetCategoryBits(WaterGeom, (int)(CollisionCategories.Water)); d.GeomSetCollideBits(WaterGeom, (int)(CollisionCategories.Space)); } geom_name_map[WaterGeom] = "Water"; d.Matrix3 R = new d.Matrix3(); Quaternion q1 = Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), 1.5707f); Quaternion q2 = Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), 1.5707f); q1 = q1 * q2; Vector3 v3; float angle; q1.GetAxisAngle(out v3, out angle); d.RFromAxisAndAngle(out R, v3.X, v3.Y, v3.Z, angle); d.GeomSetRotation(WaterGeom, ref R); d.GeomSetPosition(WaterGeom, 128, 128, 0); } } public override void Dispose() { m_rayCastManager.Dispose(); m_rayCastManager = null; lock (OdeLock) { lock (_prims) { foreach (OdePrim prm in _prims) { RemovePrim(prm); } } if (ContactgeomsArray != IntPtr.Zero) Marshal.FreeHGlobal(ContactgeomsArray); if (GlobalContactsArray != IntPtr.Zero) Marshal.FreeHGlobal(GlobalContactsArray); d.WorldDestroy(world); //d.CloseODE(); } } public override Dictionary GetTopColliders() { Dictionary returncolliders = new Dictionary(); int cnt = 0; lock (_prims) { foreach (OdePrim prm in _prims) { if (prm.CollisionScore > 0) { returncolliders.Add(prm.m_localID, prm.CollisionScore); cnt++; prm.CollisionScore = 0f; if (cnt > 25) { break; } } } } return returncolliders; } public override bool SupportsRayCast() { return true; } public override void RaycastWorld(Vector3 position, Vector3 direction, float length, RaycastCallback retMethod) { if (retMethod != null) { m_rayCastManager.QueueRequest(position, direction, length, retMethod); } } public override void RaycastWorld(Vector3 position, Vector3 direction, float length, int Count, RayCallback retMethod) { if (retMethod != null) { m_rayCastManager.QueueRequest(position, direction, length, Count, retMethod); } } // don't like this public override List RaycastWorld(Vector3 position, Vector3 direction, float length, int Count) { ContactResult[] ourResults = null; RayCallback retMethod = delegate(List results) { ourResults = new ContactResult[results.Count]; results.CopyTo(ourResults, 0); }; int waitTime = 0; m_rayCastManager.QueueRequest(position, direction, length, Count, retMethod); while (ourResults == null && waitTime < 1000) { Thread.Sleep(1); waitTime++; } if (ourResults == null) return new List(); return new List(ourResults); } public override void RaycastActor(PhysicsActor actor, Vector3 position, Vector3 direction, float length, RaycastCallback retMethod) { if (retMethod != null && actor !=null) { IntPtr geom; if (actor is OdePrim) geom = ((OdePrim)actor).prim_geom; else if (actor is OdeCharacter) geom = ((OdePrim)actor).prim_geom; else return; if (geom == IntPtr.Zero) return; m_rayCastManager.QueueRequest(geom, position, direction, length, retMethod); } } public override void RaycastActor(PhysicsActor actor, Vector3 position, Vector3 direction, float length, int Count, RayCallback retMethod) { if (retMethod != null && actor != null) { IntPtr geom; if (actor is OdePrim) geom = ((OdePrim)actor).prim_geom; else if (actor is OdeCharacter) geom = ((OdePrim)actor).prim_geom; else return; if (geom == IntPtr.Zero) return; m_rayCastManager.QueueRequest(geom,position, direction, length, Count, retMethod); } } // don't like this public override List RaycastActor(PhysicsActor actor, Vector3 position, Vector3 direction, float length, int Count) { if (actor != null) { IntPtr geom; if (actor is OdePrim) geom = ((OdePrim)actor).prim_geom; else if (actor is OdeCharacter) geom = ((OdePrim)actor).prim_geom; else return new List(); if (geom == IntPtr.Zero) return new List(); ContactResult[] ourResults = null; RayCallback retMethod = delegate(List results) { ourResults = new ContactResult[results.Count]; results.CopyTo(ourResults, 0); }; int waitTime = 0; m_rayCastManager.QueueRequest(geom,position, direction, length, Count, retMethod); while (ourResults == null && waitTime < 1000) { Thread.Sleep(1); waitTime++; } if (ourResults == null) return new List(); return new List(ourResults); } return new List(); } #if USE_DRAWSTUFF // Keyboard callback public void command(int cmd) { IntPtr geom; d.Mass mass; d.Vector3 sides = new d.Vector3(d.RandReal() * 0.5f + 0.1f, d.RandReal() * 0.5f + 0.1f, d.RandReal() * 0.5f + 0.1f); Char ch = Char.ToLower((Char)cmd); switch ((Char)ch) { case 'w': try { Vector3 rotate = (new Vector3(1, 0, 0) * Quaternion.CreateFromEulers(hpr.Z * Utils.DEG_TO_RAD, hpr.Y * Utils.DEG_TO_RAD, hpr.X * Utils.DEG_TO_RAD)); xyz.X += rotate.X; xyz.Y += rotate.Y; xyz.Z += rotate.Z; ds.SetViewpoint(ref xyz, ref hpr); } catch (ArgumentException) { hpr.X = 0; } break; case 'a': hpr.X++; ds.SetViewpoint(ref xyz, ref hpr); break; case 's': try { Vector3 rotate2 = (new Vector3(-1, 0, 0) * Quaternion.CreateFromEulers(hpr.Z * Utils.DEG_TO_RAD, hpr.Y * Utils.DEG_TO_RAD, hpr.X * Utils.DEG_TO_RAD)); xyz.X += rotate2.X; xyz.Y += rotate2.Y; xyz.Z += rotate2.Z; ds.SetViewpoint(ref xyz, ref hpr); } catch (ArgumentException) { hpr.X = 0; } break; case 'd': hpr.X--; ds.SetViewpoint(ref xyz, ref hpr); break; case 'r': xyz.Z++; ds.SetViewpoint(ref xyz, ref hpr); break; case 'f': xyz.Z--; ds.SetViewpoint(ref xyz, ref hpr); break; case 'e': xyz.Y++; ds.SetViewpoint(ref xyz, ref hpr); break; case 'q': xyz.Y--; ds.SetViewpoint(ref xyz, ref hpr); break; } } public void step(int pause) { ds.SetColor(1.0f, 1.0f, 0.0f); ds.SetTexture(ds.Texture.Wood); lock (_prims) { foreach (OdePrim prm in _prims) { //IntPtr body = d.GeomGetBody(prm.prim_geom); if (prm.prim_geom != IntPtr.Zero) { d.Vector3 pos; d.GeomCopyPosition(prm.prim_geom, out pos); //d.BodyCopyPosition(body, out pos); d.Matrix3 R; d.GeomCopyRotation(prm.prim_geom, out R); //d.BodyCopyRotation(body, out R); d.Vector3 sides = new d.Vector3(); sides.X = prm.Size.X; sides.Y = prm.Size.Y; sides.Z = prm.Size.Z; ds.DrawBox(ref pos, ref R, ref sides); } } } ds.SetColor(1.0f, 0.0f, 0.0f); lock (_characters) { foreach (OdeCharacter chr in _characters) { if (chr.Shell != IntPtr.Zero) { IntPtr body = d.GeomGetBody(chr.Shell); d.Vector3 pos; d.GeomCopyPosition(chr.Shell, out pos); //d.BodyCopyPosition(body, out pos); d.Matrix3 R; d.GeomCopyRotation(chr.Shell, out R); //d.BodyCopyRotation(body, out R); ds.DrawCapsule(ref pos, ref R, chr.Size.Z, 0.35f); d.Vector3 sides = new d.Vector3(); sides.X = 0.5f; sides.Y = 0.5f; sides.Z = 0.5f; ds.DrawBox(ref pos, ref R, ref sides); } } } } public void start(int unused) { ds.SetViewpoint(ref xyz, ref hpr); } #endif } }