Compare commits

...

15 Commits

Author SHA1 Message Date
Jon Cundill c0abc4d2e7 test keys still work 2012-03-07 19:54:25 +00:00
Jon Cundill 99bc21499b refactored command parsing 2012-01-05 05:07:24 +00:00
Jon Cundill 60cb5a018c reversed order in changes file 2011-07-24 22:07:16 +01:00
Jon Cundill b02e89189f documented changes 2011-07-24 00:24:11 +01:00
Jon Cundill e584d81e0c added ability to control flock from scripts. 2011-07-24 00:18:03 +01:00
Jon Cundill 1c410fdb81 updated changes 2011-07-22 13:49:33 +01:00
Jon Cundill f0cb3dee00 fixed README for stable 2011-07-21 22:55:57 +01:00
Jon Cundill 9083dbf23b added all files to stable branch 2011-07-21 22:37:51 +01:00
Jon Cundill 9e84d1f957 fixed up repo 2011-07-21 22:34:15 +01:00
Jon Cundill 502b66e7bb added snapshot binary 2011-07-21 22:32:04 +01:00
Jon Cundill c2ecfbaf7c fixes for stable 2011-07-21 22:30:45 +01:00
Jon Cundill 9aa909ba74 separated flock size from flocking parameters and exposed parameters to command handlers 2011-07-17 13:43:24 +01:00
Jon Cundill bf2061a391 encapsulated parameters that control flock behaviour 2011-07-17 12:42:48 +01:00
Jon Cundill c1109c1810 fixed up in world command handling somewhat, added parser 2011-07-17 11:53:35 +01:00
Jon Cundill cdbac1c921 added tests - no sogs yet 2011-07-16 08:56:20 +01:00
16 changed files with 1397 additions and 522 deletions

View File

@ -1,8 +1,19 @@
08/07/2011 - Initial pre alpha release
Basic Flocking algos implemented
Border avoidance not so good
No configuration capabilities
23/07/2011
added ability to script boid behaviour from lsl
tidied up copyright notices
further flow field work - still flying into objects, so still work to do here
flocking simulation now doesn't start until invoked by start command
flow field now recalculated on each start - so can edit scene and restart takes this into account
21/07/2011
introduced branch based on stable 0.7.1.1 opensim code. This currently ahead of master
added ability to change the flocking parameters at runtime through set commands
improved the collision algorithms somewhat
added orientation flock step that attempts to limit excessive vertical velocity vectors
13/07/2011
added more documentation
09/07/2011
Fixed Boid rotation, boid now orients with direction of travel
@ -10,5 +21,10 @@
added commands for setting size of flock, prim to use for boid
added commands to start and stop the boids flocking
13/07/2011
added more documentation
08/07/2011 - Initial pre alpha release
Basic Flocking algos implemented
Border avoidance not so good
No configuration capabilities

Binary file not shown.

Binary file not shown.

View File

@ -28,21 +28,22 @@ using System;
using System.Collections.Generic;
using log4net;
using OpenMetaverse;
using Utils = OpenSim.Framework.Util;
namespace Flocking
{
public class Boid
{
private static readonly ILog m_log = LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType);
// private static readonly ILog m_log = LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType);
private string m_id;
private Vector3 m_size;
private Vector3 m_loc;
private Vector3 m_vel;
private Vector3 m_acc;
private Random m_rndnums = new Random (Environment.TickCount);
private FlockingModel m_model;
private FlowMap m_flowMap;
private BoidBehaviour m_behaviour;
/// <summary>
@ -57,26 +58,31 @@ namespace Flocking
/// <param name='mf'>
/// Mf. max force / acceleration this boid can extert
/// </param>
public Boid (string id, FlockingModel model, FlowMap flowMap)
public Boid (string id, Vector3 size, BoidBehaviour behaviour)
{
m_id = id;
m_acc = Vector3.Zero;
m_vel = new Vector3 (m_rndnums.Next (-1, 1), m_rndnums.Next (-1, 1), m_rndnums.Next (-1, 1));
m_model = model;
m_flowMap = flowMap;
m_size = size;
m_behaviour = behaviour;
}
public Vector3 Location {
get { return m_loc;}
get { return m_loc; }
set { m_loc = value; }
}
public Vector3 Velocity {
get { return m_vel;}
get { return m_vel; }
set { m_vel = value; }
}
public Vector3 Size {
get { return m_size; }
}
public String Id {
get {return m_id;}
get { return m_id; }
}
/// <summary>
@ -85,13 +91,14 @@ namespace Flocking
/// <param name='boids'>
/// Boids. all the other chaps in the scene
/// </param>
public void MoveInSceneRelativeToFlock (List<Boid> boids)
public void MoveInSceneRelativeToFlock (List<Boid> neighbours, FlowField field)
{
// we would like to stay with our mates
Flock (boids);
Flock (neighbours);
// our first priority is to not hurt ourselves
AvoidObstacles ();
// however, our first priority is to not hurt ourselves
// so adjust where we would like to go to avoid hitting things
AvoidObstacles (field);
// then we want to avoid any threats
// this not implemented yet
@ -101,10 +108,9 @@ namespace Flocking
UpdatePositionInScene ();
}
/// <summary>
/// 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,
@ -112,29 +118,26 @@ namespace Flocking
/// our desire to move towards the flock centre
///
/// </summary>
void Flock (List<Boid> boids)
void Flock (List<Boid> 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
Vector3 ori = Orientation(); // its tricky to fly directly up or down
// 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_behaviour.separationWeighting;
ali *= m_behaviour.alignmentWeighting;
coh *= m_behaviour.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;
//m_acc += ori;
}
@ -148,11 +151,9 @@ 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_vel = Util.Limit (m_vel, m_behaviour.maxSpeed);
m_loc += m_vel;
// Reset accelertion to 0 each cycle
m_acc *= 0.0f;
@ -185,119 +186,104 @@ 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_behaviour.lookaheadDistance )) {
desired *= (m_behaviour.maxSpeed * (distance / m_behaviour.lookaheadDistance)); // This damping is somewhat arbitrary
} else {
desired *= m_model.MaxSpeed;
desired *= m_behaviour.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;
}
steer = Util.Limit (steer, m_behaviour.maxForce);
}
return steer;
}
/// <summary>
/// Borders this instance.
/// if we get too close wrap us around
/// CHANGE THIS to navigate away from whatever it is we are too close to
/// navigate away from whatever it is we are too close to
/// </summary>
void AvoidObstacles ()
void AvoidObstacles (FlowField field)
{
//look tolerance metres ahead
Vector3 normVel = Vector3.Normalize(m_vel);
Vector3 inFront = m_loc + Vector3.Multiply(normVel, m_model.Tolerance);
if( m_flowMap.WouldHitObstacle( m_loc, inFront ) ) {
AdjustVelocityToAvoidObstacles ();
//m_acc += field.AdjustVelocity( this, m_behaviour.lookaheadDistance );
Vector3 normVel = Vector3.Normalize (m_vel);
Vector3 inFront = m_loc + normVel * m_behaviour.LookaheadDistance;
Vector3 adjustedDestintation = field.FieldStrength (m_loc, m_size, inFront);
Vector3 newVel = Vector3.Normalize (adjustedDestintation - m_loc) * Vector3.Mag (m_vel);
float mOrigVel = Vector3.Mag(m_vel);
float mNewVel = Vector3.Mag(newVel);
if( mNewVel != 0f && mNewVel > mOrigVel ) {
newVel *= mOrigVel / mNewVel;
}
m_vel = newVel;
}
void AdjustVelocityToAvoidObstacles ()
{
for( int i = 1; i < 5; i++ ) {
Vector3 normVel = Vector3.Normalize(m_vel);
int xDelta = m_rndnums.Next (-i, i);
int yDelta = m_rndnums.Next (-i, i);
int zDelta = m_rndnums.Next (-i, i);
normVel.X += xDelta;
normVel.Y += yDelta;
normVel.Z += zDelta;
Vector3 inFront = m_loc + Vector3.Multiply(normVel, m_model.Tolerance);
if( !m_flowMap.WouldHitObstacle( m_loc, inFront ) ) {
m_vel.X += xDelta;
m_vel.Y += yDelta;
m_vel.Z += zDelta;
//m_log.Info("avoided");
return;
}
}
//m_log.Info("didn't avoid");
// try increaing our acceleration
// or try decreasing our acceleration
// or turn around - coz where we came from was OK
if (m_loc.X < 5 || m_loc.X > 250)
m_vel.X = -m_vel.X;
if (m_loc.Y < 5 || m_loc.Y > 250)
m_vel.Y = -m_vel.Y;
if (m_loc.Z < 21 || m_loc.Z > 271 )
m_vel.Z = -m_vel.Z;
}
/// <summary>
/// Separate ourselves from the specified boids.
/// keeps us a respectable distance from our closest neighbours whilst still
/// being part of our local flock
/// </summary>
/// <param name='boids'>
/// <param name='neighbours'>
/// Boids. all the boids in the scene
/// </param>
Vector3 Separate (List<Boid> boids)
Vector3 Separate (List<Boid> 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
float desired = m_behaviour.desiredSeparation;
//who are we too close to at the moment
List<Boid> tooCloseNeighbours = neighbours.FindAll( delegate(Boid neighbour) {
// Is the distance is less than the desired amount
return Utils.DistanceLessThan(m_loc, neighbour.Location, desired); //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;
if( diff == Vector3.Zero ) {
diff = new Vector3( (float)m_rndnums.NextDouble(), (float)m_rndnums.NextDouble(), (float)m_rndnums.NextDouble());
}
}
// Average -- divide by how many
if (count > 0) {
steer /= (float)count;
}
// As long as the vector is greater than 0
if (steer.Length () > 0) {
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_behaviour.maxSpeed;
steer -= m_vel;
//steer.limit(maxforce);
steer = Util.Limit (steer, m_model.MaxForce);
}
//don't go too fast;
steer = Util.Limit (steer, m_behaviour.maxForce);
}
return steer;
}
Vector3 Orientation() {
Vector3 retVal = Vector3.Zero;
float biggestLevel = Math.Max( m_vel.X, m_vel.Y );
if ( biggestLevel == 0f ) {
// wobble off the vertical
retVal.X += (float)(2*m_rndnums.NextDouble() - 1f);
retVal.Y += (float)(2*m_rndnums.NextDouble() - 1f);
}
return retVal;
}
/// <summary>
/// Align our boid within the flock.
@ -309,15 +295,14 @@ namespace Flocking
/// </param>
Vector3 Align (List<Boid> 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;
}
@ -326,10 +311,10 @@ namespace Flocking
if (steer.Length () > 0) {
// Implement Reynolds: Steering = Desired - Velocity
steer.Normalize ();
steer *= m_model.MaxSpeed;
steer *= m_behaviour.maxSpeed;
steer -= m_vel;
//steer.limit(maxforce);
steer = Util.Limit (steer, m_model.MaxForce);
steer = Util.Limit (steer, m_behaviour.maxForce);
}
return steer;
@ -339,22 +324,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
/// </summary>
/// <param name='boids'>
/// <param name='neighbours'>
/// Boids. the boids in the scene
/// </param>
Vector3 Cohesion (List<Boid> boids)
Vector3 Cohesion (List<Boid> 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

200
Flocking/BoidBehaviour.cs Normal file
View File

@ -0,0 +1,200 @@
/*
* Copyright (c) Contributors, https://github.com/jonc/osboids
* 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.
*/
using System;
using System.Collections.Generic;
namespace Flocking
{
public class BoidBehaviour
{
public float maxSpeed;
public float maxForce;
public float neighbourDistance;
public float desiredSeparation;
public float tolerance;
public float separationWeighting;
public float alignmentWeighting;
public float cohesionWeighting;
public float lookaheadDistance;
private Dictionary<string, string> m_paramDescriptions = new Dictionary<string, string> ();
public float AlignmentWeighting {
get {
return this.alignmentWeighting;
}
set {
alignmentWeighting = value;
}
}
public float CohesionWeighting {
get {
return this.cohesionWeighting;
}
set {
cohesionWeighting = value;
}
}
public float DesiredSeparation {
get {
return this.desiredSeparation;
}
set {
desiredSeparation = value;
}
}
public float LookaheadDistance {
get {
return this.lookaheadDistance;
}
set {
lookaheadDistance = value;
}
}
public float MaxForce {
get {
return this.maxForce;
}
set {
maxForce = value;
}
}
public float MaxSpeed {
get {
return this.maxSpeed;
}
set {
maxSpeed = value;
}
}
public float NeighbourDistance {
get {
return this.neighbourDistance;
}
set {
neighbourDistance = value;
}
}
public float SeparationWeighting {
get {
return this.separationWeighting;
}
set {
separationWeighting = value;
}
}
public float Tolerance {
get {
return this.tolerance;
}
set {
tolerance = value;
}
}
public BoidBehaviour() {
m_paramDescriptions.Add("max-speed", "max distance boid will travel per frame");
m_paramDescriptions.Add("max-force", "max acceleration od decelleration boid can exert");
m_paramDescriptions.Add("neighbour-distance", "boid will consider other boids within this distance as part of local flock");
m_paramDescriptions.Add("desired-separation", "closest distance to other boids that our boid would like to get");
m_paramDescriptions.Add("tolerance", "how close to the edges of objects or the scene should our boid get");
m_paramDescriptions.Add("separation-weighting", "factor by which closeness to other boids should be favoured when updating position in flock");
m_paramDescriptions.Add("alignment-weighting", "factor by which alignment with other boids should be favoured when updating position in flock");
m_paramDescriptions.Add("cohesion-weighting", "factor by which keeping within the local flock should be favoured when updating position in flock");
m_paramDescriptions.Add("lookahead-distance", "how far in front should the boid look for edges and boundaries");
}
public bool IsValidParameter (string name)
{
return m_paramDescriptions.ContainsKey (name);
}
public void SetParameter (string name, string newVal)
{
switch (name) {
case "max-speed":
maxSpeed = Convert.ToSingle(newVal);
break;
case "max-force":
maxForce = Convert.ToSingle(newVal);
break;
case "neighbour-distance":
neighbourDistance = Convert.ToSingle(newVal);
break;
case "desired-separation":
desiredSeparation = Convert.ToSingle(newVal);
break;
case "tolerance":
tolerance = Convert.ToSingle(newVal);
break;
case "separation-weighting":
separationWeighting = Convert.ToSingle(newVal);
break;
case "alignment-weighting":
alignmentWeighting = Convert.ToSingle(newVal);
break;
case "cohesion-weighting":
cohesionWeighting = Convert.ToSingle(newVal);
break;
case "lookahead-distance":
lookaheadDistance = Convert.ToSingle(newVal);
break;
}
}
public string GetList ()
{
string retVal = Environment.NewLine;
foreach (string name in m_paramDescriptions.Keys) {
retVal += name + " - " + m_paramDescriptions [name] + Environment.NewLine;
}
return retVal;
}
public override string ToString ()
{
return string.Format (
"alignment-weighting = {0}, " + Environment.NewLine +
"cohesion-weighting = {1}, " + Environment.NewLine +
"desired-separation = {2}, " + Environment.NewLine +
"lookahead-distance = {3}, " + Environment.NewLine +
"max-force = {4}, " + Environment.NewLine +
"max-speed = {5}, " + Environment.NewLine +
"neighbour-distance = {6}, " + Environment.NewLine +
"separation-weighting = {7}, " + Environment.NewLine +
"tolerance = {8}", AlignmentWeighting, CohesionWeighting, DesiredSeparation, LookaheadDistance, MaxForce, MaxSpeed, NeighbourDistance, SeparationWeighting, Tolerance);
}
}
}

View File

@ -1,12 +0,0 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ReferencePath>/Users/jon/osim/opensim/bin/</ReferencePath>
<LastOpenVersion>9.0.21022</LastOpenVersion>
<ProjectView>ProjectFiles</ProjectView>
<ProjectTrust>0</ProjectTrust>
</PropertyGroup>
<PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' " />
<PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
</Project>

View File

@ -1,61 +0,0 @@
<?xml version="1.0" ?>
<project name="Flocking" default="build">
<target name="build">
<echo message="Build Directory is ${project::get-base-directory()}/${build.dir}" />
<mkdir dir="${project::get-base-directory()}/${build.dir}" />
<copy todir="${project::get-base-directory()}/${build.dir}" flatten="true">
<fileset basedir="${project::get-base-directory()}">
</fileset>
</copy>
<copy todir="${project::get-base-directory()}/${build.dir}">
<fileset basedir=".">
</fileset>
</copy>
<csc target="library" debug="${build.debug}" platform="${build.platform}" unsafe="False" warnaserror="False" define="TRACE;DEBUG" nostdlib="False" main="" output="${project::get-base-directory()}/${build.dir}/${project::get-name()}.dll" noconfig="true">
<resources prefix="Flocking" dynamicprefix="true" >
<include name="Resources/Flocking.addin.xml" />
</resources>
<sources failonempty="true">
<include name="Boid.cs" />
<include name="FlockingModel.cs" />
<include name="FlockingModule.cs" />
<include name="FlockingView.cs" />
<include name="FlowMap.cs" />
<include name="Util.cs" />
</sources>
<references basedir="${project::get-base-directory()}">
<lib>
<include name="${project::get-base-directory()}" />
<include name="${project::get-base-directory()}/../../../bin" />
</lib>
<include name="../../../bin/log4net.dll"/>
<include name="../../../bin/Nini.dll"/>
<include name="../../../bin/OpenMetaverse.dll"/>
<include name="../../../bin/OpenMetaverseTypes.dll"/>
<include name="../../../bin/OpenSim.Framework.dll"/>
<include name="../../../bin/OpenSim.Framework.Communications.dll"/>
<include name="../../../bin/OpenSim.Framework.Console.dll"/>
<include name="../../../bin/OpenSim.Region.Framework.dll"/>
<include name="System.dll" />
<include name="System.Drawing.dll" />
<include name="System.Xml.dll" />
</references>
</csc>
<echo message="Copying from [${project::get-base-directory()}/${build.dir}/] to [${project::get-base-directory()}/../../../bin/" />
<mkdir dir="${project::get-base-directory()}/../../../bin/"/>
<copy todir="${project::get-base-directory()}/../../../bin/">
<fileset basedir="${project::get-base-directory()}/${build.dir}/" >
<include name="*.dll"/>
<include name="*.exe"/>
<include name="*.mdb" if='${build.debug}'/>
<include name="*.pdb" if='${build.debug}'/>
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="${bin.dir}" failonerror="false" />
<delete dir="${obj.dir}" failonerror="false" />
</target>
<target name="doc" description="Creates documentation.">
</target>
</project>

220
Flocking/FlockingCommand.cs Normal file
View File

@ -0,0 +1,220 @@
/*
* Copyright (c) Contributors, https://github.com/jonc/osboids
* 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.
*/
using System;
using log4net;
using OpenMetaverse;
using OpenSim.Region.Framework.Scenes;
namespace Flocking
{
public abstract class FlockingCommand
{
protected static readonly ILog m_log = LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType);
protected FlockingController m_controller;
protected string m_name;
protected string m_args;
protected string m_description;
public FlockingCommand( string name, string args, string description ) {
m_name = name;
m_args = args;
m_description = description;
}
public void Handle (string module, string[] args) {
if( ShouldHandleCmd() ) {
Invoke( args );
}
}
private bool ShouldHandleCmd ()
{
return View.Scene.ConsoleScene () == View.Scene;
}
public abstract void Invoke( string[] args );
public string Name {
get{ return m_name;}
}
public string Params {
get{ return m_args; }
}
public string Description {
get{ return m_description;}
}
public FlockingController Controller {
get{ return m_controller; }
set{ m_controller = value; }
}
public FlockingModel Model {
get { return Controller.Model; }
}
public FlockingView View {
get { return Controller.View; }
}
}
public class RoostCommand : FlockingCommand {
public RoostCommand() : base( "roost", "", "return all the boids to the start position and remove them from the scene") {}
public override void Invoke (string[] args) {
if( Model.Active ) {
Model.Goal = FlockGoal.Roost;
} else {
Controller.ShowResponse ("Flock is not active, roost has no effect", args);
}
}
}
public class StopCommand : FlockingCommand {
public StopCommand() : base("stop", "", "stop all Flocking"){}
public override void Invoke (string[] args) {
m_log.Info ("stopping the flocking activity");
Model.Active = false;
View.Clear ();
}
}
public class SetPositionCommand : FlockingCommand {
public SetPositionCommand() : base("position", "x,y,z", "location that the boids will start flocking from") {}
public override void Invoke (string[] args)
{
float x = Convert.ToSingle (args [1]);
float y = Convert.ToSingle (args [2]);
float z = Convert.ToSingle (args [3]);
Vector3 startPos = new Vector3 (x, y, z);
if (Model.ContainsPoint (startPos)) {
Model.StartPosition = startPos;
} else {
Controller.ShowResponse (startPos + "is not within the flow field", args);
}
}
}
public class SetParameterCommand : FlockingCommand {
public SetParameterCommand() : base("set", "name, value", "change the flock behaviour properties"){}
public override void Invoke (string[] args)
{
string name = args [1];
string newVal = args [2];
BoidBehaviour behaviour = Model.Behaviour;
if (behaviour.IsValidParameter (name)) {
behaviour.SetParameter (name, newVal);
} else {
Controller.ShowResponse (name + "is not a valid flock parameter", args);
Controller.ShowResponse ("valid parameters are: " + behaviour.GetList (), args);
}
}
}
public class SetFrameRateCommand : FlockingCommand {
public SetFrameRateCommand() : base("framerate", "num", "[debugging] only update boids every <num> frames") {}
public override void Invoke (string[] args)
{
int frameRate = Convert.ToInt32 (args [1]);
Controller.FrameUpdateRate = frameRate;
}
}
public class SetBoundsCommand : FlockingCommand {
public SetBoundsCommand() : base("bounds", "xMin,xMax,yMin,yMax,zMin,zMax", "Bounds of the 3D space that the flock will be confined to") {}
public override void Invoke (string[] args)
{
//TODO:
}
}
public class SetSizeCommand : FlockingCommand {
public SetSizeCommand() : base("size", "num", "Adjust the size of the flock ") {}
public override void Invoke (string [] args)
{
lock (Controller.UI_SYNC) {
int newSize = Convert.ToInt32 (args [1]);
Model.Size = newSize;
View.Clear ();
}
}
}
public class SetPrimCommand : FlockingCommand {
public SetPrimCommand() : base("prim", "name", "set the prim used for each boid to that passed in") {}
public override void Invoke (string[] args)
{
string primName = args [1];
lock (Controller.UI_SYNC) {
View.BoidPrim = primName;
View.Clear ();
}
}
}
public class ShowStatsCommand : FlockingCommand {
public ShowStatsCommand() : base("stats", "", "show flocking stats") {}
public override void Invoke (string[] args)
{
string str = Model.ToString ();
Controller.ShowResponse (str, args);
}
}
public class StartCommand : FlockingCommand {
public StartCommand() : base("start", "", "Start Flocking") {}
public override void Invoke(string[] args) {
if( Model.Active ) {
Controller.ShowResponse("Already active, restarting", args);
Model.Active = false;
View.Clear();
}
m_log.Info ("start the flocking capability");
Model.Goal = FlockGoal.Flock;
Controller.Start ();
Model.Active = true;
//m_module.FlockUpdate ();
}
}
}

View File

@ -0,0 +1,324 @@
/*
* Copyright (c) Contributors, https://github.com/jonc/osboids
* 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.
*/
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 FlockingController
{
private static readonly ILog m_log = LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType);
public object UI_SYNC = new object ();
private Scene m_scene;
private FlockingModel m_model;
private FlockingView m_view;
private int m_chatChannel;
private UUID m_owner;
private Dictionary<string, BoidCmdDelegate> m_commandMap = new Dictionary<string, BoidCmdDelegate> ();
private Dictionary<string, BoidCmdDefn> m_syntaxMap = new Dictionary<string, BoidCmdDefn> ();
private uint m_frame = 0;
private int m_frameUpdateRate = 1;
private Vector3 m_startPos = new Vector3 (128f, 128f, 128f);
private int m_minX = 0;
private int m_maxX = 256;
private int m_minY = 0;
private int m_maxY = 256;
private int m_minZ = 0;
private int m_maxZ = 256;
public FlockingController (Scene scene, BoidBehaviour behaviour, int channel, string prim, int flockSize)
{
//make the view
// who is the owner for the flock in this region
UUID owner = scene.RegionInfo.EstateSettings.EstateOwner;
m_view = new FlockingView (scene);
m_view.PostInitialize (owner);
m_view.BoidPrim = prim;
//make the model
FlowField field = new FlowField( scene, m_minX, m_maxX, m_minY, m_maxY, m_minZ, m_maxZ);
FlockingModel model = new FlockingModel(field, behaviour, m_startPos);
Vector3 startPos = new Vector3(128f, 128f, 128f);//scene.GetSceneObjectPart (View.BoidPrim).ParentGroup.AbsolutePosition;
model.StartPosition = startPos; // TODO: by default start from the prim
m_model = model;
m_scene = scene;
m_chatChannel = channel;
// who do we respond to in send messages
m_owner = m_scene.RegionInfo.EstateSettings.EstateOwner;
// register our event handlers
m_scene.EventManager.OnFrame += FlockUpdate; // plug in to the game loop
m_scene.EventManager.OnChatFromClient += ProcessChatCommand; //listen for commands sent from the client
IScriptModuleComms commsMod = m_scene.RequestModuleInterface<IScriptModuleComms>();
commsMod.OnScriptCommand += ProcessScriptCommand; // listen to scripts
}
public void Start()
{
//ask the view how big the boid prim is
Vector3 scale = View.GetBoidSize ();
FlowField field = new FlowField( m_scene, m_minX, m_maxX, m_minY, m_maxY, m_minZ, m_maxZ);
// init model
m_log.Info ("creating model");
// Generate initial flock values
m_model.BoidSize = scale;
m_model.Initialise (field);
m_log.Info ("done");
}
public int FrameUpdateRate {
get { return m_frameUpdateRate; }
set { m_frameUpdateRate = value; }
}
public FlockingModel Model {
get { return m_model; }
}
public FlockingView View {
get { return m_view; }
}
public void Deregister ()
{
m_scene.EventManager.OnChatFromClient -= ProcessChatCommand;
IScriptModuleComms commsMod = m_scene.RequestModuleInterface<IScriptModuleComms>();
commsMod.OnScriptCommand -= ProcessScriptCommand;
m_scene.EventManager.OnFrame -= FlockUpdate;
}
public void AddCommand (IRegionModuleBase module, FlockingCommand cmd)
{
cmd.Controller = this;
string name = cmd.Name;
string args = cmd.Params;
string help = cmd.Description;
CommandDelegate fn =cmd.Handle;
string argStr = "";
if (args.Trim ().Length > 0) {
argStr = " <" + args + "> ";
}
m_commandMap.Add (name, new BoidCmdDelegate (fn));
m_syntaxMap.Add (name, new BoidCmdDefn (name, args, help));
// register this command with the console
m_scene.AddCommand (module, "flock-" + name, "flock-" + name + argStr, help, fn);
}
#region handlers
public void FlockUpdate ()
{
if (((m_frame++ % m_frameUpdateRate) != 0) || !m_model.Active) {
return;
}
// work out where everyone has moved to
// and tell the scene to render the new positions
lock (UI_SYNC) {
List<Boid > boids = m_model.UpdateFlockPos ();
m_view.Render (boids);
}
}
public void ProcessScriptCommand (UUID scriptId, string reqId, string module, string input, string key)
{
if (FlockingModule.NAME != module) {
return;
}
string[] tokens = (input+"|<script>").Split ( new char[] { '|' }, StringSplitOptions.None);
string command = tokens [0];
m_log.Debug("Input was " + input + ", command is " + command);
BoidCmdDefn defn = null;
if (m_syntaxMap.TryGetValue (command, out defn)) {
if (CorrectSignature (tokens, defn)) {
// we got the signature of the command right
BoidCmdDelegate del = null;
if (m_commandMap.TryGetValue (command, out del)) {
m_log.Info("command ok - executing");
del (module, tokens);
} else {
// we don't understand this command
// shouldn't happen
m_log.ErrorFormat ("Unable to invoke command {0}", command);
}
} else {
m_log.Error(" signature wrong for " + command);
}
} else {
m_log.Error("no command for " + command);
}
}
void RespondToScript (UUID scriptId, int msgNum, string response)
{
IScriptModuleComms commsMod = m_scene.RequestModuleInterface<IScriptModuleComms>();
if( commsMod != null ) {
commsMod.DispatchReply (scriptId, msgNum, response, "");
} else {
Console.WriteLine("No script comms");
}
}
public void ProcessChatCommand (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 + " <ui>").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 (FlockingModule.NAME, args);
} else {
// we don't understand this command
// shouldn't happen
m_log.ErrorFormat ("Unable to invoke command {0}", args [0]);
RespondToChat (msg, "Unable to invoke command " + args [0]);
}
} else {
// we recognise the command, but we got the call wrong
RespondToChat (msg, "wrong syntax: " + defn.GetSyntax ());
}
} else {
// this is not a command we recognise
RespondToChat (msg, args [0] + " is not a valid command for osboids");
}
}
public void RespondToChat (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);
}
#endregion
private bool CorrectSignature (string[] args, BoidCmdDefn defn)
{
// args contain cmd name at 0 and <ui> tagged in last pos
return args.Length - 2 == defn.NumParams;
}
public void ShowResponse (string response, string [] cmd)
{
bool inWorld = IsInWorldCmd (cmd);
if (inWorld) {
ScenePresence owner = m_scene.GetScenePresence(m_owner);
SendMessage(owner, response);
} else {
MainConsole.Instance.Output (response);
}
}
private bool IsInWorldCmd (string [] args)
{
bool retVal = false;
if (args.Length > 0 && args [args.Length - 1].Equals ("<ui>")) {
retVal = true;
}
return retVal;
}
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
);
}
}
}

View File

@ -27,23 +27,37 @@
using System;
using System.Collections.Generic;
using OpenMetaverse;
using Utils = OpenSim.Framework.Util;
namespace Flocking
{
public enum FlockGoal
{
Roost = 0x01,
Perch = 0x02,
Flock = 0x04,
}
public class FlockingModel
{
private List<Boid> m_flock = new List<Boid>();
private FlowMap m_flowMap;
private float m_maxSpeed;
private float m_maxForce;
private float m_neighbourDistance;
private float m_desiredSeparation;
private float m_tolerance;
private Random m_rnd = new Random(Environment.TickCount);
private FlowField m_flowField;
private BoidBehaviour m_behaviour;
private int m_flockSize = 100;
private Vector3 m_boidSize;
private Vector3 m_startPos;
private FlockGoal m_goal = FlockGoal.Flock;
private bool m_active = false;
private int m_minX = 0;
private int m_maxX = 256;
private int m_minY = 0;
private int m_maxY = 256;
private int m_minZ = 0;
private int m_maxZ = 256;
public int Size {
get {return m_flock.Count;}
get {return m_flockSize;}
set {
if( value < m_flock.Count ) {
m_flock.RemoveRange( 0, m_flock.Count - value );
@ -53,71 +67,144 @@ namespace Flocking
}
}
public FlockingModel( float maxSpeed, float maxForce, float neighbourDistance, float desiredSeparation, float tolerance ) {
m_maxSpeed = maxSpeed;
m_maxForce = maxForce;
m_neighbourDistance = neighbourDistance;
m_desiredSeparation = desiredSeparation;
m_tolerance = tolerance;
public FlockGoal Goal {
get { return m_goal; }
set { m_goal = value; }
}
public int MaxX {
get {
return this.m_maxX;
}
set {
m_maxX = value;
}
}
public int MaxY {
get {
return this.m_maxY;
}
set {
m_maxY = value;
}
}
public int MaxZ {
get {
return this.m_maxZ;
}
set {
m_maxZ = value;
}
}
public int MinX {
get {
return this.m_minX;
}
set {
m_minX = value;
}
}
public int MinY {
get {
return this.m_minY;
}
set {
m_minY = value;
}
}
public int MinZ {
get {
return this.m_minZ;
}
set {
m_minZ = value;
}
}
public bool Active {
get { return m_active; }
set { m_active = value; }
}
public Vector3 StartPosition {
get { return m_startPos; }
set { m_startPos = value; }
}
public BoidBehaviour Behaviour {
get { return m_behaviour; }
set { m_behaviour = value; }
}
public Vector3 BoidSize {
set { m_boidSize = value; }
}
public FlockingModel( FlowField field, BoidBehaviour behaviour, Vector3 startPos ) {
m_flowField = field;
m_behaviour = behaviour;
m_startPos = startPos;
}
void AddBoid (string name)
{
Boid boid = new Boid (name, this, m_flowMap);
// find an initial random location for this Boid
// somewhere not within an obstacle
int xInit = m_rnd.Next(m_flowMap.LengthX);
int yInit = m_rnd.Next(m_flowMap.LengthY);
int zInit = m_rnd.Next(m_flowMap.LengthZ);
while( m_flowMap.IsWithinObstacle( xInit, yInit, zInit ) ){
xInit = m_rnd.Next(m_flowMap.LengthX);
yInit = m_rnd.Next(m_flowMap.LengthY);
zInit = m_rnd.Next(m_flowMap.LengthZ);
}
boid.Location = new Vector3 (Convert.ToSingle(xInit), Convert.ToSingle(yInit), Convert.ToSingle(zInit));
Boid boid = new Boid (name, m_boidSize, m_behaviour);
double d1 = ( Utils.RandomClass.NextDouble() - 0.5 ) * 20;
double d2 = ( Utils.RandomClass.NextDouble() - 0.5 ) * 20;
double d3 = ( Utils.RandomClass.NextDouble() - 0.5 ) * 20;
boid.Location = m_startPos + new Vector3( (float)d1, (float)d2, (float)d3 );
boid.Velocity = Vector3.UnitX;
m_flock.Add (boid);
}
public float MaxSpeed {
get {return m_maxSpeed;}
}
public float MaxForce {
get {return m_maxForce;}
}
public float NeighbourDistance {
get {return m_neighbourDistance;}
}
public float DesiredSeparation {
get {return m_desiredSeparation;}
}
public float Tolerance {
get {return m_tolerance;}
}
public void Initialise (int num, FlowMap flowMap)
public bool ContainsPoint (Vector3 position)
{
m_flowMap = flowMap;
for (int i = 0; i < num; i++) {
return m_flowField.ContainsPoint( position );
}
public void Initialise (FlowField flowField)
{
m_flowField = flowField;
for (int i = 0; i < m_flockSize; i++) {
AddBoid ("boid"+i );
//UpdateFlockPos();
}
}
public List<Boid> GetNeighbours(Boid boid) {
return m_flock.FindAll(delegate(Boid other)
{
return (boid != other) && (Utils.GetDistanceTo (boid.Location, other.Location) < m_behaviour.neighbourDistance);
});
}
public List<Boid> UpdateFlockPos ()
{
foreach (Boid b in m_flock) {
b.MoveInSceneRelativeToFlock(m_flock); // Passing the entire list of boids to each boid individually
}
if( m_active ) {
m_flock.ForEach( delegate(Boid boid) {
boid.MoveInSceneRelativeToFlock(GetNeighbours(boid), m_flowField);
} );
}
return m_flock;
}
public override string ToString ()
{
string retVal = "Num Boids: " + m_flockSize + Environment.NewLine
+ m_behaviour.ToString() + Environment.NewLine
+ m_flowField.ToString();
return retVal;
}
}
}

View File

@ -36,53 +36,45 @@ using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Framework;
using OpenSim.Framework.Console;
using OpenSim.Services.Interfaces;
namespace Flocking
{
public class FlockingModule : INonSharedRegionModule
{
private static readonly ILog m_log = LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType);
static object m_sync = new object();
public static string NAME = "FlockingModule";
private Scene m_scene;
private FlockingModel m_model;
private FlockingView m_view;
private bool m_enabled = false;
private bool m_ready = false;
private uint m_frame = 0;
private int m_frameUpdateRate = 1;
private int m_chatChannel = 118;
private string m_boidPrim;
private string m_boidPrim = "boid-prim";
private FlockingController m_controller;
private BoidBehaviour m_behaviour;
private int m_flockSize = 100;
private float m_maxSpeed;
private float m_maxForce;
private float m_neighbourDistance;
private float m_desiredSeparation;
private float m_tolerance;
private UUID m_owner;
#region IRegionModule Members
public void Initialise (IConfigSource source)
{
//check if we are in the ini files
//if so get some physical constants out of them and pass into the model
//if so get some default values out of them and pass them onto the controller
IConfig config = source.Configs ["Boids"];
if (config != null) {
m_chatChannel = config.GetInt ("chat-channel", 118);
m_boidPrim = config.GetString ("boid-prim", "boidPrim");
m_flockSize = config.GetInt ("flock-size", 100);
m_maxSpeed = config.GetFloat("max-speed", 3f);
m_maxForce = config.GetFloat("max-force", 0.25f);
m_neighbourDistance = config.GetFloat("neighbour-dist", 25f);
m_desiredSeparation = config.GetFloat("desired-separation", 20f);
m_tolerance = config.GetFloat("tolerance", 5f);
m_behaviour = new BoidBehaviour ();
m_behaviour.maxSpeed = config.GetFloat ("max-speed", 1f);
m_behaviour.maxForce = config.GetFloat ("max-force", 0.25f);
m_behaviour.neighbourDistance = config.GetFloat ("neighbour-dist", 25f);
m_behaviour.desiredSeparation = config.GetFloat ("desired-separation", 20f);
m_behaviour.tolerance = config.GetFloat ("tolerance", 5f);
m_behaviour.separationWeighting = config.GetFloat ("separation-weighting", 1.5f);
m_behaviour.alignmentWeighting = config.GetFloat ("alignment-weighting", 1f);
m_behaviour.cohesionWeighting = config.GetFloat ("cohesion-weighting", 1f);
m_behaviour.lookaheadDistance = config.GetFloat ("lookahead-dist", 100f);
// we're in the config - so turn on this module
m_enabled = true;
}
@ -90,53 +82,42 @@ namespace Flocking
public void AddRegion (Scene scene)
{
m_log.Info ("ADDING FLOCKING");
m_scene = scene;
if (m_enabled) {
//register commands
RegisterCommands ();
//register handlers
m_scene.EventManager.OnFrame += FlockUpdate;
m_scene.EventManager.OnChatFromClient += 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_view = new FlockingView (m_scene);
m_view.BoidPrim = m_boidPrim;
}
}
public void RegionLoaded (Scene scene)
{
if (m_enabled) {
//make a flow map for this scene
FlowMap flowMap = new FlowMap(scene );
flowMap.Initialise();
// Generate initial flock values
m_model.Initialise (m_flockSize, flowMap);
// who is the owner for the flock in this region
m_owner = m_scene.RegionInfo.EstateSettings.EstateOwner;
m_view.PostInitialize (m_owner);
// Mark Module Ready for duty
m_ready = true;
//set up the boid module
m_controller = new FlockingController (scene, m_behaviour, m_chatChannel, m_boidPrim, m_flockSize);
RegisterCommand (new RoostCommand());
RegisterCommand (new StopCommand());
RegisterCommand (new StartCommand());
RegisterCommand (new SetSizeCommand());
RegisterCommand (new ShowStatsCommand());
RegisterCommand (new SetPrimCommand());
RegisterCommand (new SetPositionCommand());
RegisterCommand (new SetBoundsCommand());
RegisterCommand (new SetFrameRateCommand());
RegisterCommand (new SetParameterCommand());
}
}
public void RegisterCommand (FlockingCommand cmd)
{
m_controller.AddCommand( this, cmd);
}
public void RemoveRegion (Scene scene)
{
if (m_enabled) {
m_scene.EventManager.OnFrame -= FlockUpdate;
m_scene.EventManager.OnChatFromClient -= SimChatSent;
m_controller.Deregister ();
}
}
public string Name {
get { return "FlockingModule"; }
get { return NAME; }
}
public bool IsSharedModule {
@ -145,166 +126,8 @@ namespace Flocking
#endregion
#region EventHandlers
public void FlockUpdate ()
{
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 ) {
List<Boid > boids = m_model.UpdateFlockPos ();
m_view.Render (boids);
}
}
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 + " <ui>").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
private void AddCommand (string cmd, string args, string help, CommandDelegate fn)
{
string argStr = "";
if (args.Trim ().Length > 0) {
argStr = " <" + args + "> ";
}
m_scene.AddCommand (this, "flock-" + cmd, "flock-" + cmd + argStr, help, fn);
}
private void RegisterCommands ()
{
AddCommand ("stop", "", "Stop all Flocking", HandleStopCmd);
AddCommand ("start", "", "Start Flocking", HandleStartCmd);
AddCommand ("size", "num", "Adjust the size of the flock ", HandleSetSizeCmd);
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 <num> frames", HandleSetFrameRateCmd);
}
private bool ShouldHandleCmd ()
{
return m_scene.ConsoleScene () == m_scene;
}
private bool IsInWorldCmd (ref string [] args)
{
bool retVal = false;
if (args.Length > 0 && args [args.Length - 1].Equals ("<ui>")) {
retVal = true;
}
return retVal;
}
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);
}
} else {
MainConsole.Instance.Output (response);
}
}
public void HandleStopCmd (string module, string[] args)
{
if (ShouldHandleCmd ()) {
m_log.Info ("stop the flocking capability");
m_enabled = false;
m_view.Clear ();
}
}
void HandleSetFrameRateCmd (string module, string[] args)
{
if (ShouldHandleCmd ()) {
int frameRate = Convert.ToInt32( args[1] );
m_frameUpdateRate = frameRate;
}
}
public void HandleStartCmd (string module, string[] args)
{
if (ShouldHandleCmd ()) {
m_log.Info ("start the flocking capability");
m_enabled = true;
FlockUpdate ();
}
}
public void HandleSetSizeCmd (string module, string[] args)
{
if (ShouldHandleCmd ()) {
lock( m_sync ) {
int newSize = Convert.ToInt32(args[1]);
m_model.Size = newSize;
m_view.Clear();
}
}
}
public void HandleShowStatsCmd (string module, string[] args)
{
if (ShouldHandleCmd ()) {
bool inWorld = IsInWorldCmd (ref args);
ShowResponse ("Num Boids = " + m_model.Size, inWorld);
}
}
public void HandleSetPrimCmd (string module, string[] args)
{
if (ShouldHandleCmd ()) {
string primName = args[1];
lock(m_sync) {
m_view.BoidPrim = primName;
m_view.Clear();
}
}
}
#endregion
#region IRegionModuleBase Members
public void Close ()
{
}

View File

@ -26,14 +26,18 @@
*/
using System;
using System.Collections.Generic;
using log4net;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Region.Framework.Scenes;
using Utils = OpenSim.Framework.Util;
namespace Flocking
{
public class FlockingView
{
private static readonly ILog m_log = LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType);
private Scene m_scene;
private UUID m_owner;
private String m_boidPrim;
@ -50,34 +54,46 @@ namespace Flocking
m_owner = owner;
}
public String BoidPrim {
set{ m_boidPrim = value;}
public string BoidPrim {
get{ return m_boidPrim; }
set{ m_boidPrim = value; }
}
public Scene Scene {
get {return m_scene;}
}
public Vector3 GetBoidSize ()
{
float offsetHeight;
return findByName(m_boidPrim).GetAxisAlignedBoundingBox( out offsetHeight );
}
public void Clear ()
{
//trash everything we have
foreach (string name in m_sogMap.Keys)
{
RemoveSOGFromScene(name);
}
List<string> current = new List<string> (m_sogMap.Keys);
current.ForEach (delegate(string name) {
RemoveSOGFromScene(name);
});
m_sogMap.Clear();
}
public void Render (List<Boid> 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) {
//m_log.Error( "didnt find " + boid.Id );
SceneObjectGroup group = findByName (m_boidPrim);
sog = CopyPrim (group, boid.Id);
m_sogMap [boid.Id] = sog;
@ -86,21 +102,23 @@ namespace Flocking
sog = existing.ParentGroup;
}
Quaternion rotation = CalcRotationToEndpoint (sog, sog.AbsolutePosition, boid.Location);
sog.UpdateGroupRotationPR( boid.Location, rotation);
Quaternion rotation = CalcRotationToEndpoint (sog, boid.Location);
//sog.UpdateGroupRotationPR( boid.Location, rotation);
sog.UpdateGroupPosition( boid.Location );
sog.UpdateGroupRotationR( 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);
return rot;
return Vector3.RotationBetween (currDirVec, desiredDirVec);
}
private SceneObjectGroup CopyPrim (SceneObjectGroup prim, string name)
@ -113,18 +131,22 @@ namespace Flocking
private SceneObjectGroup findByName (string name)
{
// SceneObjectGroup retVal = (SceneObjectGroup)m_scene.Entities.Find (delegate( EntityBase e ) {
// return e.Name == name;
//});
SceneObjectGroup retVal = null;
foreach (EntityBase e in m_scene.GetEntities()) {
if (e.Name == name) {
retVal = (SceneObjectGroup)e;
break;
}
}
SceneObjectPart sop = m_scene.GetSceneObjectPart(name);
// can't find it so make a default one
if (retVal == null) {
if (sop == null) {
m_log.Error("no " + name);
retVal = MakeDefaultPrim (name);
} else {
retVal = sop.ParentGroup;
}
return retVal;
}
@ -146,6 +168,7 @@ namespace Flocking
{
SceneObjectGroup sog = m_sogMap[sogName];
m_scene.DeleteSceneObject(sog, false);
//sog.SendGroupFullUpdate();
}

264
Flocking/FlowField.cs Normal file
View File

@ -0,0 +1,264 @@
/*
* Copyright (c) Contributors, https://github.com/jonc/osboids
* 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.
*/
using System;
using OpenMetaverse;
using OpenSim.Region.Framework.Scenes;
namespace Flocking
{
public class FlowField
{
private const int BUFFER = 5;
private Scene m_scene;
private float m_startX;
private float m_startY;
private float m_startZ;
private float m_endX;
private float m_endY;
private float m_endZ;
private UUID TERRAIN = UUID.Random ();
private UUID EDGE = UUID.Random ();
private UUID[,,] m_field = new UUID[256, 256, 256]; // field of objects at this position
/// <summary>
/// Initializes a new instance of the <see cref="Flocking.FlowField"/> class.
/// </summary>
/// <param name='scene'>
/// Scene.
/// </param>
/// <param name='centre'>
/// Centre.
/// </param>
/// <param name='width'>
/// Width.
/// </param>
/// <param name='depth'>
/// Depth.
/// </param>
/// <param name='height'>
/// Height.
/// </param>
///
public FlowField (Scene scene, int minX, int maxX, int minY, int maxY, int minZ, int maxZ)
{
m_scene = scene;
m_startX = Math.Max (BUFFER, minX);
m_startY = Math.Max (BUFFER, minY);
m_startZ = Math.Max (BUFFER, minZ);
m_endX = Math.Min (Util.SCENE_SIZE - BUFFER, maxX);
m_endY = Math.Min (Util.SCENE_SIZE - BUFFER, maxY);
m_endZ = Math.Min (Util.SCENE_SIZE - BUFFER, maxZ);
// build the flow field over the given bounds
Initialize ();
}
/// <summary>
/// build a flow field on the scene at the specified centre
/// position in the scene and of extent given by width, depth and height.
/// </summary>
public void Initialize ()
{
//fill in the boundaries
for (int x = 0; x < 256; x++) {
for (int y = 0; y < 256; y++) {
m_field [x, y, 0] = EDGE;
m_field [x, y, 255] = EDGE;
}
}
for (int x = 0; x < 256; x++) {
for (int z = 0; z < 256; z++) {
m_field [x, 0, z] = EDGE;
m_field [x, 255, z] = EDGE;
}
}
for (int y = 0; y < 256; y++) {
for (int z = 0; z < 256; z++) {
m_field [0, y, z] = EDGE;
m_field [255, y, z] = EDGE;
}
}
//fill in the terrain
for (int x = 0; x < 256; x++) {
for (int y = 0; y < 256; y++) {
int zMax = Convert.ToInt32 (m_scene.GetGroundHeight (x, y));
for (int z = 1; z < zMax; z++) {
m_field [x, y, z] = TERRAIN;
}
}
}
foreach (SceneObjectGroup sog in m_scene.Entities.GetAllByType<SceneObjectGroup>()) {
//todo: ignore phantom
float fmaxX, fminX, fmaxY, fminY, fmaxZ, fminZ;
int maxX, minX, maxY, minY, maxZ, minZ;
sog.GetAxisAlignedBoundingBoxRaw (out fminX, out fmaxX, out fminY, out fmaxY, out fminZ, out fmaxZ);
Vector3 pos = sog.AbsolutePosition;
minX = Convert.ToInt32 (fminX + pos.X);
maxX = Convert.ToInt32 (fmaxX + pos.X);
minY = Convert.ToInt32 (fminY + pos.Y);
maxY = Convert.ToInt32 (fmaxX + pos.Y);
minZ = Convert.ToInt32 (fminZ + pos.Z);
maxZ = Convert.ToInt32 (fmaxZ + pos.Z);
for (int x = minX; x < maxX; x++) {
for (int y = minY; y < maxY; y++) {
for (int z = minZ; z < maxZ; z++) {
if( inBounds(x,y,z) ) {
m_field [x, y, z] = sog.UUID;
} else {
Console.WriteLine(sog.Name + " OOB at " + sog.AbsolutePosition + " -> " + x + " " + y + " " + z);
}
}
}
}
}
}
public bool ContainsPoint (Vector3 p)
{
return p.X > m_startX &&
p.X < m_endX &&
p.Y > m_startY &&
p.Y < m_endY &&
p.Z > m_startZ &&
p.Z < m_endZ;
}
private bool inBounds (int x, int y, int z)
{
return x >= 0 && x < 256 && y >= 0 && y < 256 && z >= 0;
}
#if false
public Vector3 AdjustVelocity (Boid boid, float lookAheadDist)
{
Vector3 normVel = Vector3.Normalize (boid.Velocity);
Vector3 loc = boid.Location;
Vector3 inFront = loc + normVel * lookAheadDist;
Vector3 adjustedDestintation = FieldStrength (loc, boid.Size, inFront);
Vector3 newVel = Vector3.Normalize (adjustedDestintation - loc) * Vector3.Mag (boid.Velocity);
float mOrigVel = Vector3.Mag(boid.Velocity);
float mNewVel = Vector3.Mag(newVel);
if( mNewVel != 0f && mNewVel > mOrigVel ) {
newVel *= mOrigVel / mNewVel;
}
return newVel;
}
#endif
public Vector3 FieldStrength (Vector3 currentPos, Vector3 size, Vector3 targetPos)
{
float length = size.X/2;
float width = size.Y/2;
float height = size.Z/2;
//keep us in bounds
targetPos.X = Math.Min( targetPos.X, m_endX - length );
targetPos.X = Math.Max(targetPos.X, m_startX + length);
targetPos.Y = Math.Min( targetPos.Y, m_endY - width );
targetPos.Y = Math.Max(targetPos.Y, m_startY + width);
targetPos.Z = Math.Min( targetPos.Z, m_endZ - height );
targetPos.Z = Math.Max(targetPos.Z, m_startZ + height);
int count = 0;
//now get the field strength at the inbounds position
UUID collider = LookUp (targetPos);
while (collider != UUID.Zero && count < 100) {
count++;
if (collider == TERRAIN) {
// ground height at currentPos and dest averaged
float h1 = m_scene.GetGroundHeight (currentPos.X, currentPos.Y);
float h2 = m_scene.GetGroundHeight (targetPos.X, targetPos.Y);
float h = (h1 + h2) / 2;
targetPos.Z = h + height;
} else if (collider == EDGE) {
//keep us in bounds
targetPos.X = Math.Min( targetPos.X, m_endX - length );
targetPos.X = Math.Max(targetPos.X, m_startX + length);
targetPos.Y = Math.Min( targetPos.Y, m_endY - width );
targetPos.Y = Math.Max(targetPos.Y, m_startY + width);
targetPos.Z = Math.Min( targetPos.Z, m_endZ - height );
targetPos.Z = Math.Max(targetPos.Z, m_startZ + height);
} else {
//we have hit a SOG
SceneObjectGroup sog = m_scene.GetSceneObjectPart(collider).ParentGroup;
if (sog == null) {
Console.WriteLine (collider);
} else {
float sogMinX, sogMinY, sogMinZ, sogMaxX, sogMaxY, sogMaxZ;
sog.GetAxisAlignedBoundingBoxRaw (out sogMinX, out sogMaxX, out sogMinY, out sogMaxY, out sogMinZ, out sogMaxZ);
Vector3 pos = sog.AbsolutePosition;
//keep us out of the sog
// adjust up/down first if necessary
// then turn left or right
if (targetPos.Z > sogMinZ + pos.Z)
targetPos.Z = (sogMinZ + pos.Z) - height;
if (targetPos.Z < sogMaxZ + pos.Z)
targetPos.Z = (sogMaxZ + pos.Z) + height;
if (targetPos.X > sogMinX + pos.X)
targetPos.X = (sogMinX + pos.X) - length;
if (targetPos.Y > sogMinY + pos.Y)
targetPos.Y = (sogMinY + pos.Y) - width;
if (targetPos.X < sogMaxX + pos.X)
targetPos.X = (sogMaxX + pos.X) + length;
if (targetPos.Y < sogMaxY + pos.Y)
targetPos.Y = (sogMaxY + pos.Y) + width;
}
}
// we what is at the new target position
collider = LookUp (targetPos);
}
return targetPos;
}
public UUID LookUp (Vector3 loc)
{
return m_field [(int)loc.X, (int)loc.Y, (int)loc.Z];
}
public override string ToString ()
{
return string.Format ("[FlowField]" + Environment.NewLine +
"startX = {0}" + Environment.NewLine +
"endX = {1}" + Environment.NewLine +
"startY = {2}" + Environment.NewLine +
"endY = {3}" + Environment.NewLine +
"startZ = {4}" + Environment.NewLine +
"endZ = {5}", m_startX, m_endX, m_startY, m_endY, m_startZ, m_endZ);
}
}
}

View File

@ -29,8 +29,11 @@ using OpenMetaverse;
namespace Flocking
{
public class Util
{
public const int SCENE_SIZE = 256;
public static Vector3 Limit (Vector3 initial, float maxLen)
{
float currLen = initial.Length ();

37
README
View File

@ -1,6 +1,8 @@
Region Module - ability to control flocks within an OpenSim scene
This branch tracks the OpenSim 0.7.1.1 release
To build from source
Add osboids source tree under opensim/addon-modules
@ -28,11 +30,6 @@ removing the Boids group or setting enabled = false will switch off the module
In addition various config parameters are available to control the flock dynamics
flock-size = 100 the number of Boids to flock
max-speed = 3 how far each boid can travel per update
max-force = 0.25 the maximum acceleration allowed to the current velocity of the boid
neighbour-dist = 25 max distance for other boids to be considered in the same flock as us
desired-separation = 20 how far away from other boids we would like to stay
tolerance = 5 how close to the edges of things can we get without being worried
By default the module will create a flock of plain wooden spheres, however this can be overridden
@ -61,28 +58,34 @@ of the flock at runtime
flock-framerate <num> or /118 framerate - only update the flock positions every <num> frames - only really useful
- for photography and debugging boid behaviour
Additionally various parameters of the flocking algorithms can be set at runtime, these are configured using the
flock-set <name> <value> or /118 set <name> <value> commands
Boid prims
The following parameters can be set - current defaults are shown below
max-speed = 3 how far each boid can travel per update
max-force = 0.25 the maximum acceleration allowed to the current velocity of the boid
neighbour-distance = 25 max distance for other boids to be considered in the same flock as us
desired-separation = 20 how far away from other boids we would like to stay
tolerance = 5 how close to the edges of objects or the scene should our boid get
separation-weighting = 1.5 factor by which closeness to other boids should be favoured when updating position in flock
alignment-weighting = 1 factor by which alignment with other boids should be favoured when updating position in flock
cohesion-weighting = 1 factor by which keeping within the local flock should be favoured when updating position in flock
lookahead-distance = 10 how far in front should the boid look for edges and boundaries
Boidprims
Any, currently rezzed in scene, object can be used as the boid prim. However fps is very much affected by the
complexity of the entity to use. It is easier to throw a single prim (or sculpty) around the scene than it is to
throw the constituent parts of a 200 linked prim dragon.
Tests show that <= 500 single prims can be flocked effectively - depending on system and network
However maybe <= 300 simple linksets can perform as well.
Tests show that a few hundred single prims can be flocked effectively - depending on system and network
I intend to allow inventory items and UUIDs to represent the boids - this is not written yet however.
Prebuilt binaries etc.. to follow
Please Note
This module is currently only tested against opensim master. If it is found to work against a stable release,
then that behaviour ought to be considered as a bug - which I will attempt to fix in the next git push.
Prebuilt binaries etc.. proved in project root
Status

View File

@ -19,10 +19,13 @@
<Reference name="System.Drawing" />
<Reference name="System.Xml"/>
<Reference name="Nini" path="../../../bin/" />
<Reference name="nunit.framework" path="../../../bin/" />
<Reference name="OpenSim.Framework" path="../../../bin/" />
<Reference name="OpenSim.Tests.Common" path="../../../bin/" />
<Reference name="OpenSim.Framework.Communications" path="../../../bin/" />
<Reference name="OpenSim.Region.Framework" path="../../../bin/" />
<Reference name="OpenSim.Framework.Console" path="../../../bin/" />
<Reference name="OpenSim.Services.Interfaces" path="../../../bin/" />
<Reference name="OpenMetaverseTypes" path="../../../bin/" />
<Reference name="OpenMetaverse" path="../../../bin/" />
<Files>