diff --git a/Flocking/Boid.cs b/Flocking/Boid.cs index f85cf47..147ce20 100644 --- a/Flocking/Boid.cs +++ b/Flocking/Boid.cs @@ -28,6 +28,7 @@ using System; using System.Collections.Generic; using log4net; using OpenMetaverse; +using Utils = OpenSim.Framework.Util; namespace Flocking { @@ -74,7 +75,7 @@ namespace Flocking public Vector3 Velocity { get { return m_vel;} } - + public String Id { get {return m_id;} } @@ -85,10 +86,11 @@ namespace Flocking /// /// Boids. all the other chaps in the scene /// - public void MoveInSceneRelativeToFlock (List boids) + public void MoveInSceneRelativeToFlock () { + List neighbours = m_model.GetNeighbours(this); // we would like to stay with our mates - Flock (boids); + Flock (neighbours); // our first priority is to not hurt ourselves // so adjust where we would like to go to avoid hitting things @@ -102,10 +104,9 @@ namespace Flocking UpdatePositionInScene (); } - + /// - /// Move within our flock - /// + /// Move within our local flock /// We accumulate a new acceleration each time based on three rules /// these are: /// our separation from our closest neighbours, @@ -113,28 +114,22 @@ namespace Flocking /// our desire to move towards the flock centre /// /// - void Flock (List boids) + void Flock (List neighbours) { // calc the force vectors on this boid - Vector3 sep = Separate (boids); // Separation - Vector3 ali = Align (boids); // Alignment - Vector3 coh = Cohesion (boids); // Cohesion + Vector3 sep = Separate (neighbours); // Separation + Vector3 ali = Align (neighbours); // Alignment + Vector3 coh = Cohesion (neighbours); // Cohesion // Arbitrarily weight these forces - //TODO: expose these consts - sep *= 1.5f; //.mult(1.5); - //ali.mult(1.0); - ali *= 1.0f; - //coh.mult(1.0); - coh *= 1.0f; + sep *= m_model.SeparationWeighting; + ali *= m_model.AlignmentWeighting; + coh *= m_model.CohesionWeighting; // Add the force vectors to the current acceleration of the boid - //acc.add(sep); m_acc += sep; - //acc.add(ali); m_acc += ali; - //acc.add(coh); m_acc += coh; } @@ -149,10 +144,8 @@ namespace Flocking void UpdatePositionInScene () { // Update velocity - //vel.add(acc); m_vel += m_acc; // Limit speed - //m_vel.limit(maxspeed); m_vel = Util.Limit (m_vel, m_model.MaxSpeed); m_loc += m_vel; // Reset accelertion to 0 each cycle @@ -186,27 +179,24 @@ namespace Flocking /// Takes a second argument, if true, it slows down as it approaches the target Vector3 Steer (Vector3 target, bool slowdown) { - Vector3 steer; // The steering vector - Vector3 desired = Vector3.Subtract(target, m_loc); // A vector pointing from the location to the target - float d = desired.Length (); // Distance from the target is the magnitude of the vector + Vector3 steer = Vector3.Zero; // The steering vector + Vector3 desired = target - m_loc; // A vector pointing from the location to the target + float distance = desired.Length (); // Distance from the target is the magnitude of the vector // If the distance is greater than 0, calc steering (otherwise return zero vector) - if (d > 0) { + if (distance > 0) { // Normalize desired desired.Normalize (); // Two options for desired vector magnitude (1 -- based on distance, 2 -- maxspeed) - if ((slowdown) && (d < 100.0f)) { - desired *= (m_model.MaxSpeed * (d / 100.0f)); // This damping is somewhat arbitrary + if ((slowdown) && (distance < m_model.LookaheadDistance )) { + desired *= (m_model.MaxSpeed * (distance / m_model.LookaheadDistance)); // This damping is somewhat arbitrary } else { desired *= m_model.MaxSpeed; } // Steering = Desired minus Velocity - //steer = target.sub(desired,m_vel); - steer = Vector3.Subtract (desired, m_vel); + steer = desired - m_vel; //steer.limit(maxforce); // Limit to maximum steering force steer = Util.Limit (steer, m_model.MaxForce); - } else { - steer = Vector3.Zero; - } + } return steer; } @@ -226,40 +216,37 @@ namespace Flocking /// keeps us a respectable distance from our closest neighbours whilst still /// being part of our local flock /// - /// + /// /// Boids. all the boids in the scene /// - Vector3 Separate (List boids) + Vector3 Separate (List neighbours) { - Vector3 steer = new Vector3 (0, 0, 0); - int count = 0; // For every boid in the system, check if it's too close - foreach (Boid other in boids) { - float d = Vector3.Distance (m_loc, other.Location); - // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself) - if ((d > 0) && (d < m_model.DesiredSeparation)) { - // Calculate vector pointing away from neighbor - Vector3 diff = Vector3.Subtract (m_loc, other.Location); - diff.Normalize (); - diff = Vector3.Divide (diff, d); - steer = Vector3.Add (steer, diff); - count++; // Keep track of how many - } - } - // Average -- divide by how many - if (count > 0) { - steer /= (float)count; - } - - // As long as the vector is greater than 0 - if (steer.Length () > 0) { + float desired = m_model.DesiredSeparation; + //who are we too close to at the moment + List tooCloseNeighbours = neighbours.FindAll( delegate(Boid neighbour) { + // Is the distance is less than an arbitrary amount + return Utils.GetDistanceTo (m_loc, neighbour.Location) < desired; + }); + + // move a bit away from them + Vector3 steer = Vector3.Zero; + tooCloseNeighbours.ForEach( delegate(Boid neighbour) { + // Calculate vector pointing away from neighbor + Vector3 diff = m_loc - neighbour.Location; + steer += Utils.GetNormalizedVector(diff) / (float)Utils.GetDistanceTo (m_loc, neighbour.Location); + }); + + if( steer.Length () > 0 ) { + // Average -- divide by how many + steer /= (float)tooCloseNeighbours.Count; // Implement Reynolds: Steering = Desired - Velocity steer.Normalize (); steer *= m_model.MaxSpeed; steer -= m_vel; - //steer.limit(maxforce); + //don't go too fast; steer = Util.Limit (steer, m_model.MaxForce); - } + } return steer; } @@ -273,15 +260,14 @@ namespace Flocking /// Vector3 Align (List boids) { - Vector3 steer = new Vector3 (0, 0, 0); - int count = 0; - foreach (Boid other in boids) { - float d = Vector3.Distance (m_loc, other.Location); - if ((d > 0) && (d < m_model.NeighbourDistance)) { - steer += other.Velocity; - count++; - } - } + Vector3 steer = Vector3.Zero; + + boids.ForEach( delegate( Boid other ) { + steer += other.Velocity; + }); + + int count = boids.Count; + if (count > 0) { steer /= (float)count; } @@ -303,22 +289,19 @@ namespace Flocking /// MAintain the cohesion of our local flock /// For the average location (i.e. center) of all nearby boids, calculate our steering vector towards that location /// - /// + /// /// Boids. the boids in the scene /// - Vector3 Cohesion (List boids) + Vector3 Cohesion (List neighbours) { Vector3 sum = Vector3.Zero; // Start with empty vector to accumulate all locations - int count = 0; - foreach (Boid other in boids) { - float d = Vector3.Distance (m_loc, other.Location); - if ((d > 0) && (d < m_model.NeighbourDistance)) { - sum += other.Location; // Add location - count++; - } - } + neighbours.ForEach( delegate(Boid other) { + sum += other.Location; // Add location + }); + + int count = neighbours.Count; if (count > 0) { sum /= (float)count; return Steer (sum, false); // Steer towards the location diff --git a/Flocking/ChatCommandParser.cs b/Flocking/ChatCommandParser.cs new file mode 100644 index 0000000..935cf26 --- /dev/null +++ b/Flocking/ChatCommandParser.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using log4net; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Console; +using OpenSim.Region.Framework.Interfaces; + +namespace Flocking +{ + public delegate void BoidCmdDelegate (string module,string[] args); + + public class BoidCmdDefn + { + public string Help = ""; + public string Args = ""; + public int NumParams = 0; + string m_name; + + public BoidCmdDefn (string name, string args, string help) + { + Help = help; + Args = args; + m_name = name; + + if (args.Trim ().Length > 0) { + NumParams = args.Split (", ".ToCharArray ()).Length; + } else { + NumParams = 0; + } + } + + public string GetSyntax () + { + return m_name + " " + Args + " (" + Help + ")"; + } + } + + public class ChatCommandParser + { + private static readonly ILog m_log = LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType); + private string m_name; + private Scene m_scene; + private int m_chatChannel; + private Dictionary m_commandMap = new Dictionary (); + private Dictionary m_syntaxMap = new Dictionary (); + + public ChatCommandParser (IRegionModuleBase module, Scene scene, int channel) + { + m_name = module.Name; + m_scene = scene; + m_chatChannel = channel; + } + + public void AddCommand (string cmd, string args, string help, CommandDelegate fn) + { + m_commandMap.Add (cmd, new BoidCmdDelegate (fn)); + m_syntaxMap.Add (cmd, new BoidCmdDefn (cmd, args, help)); + } + + public void SimChatSent (Object x, OSChatMessage msg) + { + if (m_scene.ConsoleScene () != m_scene || msg.Channel != m_chatChannel) + return; // not for us + + // try and parse a valid cmd from this msg + string cmd = msg.Message.ToLower (); + + //stick ui in the args so we know to respond in world + //bit of a hack - but lets us use CommandDelegate inWorld + string[] args = (cmd + " ").Split (" ".ToCharArray ()); + + BoidCmdDefn defn = null; + if (m_syntaxMap.TryGetValue (args [0], out defn)) { + if (CorrectSignature (args, defn)) { + + // we got the signature of the command right + BoidCmdDelegate del = null; + if (m_commandMap.TryGetValue (args [0], out del)) { + del (m_name, args); + } else { + // we don't understand this command + // shouldn't happen + m_log.ErrorFormat ("Unable to invoke command {0}", args [0]); + RespondToMessage (msg, "Unable to invoke command " + args [0]); + } + + } else { + // we recognise the command, but we got the call wrong + RespondToMessage (msg, "wrong syntax: " + defn.GetSyntax ()); + } + } else { + // this is not a command we recognise + RespondToMessage (msg, args [0] + " is not a valid command for osboids"); + } + + } + + private bool CorrectSignature (string[] args, BoidCmdDefn defn) + { + // args contain cmd name at 0 and tagged in last pos + return args.Length - 2 == defn.NumParams; + } + + public void RespondToMessage (OSChatMessage msg, string message) + { + m_log.Debug ("sending response -> " + message); + IClientAPI sender = msg.Sender; + sender.SendChatMessage (message, (byte)ChatTypeEnum.Say, msg.Position, "osboids", msg.SenderUUID, (byte)ChatSourceType.Agent, (byte)ChatAudibleLevel.Fully); + } + + public void SendMessage (ScenePresence recipient, string message) + { + IClientAPI ownerAPI = recipient.ControllingClient; + ownerAPI.SendChatMessage (message, + (byte)ChatTypeEnum.Say, + recipient.AbsolutePosition, + "osboids", + recipient.UUID, + (byte)ChatSourceType.Agent, + (byte)ChatAudibleLevel.Fully + ); + } + } +} + diff --git a/Flocking/Flocking.dll.build b/Flocking/Flocking.dll.build index 1705ee0..339ea86 100644 --- a/Flocking/Flocking.dll.build +++ b/Flocking/Flocking.dll.build @@ -24,6 +24,7 @@ + diff --git a/Flocking/FlockingModel.cs b/Flocking/FlockingModel.cs index d14f8dc..6f425c6 100644 --- a/Flocking/FlockingModel.cs +++ b/Flocking/FlockingModel.cs @@ -27,6 +27,7 @@ using System; using System.Collections.Generic; using OpenMetaverse; +using Utils = OpenSim.Framework.Util; namespace Flocking { @@ -39,6 +40,11 @@ namespace Flocking private float m_neighbourDistance; private float m_desiredSeparation; private float m_tolerance; + private float m_separationWeighting = 1.5f; + private float m_alignmentWeighting = 1f; + private float m_cohesionWeighting = 1f; + private float m_lookaheadDistance = 100f; + private Random m_rnd = new Random(Environment.TickCount); @@ -95,6 +101,26 @@ namespace Flocking get {return m_tolerance;} } + public float SeparationWeighting { + get{ return m_separationWeighting; } + set{ m_separationWeighting = value;} + } + + public float AlignmentWeighting { + get{ return m_alignmentWeighting; } + set{ m_alignmentWeighting = value;} + } + + public float CohesionWeighting { + get{ return m_cohesionWeighting; } + set{ m_cohesionWeighting = value;} + } + + public float LookaheadDistance { + get { return m_lookaheadDistance; } + set { m_lookaheadDistance = value;} + } + public void Initialise (int num, FlowField flowField) { @@ -103,12 +129,19 @@ namespace Flocking AddBoid ("boid"+i ); } } + + public List GetNeighbours(Boid boid) { + return m_flock.FindAll(delegate(Boid other) { + return (boid != other) && (Utils.GetDistanceTo (boid.Location, other.Location) < m_neighbourDistance); + }); + } + public List UpdateFlockPos () { - foreach (Boid b in m_flock) { - b.MoveInSceneRelativeToFlock(m_flock); // Passing the entire list of boids to each boid individually - } + m_flock.ForEach( delegate(Boid boid) { + boid.MoveInSceneRelativeToFlock(); + } ); return m_flock; } diff --git a/Flocking/FlockingModule.cs b/Flocking/FlockingModule.cs index f14637d..3c2079c 100644 --- a/Flocking/FlockingModule.cs +++ b/Flocking/FlockingModule.cs @@ -60,6 +60,11 @@ namespace Flocking private float m_neighbourDistance; private float m_desiredSeparation; private float m_tolerance; + private float m_separationWeighting = 1.5f; + private float m_alignmentWeighting = 1f; + private float m_cohesionWeighting = 1f; + private float m_lookaheadDistance = 100f; + private ChatCommandParser m_chatCommandParser; private UUID m_owner; @@ -81,6 +86,10 @@ namespace Flocking m_neighbourDistance = config.GetFloat("neighbour-dist", 25f); m_desiredSeparation = config.GetFloat("desired-separation", 20f); m_tolerance = config.GetFloat("tolerance", 5f); + m_separationWeighting = config.GetFloat("separation-weighting", 1.5f); + m_alignmentWeighting = config.GetFloat("alignment-weighting", 1f); + m_cohesionWeighting = config.GetFloat("cohesion-weighting", 1f); + m_lookaheadDistance = config.GetFloat("lookahead-dist",100f); // we're in the config - so turn on this module @@ -94,27 +103,33 @@ namespace Flocking m_scene = scene; if (m_enabled) { //register commands + m_chatCommandParser = new ChatCommandParser(this, scene, m_chatChannel); RegisterCommands (); //register handlers m_scene.EventManager.OnFrame += FlockUpdate; - m_scene.EventManager.OnChatFromClient += SimChatSent; //listen for commands sent from the client + m_scene.EventManager.OnChatFromClient += m_chatCommandParser.SimChatSent; //listen for commands sent from the client // init module m_model = new FlockingModel (m_maxSpeed, m_maxForce, m_neighbourDistance, m_desiredSeparation, m_tolerance); + m_model.SeparationWeighting = m_separationWeighting; + m_model.AlignmentWeighting = m_alignmentWeighting; + m_model.CohesionWeighting = m_cohesionWeighting; + m_model.LookaheadDistance = m_lookaheadDistance; m_view = new FlockingView (m_scene); m_view.BoidPrim = m_boidPrim; } } + void chatCom (object sender, OSChatMessage chat) + { + + } + public void RegionLoaded (Scene scene) { if (m_enabled) { - //make a flow map for this scene - //FlowMap flowMap = new FlowMap(scene ); - //flowMap.Initialise(); - //build a proper flow field based on the scene FlowField field = new FlowField(scene, new Vector3(128f, 128f, 128f), 200, 200, 200); @@ -134,7 +149,7 @@ namespace Flocking { if (m_enabled) { m_scene.EventManager.OnFrame -= FlockUpdate; - m_scene.EventManager.OnChatFromClient -= SimChatSent; + m_scene.EventManager.OnChatFromClient -= m_chatCommandParser.SimChatSent; } } @@ -155,9 +170,6 @@ namespace Flocking if (((m_frame++ % m_frameUpdateRate) != 0) || !m_ready || !m_enabled) { return; } - - //m_log.InfoFormat("update my boids"); - // work out where everyone has moved to // and tell the scene to render the new positions lock( m_sync ) { @@ -166,34 +178,6 @@ namespace Flocking } } - protected void SimChatSent (Object x, OSChatMessage msg) - { - if (m_scene.ConsoleScene () != m_scene || msg.Channel != m_chatChannel) - return; // not for us - - // try and parse a valid cmd from this msg - string cmd = msg.Message.ToLower (); - - //stick ui in the args so we know to respond in world - //bit of a hack - but lets us use CommandDelegate inWorld - string[] args = (cmd + " ").Split (" ".ToCharArray ()); - - if (cmd.StartsWith ("stop")) { - HandleStopCmd ("flock", args); - } else if (cmd.StartsWith ("start")) { - HandleStartCmd ("flock", args); - } else if (cmd.StartsWith ("size")) { - HandleSetSizeCmd ("flock", args); - } else if (cmd.StartsWith ("stats")) { - HandleShowStatsCmd ("flock", args); - } else if (cmd.StartsWith ("prim")) { - HandleSetPrimCmd ("flock", args); - } else if (cmd.StartsWith ("framerate")) { - HandleSetFrameRateCmd ("flock", args); - } - - } - #endregion #region Command Handling @@ -205,6 +189,7 @@ namespace Flocking argStr = " <" + args + "> "; } m_scene.AddCommand (this, "flock-" + cmd, "flock-" + cmd + argStr, help, fn); + m_chatCommandParser.AddCommand(cmd, args, help, fn); } private void RegisterCommands () @@ -215,6 +200,7 @@ namespace Flocking AddCommand ("stats", "", "show flocking stats", HandleShowStatsCmd); AddCommand ("prim", "name", "set the prim used for each boid to that passed in", HandleSetPrimCmd); AddCommand ("framerate", "num", "[debugging] only update boids every frames", HandleSetFrameRateCmd); + AddCommand ("set", "name, value", "change the flock dynamics", HandleSetParameterCmd); } private bool ShouldHandleCmd () @@ -235,15 +221,19 @@ namespace Flocking private void ShowResponse (string response, bool inWorld) { if (inWorld) { - IClientAPI ownerAPI = null; - if (m_scene.TryGetClient (m_owner, out ownerAPI)) { - ownerAPI.SendBlueBoxMessage (m_owner, "osboids", response); - } + ScenePresence owner = m_scene.GetScenePresence(m_owner); + m_chatCommandParser.SendMessage(owner, response); } else { MainConsole.Instance.Output (response); } } + public void HandleSetParameterCmd(string module, string[] args) + { + if (ShouldHandleCmd ()) { + } + } + public void HandleStopCmd (string module, string[] args) { if (ShouldHandleCmd ()) { diff --git a/Flocking/FlockingView.cs b/Flocking/FlockingView.cs index 5db8f33..20f9816 100644 --- a/Flocking/FlockingView.cs +++ b/Flocking/FlockingView.cs @@ -57,25 +57,24 @@ namespace Flocking public void Clear () { //trash everything we have - foreach (string name in m_sogMap.Keys) - { - RemoveSOGFromScene(name); - } + List current = new List (m_sogMap.Keys); + current.ForEach (delegate(string name) { + RemoveSOGFromScene(name); + }); m_sogMap.Clear(); } public void Render (List boids) { - foreach (Boid boid in boids) { - DrawBoid (boid); - } + boids.ForEach(delegate( Boid boid ) { + DrawBoid (boid); + }); } private void DrawBoid (Boid boid) { SceneObjectPart existing = m_scene.GetSceneObjectPart (boid.Id); - SceneObjectGroup sog; if (existing == null) { SceneObjectGroup group = findByName (m_boidPrim); @@ -86,20 +85,24 @@ namespace Flocking sog = existing.ParentGroup; } - Quaternion rotation = CalcRotationToEndpoint (sog, sog.AbsolutePosition, boid.Location); + Quaternion rotation = CalcRotationToEndpoint (sog, boid.Location); sog.UpdateGroupRotationPR( boid.Location, rotation); } - private static Quaternion CalcRotationToEndpoint (SceneObjectGroup copy, Vector3 sv, Vector3 ev) + private static Quaternion CalcRotationToEndpoint (SceneObjectGroup sog, Vector3 ev) { //llSetRot(llRotBetween(<1,0,0>,llVecNorm(targetPosition - llGetPos()))); // boid wil fly x forwards and Z up + Vector3 sv = sog.AbsolutePosition; Vector3 currDirVec = Vector3.UnitX; Vector3 desiredDirVec = Vector3.Subtract (ev, sv); desiredDirVec.Normalize (); Quaternion rot = Vector3.RotationBetween (currDirVec, desiredDirVec); + + //TODO: if we turn upside down, flip us over by rotating along Y + return rot; } @@ -113,13 +116,9 @@ namespace Flocking private SceneObjectGroup findByName (string name) { - SceneObjectGroup retVal = null; - foreach (EntityBase e in m_scene.GetEntities()) { - if (e.Name == name) { - retVal = (SceneObjectGroup)e; - break; - } - } + SceneObjectGroup retVal = (SceneObjectGroup)m_scene.Entities.Find (delegate( EntityBase e ) { + return (e.Name == name) && (e is SceneObjectGroup); + }); // can't find it so make a default one if (retVal == null) { diff --git a/Flocking/FlowField.cs b/Flocking/FlowField.cs index be2a774..04cf214 100644 --- a/Flocking/FlowField.cs +++ b/Flocking/FlowField.cs @@ -64,11 +64,9 @@ namespace Flocking for( int x = 0; x < size.X; x++ ) { for( int y = 0; y < size.Y; y++ ) { for( int z = 0; z < size.Z; z++ ) { - if( IsWithinFlowField( } } } - } } diff --git a/Flocking/VectorTest.cs b/Flocking/VectorTest.cs new file mode 100644 index 0000000..5f0872e --- /dev/null +++ b/Flocking/VectorTest.cs @@ -0,0 +1,23 @@ +using System; +using OpenMetaverse; +using NUnit.Framework; + +namespace Flocking +{ + [TestFixture()] + public class VectorTest + { + [Test()] + public void TestCase () + { + Vector3 [,] field = new Vector3[3,3]; + Vector2 start = new Vector2(0f, 0f); + Assert.That( field[1,1].Z == 0 ); + + field[1,0] = Vector3.UnitZ; + + + } + } +} +